mirror of
https://github.com/woodchen-ink/Q58Bot.git
synced 2025-07-19 14:22:05 +08:00
移动部分函数到消息处理器, 保持链接过滤器的纯粹
This commit is contained in:
parent
c5acd7cd7c
commit
6dc34b3aaf
@ -1,4 +0,0 @@
|
|||||||
BOT_TOKEN=your_bot_token
|
|
||||||
ADMIN_ID=5912366993
|
|
||||||
CHAT_ID=your_chat_id
|
|
||||||
SYMBOLS=BTC/USDT,ETH/USDT
|
|
1612
.gitignore
vendored
1612
.gitignore
vendored
File diff suppressed because it is too large
Load Diff
43
Dockerfile
43
Dockerfile
@ -1,43 +0,0 @@
|
|||||||
# 使用官方 Go 镜像作为构建环境
|
|
||||||
FROM golang:1.22 AS builder
|
|
||||||
|
|
||||||
# 设置工作目录
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# 复制 go mod 和 sum 文件
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
|
|
||||||
# 下载依赖
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# 复制源代码
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# 编译应用
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
|
|
||||||
|
|
||||||
# 使用轻量级的 alpine 镜像作为运行环境
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
# 安装 ca-certificates
|
|
||||||
RUN apk --no-cache add ca-certificates
|
|
||||||
|
|
||||||
WORKDIR /root/
|
|
||||||
|
|
||||||
# 从构建阶段复制编译好的应用
|
|
||||||
COPY --from=builder /app/main .
|
|
||||||
|
|
||||||
# 设置环境变量
|
|
||||||
ENV BOT_TOKEN=""
|
|
||||||
ENV ADMIN_ID=""
|
|
||||||
ENV SYMBOLS=""
|
|
||||||
ENV DEBUG_MODE="false"
|
|
||||||
|
|
||||||
# 创建数据目录
|
|
||||||
RUN mkdir -p /app/data
|
|
||||||
|
|
||||||
# 暴露端口(如果你的应用需要的话)
|
|
||||||
# EXPOSE 8080
|
|
||||||
|
|
||||||
# 运行应用
|
|
||||||
CMD ["./main"]
|
|
50
README.md
50
README.md
@ -57,6 +57,56 @@ docker-compose up -d
|
|||||||
- 确保 Telegram 机器人已被添加到目标群组,并被赋予管理员权限
|
- 确保 Telegram 机器人已被添加到目标群组,并被赋予管理员权限
|
||||||
- 定期检查日志以确保服务正常运行
|
- 定期检查日志以确保服务正常运行
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
```
|
||||||
|
Q58Bot/
|
||||||
|
│
|
||||||
|
├── core/
|
||||||
|
│ ├── bot_commands.go # 注册机器人命令
|
||||||
|
│ ├── database.go # 数据库操作
|
||||||
|
│ ├── init.go # 初始化全局变量
|
||||||
|
│ └── ratelimiter.go # 限速器
|
||||||
|
│
|
||||||
|
├── service/
|
||||||
|
│ ├── binance/
|
||||||
|
│ │ └── binance.go # 获取币安价格信息
|
||||||
|
│ │
|
||||||
|
| |── group_member_management/
|
||||||
|
| | └── group_member_management.go # 对群组进行管理
|
||||||
|
│ │
|
||||||
|
│ ├── link_filter/
|
||||||
|
│ │ └── link_filter.go # 链接过滤器
|
||||||
|
│ │
|
||||||
|
│ ├── prompt_reply/
|
||||||
|
│ | └── prompt_reply.go # 提示词自动回复
|
||||||
|
│ │
|
||||||
|
│ └── message_handler.go # 消息处理器
|
||||||
|
│
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── Dockerfile.multi
|
||||||
|
├── go.mod
|
||||||
|
├── go.sum
|
||||||
|
├── main.go # 入口文件
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
## 设计规范
|
||||||
|
|
||||||
|
> 自己记录
|
||||||
|
|
||||||
|
1. 关于数据库的操作, 需要在运行时进行一次加载数据
|
||||||
|
2. 任何操作需要添加相关日志, 日志需要包含时间戳
|
||||||
|
3. 要使用全局的数据库实例`core.DB`, 相关代码如下:
|
||||||
|
- `init.go`里
|
||||||
|
``` go
|
||||||
|
// 初始化数据库
|
||||||
|
DB_FILE = filepath.Join("/app/data", "q58.db")
|
||||||
|
var err error
|
||||||
|
DB, err = NewDatabase()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
欢迎提交 Issues 和 Pull Requests 来帮助改进这个项目。
|
欢迎提交 Issues 和 Pull Requests 来帮助改进这个项目。
|
||||||
|
@ -14,40 +14,40 @@ import (
|
|||||||
var logger = log.New(log.Writer(), "LinkFilter: ", log.Ldate|log.Ltime|log.Lshortfile)
|
var logger = log.New(log.Writer(), "LinkFilter: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
type LinkFilter struct {
|
type LinkFilter struct {
|
||||||
keywords []string
|
Keywords []string
|
||||||
whitelist []string
|
Whitelist []string
|
||||||
linkPattern *regexp.Regexp
|
LinkPattern *regexp.Regexp
|
||||||
mu sync.RWMutex
|
Mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLinkFilter() (*LinkFilter, error) {
|
func NewLinkFilter() (*LinkFilter, error) {
|
||||||
lf := &LinkFilter{
|
lf := &LinkFilter{
|
||||||
linkPattern: regexp.MustCompile(`(?i)\b(?:(?:https?://)?(?:(?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:t\.me|telegram\.me))(?:/[^\s]*)?)`),
|
LinkPattern: regexp.MustCompile(`(?i)\b(?:(?:https?://)?(?:(?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:t\.me|telegram\.me))(?:/[^\s]*)?)`),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := lf.LoadDataFromFile(); err != nil {
|
if err := lf.LoadDataFromDatabase(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return lf, nil
|
return lf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lf *LinkFilter) LoadDataFromFile() error {
|
func (lf *LinkFilter) LoadDataFromDatabase() error {
|
||||||
lf.mu.Lock()
|
lf.Mu.Lock()
|
||||||
defer lf.mu.Unlock()
|
defer lf.Mu.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
lf.keywords, err = core.DB.GetAllKeywords()
|
lf.Keywords, err = core.DB.GetAllKeywords()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lf.whitelist, err = core.DB.GetAllWhitelist()
|
lf.Whitelist, err = core.DB.GetAllWhitelist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf("Loaded %d keywords and %d whitelist entries from database", len(lf.keywords), len(lf.whitelist))
|
logger.Printf("Loaded %d Keywords and %d Whitelist entries from database", len(lf.Keywords), len(lf.Whitelist))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ func (lf *LinkFilter) domainMatch(domain, whiteDomain string) bool {
|
|||||||
}
|
}
|
||||||
func (lf *LinkFilter) IsWhitelisted(link string) bool {
|
func (lf *LinkFilter) IsWhitelisted(link string) bool {
|
||||||
domain := lf.ExtractDomain(link)
|
domain := lf.ExtractDomain(link)
|
||||||
for _, whiteDomain := range lf.whitelist {
|
for _, whiteDomain := range lf.Whitelist {
|
||||||
if lf.domainMatch(domain, whiteDomain) {
|
if lf.domainMatch(domain, whiteDomain) {
|
||||||
logger.Printf("Whitelist check for %s: Passed (matched %s)", link, whiteDomain)
|
logger.Printf("Whitelist check for %s: Passed (matched %s)", link, whiteDomain)
|
||||||
return true
|
return true
|
||||||
@ -115,92 +115,3 @@ func (lf *LinkFilter) IsWhitelisted(link string) bool {
|
|||||||
logger.Printf("Whitelist check for %s: Failed", link)
|
logger.Printf("Whitelist check for %s: Failed", link)
|
||||||
return false
|
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 := core.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 {
|
|
||||||
removed, err := core.DB.RemoveKeyword(keyword)
|
|
||||||
if err != nil {
|
|
||||||
logger.Printf("Error removing keyword: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if removed {
|
|
||||||
lf.LoadDataFromFile()
|
|
||||||
}
|
|
||||||
return removed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *LinkFilter) RemoveKeywordsContaining(substring string) ([]string, error) {
|
|
||||||
removed, err := core.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)
|
|
||||||
|
|
||||||
lf.mu.RLock()
|
|
||||||
defer lf.mu.RUnlock()
|
|
||||||
|
|
||||||
for _, keyword := range lf.keywords {
|
|
||||||
if strings.Contains(strings.ToLower(text), strings.ToLower(keyword)) {
|
|
||||||
logger.Printf("文字包含关键字: %s", keyword)
|
|
||||||
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)
|
|
||||||
if !lf.containsKeyword(normalizedLink) {
|
|
||||||
newNonWhitelistedLinks = append(newNonWhitelistedLinks, normalizedLink)
|
|
||||||
lf.AddKeyword(normalizedLink) // 注意:这里会修改 lf.keywords,可能需要额外的锁
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(newNonWhitelistedLinks) > 0 {
|
|
||||||
logger.Printf("发现新的非白名单链接: %v", newNonWhitelistedLinks)
|
|
||||||
}
|
|
||||||
return false, newNonWhitelistedLinks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *LinkFilter) containsKeyword(link string) bool {
|
|
||||||
for _, keyword := range lf.keywords {
|
|
||||||
if keyword == link {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,10 @@ import (
|
|||||||
"github.com/woodchen-ink/Q58Bot/service/prompt_reply"
|
"github.com/woodchen-ink/Q58Bot/service/prompt_reply"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New(log.Writer(), "MessageHandler: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
)
|
||||||
|
|
||||||
// handleUpdate 处理所有传入的更新信息,包括消息和命令, 然后分开处理。
|
// handleUpdate 处理所有传入的更新信息,包括消息和命令, 然后分开处理。
|
||||||
func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update, linkFilter *link_filter.LinkFilter, rateLimiter *core.RateLimiter) {
|
func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update, linkFilter *link_filter.LinkFilter, rateLimiter *core.RateLimiter) {
|
||||||
// 检查更新是否包含消息,如果不包含则直接返回。
|
// 检查更新是否包含消息,如果不包含则直接返回。
|
||||||
@ -64,7 +68,7 @@ func processMessage(bot *tgbotapi.BotAPI, message *tgbotapi.Message, linkFilter
|
|||||||
// 如果不是管理员,才进行链接过滤
|
// 如果不是管理员,才进行链接过滤
|
||||||
if !core.IsAdmin(message.From.ID) {
|
if !core.IsAdmin(message.From.ID) {
|
||||||
// 判断消息是否应当被过滤及找出新的非白名单链接
|
// 判断消息是否应当被过滤及找出新的非白名单链接
|
||||||
shouldFilter, newLinks := linkFilter.ShouldFilter(message.Text)
|
shouldFilter, newLinks := ShouldFilter(message.Text, linkFilter)
|
||||||
if shouldFilter {
|
if shouldFilter {
|
||||||
// 记录被过滤的消息
|
// 记录被过滤的消息
|
||||||
log.Printf("消息应该被过滤: %s", message.Text)
|
log.Printf("消息应该被过滤: %s", message.Text)
|
||||||
@ -430,3 +434,81 @@ func handleDeleteWhitelist(bot *tgbotapi.BotAPI, message *tgbotapi.Message, doma
|
|||||||
sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("未能从白名单中删除域名 '%s'。", domain))
|
sendErrorMessage(bot, message.Chat.ID, fmt.Sprintf("未能从白名单中删除域名 '%s'。", domain))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addNewKeyword(keyword string) error {
|
||||||
|
exists, err := core.DB.KeywordExists(keyword)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("检查关键词时发生错误: %v", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
err = core.DB.AddKeyword(keyword)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("添加关键词时发生错误: %v", err)
|
||||||
|
}
|
||||||
|
logger.Printf("新关键词已添加: %s", keyword)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldFilter 检查消息是否包含关键词或者非白名单链接
|
||||||
|
func ShouldFilter(text string, linkFilter *link_filter.LinkFilter) (bool, []string) {
|
||||||
|
logger.Printf("Checking text: %s", text)
|
||||||
|
|
||||||
|
if containsKeyword(text, linkFilter) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
links := extractLinks(text, linkFilter)
|
||||||
|
return processLinks(links, linkFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsKeyword(text string, linkFilter *link_filter.LinkFilter) bool {
|
||||||
|
linkFilter.Mu.RLock()
|
||||||
|
defer linkFilter.Mu.RUnlock()
|
||||||
|
|
||||||
|
for _, keyword := range linkFilter.Keywords {
|
||||||
|
if strings.Contains(strings.ToLower(text), strings.ToLower(keyword)) {
|
||||||
|
logger.Printf("文字包含关键字: %s", keyword)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractLinks(text string, linkFilter *link_filter.LinkFilter) []string {
|
||||||
|
linkFilter.Mu.RLock()
|
||||||
|
defer linkFilter.Mu.RUnlock()
|
||||||
|
|
||||||
|
links := linkFilter.LinkPattern.FindAllString(text, -1)
|
||||||
|
logger.Printf("找到链接: %v", links)
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func processLinks(links []string, linkFilter *link_filter.LinkFilter) (bool, []string) {
|
||||||
|
var newNonWhitelistedLinks []string
|
||||||
|
|
||||||
|
for _, link := range links {
|
||||||
|
normalizedLink := linkFilter.NormalizeLink(link)
|
||||||
|
if !linkFilter.IsWhitelisted(normalizedLink) {
|
||||||
|
logger.Printf("链接未列入白名单: %s", normalizedLink)
|
||||||
|
if !containsKeyword(normalizedLink, linkFilter) {
|
||||||
|
newNonWhitelistedLinks = append(newNonWhitelistedLinks, normalizedLink)
|
||||||
|
err := addNewKeyword(normalizedLink)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("添加关键词时发生错误: %v", err)
|
||||||
|
}
|
||||||
|
// 如果成功添加了新关键词,更新 linkFilter 的 Keywords
|
||||||
|
linkFilter.Mu.Lock()
|
||||||
|
linkFilter.Keywords = append(linkFilter.Keywords, normalizedLink)
|
||||||
|
linkFilter.Mu.Unlock()
|
||||||
|
} else {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newNonWhitelistedLinks) > 0 {
|
||||||
|
logger.Printf("发现新的非白名单链接: %v", newNonWhitelistedLinks)
|
||||||
|
}
|
||||||
|
return false, newNonWhitelistedLinks
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user