feat(channels): implement automatic telegram username resolution
Add support for resolving Telegram usernames to chat IDs, allowing users to use friendly @username format instead of numeric IDs in: - allow_from configuration - direct message targets - API calls Changes: - Add usernameCache to TelegramChannel for caching resolved chat IDs - Implement resolveUsername() method using Telegram Bot API's getChat - Convert parseChatID to method and add automatic username resolution - Support both numeric IDs and @username format How it works: 1. When parseChatID receives a non-numeric value, it tries to resolve it 2. resolveUsername uses the getChat API with @username prefix 3. Successfully resolved usernames are cached for future use 4. Numeric IDs continue to work as before Benefits: - Users can use "@myusername" instead of "123456789" - More user-friendly configuration - Cached resolution for better performance - Backward compatible with existing numeric IDs Resolves issue where users had to find their numeric ID from https://t.me/getdivid or bot admin tools.
This commit is contained in:
parent
f871eb4880
commit
c2b92d0ae6
1 changed files with 64 additions and 18 deletions
|
|
@ -42,6 +42,7 @@ type TelegramChannel struct {
|
||||||
commands TelegramCommander
|
commands TelegramCommander
|
||||||
config *config.Config
|
config *config.Config
|
||||||
chatIDs map[string]int64
|
chatIDs map[string]int64
|
||||||
|
usernameCache map[string]int64 // Cache username -> chat ID resolution
|
||||||
transcriber *voice.GroqTranscriber
|
transcriber *voice.GroqTranscriber
|
||||||
placeholders sync.Map // chatID -> messageID
|
placeholders sync.Map // chatID -> messageID
|
||||||
stopThinking sync.Map // chatID -> thinkingCancel
|
stopThinking sync.Map // chatID -> thinkingCancel
|
||||||
|
|
@ -93,6 +94,7 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
|
||||||
bot: bot,
|
bot: bot,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
chatIDs: make(map[string]int64),
|
chatIDs: make(map[string]int64),
|
||||||
|
usernameCache: make(map[string]int64),
|
||||||
transcriber: nil,
|
transcriber: nil,
|
||||||
placeholders: sync.Map{},
|
placeholders: sync.Map{},
|
||||||
stopThinking: sync.Map{},
|
stopThinking: sync.Map{},
|
||||||
|
|
@ -103,6 +105,34 @@ func (c *TelegramChannel) SetTranscriber(transcriber *voice.GroqTranscriber) {
|
||||||
c.transcriber = transcriber
|
c.transcriber = transcriber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TelegramChannel) resolveUsername(ctx context.Context, username string) (int64, error) {
|
||||||
|
// Check cache first
|
||||||
|
chatID, exists := c.usernameCache[username]
|
||||||
|
if exists {
|
||||||
|
return chatID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use getChat API to resolve username to chat ID
|
||||||
|
// Username must be prefixed with @ for the API
|
||||||
|
usernameWithAt := "@" + username
|
||||||
|
chat, err := c.bot.GetChat(ctx, &telego.GetChatParams{
|
||||||
|
ChatID: tu.Username(usernameWithAt),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to resolve username @%s: %w", username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
c.usernameCache[username] = chat.ID
|
||||||
|
|
||||||
|
logger.DebugCF("telegram", "Resolved username to chat ID", map[string]any{
|
||||||
|
"username": username,
|
||||||
|
"chat_id": chat.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
return chat.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *TelegramChannel) Start(ctx context.Context) error {
|
func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||||
logger.InfoC("telegram", "Starting Telegram bot (polling mode)...")
|
logger.InfoC("telegram", "Starting Telegram bot (polling mode)...")
|
||||||
|
|
||||||
|
|
@ -164,7 +194,7 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
return fmt.Errorf("telegram bot not running")
|
return fmt.Errorf("telegram bot not running")
|
||||||
}
|
}
|
||||||
|
|
||||||
chatID, err := parseChatID(msg.ChatID)
|
chatID, err := c.parseChatID(ctx, msg.ChatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid chat ID: %w", err)
|
return fmt.Errorf("invalid chat ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -427,10 +457,26 @@ func (c *TelegramChannel) downloadFile(ctx context.Context, fileID, ext string)
|
||||||
return c.downloadFileWithInfo(file, ext)
|
return c.downloadFileWithInfo(file, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseChatID(chatIDStr string) (int64, error) {
|
func (c *TelegramChannel) parseChatID(ctx context.Context, chatIDStr string) (int64, error) {
|
||||||
|
// Strip @ prefix if present (username format)
|
||||||
|
chatIDStr = strings.TrimPrefix(chatIDStr, "@")
|
||||||
|
|
||||||
|
// Try parsing as numeric ID
|
||||||
var id int64
|
var id int64
|
||||||
_, err := fmt.Sscanf(chatIDStr, "%d", &id)
|
_, err := fmt.Sscanf(chatIDStr, "%d", &id)
|
||||||
return id, err
|
if err == nil {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not an integer, try to resolve as username
|
||||||
|
chatID, err := c.resolveUsername(ctx, chatIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("chat ID must be numeric (e.g., 123456789) or a valid username (@%s). "+
|
||||||
|
"Use your numeric user ID from https://t.me/getdivid or ensure the username is correct",
|
||||||
|
chatIDStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chatID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func markdownToTelegramHTML(text string) string {
|
func markdownToTelegramHTML(text string) string {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue