Skip to content

Commit

Permalink
feat(cmd): new qrcode session generator
Browse files Browse the repository at this point in the history
  • Loading branch information
EverythingSuckz committed Dec 12, 2023
1 parent 70f722e commit 5299d53
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 22 deletions.
16 changes: 15 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
61 changes: 40 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,28 @@
<a href="#how-to-make-your-own">How to make your own</a>
<ul>
<li><a href="#download-from-releases">Download and run</a></li>
</ul>
<ul>
<li><a href="#run-using-docker-compose">Run via Docker compose</a></li>
</ul>
<ul>
<li><a href="#run-using-docker">Run via Docker</a></li>
</ul>
<ul>
<li><a href="#build-from-source">Build and run</a></li>
<ul>
<li><a href="#ubuntu">Ubuntu</a></li>
</ul>
<ul>
<li><a href="#windows">Windows</a></li>
</ul>
<li><a href="#build-from-source">Build and run</a>
<ul>
<li><a href="#ubuntu">Ubuntu</a></li>
<li><a href="#windows">Windows</a></li>
</ul>
</li>
</ul>
</li>
<li>
<a href="#setting-up-things">Setting up Things</a>
<ul>
<li><a href="#required-vars">Required environment variables</a></li>
</ul>
<ul>
<li><a href="#optional-vars">Optional environment variables</a></li>
</ul>
<ul>
<li><a href="#use-multiple-bots-to-speed-up">Using multiple bots</a></li>
<li><a href="#use-multiple-bots-to-speed-up">Using user session to auto add bots</a>
<ul>
<li><a href="#what-it-does">What it does?</a></li>
<li><a href="#how-to-generate-a-session-string">How to generate a session string?</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#contributing">Contributing</a></li>
Expand All @@ -61,6 +56,8 @@
</ol>
</details>



## How to make your own

### Download from releases
Expand Down Expand Up @@ -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? <br>
> [!NOTE]
> **What it multi-client feature and what it does?** <br>
> 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>
> **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.
Expand All @@ -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 <your api id> --api-hash <your 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
Expand Down
1 change: 1 addition & 0 deletions cmd/fsb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
41 changes: 41 additions & 0 deletions cmd/fsb/session.go
Original file line number Diff line number Diff line change
@@ -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.")
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
52 changes: 52 additions & 0 deletions pkg/qrlogin/encoder.go
Original file line number Diff line number Diff line change
@@ -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
}
152 changes: 152 additions & 0 deletions pkg/qrlogin/qrcode.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 5299d53

Please sign in to comment.