feat: update Telegram client to support proxy configuration and enhance error handling

This commit is contained in:
2026-02-24 14:16:01 +03:30
parent 29cbe6a38d
commit c6a7fc4676
5 changed files with 106 additions and 37 deletions
+2
View File
@@ -0,0 +1,2 @@
.env
.vscode/launch.json
+2
View File
@@ -28,4 +28,6 @@ func main() {
if err := app.Start(startCtx); err != nil {
log.Fatalf("failed to start: %v", err)
}
app.Wait()
}
+5 -1
View File
@@ -17,6 +17,7 @@ type Config struct {
TgAppHash string
SessionPath string
ProxyURL string
}
func Load(logger *zap.Logger) *Config {
@@ -27,7 +28,7 @@ func Load(logger *zap.Logger) *Config {
phoneNumber := os.Getenv("TG_PHONE_NUMBER")
if phoneNumber == "" {
logger.Fatal("No phone number provided")
logger.Warn("No phone number provided")
}
botToken := os.Getenv("TG_BOT_TOKEN")
@@ -60,6 +61,8 @@ func Load(logger *zap.Logger) *Config {
sessionFile := filepath.Join(sessionPath, "session.json")
proxyURL := os.Getenv("TG_PROXY_URL")
return &Config{
TgPhoneNumber: strings.TrimSpace(phoneNumber),
TgBotToken: strings.TrimSpace(botToken),
@@ -67,5 +70,6 @@ func Load(logger *zap.Logger) *Config {
TgAppHash: strings.TrimSpace(appHash),
SessionPath: sessionFile,
ProxyURL: strings.TrimSpace(proxyURL),
}
}
+84 -21
View File
@@ -1,6 +1,9 @@
package telegram
import (
"context"
"net"
"net/url"
"tgss/internal/config"
"github.com/gotd/td/session"
@@ -8,24 +11,84 @@ import (
"github.com/gotd/td/telegram/dcs"
"go.uber.org/fx"
"go.uber.org/zap"
)
func NewTelegramClient(cfg *config.Config, logger *zap.Logger) *telegram.Client {
return telegram.NewClient(cfg.TgAppId, cfg.TgAppHash, telegram.Options{
DC: 2,
DCList: dcs.Prod(),
Logger: logger,
SessionStorage: &session.FileStorage{
Path: cfg.SessionPath,
},
})
}
func NewService(client *telegram.Client, logger *zap.Logger) *Service {
return &Service{client: client, log: logger}
}
var Module = fx.Provide(
NewTelegramClient,
NewService,
)
"golang.org/x/net/proxy"
)
func NewTelegramClient(cfg *config.Config, logger *zap.Logger) *telegram.Client {
opts := telegram.Options{
DC: 2,
DCList: dcs.Prod(),
Logger: logger,
SessionStorage: &session.FileStorage{Path: cfg.SessionPath},
}
if cfg.ProxyURL != "" {
proxyURL, err := url.Parse(cfg.ProxyURL)
if err != nil {
logger.Warn("Failed to parse proxy URL, ignoring proxy", zap.String("url", cfg.ProxyURL), zap.Error(err))
} else {
dialer, err := proxy.FromURL(proxyURL, proxy.Direct)
if err != nil {
logger.Warn("Failed to create proxy dialer, ignoring proxy", zap.String("url", cfg.ProxyURL), zap.Error(err))
} else {
opts.Resolver = dcs.Plain(dcs.PlainOptions{
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
})
logger.Info("Using proxy", zap.String("url", cfg.ProxyURL))
}
}
}
return telegram.NewClient(cfg.TgAppId, cfg.TgAppHash, opts)
}
func NewService(client *telegram.Client, logger *zap.Logger) *Service {
return &Service{client: client, log: logger}
}
func RunClient(lc fx.Lifecycle, client *telegram.Client, cfg *config.Config, logger *zap.Logger) {
ctx, cancel := context.WithCancel(context.Background())
lc.Append(fx.Hook{
OnStart: func(context.Context) error {
errCh := make(chan error, 1)
readyCh := make(chan struct{})
go func() {
if err := client.Run(ctx, func(ctx context.Context) error {
if _, err := client.Auth().Bot(ctx, cfg.TgBotToken); err != nil {
return err
}
logger.Info("Telegram bot authenticated and running")
close(readyCh)
<-ctx.Done()
return nil
}); err != nil {
errCh <- err
}
}()
select {
case <-readyCh:
return nil
case err := <-errCh:
return err
case <-ctx.Done():
return ctx.Err()
}
},
OnStop: func(context.Context) error {
cancel()
return nil
},
})
}
var Module = fx.Options(
fx.Provide(
NewTelegramClient,
NewService,
),
fx.Invoke(RunClient),
)
+13 -15
View File
@@ -22,27 +22,25 @@ func (s *Service) LastMessages(ctx context.Context, username string, limit int)
if err != nil {
return nil, err
}
var peer tg.InputPeerClass
for _, chat := range resolved.Chats {
if ch, ok := chat.(*tg.Channel); ok {
peer = &tg.InputPeerChannel{
ChannelID: ch.ID,
AccessHash: ch.AccessHash,
}
break
}
}
if peer == nil {
if len(resolved.Chats) == 0 {
return nil, errors.New("channel not found")
}
// Fetch history
history, err := api.MessagesGetHistory(ctx, &tg.MessagesGetHistoryRequest{
Peer: peer,
Limit: limit,
channel, ok := resolved.Chats[0].(*tg.Channel)
if !ok {
return nil, errors.New("resolved peer is not a channel")
}
history, err := s.client.API().MessagesGetHistory(ctx, &tg.MessagesGetHistoryRequest{
Peer: &tg.InputPeerChannel{
ChannelID: channel.ID,
AccessHash: channel.AccessHash,
},
Limit: 100,
})
if err != nil {
return nil, err
}
// Extract messages
var msgs []tg.MessageClass
switch h := history.(type) {