diff --git a/README.md b/README.md index 2adbef0..d19e411 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ - 发送详细的价格更新,包括当前价格、24小时变化、高低点等 - 可自定义货币对, 更新频率可自行在代码里修改 +### 虚拟币价格实时获取 +- 从币安获取所有交易对, 缓存到内存里, 每小时刷新一次 +- 当群消息触及关键词时, 会返回对应虚拟币/USDT的价格 + ### 链接拦截 - 新增: 当非管理员时, 才会进行链接拦截 - 非白名单域名链接, 在发送第二次会被拦截撤回 diff --git a/core/functions.go b/core/functions.go index 69a7554..3586fce 100644 --- a/core/functions.go +++ b/core/functions.go @@ -35,3 +35,37 @@ func DeleteMessageAfterDelay(bot *tgbotapi.BotAPI, chatID int64, messageID int, } }() } + +func SendMessage(bot *tgbotapi.BotAPI, chatID int64, text string) error { + msg := tgbotapi.NewMessage(chatID, text) + _, err := bot.Send(msg) + return err +} + +func SendErrorMessage(bot *tgbotapi.BotAPI, chatID int64, errMsg string) { + SendMessage(bot, chatID, errMsg) +} + +const ( + maxMessageLength = 4000 +) + +func SendLongMessage(bot *tgbotapi.BotAPI, chatID int64, prefix string, items []string) error { + message := prefix + "\n" + for i, item := range items { + newLine := fmt.Sprintf("%d. %s\n", i+1, item) + if len(message)+len(newLine) > maxMessageLength { + if err := SendMessage(bot, chatID, message); err != nil { + return err + } + message = "" + } + message += newLine + } + + if message != "" { + return SendMessage(bot, chatID, message) + } + + return nil +} diff --git a/service/binance/binance.go b/service/binance/binance.go index 29bfac5..0cac23e 100644 --- a/service/binance/binance.go +++ b/service/binance/binance.go @@ -118,6 +118,14 @@ func RunBinance() { symbols = core.Symbols singaporeTZ = core.SingaporeTZ + // 初始化并加载交易对 + if err := LoadSymbols(); err != nil { + log.Fatalf("Failed to load trading pairs: %v", err) + } + + // 启动每小时刷新交易对缓存 + go StartSymbolRefresh(1 * time.Hour) + // 立即发送一次价格更新 sendPriceUpdate() diff --git a/service/binance/symbols.go b/service/binance/symbols.go new file mode 100644 index 0000000..8b7ad9f --- /dev/null +++ b/service/binance/symbols.go @@ -0,0 +1,85 @@ +package binance + +import ( + "context" + "fmt" + "log" + "strings" + "sync" + "time" + + "github.com/adshao/go-binance/v2" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +var ( + symbolsMu sync.RWMutex +) + +// LoadSymbols 初始化并缓存所有交易对 +func LoadSymbols() error { + client := binance.NewClient("", "") + exchangeInfo, err := client.NewExchangeInfoService().Do(context.Background()) + if err != nil { + return err + } + + symbolsMu.Lock() + defer symbolsMu.Unlock() + + symbols = nil // 清空旧的符号列表 + for _, symbol := range exchangeInfo.Symbols { + if symbol.Status == "TRADING" && symbol.QuoteAsset == "USDT" { + symbols = append(symbols, symbol.BaseAsset) + } + } + + log.Printf("Loaded %d trading pairs", len(symbols)) + return nil +} + +// GetAllSymbols 获取缓存的交易对列表 +func GetAllSymbols() []string { + symbolsMu.RLock() + defer symbolsMu.RUnlock() + return symbols +} + +// StartSymbolRefresh 每小时刷新一次交易对缓存 +func StartSymbolRefresh(interval time.Duration) { + ticker := time.NewTicker(interval) + go func() { + for range ticker.C { + log.Println("Refreshing trading pairs...") + if err := LoadSymbols(); err != nil { + log.Printf("Failed to refresh symbols: %v", err) + } + } + }() +} + +// HandleSymbolQuery 处理虚拟币名查询 +func HandleSymbolQuery(bot *tgbotapi.BotAPI, message *tgbotapi.Message) { + // 获取所有虚拟币名 + symbols := GetAllSymbols() + + // 检查消息内容中是否包含虚拟币名 + for _, symbol := range symbols { + if strings.Contains(strings.ToUpper(message.Text), symbol) { + // 查询价格并回复 + info, err := getTickerInfo(symbol + "USDT") // 查询对应USDT价格 + if err != nil { + log.Printf("Error getting ticker info for %s: %v", symbol, err) + return + } + replyMessage := fmt.Sprintf("*%s*\n价格: $%.7f\n24h 涨跌: %s\n", + info.symbol, + info.last, + formatChange(info.changePercent)) + msg := tgbotapi.NewMessage(message.Chat.ID, replyMessage) + msg.ParseMode = "Markdown" + bot.Send(msg) + return // 找到并回复后退出 + } + } +} diff --git a/service/message_handler.go b/service/message_handler.go index 4c00ab6..14425f3 100644 --- a/service/message_handler.go +++ b/service/message_handler.go @@ -9,6 +9,7 @@ import ( tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/woodchen-ink/Q58Bot/core" + "github.com/woodchen-ink/Q58Bot/service/binance" "github.com/woodchen-ink/Q58Bot/service/group_member_management" "github.com/woodchen-ink/Q58Bot/service/link_filter" "github.com/woodchen-ink/Q58Bot/service/prompt_reply" @@ -73,7 +74,10 @@ func processMessage(bot *tgbotapi.BotAPI, message *tgbotapi.Message, linkFilter } } - // 使用现有的 CheckAndReplyPrompt 函数进行提示词回复 + // 调用 HandleSymbolQuery 处理虚拟币名查询 + binance.HandleSymbolQuery(bot, message) + + // 调用 CheckAndReplyPrompt 函数进行提示词回复 prompt_reply.CheckAndReplyPrompt(bot, message) } @@ -150,40 +154,6 @@ func RunMessageHandler() error { // // -const ( - maxMessageLength = 4000 -) - -func SendLongMessage(bot *tgbotapi.BotAPI, chatID int64, prefix string, items []string) error { - message := prefix + "\n" - for i, item := range items { - newLine := fmt.Sprintf("%d. %s\n", i+1, item) - if len(message)+len(newLine) > maxMessageLength { - if err := sendMessage(bot, chatID, message); err != nil { - return err - } - message = "" - } - message += newLine - } - - if message != "" { - return sendMessage(bot, chatID, message) - } - - return nil -} - -func sendMessage(bot *tgbotapi.BotAPI, chatID int64, text string) error { - msg := tgbotapi.NewMessage(chatID, text) - _, err := bot.Send(msg) - return err -} - -func sendErrorMessage(bot *tgbotapi.BotAPI, chatID int64, errMsg string) { - sendMessage(bot, chatID, errMsg) -} - func HandleKeywordCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, command string, args string) { args = strings.TrimSpace(args) @@ -197,60 +167,60 @@ func HandleKeywordCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, comma case "deletecontaining": handleDeleteContainingKeyword(bot, message, args) default: - sendErrorMessage(bot, message.Chat.ID, "无效的命令或参数。") + core.SendErrorMessage(bot, message.Chat.ID, "无效的命令或参数。") } } func handleListKeywords(bot *tgbotapi.BotAPI, message *tgbotapi.Message) { keywords, err := core.DB.GetAllKeywords() if err != nil { - sendErrorMessage(bot, message.Chat.ID, "获取关键词列表时发生错误。") + core.SendErrorMessage(bot, message.Chat.ID, "获取关键词列表时发生错误。") return } if len(keywords) == 0 { - sendMessage(bot, message.Chat.ID, "关键词列表为空。") + core.SendMessage(bot, message.Chat.ID, "关键词列表为空。") } else { - SendLongMessage(bot, message.Chat.ID, "当前关键词列表:", keywords) + core.SendLongMessage(bot, message.Chat.ID, "当前关键词列表:", keywords) } } func handleAddKeyword(bot *tgbotapi.BotAPI, message *tgbotapi.Message, keyword string) { if keyword == "" { - sendErrorMessage(bot, message.Chat.ID, "请提供要添加的关键词。") + core.SendErrorMessage(bot, message.Chat.ID, "请提供要添加的关键词。") return } exists, err := core.DB.KeywordExists(keyword) if err != nil { - sendErrorMessage(bot, message.Chat.ID, "检查关键词时发生错误。") + core.SendErrorMessage(bot, message.Chat.ID, "检查关键词时发生错误。") return } if !exists { err = core.DB.AddKeyword(keyword) if err != nil { - sendErrorMessage(bot, message.Chat.ID, "添加关键词时发生错误。") + core.SendErrorMessage(bot, message.Chat.ID, "添加关键词时发生错误。") } else { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("关键词 '%s' 已添加。", keyword)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("关键词 '%s' 已添加。", keyword)) } } else { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("关键词 '%s' 已存在。", keyword)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("关键词 '%s' 已存在。", keyword)) } } func handleDeleteKeyword(bot *tgbotapi.BotAPI, message *tgbotapi.Message, keyword string) { if keyword == "" { - sendErrorMessage(bot, message.Chat.ID, "请提供要删除的关键词。") + core.SendErrorMessage(bot, message.Chat.ID, "请提供要删除的关键词。") return } removed, err := core.DB.RemoveKeyword(keyword) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("删除关键词 '%s' 时发生错误: %v", keyword, err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("删除关键词 '%s' 时发生错误: %v", keyword, err)) return } if removed { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("关键词 '%s' 已成功删除。", keyword)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("关键词 '%s' 已成功删除。", keyword)) } else { handleSimilarKeywords(bot, message, keyword) } @@ -259,31 +229,31 @@ func handleDeleteKeyword(bot *tgbotapi.BotAPI, message *tgbotapi.Message, keywor func handleSimilarKeywords(bot *tgbotapi.BotAPI, message *tgbotapi.Message, keyword string) { similarKeywords, err := core.DB.SearchKeywords(keyword) if err != nil { - sendErrorMessage(bot, message.Chat.ID, "搜索关键词时发生错误。") + core.SendErrorMessage(bot, message.Chat.ID, "搜索关键词时发生错误。") return } if len(similarKeywords) > 0 { - SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("未能删除关键词 '%s'。\n\n以下是相似的关键词:", keyword), similarKeywords) + core.SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("未能删除关键词 '%s'。\n\n以下是相似的关键词:", keyword), similarKeywords) } else { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("未能删除关键词 '%s',且未找到相似的关键词。", keyword)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("未能删除关键词 '%s',且未找到相似的关键词。", keyword)) } } func handleDeleteContainingKeyword(bot *tgbotapi.BotAPI, message *tgbotapi.Message, substring string) { if substring == "" { - sendErrorMessage(bot, message.Chat.ID, "请提供要删除的子字符串。") + core.SendErrorMessage(bot, message.Chat.ID, "请提供要删除的子字符串。") return } removedKeywords, err := core.DB.RemoveKeywordsContaining(substring) if err != nil { - sendErrorMessage(bot, message.Chat.ID, "删除关键词时发生错误。") + core.SendErrorMessage(bot, message.Chat.ID, "删除关键词时发生错误。") return } if len(removedKeywords) > 0 { - SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("已删除包含 '%s' 的以下关键词:", substring), removedKeywords) + core.SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("已删除包含 '%s' 的以下关键词:", substring), removedKeywords) } else { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("没有找到包含 '%s' 的关键词。", substring)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("没有找到包含 '%s' 的关键词。", substring)) } } @@ -298,90 +268,90 @@ func HandleWhitelistCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, com case "delwhite": handleDeleteWhitelist(bot, message, args) default: - sendErrorMessage(bot, message.Chat.ID, "无效的命令或参数。") + core.SendErrorMessage(bot, message.Chat.ID, "无效的命令或参数。") } } func handleListWhitelist(bot *tgbotapi.BotAPI, message *tgbotapi.Message) { whitelist, err := core.DB.GetAllWhitelist() if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("获取白名单时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("获取白名单时发生错误: %v", err)) return } if len(whitelist) == 0 { - sendMessage(bot, message.Chat.ID, "白名单为空。") + core.SendMessage(bot, message.Chat.ID, "白名单为空。") } else { - SendLongMessage(bot, message.Chat.ID, "白名单域名列表:", whitelist) + core.SendLongMessage(bot, message.Chat.ID, "白名单域名列表:", whitelist) } } func handleAddWhitelist(bot *tgbotapi.BotAPI, message *tgbotapi.Message, domain string) { if domain == "" { - sendErrorMessage(bot, message.Chat.ID, "请提供要添加的域名。") + core.SendErrorMessage(bot, message.Chat.ID, "请提供要添加的域名。") return } domain = strings.ToLower(domain) exists, err := core.DB.WhitelistExists(domain) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("检查白名单时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("检查白名单时发生错误: %v", err)) return } if exists { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 已在白名单中。", domain)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 已在白名单中。", domain)) return } err = core.DB.AddWhitelist(domain) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("添加到白名单时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("添加到白名单时发生错误: %v", err)) return } exists, err = core.DB.WhitelistExists(domain) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("验证添加操作时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("验证添加操作时发生错误: %v", err)) return } if exists { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 已成功添加到白名单。", domain)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 已成功添加到白名单。", domain)) } else { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("未能添加域名 '%s' 到白名单。", domain)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("未能添加域名 '%s' 到白名单。", domain)) } } func handleDeleteWhitelist(bot *tgbotapi.BotAPI, message *tgbotapi.Message, domain string) { if domain == "" { - sendErrorMessage(bot, message.Chat.ID, "请提供要删除的域名。") + core.SendErrorMessage(bot, message.Chat.ID, "请提供要删除的域名。") return } domain = strings.ToLower(domain) exists, err := core.DB.WhitelistExists(domain) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("检查白名单时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("检查白名单时发生错误: %v", err)) return } if !exists { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 不在白名单中。", domain)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 不在白名单中。", domain)) return } err = core.DB.RemoveWhitelist(domain) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("从白名单删除时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("从白名单删除时发生错误: %v", err)) return } exists, err = core.DB.WhitelistExists(domain) if err != nil { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("验证删除操作时发生错误: %v", err)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("验证删除操作时发生错误: %v", err)) return } if !exists { - sendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 已成功从白名单中删除。", domain)) + core.SendMessage(bot, message.Chat.ID, fmt.Sprintf("域名 '%s' 已成功从白名单中删除。", domain)) } else { - sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("未能从白名单中删除域名 '%s'。", domain)) + core.SendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("未能从白名单中删除域名 '%s'。", domain)) } }