整体构架重构

This commit is contained in:
wood chen 2024-09-19 21:58:27 +08:00
parent a12a9b789b
commit 4291640190
17 changed files with 873 additions and 718 deletions

View File

@ -7,18 +7,8 @@
![image](https://github.com/user-attachments/assets/57017af9-7ec1-41c6-b287-a8b2decd60f8)
## 项目简介
## 项目功能
这个项目主要功能:
1. TeleGuard一个 Telegram 机器人,用于管理群组中的关键词并自动删除包含这些关键词的消息。
2. 币安价格更新器:定期获取并发送指定加密货币的价格信息。
3. 链接拦截:拦截并撤回非白名单域名链接的第二次发送, 这里不去掉查询参数, 但是去掉头部的http协议。
这些功能被整合到一个 Docker 容器中,可以同时运行。
## 功能特点
### TeleGuard
- 自动删除包含指定关键词的消息
@ -28,44 +18,43 @@
### 币安价格更新器
- 定期获取指定加密货币的价格信息
- 发送详细的价格更新包括当前价格、24小时变化、高低点等
- 可自定义更新频率和货币对
- 可自定义货币对, 更新频率可自行在代码里修改
### 链接拦截
- 新增: 当非管理员时, 才会进行链接拦截
- 非白名单域名链接, 在发送第二次会被拦截撤回
### 白名单域名
- 当用户发送链接, 属于白名单域名, 则不进行操作. 如果不属于白名单域名, 则会第一次允许发送, 第二次进行撤回操作.
- 会匹配链接中的域名, 包括二级域名和三级域名
- 例如,如果白名单中有 "example.com",它将匹配 "example.com"、"sub.example.com" 和 "sub.sub.example.com"。
- 同时,如果白名单中有 "sub.example.com",它将匹配 "sub.example.com" 和 "subsub.sub.example.com",但不会匹配 "example.com" 或 "othersub.example.com"。
### 提示词自动回复
- 当用户发送包含特定关键词的消息时,机器人将自动回复提示词。
- 管理员通过`/prompt`进行设置, 支持添加, 删除, 列出.
### 群组快捷管理
- 管理员可以对成员消息回复`/ban`, 会进行以下处理:
1. 将成员消息撤回, 无限期封禁成员, 并发送封禁通知
2. 在3分钟后, 撤回管理员指令消息和机器人的封禁通知
## 安装与配置
1. 克隆此仓库到本地
2. 确保已安装 Docker 和 Docker Compose
3. 使用 `docker-compose.yml` 文件构建和启动容器
1. 确保服务器已安装 Docker 和 Docker Compose
2. 使用 `docker-compose.yml` 文件构建和启动容器
## 使用方法
1. 构建并启动 Docker 容器:
```
docker-compose up -d
```
2. 查看日志:
```
docker-compose logs -f
```
3. TeleGuard 命令:
- `/add 关键词`:添加新的关键词
- `/delete 关键词`:删除现有的关键词
- `/list`:列出所有当前的关键词
构建并启动 Docker 容器:
```
docker-compose up -d
```
## 注意事项
- 确保 Telegram 机器人已被添加到目标群组,并被赋予管理员权限
- 币安 API 可能有请求限制,请注意控制请求频率
- 定期检查日志以确保服务正常运行
## 贡献

View File

@ -1,5 +1,6 @@
package core
// 注册命令
import (
"fmt"
"log"
@ -28,9 +29,9 @@ func RegisterCommands(bot *tgbotapi.BotAPI) error {
_, err := bot.Request(config)
if err != nil {
return fmt.Errorf("failed to register bot commands: %w", err)
return fmt.Errorf("注册机器人命令失败: %w", err)
}
log.Println("Bot commands registered successfully.")
log.Println("机器人命令注册成功。")
return nil
}

View File

@ -1,28 +0,0 @@
package core
import (
"os"
"path/filepath"
)
var (
BOT_TOKEN string
ADMIN_ID int64
DB_FILE string
DEBUG_MODE bool
)
func InitGlobalVariables(botToken string, adminID int64) {
BOT_TOKEN = botToken
ADMIN_ID = adminID
// 设置数据库文件路径
DB_FILE = filepath.Join("/app/data", "q58.db")
// 从环境变量中读取调试模式设置
DEBUG_MODE = os.Getenv("DEBUG_MODE") == "true"
}
func IsAdmin(userID int64) bool {
return userID == ADMIN_ID
}

View File

@ -1,5 +1,6 @@
package core
//数据库处理
import (
"database/sql"
"os"

View File

@ -1,90 +0,0 @@
package core
import (
"fmt"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
// SendLongMessage sends a long message by splitting it into multiple messages if necessary
// SendLongMessage sends a long message by splitting it into multiple messages if necessary
func SendLongMessage(bot *tgbotapi.BotAPI, chatID int64, prefix string, items []string) error {
const maxMessageLength = 4000 // Leave some room for Telegram's message limit
message := prefix + "\n"
for i, item := range items {
newLine := fmt.Sprintf("%d. %s\n", i+1, item)
if len(message)+len(newLine) > maxMessageLength {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
return err
}
message = ""
}
message += newLine
}
if message != "" {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
return err
}
}
return nil
}
// SendLongMessageWithoutNumbering sends a long message without numbering the items
func SendLongMessageWithoutNumbering(bot *tgbotapi.BotAPI, chatID int64, prefix string, items []string) error {
const maxMessageLength = 4000 // Leave some room for Telegram's message limit
message := prefix + "\n"
for _, item := range items {
newLine := item + "\n"
if len(message)+len(newLine) > maxMessageLength {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
return err
}
message = ""
}
message += newLine
}
if message != "" {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
return err
}
}
return nil
}
// JoinLongMessage joins items into a single long message, splitting it if necessary
func JoinLongMessage(prefix string, items []string) []string {
const maxMessageLength = 4000 // Leave some room for Telegram's message limit
var messages []string
message := prefix + "\n"
for i, item := range items {
newLine := fmt.Sprintf("%d. %s\n", i+1, item)
if len(message)+len(newLine) > maxMessageLength {
messages = append(messages, strings.TrimSpace(message))
message = ""
}
message += newLine
}
if message != "" {
messages = append(messages, strings.TrimSpace(message))
}
return messages
}

84
core/init.go Normal file
View File

@ -0,0 +1,84 @@
package core
import (
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var (
Bot *tgbotapi.BotAPI
BOT_TOKEN string
ChatID int64
ADMIN_ID int64
Symbols []string
SingaporeTZ *time.Location
DB_FILE string
DEBUG_MODE bool
err error
)
func IsAdmin(userID int64) bool {
return userID == ADMIN_ID
}
func mustParseInt64(s string) (int64, error) {
if s == "" {
return 0, fmt.Errorf("空字符串")
}
value, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, fmt.Errorf("未能将'%s'解析为 int64: %v", s, err)
}
return value, nil
}
func Init(botToken string, adminID int64) error {
// 设置数据库文件路径
DB_FILE = filepath.Join("/app/data", "q58.db")
// 从环境变量中读取调试模式设置
DEBUG_MODE = os.Getenv("DEBUG_MODE") == "true"
// 设置时区
loc := time.FixedZone("Asia/Singapore", 8*60*60)
time.Local = loc
// 初始化 Chat ID
chatIDStr := os.Getenv("CHAT_ID")
ChatID, err = mustParseInt64(chatIDStr)
if err != nil {
log.Fatalf("Invalid CHAT_ID: %v", err)
}
// 初始化 Symbols
symbolsRaw := strings.Split(os.Getenv("SYMBOLS"), ",")
Symbols = make([]string, len(symbolsRaw))
for i, s := range symbolsRaw {
Symbols[i] = strings.ReplaceAll(s, "/", "")
}
// 初始化新加坡时区
SingaporeTZ, err = time.LoadLocation("Asia/Singapore")
if err != nil {
log.Printf("加载新加坡时区时出错: %v", err)
log.Println("回落至 UTC+8")
SingaporeTZ = time.FixedZone("Asia/Singapore", 8*60*60)
}
// 初始化 Bot API
Bot, err = tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Fatal(err)
}
log.Printf("账户已授权 %s", Bot.Self.UserName)
return nil
}

41
core/ratelimiter.go Normal file
View File

@ -0,0 +1,41 @@
package core
import (
"sync"
"time"
)
// 为了简单, 直接把速率限制写死在这里
const (
maxCalls = 20
period = time.Second
)
type RateLimiter struct {
mu sync.Mutex
calls []time.Time
}
func NewRateLimiter() *RateLimiter {
return &RateLimiter{
calls: make([]time.Time, 0, maxCalls),
}
}
func (r *RateLimiter) Allow() bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
if len(r.calls) < maxCalls {
r.calls = append(r.calls, now)
return true
}
if now.Sub(r.calls[0]) >= period {
r.calls = append(r.calls[1:], now)
return true
}
return false
}

15
main.go
View File

@ -5,26 +5,33 @@ import (
"os"
"strconv"
"github.com/woodchen-ink/Q58Bot/core"
"github.com/woodchen-ink/Q58Bot/service"
"github.com/woodchen-ink/Q58Bot/service/binance"
)
func main() {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
botToken := os.Getenv("BOT_TOKEN")
adminIDStr := os.Getenv("ADMIN_ID")
adminID, err := strconv.ParseInt(adminIDStr, 10, 64)
if err != nil {
log.Fatalf("Invalid ADMIN_ID: %v", err)
log.Fatalf("Failed to get ADMIN_ID: %v", err)
}
err = service.Init(botToken, adminID)
err = core.Init(botToken, adminID)
if err != nil {
log.Fatalf("Failed to initialize service: %v", err)
}
go service.RunGuard()
go service.RunBinance()
err = service.RunMessageHandler()
if err != nil {
log.Fatalf("Error in RunMessageHandler: %v", err)
}
go binance.RunBinance()
select {}
}

View File

@ -1,12 +1,11 @@
package service
package binance
//币安价格推送
import (
"context"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/adshao/go-binance/v2"
@ -22,38 +21,6 @@ var (
singaporeTZ *time.Location
)
func init() {
var err error
botToken = os.Getenv("BOT_TOKEN")
chatID = mustParseInt64(os.Getenv("CHAT_ID"))
symbolsRaw := strings.Split(os.Getenv("SYMBOLS"), ",")
symbols = make([]string, len(symbolsRaw))
for i, s := range symbolsRaw {
symbols[i] = strings.ReplaceAll(s, "/", "")
}
singaporeTZ, err = time.LoadLocation("Asia/Singapore")
if err != nil {
log.Printf("Error loading Singapore time zone: %v", err)
log.Println("Falling back to UTC+8")
singaporeTZ = time.FixedZone("Asia/Singapore", 8*60*60)
}
bot, err = tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Fatal(err)
}
}
func mustParseInt64(s string) int64 {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
log.Fatal(err)
}
return i
}
type tickerInfo struct {
symbol string
last float64

View File

@ -0,0 +1,70 @@
package group_member_management
import (
"fmt"
"log"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/woodchen-ink/Q58Bot/core"
)
func HandleBanCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) {
// 检查是否是管理员
if !core.IsAdmin(message.From.ID) {
return
}
// 检查是否是回复消息
if message.ReplyToMessage == nil {
return
}
chatID := message.Chat.ID
userToBan := message.ReplyToMessage.From
// 立即删除被回复的原消息
deleteConfig := tgbotapi.NewDeleteMessage(chatID, message.ReplyToMessage.MessageID)
_, err := bot.Request(deleteConfig)
if err != nil {
log.Printf("删除原消息时出错: %v", err)
}
// 踢出用户
kickChatMemberConfig := tgbotapi.KickChatMemberConfig{
ChatMemberConfig: tgbotapi.ChatMemberConfig{
ChatID: chatID,
UserID: userToBan.ID,
},
UntilDate: 0, // 0 means ban forever
}
_, err = bot.Request(kickChatMemberConfig)
if err != nil {
log.Printf("禁止用户时出错: %v", err)
return
}
// 发送提示消息
banMessage := fmt.Sprintf("用户 %s 已被封禁并踢出群组。", userToBan.UserName)
msg := tgbotapi.NewMessage(chatID, banMessage)
sentMsg, err := bot.Send(msg)
if err != nil {
log.Printf("发送禁止消息时出错: %v", err)
return
}
// 3分钟后删除机器人的消息和管理员的指令消息
go deleteMessagesAfterDelay(bot, chatID, []int{sentMsg.MessageID, message.MessageID}, 3*time.Minute)
}
func deleteMessagesAfterDelay(bot *tgbotapi.BotAPI, chatID int64, messageIDs []int, delay time.Duration) {
time.Sleep(delay)
for _, msgID := range messageIDs {
deleteConfig := tgbotapi.NewDeleteMessage(chatID, msgID)
_, err := bot.Request(deleteConfig)
if err != nil {
log.Printf("删除消息 %d 时出错: %v", msgID, err)
}
}
}

View File

@ -1,185 +0,0 @@
package service
import (
"fmt"
"log"
"sync"
"time"
"github.com/woodchen-ink/Q58Bot/core"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type RateLimiter struct {
mu sync.Mutex
maxCalls int
period time.Duration
calls []time.Time
}
func NewRateLimiter(maxCalls int, period time.Duration) *RateLimiter {
return &RateLimiter{
maxCalls: maxCalls,
period: period,
calls: make([]time.Time, 0, maxCalls),
}
}
func (r *RateLimiter) Allow() bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
if len(r.calls) < r.maxCalls {
r.calls = append(r.calls, now)
return true
}
if now.Sub(r.calls[0]) >= r.period {
r.calls = append(r.calls[1:], now)
return true
}
return false
}
func deleteMessageAfterDelay(bot *tgbotapi.BotAPI, chatID int64, messageID int, delay time.Duration) {
time.Sleep(delay)
deleteMsg := tgbotapi.NewDeleteMessage(chatID, messageID)
_, err := bot.Request(deleteMsg)
if err != nil {
log.Printf("Failed to delete message: %v", err)
}
}
func RunGuard() {
baseDelay := time.Second
maxDelay := 5 * time.Minute
delay := baseDelay
for {
err := startBot()
if err != nil {
log.Printf("Bot encountered an error: %v", err)
log.Printf("Attempting to restart in %v...", delay)
time.Sleep(delay)
delay *= 2
if delay > maxDelay {
delay = maxDelay
}
} else {
delay = baseDelay
log.Println("Bot disconnected. Attempting to restart immediately...")
}
}
}
func startBot() error {
bot, err := tgbotapi.NewBotAPI(core.BOT_TOKEN)
if err != nil {
return fmt.Errorf("failed to create bot: %w", err)
}
bot.Debug = core.DEBUG_MODE
log.Printf("Authorized on account %s", bot.Self.UserName)
err = core.RegisterCommands(bot)
if err != nil {
return fmt.Errorf("error registering commands: %w", err)
}
linkFilter, err := NewLinkFilter()
if err != nil {
return fmt.Errorf("failed to create LinkFilter: %v", err)
}
rateLimiter := NewRateLimiter(10, time.Second)
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
for update := range updates {
go handleUpdate(bot, update, linkFilter, rateLimiter)
}
return nil
}
func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update, linkFilter *LinkFilter, rateLimiter *RateLimiter) {
if update.Message == nil {
return
}
if update.Message.Chat.Type == "private" && update.Message.From.ID == core.ADMIN_ID {
handleAdminCommand(bot, update.Message, linkFilter)
return
}
if update.Message.Chat.Type != "private" && rateLimiter.Allow() {
processMessage(bot, update.Message, linkFilter)
}
}
func handleAdminCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, linkFilter *LinkFilter) {
command := message.Command()
args := message.CommandArguments()
switch command {
case "add", "delete", "list", "deletecontaining":
linkFilter.HandleKeywordCommand(bot, message, command, args)
case "addwhite", "delwhite", "listwhite":
linkFilter.HandleWhitelistCommand(bot, message, command, args)
case "prompt":
HandlePromptCommand(bot, message)
default:
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "未知命令"))
}
}
func processMessage(bot *tgbotapi.BotAPI, message *tgbotapi.Message, linkFilter *LinkFilter) {
log.Printf("Processing message: %s", message.Text)
shouldFilter, newLinks := linkFilter.ShouldFilter(message.Text)
if shouldFilter {
log.Printf("Message should be filtered: %s", message.Text)
if message.From.ID != core.ADMIN_ID {
// 删除原始消息
deleteMsg := tgbotapi.NewDeleteMessage(message.Chat.ID, message.MessageID)
_, err := bot.Request(deleteMsg)
if err != nil {
log.Printf("Failed to delete message: %v", err)
}
// 发送提示消息
notification := tgbotapi.NewMessage(message.Chat.ID, "已撤回该消息。注:一个链接不能发两次.")
sent, err := bot.Send(notification)
if err != nil {
log.Printf("Failed to send notification: %v", err)
} else {
// 3分钟后删除提示消息
go deleteMessageAfterDelay(bot, message.Chat.ID, sent.MessageID, 3*time.Minute)
}
}
return
}
if len(newLinks) > 0 {
log.Printf("New non-whitelisted links found: %v", newLinks)
}
// 检查并回复提示词
if reply, found := GetPromptReply(message.Text); found {
replyMsg := tgbotapi.NewMessage(message.Chat.ID, reply)
replyMsg.ReplyToMessageID = message.MessageID
sent, err := bot.Send(replyMsg)
if err != nil {
log.Printf("Failed to send prompt reply: %v", err)
} else {
// 3分钟后删除提示词回复
go deleteMessageAfterDelay(bot, message.Chat.ID, sent.MessageID, 3*time.Minute)
}
}
}

View File

@ -1,23 +0,0 @@
package service
import (
"time"
"github.com/woodchen-ink/Q58Bot/core"
)
func Init(botToken string, adminID int64) error {
core.InitGlobalVariables(botToken, adminID)
// 初始化提示词服务
err := InitPromptService()
if err != nil {
return err
}
// 设置时区
loc := time.FixedZone("Asia/Singapore", 8*60*60)
time.Local = loc
return nil
}

View File

@ -1,311 +0,0 @@
package service
import (
"fmt"
"log"
"net/url"
"regexp"
"strings"
"github.com/woodchen-ink/Q58Bot/core"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var logger = log.New(log.Writer(), "LinkFilter: ", log.Ldate|log.Ltime|log.Lshortfile)
type LinkFilter struct {
db *core.Database
keywords []string
whitelist []string
linkPattern *regexp.Regexp
}
func NewLinkFilter() (*LinkFilter, error) {
db, err := core.NewDatabase()
if err != nil {
return nil, err
}
lf := &LinkFilter{
db: db,
}
lf.linkPattern = regexp.MustCompile(`(?i)\b(?:(?:https?://)?(?:(?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:t\.me|telegram\.me))(?:/[^\s]*)?)`)
err = lf.LoadDataFromFile()
if err != nil {
return nil, err
}
return lf, nil
}
func (lf *LinkFilter) LoadDataFromFile() error {
var err error
lf.keywords, err = lf.db.GetAllKeywords()
if err != nil {
return err
}
lf.whitelist, err = lf.db.GetAllWhitelist()
if err != nil {
return err
}
logger.Printf("Loaded %d keywords and %d whitelist entries from database", len(lf.keywords), len(lf.whitelist))
return nil
}
func (lf *LinkFilter) NormalizeLink(link string) string {
link = regexp.MustCompile(`^https?://`).ReplaceAllString(link, "")
link = strings.TrimPrefix(link, "/")
parsedURL, err := url.Parse("http://" + link)
if err != nil {
logger.Printf("Error parsing URL: %v", err)
return link
}
normalized := fmt.Sprintf("%s%s", parsedURL.Hostname(), parsedURL.EscapedPath())
if parsedURL.RawQuery != "" {
normalized += "?" + parsedURL.RawQuery
}
result := strings.TrimSuffix(normalized, "/")
logger.Printf("Normalized link: %s -> %s", link, result)
return result
}
func (lf *LinkFilter) ExtractDomain(urlStr string) string {
parsedURL, err := url.Parse(urlStr)
if err != nil {
logger.Printf("Error parsing URL: %v", err)
return urlStr
}
return strings.ToLower(parsedURL.Hostname())
}
func (lf *LinkFilter) domainMatch(domain, whiteDomain string) bool {
domainParts := strings.Split(domain, ".")
whiteDomainParts := strings.Split(whiteDomain, ".")
if len(domainParts) < len(whiteDomainParts) {
return false
}
for i := 1; i <= len(whiteDomainParts); i++ {
if domainParts[len(domainParts)-i] != whiteDomainParts[len(whiteDomainParts)-i] {
return false
}
}
return true
}
func (lf *LinkFilter) IsWhitelisted(link string) bool {
domain := lf.ExtractDomain(link)
for _, whiteDomain := range lf.whitelist {
if lf.domainMatch(domain, whiteDomain) {
logger.Printf("Whitelist check for %s: Passed (matched %s)", link, whiteDomain)
return true
}
}
logger.Printf("Whitelist check for %s: Failed", link)
return false
}
func (lf *LinkFilter) AddKeyword(keyword string) error {
if lf.linkPattern.MatchString(keyword) {
keyword = lf.NormalizeLink(keyword)
}
keyword = strings.TrimPrefix(keyword, "/")
for _, k := range lf.keywords {
if k == keyword {
logger.Printf("Keyword already exists: %s", keyword)
return nil
}
}
err := lf.db.AddKeyword(keyword)
if err != nil {
return err
}
logger.Printf("New keyword added: %s", keyword)
return lf.LoadDataFromFile()
}
func (lf *LinkFilter) RemoveKeyword(keyword string) bool {
for _, k := range lf.keywords {
if k == keyword {
lf.db.RemoveKeyword(keyword)
lf.LoadDataFromFile()
return true
}
}
return false
}
func (lf *LinkFilter) RemoveKeywordsContaining(substring string) ([]string, error) {
removed, err := lf.db.RemoveKeywordsContaining(substring)
if err != nil {
return nil, err
}
err = lf.LoadDataFromFile()
if err != nil {
return nil, err
}
return removed, nil
}
func (lf *LinkFilter) ShouldFilter(text string) (bool, []string) {
logger.Printf("Checking text: %s", text)
for _, keyword := range lf.keywords {
if strings.Contains(strings.ToLower(text), strings.ToLower(keyword)) {
logger.Printf("Text contains keyword: %s", text)
return true, nil
}
}
links := lf.linkPattern.FindAllString(text, -1)
logger.Printf("Found links: %v", links)
var newNonWhitelistedLinks []string
for _, link := range links {
normalizedLink := lf.NormalizeLink(link)
if !lf.IsWhitelisted(normalizedLink) {
logger.Printf("Link not whitelisted: %s", normalizedLink)
found := false
for _, keyword := range lf.keywords {
if keyword == normalizedLink {
logger.Printf("Existing keyword found: %s", normalizedLink)
return true, nil
}
}
if !found {
newNonWhitelistedLinks = append(newNonWhitelistedLinks, normalizedLink)
lf.AddKeyword(normalizedLink)
}
}
}
if len(newNonWhitelistedLinks) > 0 {
logger.Printf("New non-whitelisted links found: %v", newNonWhitelistedLinks)
}
return false, newNonWhitelistedLinks
}
func (lf *LinkFilter) HandleKeywordCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, command string, args string) {
switch command {
case "list":
keywords, err := lf.db.GetAllKeywords()
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "获取关键词列表时发生错误。"))
return
}
if len(keywords) == 0 {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "关键词列表为空。"))
} else {
core.SendLongMessage(bot, message.Chat.ID, "当前关键词列表:", keywords)
}
case "add":
if args != "" {
keyword := args
exists, err := lf.db.KeywordExists(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "检查关键词时发生错误。"))
return
}
if !exists {
err = lf.AddKeyword(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "添加关键词时发生错误。"))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 已添加。", keyword)))
}
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 已存在。", keyword)))
}
}
case "delete":
if args != "" {
keyword := args
if lf.RemoveKeyword(keyword) {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 已删除。", keyword)))
} else {
similarKeywords, err := lf.db.SearchKeywords(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "搜索关键词时发生错误。"))
return
}
if len(similarKeywords) > 0 {
core.SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("未找到精确匹配的关键词 '%s'。\n\n以下是相似的关键词", keyword), similarKeywords)
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 不存在。", keyword)))
}
}
}
case "deletecontaining":
if args != "" {
substring := args
removedKeywords, err := lf.RemoveKeywordsContaining(substring)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "删除关键词时发生错误。"))
return
}
if len(removedKeywords) > 0 {
core.SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("已删除包含 '%s' 的以下关键词:", substring), removedKeywords)
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("没有找到包含 '%s' 的关键词。", substring)))
}
}
default:
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "无效的命令或参数。"))
}
}
func (lf *LinkFilter) HandleWhitelistCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, command string, args string) {
switch command {
case "listwhite":
whitelist, err := lf.db.GetAllWhitelist()
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "获取白名单时发生错误。"))
return
}
if len(whitelist) == 0 {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "白名单为空。"))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "白名单域名列表:\n"+strings.Join(whitelist, "\n")))
}
case "addwhite":
if args != "" {
domain := strings.ToLower(args)
exists, err := lf.db.WhitelistExists(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "检查白名单时发生错误。"))
return
}
if !exists {
err = lf.db.AddWhitelist(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "添加到白名单时发生错误。"))
return
}
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 已添加到白名单。", domain)))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 已在白名单中。", domain)))
}
}
case "delwhite":
if args != "" {
domain := strings.ToLower(args)
exists, err := lf.db.WhitelistExists(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "检查白名单时发生错误。"))
return
}
if exists {
err = lf.db.RemoveWhitelist(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "从白名单删除时发生错误。"))
return
}
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 已从白名单中删除。", domain)))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 不在白名单中。", domain)))
}
}
default:
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "无效的命令或参数。"))
}
}

View File

@ -0,0 +1,240 @@
package link_filter
// 链接处理
import (
"fmt"
"log"
"net/url"
"regexp"
"strings"
"github.com/woodchen-ink/Q58Bot/core"
)
var logger = log.New(log.Writer(), "LinkFilter: ", log.Ldate|log.Ltime|log.Lshortfile)
type LinkFilter struct {
db *core.Database
keywords []string
whitelist []string
linkPattern *regexp.Regexp
}
// NewLinkFilter 创建一个新的LinkFilter实例。这个实例用于过滤链接且在创建时会初始化数据库连接和链接过滤正则表达式。
// 它首先尝试创建一个数据库连接然后加载链接过滤所需的配置最后返回一个包含所有初始化设置的LinkFilter实例。
// 如果在任何步骤中发生错误错误将被返回LinkFilter实例将不会被创建。
func NewLinkFilter() (*LinkFilter, error) {
// 初始化数据库连接
db, err := core.NewDatabase()
if err != nil {
return nil, err
}
// 创建LinkFilter实例
lf := &LinkFilter{
db: db,
}
// 编译链接过滤正则表达式
lf.linkPattern = regexp.MustCompile(`(?i)\b(?:(?:https?://)?(?:(?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:t\.me|telegram\.me))(?:/[^\s]*)?)`)
// 从文件中加载额外的链接过滤数据
err = lf.LoadDataFromFile()
if err != nil {
return nil, err
}
return lf, nil
}
// LoadDataFromFile 从文件中加载数据到 LinkFilter 结构体的 keywords 和 whitelist 字段。
// 它首先从数据库中获取所有的关键词和白名单条目,如果数据库操作出现错误,它会立即返回错误。
// 一旦数据成功加载,它会通过日志记录加载的关键词和白名单条目的数量。
// 参数: 无
// 返回值: 如果加载过程中发生错误,返回该错误;否则返回 nil。
func (lf *LinkFilter) LoadDataFromFile() error {
// 从数据库中加载所有关键词到 lf.keywords
var err error
lf.keywords, err = lf.db.GetAllKeywords()
if err != nil {
// 如果发生错误,立即返回
return err
}
// 从数据库中加载所有白名单条目到 lf.whitelist
lf.whitelist, err = lf.db.GetAllWhitelist()
if err != nil {
// 如果发生错误,立即返回
return err
}
// 记录成功加载的关键词和白名单条目的数量
logger.Printf("Loaded %d keywords and %d whitelist entries from database", len(lf.keywords), len(lf.whitelist))
// 数据加载成功,返回 nil
return nil
}
// NormalizeLink 标准化链接地址。
//
// 该函数接受一个链接字符串,对其进行标准化处理,并返回处理后的链接。
// 标准化过程包括移除协议头http或https、TrimPrefix去除链接中的斜杠、
// 解析URL以获取主机名和路径、将查询参数附加到URL末尾。
//
// 参数:
//
// link - 需要被标准化的链接字符串。
//
// 返回值:
//
// 标准化后的链接字符串。
func (lf *LinkFilter) NormalizeLink(link string) string {
// 移除链接中的协议头http或https
link = regexp.MustCompile(`^https?://`).ReplaceAllString(link, "")
// 去除链接中的斜杠
link = strings.TrimPrefix(link, "/")
// 解析URL此处默认使用http协议因为协议头部已被移除
parsedURL, err := url.Parse("http://" + link)
if err != nil {
// 如果URL解析失败记录错误信息并返回原始链接
logger.Printf("Error parsing URL: %v", err)
return link
}
// 构建标准化的URL包含主机名和转义后的路径
normalized := fmt.Sprintf("%s%s", parsedURL.Hostname(), parsedURL.EscapedPath())
// 如果URL有查询参数将其附加到标准化的URL后面
if parsedURL.RawQuery != "" {
normalized += "?" + parsedURL.RawQuery
}
// 移除标准化URL末尾的斜杠如果有
result := strings.TrimSuffix(normalized, "/")
// 记录标准化后的链接信息
logger.Printf("Normalized link: %s -> %s", link, result)
return result
}
// ExtractDomain 从给定的URL字符串中提取域名。
// 该函数首先解析URL字符串然后返回解析得到的主机名同时将其转换为小写。
// 如果URL解析失败错误信息将被记录并且函数会返回原始的URL字符串。
// 参数:
//
// urlStr - 待处理的URL字符串。
//
// 返回值:
//
// 解析后的主机名如果解析失败则返回原始的URL字符串。
func (lf *LinkFilter) ExtractDomain(urlStr string) string {
// 尝试解析给定的URL字符串。
parsedURL, err := url.Parse(urlStr)
if err != nil {
// 如果解析过程中出现错误记录错误信息并返回原始URL字符串。
logger.Printf("Error parsing URL: %v", err)
return urlStr
}
// 返回解析得到的主机名,转换为小写。
return strings.ToLower(parsedURL.Hostname())
}
func (lf *LinkFilter) domainMatch(domain, whiteDomain string) bool {
domainParts := strings.Split(domain, ".")
whiteDomainParts := strings.Split(whiteDomain, ".")
if len(domainParts) < len(whiteDomainParts) {
return false
}
for i := 1; i <= len(whiteDomainParts); i++ {
if domainParts[len(domainParts)-i] != whiteDomainParts[len(whiteDomainParts)-i] {
return false
}
}
return true
}
func (lf *LinkFilter) IsWhitelisted(link string) bool {
domain := lf.ExtractDomain(link)
for _, whiteDomain := range lf.whitelist {
if lf.domainMatch(domain, whiteDomain) {
logger.Printf("Whitelist check for %s: Passed (matched %s)", link, whiteDomain)
return true
}
}
logger.Printf("Whitelist check for %s: Failed", link)
return false
}
func (lf *LinkFilter) AddKeyword(keyword string) error {
if lf.linkPattern.MatchString(keyword) {
keyword = lf.NormalizeLink(keyword)
}
keyword = strings.TrimPrefix(keyword, "/")
for _, k := range lf.keywords {
if k == keyword {
logger.Printf("Keyword already exists: %s", keyword)
return nil
}
}
err := lf.db.AddKeyword(keyword)
if err != nil {
return err
}
logger.Printf("New keyword added: %s", keyword)
return lf.LoadDataFromFile()
}
func (lf *LinkFilter) RemoveKeyword(keyword string) bool {
for _, k := range lf.keywords {
if k == keyword {
lf.db.RemoveKeyword(keyword)
lf.LoadDataFromFile()
return true
}
}
return false
}
func (lf *LinkFilter) RemoveKeywordsContaining(substring string) ([]string, error) {
removed, err := lf.db.RemoveKeywordsContaining(substring)
if err != nil {
return nil, err
}
err = lf.LoadDataFromFile()
if err != nil {
return nil, err
}
return removed, nil
}
// 检查消息是否包含关键词或者非白名单链接
func (lf *LinkFilter) ShouldFilter(text string) (bool, []string) {
logger.Printf("Checking text: %s", text)
for _, keyword := range lf.keywords {
if strings.Contains(strings.ToLower(text), strings.ToLower(keyword)) {
logger.Printf("文字包含关键字: %s", text)
return true, nil
}
}
links := lf.linkPattern.FindAllString(text, -1)
logger.Printf("找到链接: %v", links)
var newNonWhitelistedLinks []string
for _, link := range links {
normalizedLink := lf.NormalizeLink(link)
if !lf.IsWhitelisted(normalizedLink) {
logger.Printf("链接未列入白名单: %s", normalizedLink)
found := false
for _, keyword := range lf.keywords {
if keyword == normalizedLink {
logger.Printf("找到现有关键字: %s", normalizedLink)
return true, nil
}
}
if !found {
newNonWhitelistedLinks = append(newNonWhitelistedLinks, normalizedLink)
lf.AddKeyword(normalizedLink)
}
}
}
if len(newNonWhitelistedLinks) > 0 {
logger.Printf("发现新的非白名单链接: %v", newNonWhitelistedLinks)
}
return false, newNonWhitelistedLinks
}

175
service/message_handler.go Normal file
View File

@ -0,0 +1,175 @@
// 消息处理函数
package service
import (
"fmt"
"log"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/woodchen-ink/Q58Bot/core"
"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"
)
// handleUpdate 处理所有传入的更新信息,包括消息和命令, 然后分开处理。
func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update, linkFilter *link_filter.LinkFilter, rateLimiter *core.RateLimiter, db *core.Database) {
// 检查更新是否包含消息,如果不包含则直接返回。
if update.Message == nil {
return
}
// 如果消息来自私聊且发送者是预定义的管理员,调用处理管理员命令的函数。
if update.Message.Chat.Type == "private" && update.Message.From.ID == core.ADMIN_ID {
handleAdminCommand(bot, update.Message, db)
return
}
// 如果消息来自群聊且通过了速率限制器的检查,调用处理普通消息的函数。
if update.Message.Chat.Type != "private" && rateLimiter.Allow() {
processMessage(bot, update.Message, linkFilter)
}
}
// 处理管理员私聊消息
func handleAdminCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, db *core.Database) {
command := message.Command()
args := message.CommandArguments()
switch command {
case "add", "delete", "list", "deletecontaining":
HandleKeywordCommand(bot, message, command, args, db)
case "addwhite", "delwhite", "listwhite":
HandleWhitelistCommand(bot, message, command, args, db)
case "prompt":
prompt_reply.HandlePromptCommand(bot, message)
default:
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "未知命令, 听不懂"))
}
}
// processMessage 处理群里接收到的消息。
func processMessage(bot *tgbotapi.BotAPI, message *tgbotapi.Message, linkFilter *link_filter.LinkFilter) {
// 记录消息内容
log.Printf("Processing message: %s", message.Text)
// 处理 /ban 命令
if message.ReplyToMessage != nil && message.Text == "/ban" {
group_member_management.HandleBanCommand(bot, message)
return
}
// 如果不是管理员,才进行链接过滤
if !core.IsAdmin(message.From.ID) {
// 判断消息是否应当被过滤及找出新的非白名单链接
shouldFilter, newLinks := linkFilter.ShouldFilter(message.Text)
if shouldFilter {
// 记录被过滤的消息
log.Printf("消息应该被过滤: %s", message.Text)
// 删除原始消息
deleteMsg := tgbotapi.NewDeleteMessage(message.Chat.ID, message.MessageID)
_, err := bot.Request(deleteMsg)
if err != nil {
// 删除消息失败时记录错误
log.Printf("删除消息失败: %v", err)
}
// 发送提示消息
notification := tgbotapi.NewMessage(message.Chat.ID, "已撤回该消息。注:一个链接不能发两次.")
sent, err := bot.Send(notification)
if err != nil {
// 发送通知失败时记录错误
log.Printf("发送通知失败: %v", err)
} else {
// 3分钟后删除提示消息
go deleteMessageAfterDelay(bot, message.Chat.ID, sent.MessageID, 3*time.Minute)
}
// 结束处理
return
}
// 如果发现新的非白名单链接
if len(newLinks) > 0 {
// 记录新的非白名单链接
log.Printf("发现新的非白名单链接: %v", newLinks)
}
}
// 检查消息文本是否匹配预设的提示词并回复
if reply, found := prompt_reply.GetPromptReply(message.Text); found {
// 创建回复消息
replyMsg := tgbotapi.NewMessage(message.Chat.ID, reply)
replyMsg.ReplyToMessageID = message.MessageID
sent, err := bot.Send(replyMsg)
if err != nil {
// 发送回复失败时记录错误
log.Printf("未能发送及时回复: %v", err)
} else {
// 3分钟后删除回复消息
go deleteMessageAfterDelay(bot, message.Chat.ID, sent.MessageID, 3*time.Minute)
}
}
}
func RunMessageHandler() error {
log.Println("消息处理器启动...")
baseDelay := time.Second
maxDelay := 5 * time.Minute
delay := baseDelay
db, err := core.NewDatabase()
if err != nil {
return fmt.Errorf("failed to initialize database: %w", err)
}
defer db.Close() // 确保在函数结束时关闭数据库连接
for {
err := func() error {
bot, err := tgbotapi.NewBotAPI(core.BOT_TOKEN)
if err != nil {
return fmt.Errorf("failed to create bot: %w", err)
}
bot.Debug = core.DEBUG_MODE
log.Printf("Authorized on account %s", bot.Self.UserName)
err = core.RegisterCommands(bot)
if err != nil {
return fmt.Errorf("error registering commands: %w", err)
}
linkFilter, err := link_filter.NewLinkFilter()
if err != nil {
return fmt.Errorf("failed to create LinkFilter: %v", err)
}
rateLimiter := core.NewRateLimiter()
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
for update := range updates {
go handleUpdate(bot, update, linkFilter, rateLimiter, db)
}
return nil
}()
if err != nil {
log.Printf("Bot encountered an error: %v", err)
log.Printf("Attempting to restart in %v...", delay)
time.Sleep(delay)
delay *= 2
if delay > maxDelay {
delay = maxDelay
}
} else {
delay = baseDelay
log.Println("Bot disconnected. Attempting to restart immediately...")
}
}
}

View File

@ -0,0 +1,225 @@
package service
// 消息处理辅助函数
import (
"fmt"
"log"
"strings"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/woodchen-ink/Q58Bot/core"
)
// deleteMessageAfterDelay 根据指定延迟删除消息。
func deleteMessageAfterDelay(bot *tgbotapi.BotAPI, chatID int64, messageID int, delay time.Duration) {
// 让线程暂停指定的延迟时间。
time.Sleep(delay)
// 创建一个删除消息的请求。
deleteMsg := tgbotapi.NewDeleteMessage(chatID, messageID)
// 尝试发送删除消息的请求,并检查是否有错误发生。
// 注意: 错误情况下只是记录错误,不进行其他操作。
_, err := bot.Request(deleteMsg)
if err != nil {
log.Printf("删除消息失败: %v", err)
}
}
// SendLongMessage 如有必要,可将长消息拆分为多条消息来发送
func SendLongMessage(bot *tgbotapi.BotAPI, chatID int64, prefix string, items []string) error {
const maxMessageLength = 4000 // Leave some room for Telegram's message limit
message := prefix + "\n"
for i, item := range items {
newLine := fmt.Sprintf("%d. %s\n", i+1, item)
if len(message)+len(newLine) > maxMessageLength {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
return err
}
message = ""
}
message += newLine
}
if message != "" {
msg := tgbotapi.NewMessage(chatID, message)
_, err := bot.Send(msg)
if err != nil {
return err
}
}
return nil
}
// HandleKeywordCommand 处理关键词命令
func HandleKeywordCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, command string, args string, db *core.Database) {
switch command {
case "list":
keywords, err := db.GetAllKeywords()
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "获取关键词列表时发生错误。"))
return
}
if len(keywords) == 0 {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "关键词列表为空。"))
} else {
SendLongMessage(bot, message.Chat.ID, "当前关键词列表:", keywords)
}
case "add":
if args != "" {
keyword := args
exists, err := db.KeywordExists(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "检查关键词时发生错误。"))
return
}
if !exists {
err = db.AddKeyword(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "添加关键词时发生错误。"))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 已添加。", keyword)))
}
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 已存在。", keyword)))
}
}
case "delete":
if args != "" {
keyword := args
err := db.RemoveKeyword(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("删除关键词 '%s' 时发生错误: %v", keyword, err)))
return
}
// 检查关键词是否仍然存在
exists, err := db.KeywordExists(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("检查关键词 '%s' 是否存在时发生错误: %v", keyword, err)))
return
}
if !exists {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("关键词 '%s' 已成功删除。", keyword)))
} else {
similarKeywords, err := db.SearchKeywords(keyword)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "搜索关键词时发生错误。"))
return
}
if len(similarKeywords) > 0 {
SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("未能删除关键词 '%s'。\n\n以下是相似的关键词", keyword), similarKeywords)
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("未能删除关键词 '%s',且未找到相似的关键词。", keyword)))
}
}
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "请提供要删除的关键词。"))
}
case "deletecontaining":
if args != "" {
substring := args
removedKeywords, err := db.RemoveKeywordsContaining(substring)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "删除关键词时发生错误。"))
return
}
if len(removedKeywords) > 0 {
SendLongMessage(bot, message.Chat.ID, fmt.Sprintf("已删除包含 '%s' 的以下关键词:", substring), removedKeywords)
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("没有找到包含 '%s' 的关键词。", substring)))
}
}
default:
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "无效的命令或参数。"))
}
}
func HandleWhitelistCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, command string, args string, db *core.Database) {
switch command {
case "listwhite":
whitelist, err := db.GetAllWhitelist()
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("获取白名单时发生错误: %v", err)))
return
}
if len(whitelist) == 0 {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "白名单为空。"))
} else {
SendLongMessage(bot, message.Chat.ID, "白名单域名列表:", whitelist)
}
case "addwhite":
if args == "" {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "请提供要添加的域名。"))
return
}
domain := strings.ToLower(args)
exists, err := db.WhitelistExists(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("检查白名单时发生错误: %v", err)))
return
}
if exists {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 已在白名单中。", domain)))
return
}
err = db.AddWhitelist(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("添加到白名单时发生错误: %v", err)))
return
}
// 再次检查以确保添加成功
exists, err = db.WhitelistExists(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("验证添加操作时发生错误: %v", err)))
return
}
if exists {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 已成功添加到白名单。", domain)))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("未能添加域名 '%s' 到白名单。", domain)))
}
case "delwhite":
if args == "" {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "请提供要删除的域名。"))
return
}
domain := strings.ToLower(args)
exists, err := db.WhitelistExists(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("检查白名单时发生错误: %v", err)))
return
}
if !exists {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 不在白名单中。", domain)))
return
}
err = db.RemoveWhitelist(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("从白名单删除时发生错误: %v", err)))
return
}
// 再次检查以确保删除成功
exists, err = db.WhitelistExists(domain)
if err != nil {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("验证删除操作时发生错误: %v", err)))
return
}
if !exists {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("域名 '%s' 已成功从白名单中删除。", domain)))
} else {
bot.Send(tgbotapi.NewMessage(message.Chat.ID, fmt.Sprintf("未能从白名单中删除域名 '%s'。", domain)))
}
default:
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "无效的命令或参数。"))
}
}

View File

@ -1,5 +1,6 @@
package service
package prompt_reply
//提示词回复
import (
"fmt"
"log"
@ -12,15 +13,6 @@ import (
var db *core.Database
func InitPromptService() error {
var err error
db, err = core.NewDatabase()
if err != nil {
return fmt.Errorf("failed to initialize database: %v", err)
}
return nil
}
func SetPromptReply(prompt, reply string) error {
return db.AddPromptReply(prompt, reply)
}