mirror of
https://github.com/woodchen-ink/Q58Bot.git
synced 2025-07-18 05:42:06 +08:00
python转成go测试
This commit is contained in:
parent
8c265e8529
commit
3697051ecf
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -5,7 +5,7 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip" # See documentation for possible values
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
17
.github/workflows/build and depoly.yml
vendored
17
.github/workflows/build and depoly.yml
vendored
@ -18,15 +18,16 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
python-version: '3.12.5'
|
||||
go-version: '1.22' # 使用你项目需要的 Go 版本
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Build for amd64
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main-amd64 .
|
||||
|
||||
- name: Build for arm64
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o main-arm64 .
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@ -44,7 +45,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
file: Dockerfile.multi
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
|
48
Dockerfile
48
Dockerfile
@ -1,15 +1,43 @@
|
||||
FROM python:3.12.5-slim
|
||||
|
||||
# 设置时区
|
||||
ENV TZ=Asia/Singapore
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
# 使用官方 Go 镜像作为构建环境
|
||||
FROM golang:1.22 AS builder
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
# 复制 go mod 和 sum 文件
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
COPY src /app/src
|
||||
COPY data /app/data
|
||||
# 下载依赖
|
||||
RUN go mod download
|
||||
|
||||
CMD ["python", "src/main.py"]
|
||||
# 复制源代码
|
||||
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"]
|
||||
|
25
Dockerfile.multi
Normal file
25
Dockerfile.multi
Normal file
@ -0,0 +1,25 @@
|
||||
# 使用轻量级的基础镜像
|
||||
FROM alpine:latest
|
||||
|
||||
# 安装 ca-certificates,通常需要用于 HTTPS
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
# 创建工作目录
|
||||
WORKDIR /root/
|
||||
|
||||
# 复制编译好的可执行文件
|
||||
COPY main-amd64 main-arm64 ./
|
||||
|
||||
# 使用 TARGETARCH 参数来选择正确的二进制文件
|
||||
ARG TARGETARCH
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
mv main-amd64 main && rm main-arm64; \
|
||||
elif [ "$TARGETARCH" = "arm64" ]; then \
|
||||
mv main-arm64 main && rm main-amd64; \
|
||||
fi
|
||||
|
||||
# 设置执行权限
|
||||
RUN chmod +x main
|
||||
|
||||
# 运行应用
|
||||
CMD ["./main"]
|
34
core/bot_commands.go
Normal file
34
core/bot_commands.go
Normal file
@ -0,0 +1,34 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func RegisterCommands(bot *tgbotapi.BotAPI, adminID int64) error {
|
||||
commands := []tgbotapi.BotCommand{
|
||||
{Command: "add", Description: "添加新的关键词"},
|
||||
{Command: "delete", Description: "删除现有的关键词"},
|
||||
{Command: "list", Description: "列出所有当前的关键词"},
|
||||
{Command: "deletecontaining", Description: "删除所有包含指定词语的关键词"},
|
||||
{Command: "addwhite", Description: "添加域名到白名单"},
|
||||
{Command: "delwhite", Description: "从白名单移除域名"},
|
||||
{Command: "listwhite", Description: "列出白名单域名"},
|
||||
}
|
||||
|
||||
scope := tgbotapi.NewBotCommandScopeChatAdministrators(adminID)
|
||||
|
||||
config := tgbotapi.NewSetMyCommands(commands...)
|
||||
config.Scope = &scope // 注意这里使用 &scope 来获取指针
|
||||
config.LanguageCode = "" // 空字符串表示默认语言
|
||||
|
||||
_, err := bot.Request(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register bot commands: %w", err)
|
||||
}
|
||||
|
||||
log.Println("Bot commands registered successfully.")
|
||||
return nil
|
||||
}
|
184
core/database.go
Normal file
184
core/database.go
Normal file
@ -0,0 +1,184 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
dbFile string
|
||||
keywordsCache []string
|
||||
whitelistCache []string
|
||||
cacheTime time.Time
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewDatabase(dbFile string) (*Database, error) {
|
||||
os.MkdirAll(filepath.Dir(dbFile), os.ModePerm)
|
||||
db, err := sql.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
database := &Database{
|
||||
db: db,
|
||||
dbFile: dbFile,
|
||||
}
|
||||
|
||||
if err := database.createTables(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return database, nil
|
||||
}
|
||||
|
||||
func (d *Database) createTables() error {
|
||||
queries := []string{
|
||||
`CREATE TABLE IF NOT EXISTS keywords
|
||||
(id INTEGER PRIMARY KEY, keyword TEXT UNIQUE)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_keyword ON keywords(keyword)`,
|
||||
`CREATE TABLE IF NOT EXISTS whitelist
|
||||
(id INTEGER PRIMARY KEY, domain TEXT UNIQUE)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_domain ON whitelist(domain)`,
|
||||
`CREATE VIRTUAL TABLE IF NOT EXISTS keywords_fts USING fts5(keyword)`,
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
_, err := d.db.Exec(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) executeQuery(query string, args ...interface{}) ([]string, error) {
|
||||
rows, err := d.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []string
|
||||
for rows.Next() {
|
||||
var result string
|
||||
if err := rows.Scan(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (d *Database) AddKeyword(keyword string) error {
|
||||
_, err := d.db.Exec("INSERT OR IGNORE INTO keywords (keyword) VALUES (?)", keyword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.db.Exec("INSERT OR IGNORE INTO keywords_fts (keyword) VALUES (?)", keyword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.invalidateCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) RemoveKeyword(keyword string) error {
|
||||
_, err := d.db.Exec("DELETE FROM keywords WHERE keyword = ?", keyword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.db.Exec("DELETE FROM keywords_fts WHERE keyword = ?", keyword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.invalidateCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) GetAllKeywords() ([]string, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.keywordsCache == nil || time.Since(d.cacheTime) > 5*time.Minute {
|
||||
keywords, err := d.executeQuery("SELECT keyword FROM keywords")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.keywordsCache = keywords
|
||||
d.cacheTime = time.Now()
|
||||
}
|
||||
|
||||
return d.keywordsCache, nil
|
||||
}
|
||||
|
||||
func (d *Database) RemoveKeywordsContaining(substring string) error {
|
||||
_, err := d.db.Exec("DELETE FROM keywords WHERE keyword LIKE ?", "%"+substring+"%")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.db.Exec("DELETE FROM keywords_fts WHERE keyword LIKE ?", "%"+substring+"%")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.invalidateCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) AddWhitelist(domain string) error {
|
||||
_, err := d.db.Exec("INSERT OR IGNORE INTO whitelist (domain) VALUES (?)", domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.invalidateCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) RemoveWhitelist(domain string) error {
|
||||
_, err := d.db.Exec("DELETE FROM whitelist WHERE domain = ?", domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.invalidateCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) GetAllWhitelist() ([]string, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.whitelistCache == nil || time.Since(d.cacheTime) > 5*time.Minute {
|
||||
whitelist, err := d.executeQuery("SELECT domain FROM whitelist")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.whitelistCache = whitelist
|
||||
d.cacheTime = time.Now()
|
||||
}
|
||||
|
||||
return d.whitelistCache, nil
|
||||
}
|
||||
|
||||
func (d *Database) SearchKeywords(pattern string) ([]string, error) {
|
||||
return d.executeQuery("SELECT keyword FROM keywords_fts WHERE keyword MATCH ?", pattern)
|
||||
}
|
||||
|
||||
func (d *Database) invalidateCache() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.keywordsCache = nil
|
||||
d.whitelistCache = nil
|
||||
d.cacheTime = time.Time{}
|
||||
}
|
||||
|
||||
func (d *Database) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
90
core/functions.go
Normal file
90
core/functions.go
Normal file
@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
227
core/link_filter.go
Normal file
227
core/link_filter.go
Normal file
@ -0,0 +1,227 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
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 *Database
|
||||
keywords []string
|
||||
whitelist []string
|
||||
linkPattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewLinkFilter(dbFile string) *LinkFilter {
|
||||
lf := &LinkFilter{
|
||||
db: NewDatabase(dbFile),
|
||||
}
|
||||
lf.linkPattern = regexp.MustCompile(`(?i)\b(?:(?:https?://)?(?:(?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:t\.me|telegram\.me))(?:/[^\s]*)?)`)
|
||||
lf.LoadDataFromFile()
|
||||
return lf
|
||||
}
|
||||
|
||||
func (lf *LinkFilter) LoadDataFromFile() {
|
||||
lf.keywords = lf.db.GetAllKeywords()
|
||||
lf.whitelist = lf.db.GetAllWhitelist()
|
||||
logger.Printf("Loaded %d keywords and %d whitelist entries from database", len(lf.keywords), len(lf.whitelist))
|
||||
}
|
||||
|
||||
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())
|
||||
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
|
||||
}
|
||||
domain := parsedURL.Hostname()
|
||||
parts := strings.Split(domain, ".")
|
||||
if len(parts) > 2 {
|
||||
domain = strings.Join(parts[len(parts)-2:], ".")
|
||||
}
|
||||
return strings.ToLower(domain)
|
||||
}
|
||||
|
||||
func (lf *LinkFilter) IsWhitelisted(link string) bool {
|
||||
domain := lf.ExtractDomain(link)
|
||||
for _, whiteDomain := range lf.whitelist {
|
||||
if domain == whiteDomain {
|
||||
logger.Printf("Whitelist check for %s: Passed", link)
|
||||
return true
|
||||
}
|
||||
}
|
||||
logger.Printf("Whitelist check for %s: Failed", link)
|
||||
return false
|
||||
}
|
||||
|
||||
func (lf *LinkFilter) AddKeyword(keyword string) {
|
||||
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
|
||||
}
|
||||
}
|
||||
lf.db.AddKeyword(keyword)
|
||||
logger.Printf("New keyword added: %s", keyword)
|
||||
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 {
|
||||
removed := lf.db.RemoveKeywordsContaining(substring)
|
||||
lf.LoadDataFromFile()
|
||||
return removed
|
||||
}
|
||||
|
||||
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)
|
||||
normalizedLink = strings.TrimPrefix(normalizedLink, "/")
|
||||
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 := lf.db.GetAllKeywords()
|
||||
if len(keywords) == 0 {
|
||||
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "关键词列表为空。"))
|
||||
} else {
|
||||
SendLongMessage(bot, message.Chat.ID, "当前关键词列表:", keywords)
|
||||
}
|
||||
case "add":
|
||||
if args != "" {
|
||||
keyword := args
|
||||
if !lf.db.KeywordExists(keyword) {
|
||||
lf.AddKeyword(keyword)
|
||||
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 := lf.db.SearchKeywords(keyword)
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
case "deletecontaining":
|
||||
if args != "" {
|
||||
substring := args
|
||||
removedKeywords := lf.RemoveKeywordsContaining(substring)
|
||||
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 (lf *LinkFilter) HandleWhitelistCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message, command string, args string) {
|
||||
switch command {
|
||||
case "listwhite":
|
||||
whitelist := lf.db.GetAllWhitelist()
|
||||
if len(whitelist) == 0 {
|
||||
bot.Send(tgbotapi.NewMessage(message.Chat.ID, "白名单为空。"))
|
||||
} else {
|
||||
SendLongMessageWithoutNumbering(bot, message.Chat.ID, "白名单域名列表:", whitelist)
|
||||
}
|
||||
case "addwhite":
|
||||
if args != "" {
|
||||
domain := strings.ToLower(args)
|
||||
if !lf.db.WhitelistExists(domain) {
|
||||
lf.db.AddWhitelist(domain)
|
||||
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)
|
||||
if lf.db.WhitelistExists(domain) {
|
||||
lf.db.RemoveWhitelist(domain)
|
||||
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, "无效的命令或参数。"))
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
["推广", "广告", "ad", "promotion"]
|
@ -1,3 +0,0 @@
|
||||
[
|
||||
"q58.org"
|
||||
]
|
18
go.mod
Normal file
18
go.mod
Normal file
@ -0,0 +1,18 @@
|
||||
module github.com/woodchen-ink/Q58Bot
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/adshao/go-binance/v2 v2.6.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.23
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
)
|
37
go.sum
Normal file
37
go.sum
Normal file
@ -0,0 +1,37 @@
|
||||
github.com/adshao/go-binance/v2 v2.6.0 h1:sXPkfix+SgBojJmkt+sNJbJBQZOJK5GFP/WtAu+B5r0=
|
||||
github.com/adshao/go-binance/v2 v2.6.0/go.mod h1:41Up2dG4NfMXpCldrDPETEtiOq+pHoGsFZ73xGgaumo=
|
||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
107
main.go
Normal file
107
main.go
Normal file
@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
binance "github.com/woodchen-ink/Q58Bot/service"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
var (
|
||||
BOT_TOKEN string
|
||||
ADMIN_ID int64
|
||||
)
|
||||
|
||||
func init() {
|
||||
BOT_TOKEN = os.Getenv("BOT_TOKEN")
|
||||
adminIDStr := os.Getenv("ADMIN_ID")
|
||||
var err error
|
||||
ADMIN_ID, err = strconv.ParseInt(adminIDStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid ADMIN_ID: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupBot() {
|
||||
bot, err := tgbotapi.NewBotAPI(BOT_TOKEN)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
bot.Debug = true
|
||||
|
||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
|
||||
updates := bot.GetUpdatesChan(u)
|
||||
|
||||
for update := range updates {
|
||||
if update.Message == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if update.Message.Chat.ID != ADMIN_ID {
|
||||
continue
|
||||
}
|
||||
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
|
||||
|
||||
switch update.Message.Command() {
|
||||
case "start":
|
||||
msg.Text = "Hello! I'm your bot."
|
||||
case "help":
|
||||
msg.Text = "I can help you with various tasks."
|
||||
default:
|
||||
msg.Text = "I don't know that command"
|
||||
}
|
||||
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runGuard() {
|
||||
for {
|
||||
try(func() {
|
||||
guard.Run()
|
||||
}, "Guard")
|
||||
}
|
||||
}
|
||||
|
||||
func runBinance() {
|
||||
for {
|
||||
try(func() {
|
||||
binance.Run()
|
||||
}, "Binance")
|
||||
}
|
||||
}
|
||||
|
||||
func try(fn func(), name string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("%s process crashed: %v", name, r)
|
||||
log.Printf("Restarting %s process...", name)
|
||||
time.Sleep(time.Second) // 添加短暂延迟以防止过快重启
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||
|
||||
// 使用 goroutines 运行 bot、guard 和 binance 服务
|
||||
go setupBot()
|
||||
go runGuard()
|
||||
go runBinance()
|
||||
|
||||
// 保持主程序运行
|
||||
select {}
|
||||
}
|
149
service/binance.go
Normal file
149
service/binance.go
Normal file
@ -0,0 +1,149 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/adshao/go-binance/v2"
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
var (
|
||||
botToken string
|
||||
chatID int64
|
||||
symbols []string
|
||||
bot *tgbotapi.BotAPI
|
||||
lastMsgID int
|
||||
singaporeTZ *time.Location
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
botToken = os.Getenv("BOT_TOKEN")
|
||||
chatID = mustParseInt64(os.Getenv("CHAT_ID"))
|
||||
symbols = strings.Split(os.Getenv("SYMBOLS"), ",")
|
||||
singaporeTZ, err = time.LoadLocation("Asia/Singapore")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
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
|
||||
changePercent float64
|
||||
}
|
||||
|
||||
func getTickerInfo(symbol string) (tickerInfo, error) {
|
||||
client := binance.NewClient("", "")
|
||||
|
||||
// 获取当前价格
|
||||
ticker, err := client.NewListPricesService().Symbol(symbol).Do(binance.NewContext())
|
||||
if err != nil {
|
||||
return tickerInfo{}, err
|
||||
}
|
||||
if len(ticker) == 0 {
|
||||
return tickerInfo{}, fmt.Errorf("no ticker found for symbol %s", symbol)
|
||||
}
|
||||
last, err := ticker[0].Price.Float64()
|
||||
if err != nil {
|
||||
return tickerInfo{}, err
|
||||
}
|
||||
|
||||
// 获取24小时价格变化
|
||||
stats, err := client.NewListPriceChangeStatsService().Symbol(symbol).Do(binance.NewContext())
|
||||
if err != nil {
|
||||
return tickerInfo{}, err
|
||||
}
|
||||
if len(stats) == 0 {
|
||||
return tickerInfo{}, fmt.Errorf("no price change stats found for symbol %s", symbol)
|
||||
}
|
||||
changePercent, err := stats[0].PriceChangePercent.Float64()
|
||||
if err != nil {
|
||||
return tickerInfo{}, err
|
||||
}
|
||||
|
||||
return tickerInfo{
|
||||
symbol: symbol,
|
||||
last: last,
|
||||
changePercent: changePercent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func formatChange(changePercent float64) string {
|
||||
if changePercent > 0 {
|
||||
return fmt.Sprintf("🔼 +%.2f%%", changePercent)
|
||||
} else if changePercent < 0 {
|
||||
return fmt.Sprintf("🔽 %.2f%%", changePercent)
|
||||
}
|
||||
return fmt.Sprintf("◀▶ %.2f%%", changePercent)
|
||||
}
|
||||
|
||||
func sendPriceUpdate() {
|
||||
now := time.Now().In(singaporeTZ)
|
||||
message := fmt.Sprintf("市场更新 - %s (SGT)\n\n", now.Format("2006-01-02 15:04:05"))
|
||||
|
||||
for _, symbol := range symbols {
|
||||
info, err := getTickerInfo(symbol)
|
||||
if err != nil {
|
||||
log.Printf("Error getting ticker info for %s: %v", symbol, err)
|
||||
continue
|
||||
}
|
||||
|
||||
changeStr := formatChange(info.changePercent)
|
||||
|
||||
message += fmt.Sprintf("*%s*\n", info.symbol)
|
||||
message += fmt.Sprintf("价格: $%.7f\n", info.last)
|
||||
message += fmt.Sprintf("24h 涨跌: %s\n\n", changeStr)
|
||||
}
|
||||
|
||||
if lastMsgID != 0 {
|
||||
deleteMsg := tgbotapi.NewDeleteMessage(chatID, lastMsgID)
|
||||
_, err := bot.Request(deleteMsg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to delete previous message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
msg := tgbotapi.NewMessage(chatID, message)
|
||||
msg.ParseMode = "Markdown"
|
||||
sentMsg, err := bot.Send(msg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to send message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
lastMsgID = sentMsg.MessageID
|
||||
}
|
||||
|
||||
func Run() {
|
||||
log.Println("Sending initial price update...")
|
||||
sendPriceUpdate()
|
||||
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
sendPriceUpdate()
|
||||
}
|
||||
}
|
||||
}
|
193
service/guard.go
Normal file
193
service/guard.go
Normal file
@ -0,0 +1,193 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"your-project-name/core"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
var (
|
||||
botToken string
|
||||
adminID int64
|
||||
dbFile string
|
||||
debugMode bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
botToken = os.Getenv("BOT_TOKEN")
|
||||
adminIDStr := os.Getenv("ADMIN_ID")
|
||||
var err error
|
||||
adminID, err = strconv.ParseInt(adminIDStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid ADMIN_ID: %v", err)
|
||||
}
|
||||
dbFile = "/app/data/q58.db" // 新的数据库文件路径
|
||||
debugMode = os.Getenv("DEBUG_MODE") == "true"
|
||||
}
|
||||
|
||||
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 processMessage(bot *tgbotapi.BotAPI, message *tgbotapi.Message, linkFilter *core.LinkFilter) {
|
||||
if message.Chat.Type != "private" {
|
||||
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 != adminID {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func messageHandler(bot *tgbotapi.BotAPI, update tgbotapi.Update, linkFilter *core.LinkFilter, rateLimiter *RateLimiter) {
|
||||
if update.Message == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if update.Message.Chat.Type != "private" || update.Message.From.ID != adminID {
|
||||
if rateLimiter.Allow() {
|
||||
processMessage(bot, update.Message, linkFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func commandHandler(bot *tgbotapi.BotAPI, update tgbotapi.Update, linkFilter *core.LinkFilter) {
|
||||
if update.Message == nil || update.Message.Chat.Type != "private" || update.Message.From.ID != adminID {
|
||||
return
|
||||
}
|
||||
|
||||
linkFilter.LoadDataFromFile()
|
||||
|
||||
command := update.Message.Command()
|
||||
args := update.Message.CommandArguments()
|
||||
|
||||
switch command {
|
||||
case "add", "delete", "list", "deletecontaining":
|
||||
linkFilter.HandleKeywordCommand(bot, update.Message, command, args)
|
||||
case "addwhite", "delwhite", "listwhite":
|
||||
linkFilter.HandleWhitelistCommand(bot, update.Message, command, args)
|
||||
}
|
||||
|
||||
if command == "add" || command == "delete" || command == "deletecontaining" || command == "list" || command == "addwhite" || command == "delwhite" || command == "listwhite" {
|
||||
linkFilter.LoadDataFromFile()
|
||||
}
|
||||
}
|
||||
|
||||
func StartBot() error {
|
||||
bot, err := tgbotapi.NewBotAPI(botToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create bot: %w", err)
|
||||
}
|
||||
|
||||
bot.Debug = debugMode
|
||||
|
||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||
|
||||
err = core.RegisterCommands(bot, adminID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error registering commands: %w", err)
|
||||
}
|
||||
|
||||
linkFilter := core.NewLinkFilter(dbFile)
|
||||
rateLimiter := NewRateLimiter(10, time.Second)
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
|
||||
updates := bot.GetUpdatesChan(u)
|
||||
|
||||
for update := range updates {
|
||||
go messageHandler(bot, update, linkFilter, rateLimiter)
|
||||
go commandHandler(bot, update, linkFilter)
|
||||
}
|
||||
|
||||
return nil // 如果 bot 正常退出,返回 nil
|
||||
}
|
||||
|
||||
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 {
|
||||
// 如果 bot 正常退出,重置延迟
|
||||
delay = baseDelay
|
||||
log.Println("Bot disconnected. Attempting to restart immediately...")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user