Skip to content

Commit 5299d53

Browse files
feat(cmd): new qrcode session generator
1 parent 70f722e commit 5299d53

File tree

8 files changed

+304
-22
lines changed

8 files changed

+304
-22
lines changed

.vscode/launch.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,21 @@
99
"type": "go",
1010
"request": "launch",
1111
"mode": "auto",
12-
"program": "./cmd/fsb/main.go",
12+
"program": "./cmd/fsb/",
13+
"args": [
14+
"run"
15+
]
16+
},
17+
{
18+
"name": "Generate Session",
19+
"type": "go",
20+
"request": "launch",
21+
"mode": "auto",
22+
"program": "./cmd/fsb/",
23+
"args": [
24+
"session"
25+
],
26+
"console": "integratedTerminal"
1327
}
1428
]
1529
}

README.md

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,28 @@
2626
<a href="#how-to-make-your-own">How to make your own</a>
2727
<ul>
2828
<li><a href="#download-from-releases">Download and run</a></li>
29-
</ul>
30-
<ul>
3129
<li><a href="#run-using-docker-compose">Run via Docker compose</a></li>
32-
</ul>
33-
<ul>
3430
<li><a href="#run-using-docker">Run via Docker</a></li>
35-
</ul>
36-
<ul>
37-
<li><a href="#build-from-source">Build and run</a></li>
38-
<ul>
39-
<li><a href="#ubuntu">Ubuntu</a></li>
40-
</ul>
41-
<ul>
42-
<li><a href="#windows">Windows</a></li>
43-
</ul>
31+
<li><a href="#build-from-source">Build and run</a>
32+
<ul>
33+
<li><a href="#ubuntu">Ubuntu</a></li>
34+
<li><a href="#windows">Windows</a></li>
35+
</ul>
36+
</li>
4437
</ul>
4538
</li>
4639
<li>
4740
<a href="#setting-up-things">Setting up Things</a>
4841
<ul>
4942
<li><a href="#required-vars">Required environment variables</a></li>
50-
</ul>
51-
<ul>
5243
<li><a href="#optional-vars">Optional environment variables</a></li>
53-
</ul>
54-
<ul>
5544
<li><a href="#use-multiple-bots-to-speed-up">Using multiple bots</a></li>
45+
<li><a href="#use-multiple-bots-to-speed-up">Using user session to auto add bots</a>
46+
<ul>
47+
<li><a href="#what-it-does">What it does?</a></li>
48+
<li><a href="#how-to-generate-a-session-string">How to generate a session string?</a></li>
49+
</ul>
50+
</li>
5651
</ul>
5752
</li>
5853
<li><a href="#contributing">Contributing</a></li>
@@ -61,6 +56,8 @@
6156
</ol>
6257
</details>
6358

59+
60+
6461
## How to make your own
6562

6663
### Download from releases
@@ -194,11 +191,11 @@ In addition to the mandatory variables, you can also set the following optional
194191

195192
### Use Multiple Bots to speed up
196193

197-
> **Note**
198-
> What it multi-client feature and what it does? <br>
194+
> [!NOTE]
195+
> **What it multi-client feature and what it does?** <br>
199196
> This feature shares the Telegram API requests between worker bots to speed up download speed when many users are using the server and to avoid the flood limits that are set by Telegram. <br>
200197
201-
> **Note**
198+
> [!NOTE]
202199
> You can add up to 50 bots since 50 is the max amount of bot admins you can set in a Telegram Channel.
203200
204201
To enable multi-client, generate new bot tokens and add it as your `fsb.env` with the following key names.
@@ -210,9 +207,31 @@ To enable multi-client, generate new bot tokens and add it as your `fsb.env` wit
210207
you may also add as many as bots you want. (max limit is 50)
211208
`MULTI_TOKEN3`, `MULTI_TOKEN4`, etc.
212209

213-
> **Warning**
210+
> [!WARNING]
214211
> Don't forget to add all these worker bots to the `LOG_CHANNEL` for the proper functioning
215212
213+
### Using user session to auto add bots
214+
215+
> [!WARNING]
216+
> This might sometimes result in your account getting resticted or banned.
217+
> **Only newly created accounts are prone to this.**
218+
219+
To use this feature, you need to generate a pyrogram session string for the user account and add it to the `USER_SESSION` variable in the `fsb.env` file.
220+
221+
#### What it does?
222+
223+
This feature is used to auto add the worker bots to the `LOG_CHANNEL` when they are started. This is useful when you have a lot of worker bots and you don't want to add them manually to the `LOG_CHANNEL`.
224+
225+
#### How to generate a session string?
226+
227+
The easiest way to generate a session string is by running
228+
229+
```sh
230+
./fsb session --api-id <your api id> --api-hash <your api hash>
231+
```
232+
233+
This will generate a session string for your user account using QR code authentication. Authentication via phone number is not supported yet and will be added in the future.
234+
216235
## Contributing
217236

218237
Feel free to contribute to this project if you have any further ideas

cmd/fsb/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var rootCmd = &cobra.Command{
2525
func init() {
2626
config.SetFlagsFromConfig(runCmd)
2727
rootCmd.AddCommand(runCmd)
28+
rootCmd.AddCommand(sessionCmd)
2829
rootCmd.SetVersionTemplate(fmt.Sprintf(`Telegram File Stream Bot version %s`, versionString))
2930
}
3031

cmd/fsb/session.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"EverythingSuckz/fsb/pkg/qrlogin"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var sessionCmd = &cobra.Command{
12+
Use: "session",
13+
Short: "Generate a string session.",
14+
DisableSuggestions: false,
15+
Run: generateSession,
16+
}
17+
18+
func init() {
19+
sessionCmd.Flags().StringP("login-type", "T", "qr", "The login type to use. Can be either 'qr' or 'phone'")
20+
sessionCmd.Flags().Int32P("api-id", "I", 0, "The API ID to use for the session (required).")
21+
sessionCmd.Flags().StringP("api-hash", "H", "", "The API hash to use for the session (required).")
22+
sessionCmd.MarkFlagRequired("api-id")
23+
sessionCmd.MarkFlagRequired("api-hash")
24+
}
25+
26+
func generateSession(cmd *cobra.Command, args []string) {
27+
loginType, _ := cmd.Flags().GetString("login-type")
28+
apiId, _ := cmd.Flags().GetInt32("api-id")
29+
apiHash, _ := cmd.Flags().GetString("api-hash")
30+
if loginType == "qr" {
31+
qrlogin.GenerateQRSession(int(apiId), apiHash)
32+
} else if loginType == "phone" {
33+
generatePhoneSession()
34+
} else {
35+
fmt.Println("Invalid login type. Please use either 'qr' or 'phone'")
36+
}
37+
}
38+
39+
func generatePhoneSession() {
40+
fmt.Println("Phone session is not implemented yet.")
41+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ require (
6262
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
6363
github.com/leodido/go-urn v1.2.4 // indirect
6464
github.com/mattn/go-isatty v0.0.20 // indirect
65+
github.com/mdp/qrterminal v1.0.1
6566
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6667
github.com/modern-go/reflect2 v1.0.2 // indirect
6768
github.com/pelletier/go-toml/v2 v2.0.8 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
9090
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
9191
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
9292
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
93+
github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c=
94+
github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
9395
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
9496
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
9597
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

pkg/qrlogin/encoder.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// This file is a part of EverythingSuckz/TG-FileStreamBot
2+
// And is licenced under the Affero General Public License.
3+
// Any distributions of this code MUST be accompanied by a copy of the AGPL
4+
// with proper attribution to the original author(s).
5+
6+
package qrlogin
7+
8+
import (
9+
"bytes"
10+
"encoding/base64"
11+
"encoding/binary"
12+
"errors"
13+
"strings"
14+
15+
"github.com/gotd/td/session"
16+
)
17+
18+
func EncodeToPyrogramSession(data *session.Data, appID int32) (string, error) {
19+
buf := new(bytes.Buffer)
20+
if err := buf.WriteByte(byte(data.DC)); err != nil {
21+
return "", err
22+
}
23+
if err := binary.Write(buf, binary.BigEndian, appID); err != nil {
24+
return "", err
25+
}
26+
var testMode byte
27+
if data.Config.TestMode {
28+
testMode = 1
29+
}
30+
if err := buf.WriteByte(testMode); err != nil {
31+
return "", err
32+
}
33+
if len(data.AuthKey) != 256 {
34+
return "", errors.New("auth key must be 256 bytes long")
35+
}
36+
if _, err := buf.Write(data.AuthKey); err != nil {
37+
return "", err
38+
}
39+
if len(data.AuthKeyID) != 8 {
40+
return "", errors.New("auth key ID must be 8 bytes long")
41+
}
42+
if _, err := buf.Write(data.AuthKeyID); err != nil {
43+
return "", err
44+
}
45+
if err := buf.WriteByte(0); err != nil {
46+
return "", err
47+
}
48+
// Convert the bytes buffer to a base64 string
49+
encodedString := base64.URLEncoding.EncodeToString(buf.Bytes())
50+
trimmedEncoded := strings.TrimRight(encodedString, "=")
51+
return trimmedEncoded, nil
52+
}

pkg/qrlogin/qrcode.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// This file is a part of EverythingSuckz/TG-FileStreamBot
2+
// And is licenced under the Affero General Public License.
3+
// Any distributions of this code MUST be accompanied by a copy of the AGPL
4+
// with proper attribution to the original author(s).
5+
6+
package qrlogin
7+
8+
import (
9+
"bufio"
10+
"context"
11+
"encoding/json"
12+
"errors"
13+
"fmt"
14+
"os"
15+
"runtime"
16+
"strings"
17+
"time"
18+
19+
"github.com/gotd/td/session"
20+
"github.com/gotd/td/telegram"
21+
"github.com/gotd/td/telegram/auth/qrlogin"
22+
"github.com/gotd/td/tg"
23+
"github.com/gotd/td/tgerr"
24+
"github.com/mdp/qrterminal"
25+
)
26+
27+
type CustomWriter struct {
28+
LineLength int
29+
}
30+
31+
func (w *CustomWriter) Write(p []byte) (n int, err error) {
32+
for _, c := range p {
33+
if c == '\n' {
34+
w.LineLength++
35+
}
36+
}
37+
return os.Stdout.Write(p)
38+
}
39+
40+
func printQrCode(data string, writer *CustomWriter) {
41+
qrterminal.GenerateHalfBlock(data, qrterminal.L, writer)
42+
}
43+
44+
func clearQrCode(writer *CustomWriter) {
45+
for i := 0; i < writer.LineLength; i++ {
46+
fmt.Printf("\033[F\033[K")
47+
}
48+
writer.LineLength = 0
49+
}
50+
51+
func GenerateQRSession(apiId int, apiHash string) error {
52+
ctx, cancel := context.WithCancel(context.Background())
53+
defer cancel()
54+
fmt.Println("Generating QR session...")
55+
reader := bufio.NewReader(os.Stdin)
56+
dispatcher := tg.NewUpdateDispatcher()
57+
loggedIn := qrlogin.OnLoginToken(dispatcher)
58+
sessionStorage := &session.StorageMemory{}
59+
client := telegram.NewClient(apiId, apiHash, telegram.Options{
60+
UpdateHandler: dispatcher,
61+
SessionStorage: sessionStorage,
62+
Device: telegram.DeviceConfig{
63+
DeviceModel: "Pyrogram",
64+
SystemVersion: runtime.GOOS,
65+
AppVersion: "2.0",
66+
},
67+
})
68+
var stringSession string
69+
qrWriter := &CustomWriter{}
70+
tickerCtx, cancelTicker := context.WithCancel(context.Background())
71+
err := client.Run(ctx, func(ctx context.Context) error {
72+
authorization, err := client.QR().Auth(ctx, loggedIn, func(ctx context.Context, token qrlogin.Token) error {
73+
if qrWriter.LineLength == 0 {
74+
fmt.Printf("\033[F\033[K")
75+
}
76+
clearQrCode(qrWriter)
77+
printQrCode(token.URL(), qrWriter)
78+
qrWriter.Write([]byte("\nTo log in, Open your Telegram app and go to Settings > Devices > Scan QR and scan the QR code.\n"))
79+
go func(ctx context.Context) {
80+
ticker := time.NewTicker(1 * time.Second)
81+
defer ticker.Stop()
82+
for {
83+
select {
84+
case <-ctx.Done():
85+
return
86+
case <-ticker.C:
87+
expiresIn := time.Until(token.Expires())
88+
if expiresIn <= 0 {
89+
return
90+
}
91+
fmt.Printf("\rThis code expires in %s", expiresIn.Truncate(time.Second))
92+
}
93+
}
94+
}(tickerCtx)
95+
return nil
96+
})
97+
if err != nil {
98+
if tgerr.Is(err, "SESSION_PASSWORD_NEEDED") {
99+
cancelTicker()
100+
fmt.Println("\n2FA password is required, enter it below: ")
101+
passkey, _ := reader.ReadString('\n')
102+
strippedPasskey := strings.TrimSpace(passkey)
103+
authorization, err = client.Auth().Password(ctx, strippedPasskey)
104+
if err != nil {
105+
if err.Error() == "invalid password" {
106+
fmt.Println("Invalid password, please try again.")
107+
}
108+
fmt.Println("Error while logging in: ", err)
109+
return nil
110+
}
111+
}
112+
}
113+
if authorization == nil {
114+
cancel()
115+
return errors.New("authorization is nil")
116+
}
117+
user, err := client.Self(ctx)
118+
if err != nil {
119+
return err
120+
}
121+
if user.Username == "" {
122+
fmt.Println("Logged in as ", user.FirstName, user.LastName)
123+
} else {
124+
fmt.Println("Logged in as @", user.Username)
125+
}
126+
res, _ := sessionStorage.LoadSession(ctx)
127+
type jsonDataStruct struct {
128+
Version int
129+
Data session.Data
130+
}
131+
var jsonData jsonDataStruct
132+
json.Unmarshal(res, &jsonData)
133+
stringSession, err = EncodeToPyrogramSession(&jsonData.Data, int32(apiId))
134+
if err != nil {
135+
return err
136+
}
137+
fmt.Println("Your pyrogram session string:", stringSession)
138+
client.API().MessagesSendMessage(
139+
ctx,
140+
&tg.MessagesSendMessageRequest{
141+
NoWebpage: true,
142+
Peer: &tg.InputPeerSelf{},
143+
Message: "Your pyrogram session string: " + stringSession,
144+
},
145+
)
146+
return nil
147+
})
148+
if err != nil {
149+
return err
150+
}
151+
return nil
152+
}

0 commit comments

Comments
 (0)