diff --git a/.vscode/launch.json b/.vscode/launch.json
index 805ef73b..e0608f20 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -9,7 +9,21 @@
"type": "go",
"request": "launch",
"mode": "auto",
- "program": "./cmd/fsb/main.go",
+ "program": "./cmd/fsb/",
+ "args": [
+ "run"
+ ]
+ },
+ {
+ "name": "Generate Session",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "./cmd/fsb/",
+ "args": [
+ "session"
+ ],
+ "console": "integratedTerminal"
}
]
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 47978798..5bb3a264 100644
--- a/README.md
+++ b/README.md
@@ -26,33 +26,28 @@
How to make your own
-
-
-
Setting up Things
-
-
Contributing
@@ -61,6 +56,8 @@
+
+
## How to make your own
### Download from releases
@@ -194,11 +191,11 @@ In addition to the mandatory variables, you can also set the following optional
### Use Multiple Bots to speed up
-> **Note**
-> What it multi-client feature and what it does?
+> [!NOTE]
+> **What it multi-client feature and what it does?**
> 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.
-> **Note**
+> [!NOTE]
> You can add up to 50 bots since 50 is the max amount of bot admins you can set in a Telegram Channel.
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
you may also add as many as bots you want. (max limit is 50)
`MULTI_TOKEN3`, `MULTI_TOKEN4`, etc.
-> **Warning**
+> [!WARNING]
> Don't forget to add all these worker bots to the `LOG_CHANNEL` for the proper functioning
+### Using user session to auto add bots
+
+> [!WARNING]
+> This might sometimes result in your account getting resticted or banned.
+> **Only newly created accounts are prone to this.**
+
+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.
+
+#### What it does?
+
+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`.
+
+#### How to generate a session string?
+
+The easiest way to generate a session string is by running
+
+```sh
+./fsb session --api-id --api-hash
+```
+
+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.
+
## Contributing
Feel free to contribute to this project if you have any further ideas
diff --git a/cmd/fsb/main.go b/cmd/fsb/main.go
index 2e9eaf4e..513e48e0 100644
--- a/cmd/fsb/main.go
+++ b/cmd/fsb/main.go
@@ -25,6 +25,7 @@ var rootCmd = &cobra.Command{
func init() {
config.SetFlagsFromConfig(runCmd)
rootCmd.AddCommand(runCmd)
+ rootCmd.AddCommand(sessionCmd)
rootCmd.SetVersionTemplate(fmt.Sprintf(`Telegram File Stream Bot version %s`, versionString))
}
diff --git a/cmd/fsb/session.go b/cmd/fsb/session.go
new file mode 100644
index 00000000..d8ea504f
--- /dev/null
+++ b/cmd/fsb/session.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+
+ "EverythingSuckz/fsb/pkg/qrlogin"
+
+ "github.com/spf13/cobra"
+)
+
+var sessionCmd = &cobra.Command{
+ Use: "session",
+ Short: "Generate a string session.",
+ DisableSuggestions: false,
+ Run: generateSession,
+}
+
+func init() {
+ sessionCmd.Flags().StringP("login-type", "T", "qr", "The login type to use. Can be either 'qr' or 'phone'")
+ sessionCmd.Flags().Int32P("api-id", "I", 0, "The API ID to use for the session (required).")
+ sessionCmd.Flags().StringP("api-hash", "H", "", "The API hash to use for the session (required).")
+ sessionCmd.MarkFlagRequired("api-id")
+ sessionCmd.MarkFlagRequired("api-hash")
+}
+
+func generateSession(cmd *cobra.Command, args []string) {
+ loginType, _ := cmd.Flags().GetString("login-type")
+ apiId, _ := cmd.Flags().GetInt32("api-id")
+ apiHash, _ := cmd.Flags().GetString("api-hash")
+ if loginType == "qr" {
+ qrlogin.GenerateQRSession(int(apiId), apiHash)
+ } else if loginType == "phone" {
+ generatePhoneSession()
+ } else {
+ fmt.Println("Invalid login type. Please use either 'qr' or 'phone'")
+ }
+}
+
+func generatePhoneSession() {
+ fmt.Println("Phone session is not implemented yet.")
+}
diff --git a/go.mod b/go.mod
index 9486100e..bde8183e 100644
--- a/go.mod
+++ b/go.mod
@@ -62,6 +62,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mdp/qrterminal v1.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
diff --git a/go.sum b/go.sum
index 806c8d9d..d596e27d 100644
--- a/go.sum
+++ b/go.sum
@@ -90,6 +90,8 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c=
+github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
diff --git a/pkg/qrlogin/encoder.go b/pkg/qrlogin/encoder.go
new file mode 100644
index 00000000..d3b04e04
--- /dev/null
+++ b/pkg/qrlogin/encoder.go
@@ -0,0 +1,52 @@
+// This file is a part of EverythingSuckz/TG-FileStreamBot
+// And is licenced under the Affero General Public License.
+// Any distributions of this code MUST be accompanied by a copy of the AGPL
+// with proper attribution to the original author(s).
+
+package qrlogin
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/binary"
+ "errors"
+ "strings"
+
+ "github.com/gotd/td/session"
+)
+
+func EncodeToPyrogramSession(data *session.Data, appID int32) (string, error) {
+ buf := new(bytes.Buffer)
+ if err := buf.WriteByte(byte(data.DC)); err != nil {
+ return "", err
+ }
+ if err := binary.Write(buf, binary.BigEndian, appID); err != nil {
+ return "", err
+ }
+ var testMode byte
+ if data.Config.TestMode {
+ testMode = 1
+ }
+ if err := buf.WriteByte(testMode); err != nil {
+ return "", err
+ }
+ if len(data.AuthKey) != 256 {
+ return "", errors.New("auth key must be 256 bytes long")
+ }
+ if _, err := buf.Write(data.AuthKey); err != nil {
+ return "", err
+ }
+ if len(data.AuthKeyID) != 8 {
+ return "", errors.New("auth key ID must be 8 bytes long")
+ }
+ if _, err := buf.Write(data.AuthKeyID); err != nil {
+ return "", err
+ }
+ if err := buf.WriteByte(0); err != nil {
+ return "", err
+ }
+ // Convert the bytes buffer to a base64 string
+ encodedString := base64.URLEncoding.EncodeToString(buf.Bytes())
+ trimmedEncoded := strings.TrimRight(encodedString, "=")
+ return trimmedEncoded, nil
+}
diff --git a/pkg/qrlogin/qrcode.go b/pkg/qrlogin/qrcode.go
new file mode 100644
index 00000000..b52b292f
--- /dev/null
+++ b/pkg/qrlogin/qrcode.go
@@ -0,0 +1,152 @@
+// This file is a part of EverythingSuckz/TG-FileStreamBot
+// And is licenced under the Affero General Public License.
+// Any distributions of this code MUST be accompanied by a copy of the AGPL
+// with proper attribution to the original author(s).
+
+package qrlogin
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/gotd/td/session"
+ "github.com/gotd/td/telegram"
+ "github.com/gotd/td/telegram/auth/qrlogin"
+ "github.com/gotd/td/tg"
+ "github.com/gotd/td/tgerr"
+ "github.com/mdp/qrterminal"
+)
+
+type CustomWriter struct {
+ LineLength int
+}
+
+func (w *CustomWriter) Write(p []byte) (n int, err error) {
+ for _, c := range p {
+ if c == '\n' {
+ w.LineLength++
+ }
+ }
+ return os.Stdout.Write(p)
+}
+
+func printQrCode(data string, writer *CustomWriter) {
+ qrterminal.GenerateHalfBlock(data, qrterminal.L, writer)
+}
+
+func clearQrCode(writer *CustomWriter) {
+ for i := 0; i < writer.LineLength; i++ {
+ fmt.Printf("\033[F\033[K")
+ }
+ writer.LineLength = 0
+}
+
+func GenerateQRSession(apiId int, apiHash string) error {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ fmt.Println("Generating QR session...")
+ reader := bufio.NewReader(os.Stdin)
+ dispatcher := tg.NewUpdateDispatcher()
+ loggedIn := qrlogin.OnLoginToken(dispatcher)
+ sessionStorage := &session.StorageMemory{}
+ client := telegram.NewClient(apiId, apiHash, telegram.Options{
+ UpdateHandler: dispatcher,
+ SessionStorage: sessionStorage,
+ Device: telegram.DeviceConfig{
+ DeviceModel: "Pyrogram",
+ SystemVersion: runtime.GOOS,
+ AppVersion: "2.0",
+ },
+ })
+ var stringSession string
+ qrWriter := &CustomWriter{}
+ tickerCtx, cancelTicker := context.WithCancel(context.Background())
+ err := client.Run(ctx, func(ctx context.Context) error {
+ authorization, err := client.QR().Auth(ctx, loggedIn, func(ctx context.Context, token qrlogin.Token) error {
+ if qrWriter.LineLength == 0 {
+ fmt.Printf("\033[F\033[K")
+ }
+ clearQrCode(qrWriter)
+ printQrCode(token.URL(), qrWriter)
+ qrWriter.Write([]byte("\nTo log in, Open your Telegram app and go to Settings > Devices > Scan QR and scan the QR code.\n"))
+ go func(ctx context.Context) {
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-ticker.C:
+ expiresIn := time.Until(token.Expires())
+ if expiresIn <= 0 {
+ return
+ }
+ fmt.Printf("\rThis code expires in %s", expiresIn.Truncate(time.Second))
+ }
+ }
+ }(tickerCtx)
+ return nil
+ })
+ if err != nil {
+ if tgerr.Is(err, "SESSION_PASSWORD_NEEDED") {
+ cancelTicker()
+ fmt.Println("\n2FA password is required, enter it below: ")
+ passkey, _ := reader.ReadString('\n')
+ strippedPasskey := strings.TrimSpace(passkey)
+ authorization, err = client.Auth().Password(ctx, strippedPasskey)
+ if err != nil {
+ if err.Error() == "invalid password" {
+ fmt.Println("Invalid password, please try again.")
+ }
+ fmt.Println("Error while logging in: ", err)
+ return nil
+ }
+ }
+ }
+ if authorization == nil {
+ cancel()
+ return errors.New("authorization is nil")
+ }
+ user, err := client.Self(ctx)
+ if err != nil {
+ return err
+ }
+ if user.Username == "" {
+ fmt.Println("Logged in as ", user.FirstName, user.LastName)
+ } else {
+ fmt.Println("Logged in as @", user.Username)
+ }
+ res, _ := sessionStorage.LoadSession(ctx)
+ type jsonDataStruct struct {
+ Version int
+ Data session.Data
+ }
+ var jsonData jsonDataStruct
+ json.Unmarshal(res, &jsonData)
+ stringSession, err = EncodeToPyrogramSession(&jsonData.Data, int32(apiId))
+ if err != nil {
+ return err
+ }
+ fmt.Println("Your pyrogram session string:", stringSession)
+ client.API().MessagesSendMessage(
+ ctx,
+ &tg.MessagesSendMessageRequest{
+ NoWebpage: true,
+ Peer: &tg.InputPeerSelf{},
+ Message: "Your pyrogram session string: " + stringSession,
+ },
+ )
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ return nil
+}