File size: 3,242 Bytes
49b198e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f592c10
49b198e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package bot

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/celestix/gotgproto"
	"github.com/celestix/gotgproto/sessionMaker"
	"github.com/glebarez/sqlite"
	"go.uber.org/zap"
)

// BotInfo contains information about a validated bot
type BotInfo struct {
	ID       int64  `json:"id"`
	Username string `json:"username"`
	FirstName string `json:"first_name"`
	IsBot    bool   `json:"is_bot"`
}

// BotValidator handles bot token validation
type BotValidator struct {
	log *zap.Logger
}

// NewBotValidator creates a new bot validator
func NewBotValidator(log *zap.Logger) *BotValidator {
	return &BotValidator{
		log: log.Named("BotValidator"),
	}
}

// ValidateBotToken validates a bot token and returns bot information
func (bv *BotValidator) ValidateBotToken(token string) (*BotInfo, error) {
	// Basic token format validation
	if !bv.isValidTokenFormat(token) {
		return nil, fmt.Errorf("invalid token format")
	}

	// Create a temporary client to test the token
	_, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Use a temporary session file
	tempSessionPath := fmt.Sprintf("/tmp/validate_%d.session", time.Now().UnixNano())
	
	client, err := gotgproto.NewClient(
		0, // We don't need API ID/Hash for bot validation
		"",
		gotgproto.ClientTypeBot(token),
		&gotgproto.ClientOpts{
			Session: sessionMaker.SqlSession(
				sqlite.Open(tempSessionPath),
			),
			DisableCopyright: true,
		},
	)

	if err != nil {
		bv.log.Error("Failed to create validation client", zap.Error(err))
		return nil, fmt.Errorf("invalid bot token: %w", err)
	}

	// Clean up the temporary session
	defer func() {
		if client != nil {
			client.Stop()
		}
	}()

	// Get bot information
	self := client.Self
	if self == nil {
		return nil, fmt.Errorf("failed to get bot information")
	}

	if !self.Bot {
		return nil, fmt.Errorf("token belongs to a user account, not a bot")
	}

	botInfo := &BotInfo{
		ID:        self.ID,
		Username:  self.Username,
		FirstName: self.FirstName,
		IsBot:     self.Bot,
	}

	bv.log.Info("Successfully validated bot token",
		zap.Int64("botID", botInfo.ID),
		zap.String("username", botInfo.Username))

	return botInfo, nil
}

// isValidTokenFormat checks if the token has the correct format
func (bv *BotValidator) isValidTokenFormat(token string) bool {
	// Bot tokens have format: {bot_id}:{auth_token}
	// Example: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz
	
	if len(token) < 35 || len(token) > 50 {
		return false
	}

	parts := strings.Split(token, ":")
	if len(parts) != 2 {
		return false
	}

	// First part should be numeric (bot ID)
	botID := parts[0]
	if len(botID) < 8 || len(botID) > 12 {
		return false
	}

	for _, char := range botID {
		if char < '0' || char > '9' {
			return false
		}
	}

	// Second part should be the auth token (alphanumeric + some special chars)
	authToken := parts[1]
	if len(authToken) < 25 || len(authToken) > 40 {
		return false
	}

	return true
}

// TestBotPermissions tests if the bot has necessary permissions
func (bv *BotValidator) TestBotPermissions(token string) error {
	// This could be extended to test specific permissions
	// For now, just validate that we can create a client
	_, err := bv.ValidateBotToken(token)
	return err
}