From 67e1365c71a0b9dd07cf7cbeba714d0637bac38a Mon Sep 17 00:00:00 2001
From: Hubert Chen <01@trle5.xyz>
Date: Thu, 29 May 2025 01:59:07 +0800
Subject: [PATCH 01/27] refactor
add `SaveDatabase` and `ReadDatabase` database operate function
import `github.com/rs/zerolog` use as logger
move most config into yaml config file
use `filepath.Join()` to combine file path
separate some function from `utils`
move signal channel to `signals` package
make exit code in `SignalsHandler()`
change debug script output directory to `${workspaceFolder}/__debug_bin`
---
.gitignore | 5 +-
.vscode/launch.json | 2 +-
database/initial.go | 7 +
database/operates.go | 34 +++++
database/redis_db/redis.go | 20 +--
database/yaml_db/yaml.go | 58 +++++---
example.env | 3 +
go.mod | 3 +
go.sum | 14 ++
handlers.go | 82 ++++++-----
main.go | 116 ++++++++-------
plugins/plugin_detect_keyword.go | 11 +-
plugins/plugin_limit_message.go | 11 +-
plugins/plugin_sticker.go | 19 +--
plugins/plugin_teamspeak3.go | 66 +++++----
plugins/plugin_udonese.go | 73 +++++-----
plugins/plugin_voicelist.go | 13 +-
plugins/saved_message/functions.go | 7 +-
plugins/saved_message/utils.go | 11 +-
utils/configs/config.go | 85 +++++++++++
utils/configs/init.go | 225 +++++++++++++++++++++++++++++
utils/configs/webhook.go | 105 ++++++++++++++
utils/consts/consts.go | 52 +------
utils/internal_plugin/register.go | 14 +-
utils/mess/mess.go | 92 +-----------
utils/signals/signals.go | 65 ++++++---
utils/utils.go | 68 +++------
utils/yaml/yaml.go | 42 ++++++
28 files changed, 862 insertions(+), 441 deletions(-)
create mode 100644 utils/configs/config.go
create mode 100644 utils/configs/init.go
create mode 100644 utils/configs/webhook.go
create mode 100644 utils/yaml/yaml.go
diff --git a/.gitignore b/.gitignore
index 638e4ea..ed735e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,10 +25,11 @@ go.work
log.txt
trbot
__debug_bin*
-db_yaml/metadata.yaml
-/db_yaml/udonese
/cache
+/db_yaml/metadata.yaml
+/db_yaml/udonese
/db_yaml/savedmessage
/db_yaml/limitmessage
/db_yaml/detectkeyword
/db_yaml/teamspeak
+config.yaml
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 9370bde..94a13e4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,7 +12,7 @@
"env": {},
"args": [],
"cwd": "${workspaceFolder}",
- "output": "${workspaceFolder}/output/debug_app"
+ "output": "${workspaceFolder}/__debug_bin"
}
]
}
diff --git a/database/initial.go b/database/initial.go
index 8aafe1a..f563136 100644
--- a/database/initial.go
+++ b/database/initial.go
@@ -22,6 +22,10 @@ type DatabaseBackend struct {
IsInitialized bool
InitializedErr error
+ // 数据库保存和读取函数
+ SaveDatabase func(ctx context.Context) error
+ ReadDatabase func(ctx context.Context) error
+
// 操作数据库的函数
InitUser func(ctx context.Context, user *models.User) error
InitChat func(ctx context.Context, chat *models.Chat) error
@@ -77,6 +81,9 @@ func InitAndListDatabases() {
IsLowLevel: true,
Initializer: yaml_db.InitializeDB,
+ SaveDatabase: yaml_db.SaveDatabase,
+ ReadDatabase: yaml_db.ReadDatabase,
+
InitUser: yaml_db.InitUser,
InitChat: yaml_db.InitChat,
GetChatInfo: yaml_db.GetChatInfo,
diff --git a/database/operates.go b/database/operates.go
index 74f88bc..7cbe4f1 100644
--- a/database/operates.go
+++ b/database/operates.go
@@ -120,3 +120,37 @@ func SetCustomFlag(ctx context.Context, chatID int64, fieldName db_struct.ChatIn
}
return allErr
}
+
+func SaveDatabase(ctx context.Context) error {
+ var allErr error
+ for _, db := range DBBackends {
+ err := db.SaveDatabase(ctx)
+ if err != nil {
+ allErr = err
+ }
+ }
+ for _, db := range DBBackends_LowLevel {
+ err := db.SaveDatabase(ctx)
+ if err != nil {
+ allErr = fmt.Errorf("%s, %s", allErr, err)
+ }
+ }
+ return allErr
+}
+
+func ReadDatabase(ctx context.Context) error {
+ var allErr error
+ for _, db := range DBBackends {
+ err := db.ReadDatabase(ctx)
+ if err != nil {
+ allErr = err
+ }
+ }
+ for _, db := range DBBackends_LowLevel {
+ err := db.ReadDatabase(ctx)
+ if err != nil {
+ allErr = fmt.Errorf("%s, %s", allErr, err)
+ }
+ }
+ return allErr
+}
diff --git a/database/redis_db/redis.go b/database/redis_db/redis.go
index c0617d8..bf15493 100644
--- a/database/redis_db/redis.go
+++ b/database/redis_db/redis.go
@@ -10,7 +10,7 @@ import (
"trbot/database/db_struct"
"trbot/utils"
- "trbot/utils/consts"
+ "trbot/utils/configs"
"github.com/go-telegram/bot/models"
"github.com/redis/go-redis/v9"
@@ -22,23 +22,23 @@ var UserDB *redis.Client // 用户数据
var ctxbg = context.Background()
func InitializeDB() (bool, error) {
- if consts.RedisURL != "" {
- if consts.RedisMainDB != -1 {
+ if configs.BotConfig.RedisURL != "" {
+ if configs.BotConfig.RedisMainDB != -1 {
MainDB = redis.NewClient(&redis.Options{
- Addr: consts.RedisURL,
- Password: consts.RedisPassword,
- DB: consts.RedisMainDB,
+ Addr: configs.BotConfig.RedisURL,
+ Password: configs.BotConfig.RedisPassword,
+ DB: configs.BotConfig.RedisMainDB,
})
err := PingRedis(ctxbg, MainDB)
if err != nil {
return false, fmt.Errorf("error ping Redis MainDB: %s", err)
}
}
- if consts.RedisUserInfoDB != -1 {
+ if configs.BotConfig.RedisUserInfoDB != -1 {
UserDB = redis.NewClient(&redis.Options{
- Addr: consts.RedisURL,
- Password: consts.RedisPassword,
- DB: consts.RedisUserInfoDB,
+ Addr: configs.BotConfig.RedisURL,
+ Password: configs.BotConfig.RedisPassword,
+ DB: configs.BotConfig.RedisUserInfoDB,
})
err := PingRedis(ctxbg, UserDB)
if err != nil {
diff --git a/database/yaml_db/yaml.go b/database/yaml_db/yaml.go
index 89cd1d3..0a4b94c 100644
--- a/database/yaml_db/yaml.go
+++ b/database/yaml_db/yaml.go
@@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
+ "path/filepath"
"reflect"
"time"
"trbot/database/db_struct"
@@ -30,9 +31,9 @@ type DataBaseYaml struct {
}
func InitializeDB() (bool, error) {
- if consts.DB_path != "" {
+ if consts.YAMLDataBasePath != "" {
var err error
- Database, err = ReadYamlDB(consts.DB_path + consts.MetadataFileName)
+ Database, err = ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))
if err != nil {
return false, fmt.Errorf("read yaml db error: %s", err)
}
@@ -42,11 +43,22 @@ func InitializeDB() (bool, error) {
}
}
+func SaveDatabase(ctx context.Context) error {
+ Database.UpdateTimestamp = time.Now().Unix()
+ return SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database)
+}
+
+func ReadDatabase(ctx context.Context) error {
+ var err error
+ Database, err = ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))
+ return err
+}
+
func ReadYamlDB(pathToFile string) (DataBaseYaml, error) {
file, err := os.Open(pathToFile)
if err != nil {
log.Println("[Database_yaml]: Not found Database file. Created new one")
- err = SaveYamlDB(consts.DB_path, consts.MetadataFileName, DataBaseYaml{})
+ err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, DataBaseYaml{})
if err != nil {
return DataBaseYaml{}, err
} else {
@@ -61,7 +73,7 @@ func ReadYamlDB(pathToFile string) (DataBaseYaml, error) {
if err != nil {
if err == io.EOF {
log.Println("[Database]: Database looks empty. now format it")
- SaveYamlDB(consts.DB_path, consts.MetadataFileName, DataBaseYaml{})
+ SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, DataBaseYaml{})
return DataBaseYaml{}, nil
}
return DataBaseYaml{}, err
@@ -98,17 +110,17 @@ func addToYamlDB(params *db_struct.ChatInfo) {
func AutoSaveDatabaseHandler() {
// 先读取一下数据库文件
- savedDatabase, err := ReadYamlDB(consts.DB_path + consts.MetadataFileName)
+ savedDatabase, err := ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))
if err != nil {
log.Println("some issues when read Database file", err)
// 如果读取数据库文件时发现数据库为空,使用当前的数据重建数据库文件
if reflect.DeepEqual(savedDatabase, DataBaseYaml{}){
mess.PrintLogAndSave("The Database file is empty, recovering Database file using current data")
- err = SaveYamlDB(consts.DB_path, consts.MetadataFileName, Database)
+ err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when recovering empty Database:", err))
} else {
- mess.PrintLogAndSave(fmt.Sprintf("The Database is recovered to %s", consts.DB_path + consts.MetadataFileName))
+ mess.PrintLogAndSave(fmt.Sprintf("The Database is recovered to %s", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)))
}
return
}
@@ -119,21 +131,21 @@ func AutoSaveDatabaseHandler() {
} else {
// 如果数据库文件中有设定专用的 `FORCEOVERWRITE: true` 覆写标记,无论任何修改,先保存程序中的数据,再读取新的数据替换掉当前的数据并保存
if savedDatabase.ForceOverwrite {
- mess.PrintLogAndSave(fmt.Sprintf("The `FORCEOVERWRITE: true` in %s is detected", consts.DB_path + consts.MetadataFileName))
- oldFileName := fmt.Sprintf("beforeOverwritten_%d_%s", time.Now().Unix(), consts.MetadataFileName)
- err := SaveYamlDB(consts.DB_path, oldFileName, savedDatabase)
+ mess.PrintLogAndSave(fmt.Sprintf("The `FORCEOVERWRITE: true` in %s is detected", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)))
+ oldFileName := fmt.Sprintf("beforeOverwritten_%d_%s", time.Now().Unix(), consts.YAMLFileName)
+ err := SaveYamlDB(consts.YAMLDataBasePath, oldFileName, savedDatabase)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when saving the Database before overwritten:", err))
} else {
- mess.PrintLogAndSave(fmt.Sprintf("The Database before overwritten is saved to %s", consts.DB_path + oldFileName))
+ mess.PrintLogAndSave(fmt.Sprintf("The Database before overwritten is saved to %s", consts.YAMLDataBasePath + oldFileName))
}
Database = savedDatabase
Database.ForceOverwrite = false // 移除强制覆盖标记
- err = SaveYamlDB(consts.DB_path, consts.MetadataFileName, Database)
+ err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when recreat Database using new Database:", err))
} else {
- mess.PrintLogAndSave(fmt.Sprintf("Success read data from the new file and saved to %s", consts.DB_path + consts.MetadataFileName))
+ mess.PrintLogAndSave(fmt.Sprintf("Success read data from the new file and saved to %s", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)))
}
} else if savedDatabase.UpdateTimestamp > Database.UpdateTimestamp { // 没有设定覆写标记,检测到本地的数据库更新时间比程序中的更新时间更晚
log.Println("The saved Database is newer than current data in the program")
@@ -141,7 +153,7 @@ func AutoSaveDatabaseHandler() {
if reflect.DeepEqual(savedDatabase.Data, Database.Data) {
log.Println("But current data and Database is the same, updating UpdateTimestamp in the Database only")
Database.UpdateTimestamp = time.Now().Unix()
- err := SaveYamlDB(consts.DB_path, consts.MetadataFileName, Database)
+ err := SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when update Timestamp in Database:", err))
} else {
@@ -150,28 +162,28 @@ func AutoSaveDatabaseHandler() {
} else {
// 数据库文件与程序中的数据不同,将新的数据文件改名另存为 `edited_时间戳_文件名`,再使用程序中的数据还原数据文件
log.Println("Saved Database is different from the current Database")
- editedFileName := fmt.Sprintf("edited_%d_%s", time.Now().Unix(), consts.MetadataFileName)
+ editedFileName := fmt.Sprintf("edited_%d_%s", time.Now().Unix(), consts.YAMLFileName)
// 提示不要在程序运行的时候乱动数据库文件
log.Println("Do not modify the Database file while the program is running, saving modified file and recovering Database file using current data")
- err := SaveYamlDB(consts.DB_path, editedFileName, savedDatabase)
+ err := SaveYamlDB(consts.YAMLDataBasePath, editedFileName, savedDatabase)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when saving modified Database:", err))
} else {
- mess.PrintLogAndSave(fmt.Sprintf("The modified Database is saved to %s", consts.DB_path + editedFileName))
+ mess.PrintLogAndSave(fmt.Sprintf("The modified Database is saved to %s", consts.YAMLDataBasePath + editedFileName))
}
- err = SaveYamlDB(consts.DB_path, consts.MetadataFileName, Database)
+ err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when recovering Database:", err))
} else {
- mess.PrintLogAndSave(fmt.Sprintf("The Database is recovered to %s", consts.DB_path + consts.MetadataFileName))
+ mess.PrintLogAndSave(fmt.Sprintf("The Database is recovered to %s", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)))
}
}
} else { // 数据有更改,程序内的更新时间也比本地数据库晚,正常保存
// 正常情况下更新时间就是会比程序内的时间晚,手动修改数据库途中如果有数据变动,而手动修改的时候没有修改时间戳,不会触发上面的保护机制,会直接覆盖手动修改的内容
// 所以无论如何都尽量不要手动修改数据库文件,如果必要也请在开头添加专用的 `FORCEOVERWRITE: true` 覆写标记,或停止程序后再修改
Database.UpdateTimestamp = time.Now().Unix()
- err := SaveYamlDB(consts.DB_path, consts.MetadataFileName, Database)
+ err := SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database)
if err != nil {
mess.PrintLogAndSave(fmt.Sprintln("some issues happend when auto saving Database:", err))
} else if consts.IsDebugMode {
@@ -194,8 +206,7 @@ func InitChat(ctx context.Context, chat *models.Chat) error {
ChatName: utils.ShowChatName(chat),
AddTime: time.Now().Format(time.RFC3339),
})
- consts.SignalsChannel.Database_save <- true
- return nil
+ return SaveDatabase(ctx)
}
func InitUser(ctx context.Context, user *models.User) error {
@@ -210,8 +221,7 @@ func InitUser(ctx context.Context, user *models.User) error {
ChatName: utils.ShowUserName(user),
AddTime: time.Now().Format(time.RFC3339),
})
- consts.SignalsChannel.Database_save <- true
- return nil
+ return SaveDatabase(ctx)
}
// 获取 ID 信息
diff --git a/example.env b/example.env
index c8fc326..0e3a2c3 100644
--- a/example.env
+++ b/example.env
@@ -1,3 +1,6 @@
BOT_TOKEN="114514:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
WEBHOOK_URL="https://api.example.com/telegram-webhook"
DEBUG="true"
+LOG_LEVEL="info"
+CONFIG_PATH_TO_FILE="./path/to/your_config_file.yaml"
+CONFIG_DIRECTORY="." # if use this, must have a config file name `config.yaml` in directory
diff --git a/go.mod b/go.mod
index 78bd1cd..b4152ca 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
github.com/joho/godotenv v1.5.1
github.com/multiplay/go-ts3 v1.2.0
github.com/redis/go-redis/v9 v9.7.1
+ github.com/rs/zerolog v1.34.0
golang.org/x/image v0.23.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -16,6 +17,8 @@ require (
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.32.0 // indirect
diff --git a/go.sum b/go.sum
index 88eb9b7..cdcf385 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,7 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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=
@@ -13,16 +14,26 @@ github.com/go-telegram/bot v1.14.0 h1:qknBErnf5O1CTWZDdDK/qqV8f7wWTf98gFIVW42m6d
github.com/go-telegram/bot v1.14.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
github.com/go-telegram/bot v1.15.0 h1:/ba5pp084MUhjR5sQDymQ7JNZ001CQa7QjtxLWcuGpg=
github.com/go-telegram/bot v1.15.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/multiplay/go-ts3 v1.2.0 h1:LaN6iz9TZjHXxhLwfU0gjUgDxX0Hq7BCbuyuRhYMl3U=
github.com/multiplay/go-ts3 v1.2.0/go.mod h1:OdNmiO3uV++4SldaJDQTIGg8gNAu5MOiccZiAqVqUZA=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
+github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -50,7 +61,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/handlers.go b/handlers.go
index 6ab05f5..ba9ee0d 100644
--- a/handlers.go
+++ b/handlers.go
@@ -10,6 +10,7 @@ import (
"trbot/database"
"trbot/database/db_struct"
"trbot/utils"
+ "trbot/utils/configs"
"trbot/utils/consts"
"trbot/utils/handler_structs"
"trbot/utils/mess"
@@ -18,23 +19,19 @@ import (
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
+ "github.com/rs/zerolog"
)
func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) {
defer utils.PanicCatcher("defaultHandler")
+ logger := zerolog.Ctx(ctx)
+
- var opts = handler_structs.SubHandlerParams{
- Ctx: ctx,
- Thebot: thebot,
- Update: update,
- }
var err error
-
- if update.Message != nil {
- // fmt.Println(getMessageType(update.Message))
- if update.Message.Chat.Type == "private" {
- // plugin_utils.AllPlugins.DefaultHandlerByMessageTypeForPrivate
- }
+ var opts = handler_structs.SubHandlerParams{
+ Ctx: ctx,
+ Thebot: thebot,
+ Update: update,
}
// 需要重写来配合 handler by update type
@@ -46,28 +43,35 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
database.RecordLatestData(opts.Ctx, update.Message.Chat.ID, db_struct.LatestMessage, update.Message.Text)
opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, update.Message.Chat.ID)
if err != nil {
- log.Println(err)
+ logger.Warn().
+ Err(err).
+ Int64("chatID", update.Message.Chat.ID).
+ Msg("Get chatinfo error")
}
-
if consts.IsDebugMode {
if update.Message.Photo != nil {
- log.Printf("photo message from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], (%d) caption: [%s]",
- utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID,
- utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID,
- update.Message.ID, update.Message.Caption,
- )
+ logger.Debug().
+ Str("user", fmt.Sprintf("%s(%s)[%d]", utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID)).
+ Str("chat", fmt.Sprintf("%s(%s)[%d]", utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID)).
+ Int("messageID", update.Message.ID).
+ Str("caption", update.Message.Caption).
+ Msg("photo message")
} else if update.Message.Sticker != nil {
- log.Printf("sticker message from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], (%d) sticker: %s[%s:%s]",
- utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID,
- utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID,
- update.Message.ID, update.Message.Sticker.Emoji, update.Message.Sticker.SetName, update.Message.Sticker.FileID,
- )
+ logger.Debug().
+ Str("user", fmt.Sprintf("%s(%s)[%d]", utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID)).
+ Str("chat", fmt.Sprintf("%s(%s)[%d]", utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID)).
+ Int("messageID", update.Message.ID).
+ Str("sticker emoji", update.Message.Sticker.Emoji).
+ Str("sticker setname", update.Message.Sticker.SetName).
+ Str("sticker file ID", update.Message.Sticker.FileID).
+ Msg("sticker message")
} else {
- log.Printf("message from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], (%d) message: [%s]",
- utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID,
- utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID,
- update.Message.ID, update.Message.Text,
- )
+ logger.Debug().
+ Str("user", fmt.Sprintf("%s(%s)[%d]", utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID)).
+ Str("chat", fmt.Sprintf("%s(%s)[%d]", utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID)).
+ Int("messageID", update.Message.ID).
+ Str("text", update.Message.Text).
+ Msg("message")
}
}
@@ -328,7 +332,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Text: "不存在的命令",
})
database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand)
- if consts.Private_log { mess.PrivateLogToChat(opts.Ctx, opts.Thebot, opts.Update) }
+ if configs.BotConfig.LogChatID != 0 { mess.PrivateLogToChat(opts.Ctx, opts.Thebot, opts.Update) }
} else if strings.HasSuffix(opts.Fields[0], "@" + consts.BotMe.Username) {
// 当使用一个不存在的命令,但是命令末尾指定为此 bot 处理
// 为防止与其他 bot 的命令冲突,默认不会处理不在命令列表中的命令
@@ -438,9 +442,9 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
func inlineHandler(opts *handler_structs.SubHandlerParams) {
defer utils.PanicCatcher("inlineHandler")
- var IsAdmin bool = utils.AnyContains(opts.Update.InlineQuery.From.ID, consts.LogMan_IDs)
+ var IsAdmin bool = utils.AnyContains(opts.Update.InlineQuery.From.ID, configs.BotConfig.AdminIDs)
- if opts.Update.InlineQuery.Query == consts.InlineSubCommandSymbol {
+ if opts.Update.InlineQuery.Query == configs.BotConfig.InlineSubCommandSymbol {
// 仅输入了命令符号,展示命令列表
var inlineButton = &models.InlineQueryResultsButton{
Text: "点击此处修改默认命令",
@@ -486,7 +490,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
log.Printf("Error sending inline query response: %v", err)
return
}
- } else if strings.HasPrefix(opts.Update.InlineQuery.Query, consts.InlineSubCommandSymbol) {
+ } else if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol) {
// 用户输入了分页符号和一些字符,判断接着的命令是否正确,正确则交给对应的插件处理,否则提示错误
// 插件处理完后返回全部列表,由设定好的函数进行分页输出
@@ -526,7 +530,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin {
continue
}
- if strings.HasPrefix(opts.Update.InlineQuery.Query, consts.InlineSubCommandSymbol + plugin.PrefixCommand) {
+ if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol + plugin.PrefixCommand) {
if plugin.Handler == nil { continue }
plugin.Handler(opts)
return
@@ -620,7 +624,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
log.Println("Error when answering inline default command invailid:", err)
}
return
- } else if consts.InlineDefaultHandler != "" {
+ } else if configs.BotConfig.InlineDefaultHandler != "" {
// 全局设定里设定的默认命令
// 插件处理完后返回全部列表,由设定好的函数进行分页输出
@@ -628,7 +632,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin {
continue
}
- if consts.InlineDefaultHandler == plugin.Command {
+ if configs.BotConfig.InlineDefaultHandler == plugin.Command {
if plugin.Handler == nil { continue }
ResultList := plugin.Handler(opts)
_, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
@@ -653,7 +657,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin {
continue
}
- if consts.InlineDefaultHandler == plugin.Command {
+ if configs.BotConfig.InlineDefaultHandler == plugin.Command {
if plugin.Handler == nil { continue }
plugin.Handler(opts)
return
@@ -676,7 +680,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
if len(plugin_utils.AllPlugins.InlineCommandList) == 0 {
pendingMessage = "此 bot 似乎并没有使用任何 inline 模式插件,请联系管理员"
} else {
- pendingMessage = fmt.Sprintf("您可以继续输入 %s 号来查看其他可用的命令", consts.InlineSubCommandSymbol)
+ pendingMessage = fmt.Sprintf("您可以继续输入 %s 号来查看其他可用的命令", configs.BotConfig.InlineSubCommandSymbol)
}
_, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
InlineQueryID: opts.Update.InlineQuery.ID,
@@ -711,7 +715,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
if command.Attr.IsHideInCommandList {
continue
}
- message += fmt.Sprintf("命令: %s%s\n", consts.InlineSubCommandSymbol, command.Command)
+ message += fmt.Sprintf("命令: %s%s\n", configs.BotConfig.InlineSubCommandSymbol, command.Command)
if command.Description != "" {
message += fmt.Sprintf("描述: %s\n", command.Description)
}
@@ -722,7 +726,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
InlineQueryID: opts.Update.InlineQuery.ID,
Results: []models.InlineQueryResult{&models.InlineQueryResultArticle{
ID: "nodefaulthandler",
- Title: fmt.Sprintf("请继续输入 %s 来查看可用的命令", consts.InlineSubCommandSymbol),
+ Title: fmt.Sprintf("请继续输入 %s 来查看可用的命令", configs.BotConfig.InlineSubCommandSymbol),
Description: "由于管理员没有设定默认命令,您需要手动选择一个命令,点击此处查看命令列表",
InputMessageContent: &models.InputTextMessageContent{
MessageText: message,
diff --git a/main.go b/main.go
index f7e515a..313d1a3 100644
--- a/main.go
+++ b/main.go
@@ -2,98 +2,102 @@ package main
import (
"context"
- "fmt"
- "log"
"net/http"
"os"
"os/signal"
"time"
"trbot/database"
+ "trbot/utils/configs"
"trbot/utils/consts"
- "trbot/utils/mess"
"trbot/utils/internal_plugin"
"trbot/utils/signals"
"github.com/go-telegram/bot"
- "github.com/go-telegram/bot/models"
+ "github.com/rs/zerolog"
+ "github.com/rs/zerolog/log"
)
-
func main() {
- consts.BotToken = mess.WhereIsBotToken()
-
- consts.IsDebugMode = os.Getenv("DEBUG") == "true"
- if consts.IsDebugMode {
- log.Println("running in debug mode, all log will be printed to stdout")
- }
-
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
- allowedUpdates := bot.AllowedUpdates{
- models.AllowedUpdateMessage,
- models.AllowedUpdateEditedMessage,
- models.AllowedUpdateChannelPost,
- models.AllowedUpdateEditedChannelPost,
- models.AllowedUpdateInlineQuery,
- models.AllowedUpdateChosenInlineResult,
- models.AllowedUpdateCallbackQuery,
+ // create a logger and attached it into ctx
+ logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
+ ctx = logger.WithContext(ctx)
+
+ // read configs
+ if err := configs.InitBot(ctx); err != nil {
+ logger.Fatal().Err(err).Msg("Failed to read bot configs")
+ }
+
+ // set log level from config
+ zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog())
+ if zerolog.GlobalLevel() == zerolog.DebugLevel {
+ consts.IsDebugMode = true
}
opts := []bot.Option{
bot.WithDefaultHandler(defaultHandler),
- bot.WithAllowedUpdates(allowedUpdates),
+ bot.WithAllowedUpdates(configs.BotConfig.AllowedUpdates),
}
- thebot, err := bot.New(consts.BotToken, opts...)
- if err != nil { panic(err) }
+ thebot, err := bot.New(configs.BotConfig.BotToken, opts...)
+ if err != nil { logger.Panic().Err(err).Msg("Failed to initialize bot") }
- consts.BotMe, _ = thebot.GetMe(ctx)
- log.Printf("name[%s] [@%s] id[%d]", consts.BotMe.FirstName, consts.BotMe.Username, consts.BotMe.ID)
-
- log.Printf("starting %d\n", consts.BotMe.ID)
- log.Printf("logChat_ID: %v", consts.LogChat_ID)
+ consts.BotMe, err = thebot.GetMe(ctx)
+ if err != nil { logger.Panic().Err(err).Msg("Failed to get bot info") }
+ logger.Info().
+ Str("name", consts.BotMe.FirstName).
+ Str("username", consts.BotMe.Username).
+ Int64("ID", consts.BotMe.ID).
+ Msg("bot initialized")
+ if configs.BotConfig.LogChatID != 0 {
+ logger.Info().
+ Int64("LogChatID", configs.BotConfig.LogChatID).
+ Msg("Enabled log to chat")
+ }
database.InitAndListDatabases()
- go signals.SignalsHandler(ctx, consts.SignalsChannel)
+ // start handler custom signals
+ go signals.SignalsHandler(ctx)
- // 初始化插件
+ // register plugin (internal first, then external)
internal_plugin.Register()
- // 检查是否设定了 webhookURL 环境变量
- if mess.UsingWebhook() { // Webhook
- mess.SetUpWebhook(ctx, thebot, &bot.SetWebhookParams{
- URL: consts.WebhookURL,
- AllowedUpdates: allowedUpdates,
+ // Select mode by Webhook config
+ if configs.IsUsingWebhook(ctx) { // Webhook
+ configs.SetUpWebhook(ctx, thebot, &bot.SetWebhookParams{
+ URL: configs.BotConfig.WebhookURL,
+ AllowedUpdates: configs.BotConfig.AllowedUpdates,
})
- log.Println("Working at Webhook Mode")
+ logger.Info().
+ Str("listenAddress", consts.WebhookListenPort).
+ Msg("Working at Webhook Mode")
go thebot.StartWebhook(ctx)
- go func() {
- err := http.ListenAndServe(consts.WebhookPort, thebot.WebhookHandler())
- if err != nil { log.Panicln(err) }
- }()
- } else { // getUpdate, aka Long Polling
- // 保存并清理云端 Webhook URL,否则该模式会不生效 https://core.telegram.org/bots/api#getupdates
- mess.SaveAndCleanRemoteWebhookURL(ctx, thebot)
- log.Println("Working at Long Polling Mode")
- if consts.IsDebugMode {
- fmt.Printf("If in debug, visit https://api.telegram.org/bot%s/getWebhookInfo to check infos \n", consts.BotToken)
- fmt.Printf("If in debug, visit https://api.telegram.org/bot%s/setWebhook?url=https://api.trle5.xyz/webhook-trbot to reset webhook\n", consts.BotToken)
+ err := http.ListenAndServe(consts.WebhookListenPort, thebot.WebhookHandler())
+ if err != nil {
+ logger.Panic().
+ Err(err).
+ Msg("webhook server failed")
}
+ } else { // getUpdate, aka Long Polling
+ // save and clean remove Webhook URL befor using getUpdate https://core.telegram.org/bots/api#getupdates
+ configs.SaveAndCleanRemoteWebhookURL(ctx, thebot)
+ logger.Info().
+ Msg("Working at Long Polling Mode")
+ logger.Debug().
+ Msgf("visit https://api.telegram.org/bot%s/getWebhookInfo to check infos", configs.BotConfig.BotToken)
thebot.Start(ctx)
}
+ // a loop wait for getUpdate mode, this program will exit in `utils\signals\signals.go`.
+ // This loop will only run when the exit signal is received in getUpdate mode.
+ // Webhook won't reach here, http.ListenAndServe() will keep program running till exit.
+ // They use the same code to exit, this loop is to give some time to save the database when receive exit signal.
for {
- select {
- case <- consts.SignalsChannel.WorkDone:
- log.Println("manually stopped")
- return
- default:
- // log.Println("still waiting...") // 不在调式模式下,这个日志会非常频繁
- time.Sleep(1 * time.Second)
- }
+ logger.Info().Msg("still waiting...")
+ time.Sleep(2 * time.Second)
}
-
}
diff --git a/plugins/plugin_detect_keyword.go b/plugins/plugin_detect_keyword.go
index c595898..2b7a7b6 100644
--- a/plugins/plugin_detect_keyword.go
+++ b/plugins/plugin_detect_keyword.go
@@ -5,6 +5,7 @@ import (
"io"
"log"
"os"
+ "path/filepath"
"strconv"
"strings"
"time"
@@ -23,7 +24,7 @@ var KeywordDataList KeywordData = KeywordData{
Users: map[int64]KeywordUserList{},
}
var KeywordDataErr error
-var KeywordData_path string = consts.DB_path + "detectkeyword/"
+var KeywordData_path string = filepath.Join(consts.YAMLDataBasePath, "detectkeyword/")
func init() {
ReadKeywordList()
@@ -151,7 +152,7 @@ type ChatForUser struct {
func ReadKeywordList() {
var lists KeywordData
- file, err := os.Open(KeywordData_path + consts.MetadataFileName)
+ file, err := os.Open(filepath.Join(KeywordData_path, consts.YAMLFileName))
if err != nil {
// 如果是找不到目录,新建一个
log.Println("[DetectKeyword]: Not found database file. Created new one")
@@ -189,14 +190,14 @@ func SaveKeywordList() error {
}
}
- if _, err := os.Stat(KeywordData_path + consts.MetadataFileName); os.IsNotExist(err) {
- _, err := os.Create(KeywordData_path + consts.MetadataFileName)
+ if _, err := os.Stat(filepath.Join(KeywordData_path, consts.YAMLFileName)); os.IsNotExist(err) {
+ _, err := os.Create(filepath.Join(KeywordData_path, consts.YAMLFileName))
if err != nil {
return err
}
}
- return os.WriteFile(KeywordData_path + consts.MetadataFileName, data, 0644)
+ return os.WriteFile(filepath.Join(KeywordData_path, consts.YAMLFileName), data, 0644)
}
func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
diff --git a/plugins/plugin_limit_message.go b/plugins/plugin_limit_message.go
index c5ded28..9f7f6f0 100644
--- a/plugins/plugin_limit_message.go
+++ b/plugins/plugin_limit_message.go
@@ -5,6 +5,7 @@ import (
"io"
"log"
"os"
+ "path/filepath"
"reflect"
"strings"
"time"
@@ -23,7 +24,7 @@ import (
var LimitMessageList map[int64]AllowMessages
var LimitMessageErr error
-var LimitMessage_path string = consts.DB_path + "limitmessage/"
+var LimitMessage_path string = filepath.Join(consts.YAMLDataBasePath, "limitmessage/")
type AllowMessages struct {
IsEnable bool `yaml:"IsEnable"`
@@ -70,20 +71,20 @@ func SaveLimitMessageList() error {
}
}
- if _, err := os.Stat(LimitMessage_path + consts.MetadataFileName); os.IsNotExist(err) {
- _, err := os.Create(LimitMessage_path + consts.MetadataFileName)
+ if _, err := os.Stat(filepath.Join(LimitMessage_path, consts.YAMLFileName)); os.IsNotExist(err) {
+ _, err := os.Create(filepath.Join(LimitMessage_path, consts.YAMLFileName))
if err != nil {
return err
}
}
- return os.WriteFile(LimitMessage_path + consts.MetadataFileName, data, 0644)
+ return os.WriteFile(filepath.Join(LimitMessage_path, consts.YAMLFileName), data, 0644)
}
func ReadLimitMessageList() {
var limitMessageList map[int64]AllowMessages
- file, err := os.Open(LimitMessage_path + consts.MetadataFileName)
+ file, err := os.Open(filepath.Join(LimitMessage_path, consts.YAMLFileName))
if err != nil {
// 如果是找不到目录,新建一个
log.Println("[LimitMessage]: Not found database file. Created new one")
diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go
index db868ed..24f6ed6 100644
--- a/plugins/plugin_sticker.go
+++ b/plugins/plugin_sticker.go
@@ -12,6 +12,7 @@ import (
"strings"
"trbot/database"
"trbot/database/db_struct"
+ "trbot/utils/configs"
"trbot/utils/consts"
"trbot/utils/handler_structs"
"trbot/utils/plugin_utils"
@@ -22,9 +23,9 @@ import (
"golang.org/x/image/webp"
)
-var StickerCache_path string = consts.Cache_path + "sticker/"
-var StickerCachePNG_path string = consts.Cache_path + "sticker_png/"
-var StickerCacheZip_path string = consts.Cache_path + "sticker_zip/"
+var StickerCache_path string = filepath.Join(consts.CacheDirectory, "sticker/")
+var StickerCachePNG_path string = filepath.Join(consts.CacheDirectory, "sticker_png/")
+var StickerCacheZip_path string = filepath.Join(consts.CacheDirectory, "sticker_zip/")
func init() {
plugin_utils.AddCallbackQueryCommandPlugins([]plugin_utils.CallbackQuery{
@@ -112,11 +113,11 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error)
stickerFileNameWithDot = fmt.Sprintf("%s.", opts.Update.Message.Sticker.FileID)
}
- var filePath string = StickerCache_path + stickerSetNamePrivate + "/" // 保存贴纸源文件的目录 .cache/sticker/setName/
- var originFullPath string = filePath + stickerFileNameWithDot + fileSuffix // 到贴纸文件的完整目录 .cache/sticker/setName/stickerFileName.webp
+ var filePath string = StickerCache_path + stickerSetNamePrivate + "/" // 保存贴纸源文件的目录 .cache/sticker/setName/
+ var originFullPath string = filePath + stickerFileNameWithDot + fileSuffix // 到贴纸文件的完整目录 .cache/sticker/setName/stickerFileName.webp
- var filePathPNG string = StickerCachePNG_path + stickerSetNamePrivate + "/" // 转码后为 png 格式的目录 .cache/sticker_png/setName/
- var toPNGFullPath string = filePathPNG + stickerFileNameWithDot + "png" // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png
+ var filePathPNG string = StickerCachePNG_path + stickerSetNamePrivate + "/" // 转码后为 png 格式的目录 .cache/sticker_png/setName/
+ var toPNGFullPath string = filePathPNG + stickerFileNameWithDot + "png" // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png
_, err := os.Stat(originFullPath) // 检查贴纸源文件是否已缓存
// 如果文件不存在,进行下载,否则跳过
@@ -129,7 +130,7 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error)
if consts.IsDebugMode { log.Printf("file [%s] doesn't exist, downloading %s", originFullPath, fileinfo.FilePath) }
// 组合链接下载贴纸源文件
- resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", consts.BotToken, fileinfo.FilePath))
+ resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", configs.BotConfig.BotToken, fileinfo.FilePath))
if err != nil { return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) }
defer resp.Body.Close()
@@ -236,7 +237,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S
}
// 下载贴纸文件
- resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", consts.BotToken, fileinfo.FilePath))
+ resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", configs.BotConfig.BotToken, fileinfo.FilePath))
if err != nil { return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) }
defer resp.Body.Close()
diff --git a/plugins/plugin_teamspeak3.go b/plugins/plugin_teamspeak3.go
index 2af44db..947869b 100644
--- a/plugins/plugin_teamspeak3.go
+++ b/plugins/plugin_teamspeak3.go
@@ -4,11 +4,12 @@ import (
"fmt"
"log"
"os"
+ "path/filepath"
"time"
- "trbot/utils"
"trbot/utils/consts"
"trbot/utils/handler_structs"
"trbot/utils/plugin_utils"
+ "trbot/utils/yaml"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
@@ -23,7 +24,7 @@ import (
var tsClient *ts3.Client
var tsErr error
-var tsData_path string = consts.DB_path + "teamspeak/"
+var tsData_path string = filepath.Join(consts.YAMLDataBasePath, "teamspeak/")
var botNickName string = "trbot_teamspeak_plugin"
var isCanReInit bool = true
@@ -52,7 +53,7 @@ func init() {
// 初始化不成功时依然注册 `/ts3` 命令,使用命令式输出初始化时的错误
if initTeamSpeak() {
isSuccessInit = true
- log.Println("TeamSpeak plugin loaded")
+ log.Println("[TeamSpeak] plugin loaded")
// 需要以群组 ID 来触发 handler 来获取 opts
plugin_utils.AddHandlerByChatIDPlugins(plugin_utils.HandlerByChatID{
@@ -62,7 +63,7 @@ func init() {
})
hasHandlerByChatID = true
} else {
- log.Println("TeamSpeak plugin loaded failed:", tsErr)
+ log.Println("[TeamSpeak] plugin loaded failed:", tsErr)
}
plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{
@@ -83,15 +84,16 @@ func initTeamSpeak() bool {
if err != nil {
if os.IsNotExist(err) {
// 不存在,创建一份空文件
- err = utils.SaveYAML(tsData_path + consts.MetadataFileName, &TSServerQuery{})
+ err = yaml.SaveYAML(filepath.Join(tsData_path, consts.YAMLFileName), &TSServerQuery{})
if err != nil {
- log.Println("[teamspeak] empty config create faild:", err)
+ log.Println("[TeamSpeak] empty config create faild:", err)
} else {
- log.Printf("[teamspeak] empty config created at [ %s ]", tsData_path)
+ log.Printf("[TeamSpeak] empty config created at [ %s ]", tsData_path)
}
+ tsErr = fmt.Errorf("config file not exist, created empty config file")
} else {
// 文件存在,但是遇到了其他错误
- tsErr = fmt.Errorf("[teamspeak] some error when read config file: %w", err)
+ tsErr = fmt.Errorf("some error when read config file: %w", err)
}
// 无法获取到服务器地址和账号,无法初始化并设定不可重新启动
@@ -99,38 +101,38 @@ func initTeamSpeak() bool {
return false
}
- err = utils.LoadYAML(tsData_path + consts.MetadataFileName, &tsServerQuery)
+ err = yaml.LoadYAML(filepath.Join(tsData_path, consts.YAMLFileName), &tsServerQuery)
if err != nil {
// if err != nil || tsServerQuery == nil {
// 读取配置文件内容失败也不允许重新启动
- tsErr = fmt.Errorf("[teamspeak] read config error: %w", err)
+ tsErr = fmt.Errorf("read config error: %w", err)
isCanReInit = false
return false
}
// 如果服务器地址为空不允许重新启动
if tsServerQuery.URL == "" {
- tsErr = fmt.Errorf("[teamspeak] no URL in config")
+ tsErr = fmt.Errorf("no URL in config")
isCanReInit = false
return false
} else {
if tsClient != nil { tsClient.Close() }
tsClient, tsErr = ts3.NewClient(tsServerQuery.URL)
if tsErr != nil {
- tsErr = fmt.Errorf("[teamspeak] connect error: %w", tsErr)
+ tsErr = fmt.Errorf("connect error: %w", tsErr)
return false
}
}
// ServerQuery 账号名或密码为空也不允许重新启动
if tsServerQuery.Name == "" || tsServerQuery.Password == "" {
- tsErr = fmt.Errorf("[teamspeak] no Name/Password in config")
+ tsErr = fmt.Errorf("no Name/Password in config")
isCanReInit = false
return false
} else {
err = tsClient.Login(tsServerQuery.Name, tsServerQuery.Password)
if err != nil {
- tsErr = fmt.Errorf("[teamspeak] login error: %w", err)
+ tsErr = fmt.Errorf("login error: %w", err)
isLoginFailed = true
return false
} else {
@@ -140,7 +142,7 @@ func initTeamSpeak() bool {
// 检查要设定通知的群组 ID 是否存在
if tsServerQuery.GroupID == 0 {
- tsErr = fmt.Errorf("[teamspeak] no GroupID in config")
+ tsErr = fmt.Errorf("no GroupID in config")
isCanReInit = false
return false
}
@@ -148,28 +150,28 @@ func initTeamSpeak() bool {
// 显示服务端版本测试一下连接
v, err := tsClient.Version()
if err != nil {
- tsErr = fmt.Errorf("[teamspeak] show version error: %w", err)
+ tsErr = fmt.Errorf("show version error: %w", err)
return false
} else {
- log.Printf("[teamspeak] running: %v", v)
+ log.Printf("[TeamSpeak] running: %v", v)
}
// 切换默认虚拟服务器
err = tsClient.Use(1)
if err != nil {
- tsErr = fmt.Errorf("[teamspeak] switch server error: %w", err)
+ tsErr = fmt.Errorf("switch server error: %w", err)
return false
}
// 改一下 bot 自己的 nickname,使得在检测用户列表时默认不显示自己
m, err := tsClient.Whoami()
if err != nil {
- tsErr = fmt.Errorf("[teamspeak] get my info error: %w", err)
+ tsErr = fmt.Errorf("get my info error: %w", err)
} else if m != nil && m.ClientName != botNickName {
// 当 bot 自己的 nickname 不等于配置文件中的 nickname 时,才进行修改
err = tsClient.SetNick(botNickName)
if err != nil {
- tsErr = fmt.Errorf("[teamspeak] set nickname error: %w", err)
+ tsErr = fmt.Errorf("set nickname error: %w", err)
}
}
@@ -182,10 +184,10 @@ func getOptsHandler(opts *handler_structs.SubHandlerParams) {
if !isListening && isCanReInit && opts.Update.Message.Chat.ID == tsServerQuery.GroupID {
privateOpts = opts
isCanListening = true
- if consts.IsDebugMode { log.Println("[teamspeak] success get opts by handler") }
+ if consts.IsDebugMode { log.Println("[TeamSpeak] success get opts by handler") }
if !isLoginFailed {
go listenUserStatus()
- if consts.IsDebugMode { log.Println("[teamspeak] success start listening") }
+ if consts.IsDebugMode { log.Println("[TeamSpeak] success start listening") }
}
}
}
@@ -198,10 +200,10 @@ func showStatus(opts *handler_structs.SubHandlerParams) {
if !isListening && isCanReInit && opts.Update.Message.Chat.ID == tsServerQuery.GroupID {
privateOpts = opts
isCanListening = true
- if consts.IsDebugMode { log.Println("[teamspeak] success get opts") }
+ if consts.IsDebugMode { log.Println("[TeamSpeak] success get opts") }
if !isLoginFailed {
go listenUserStatus()
- if consts.IsDebugMode { log.Println("[teamspeak] success start listening") }
+ if consts.IsDebugMode { log.Println("[TeamSpeak] success start listening") }
}
// pendingMessage += fmt.Sprintln("已准备好发送用户状态")
}
@@ -209,7 +211,7 @@ func showStatus(opts *handler_structs.SubHandlerParams) {
if isSuccessInit && isCanListening {
olClient, err := tsClient.Server.ClientList()
if err != nil {
- log.Println("[teamspeak] get online client error:", err)
+ log.Println("[TeamSpeak] get online client error:", err)
pendingMessage = fmt.Sprintf("连接到 teamspeak 服务器发生错误:\n
%s", err) } else { pendingMessage += fmt.Sprintln("在线客户端:") @@ -238,7 +240,7 @@ func showStatus(opts *handler_structs.SubHandlerParams) { tsErr = fmt.Errorf("") if !isListening && !isLoginFailed { go listenUserStatus() - if consts.IsDebugMode { log.Println("[teamspeak] success start listening") } + if consts.IsDebugMode { log.Println("[TeamSpeak] success start listening") } } resetListenTicker <- true pendingMessage = "尝试重新初始化成功,现可正常运行" @@ -260,7 +262,7 @@ func showStatus(opts *handler_structs.SubHandlerParams) { ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, }) if err != nil { - log.Println("[teamspeak] can't answer `/ts3` command:",err) + log.Println("[TeamSpeak] can't answer `/ts3` command:",err) } } @@ -288,7 +290,7 @@ func listenUserStatus() { if isSuccessInit && isCanListening { beforeOnlineClient = checkOnlineClientChange(beforeOnlineClient) } else { - if consts.IsDebugMode { log.Println("[teamspeak] try reconnect...") } + if consts.IsDebugMode { log.Println("[TeamSpeak] try reconnect...") } // 出现错误时,先降低 ticker 速度,然后尝试重新初始化 listenTicker.Reset(time.Duration(retryCount) * 20 * time.Second) if retryCount < 15 { retryCount++ } @@ -298,7 +300,7 @@ func listenUserStatus() { // 重新初始化成功则恢复 ticker 速度 retryCount = 1 listenTicker.Reset(pollingInterval) - if consts.IsDebugMode { log.Println("[teamspeak] reconnect success") } + if consts.IsDebugMode { log.Println("[TeamSpeak] reconnect success") } privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ ChatID: privateOpts.Update.Message.Chat.ID, Text: "已成功与服务器重新建立连接", @@ -306,7 +308,7 @@ func listenUserStatus() { }) } else { // 无法成功则等待下一个周期继续尝试 - if consts.IsDebugMode { log.Printf("[teamspeak] connect failed [%s], retry in %ds", tsErr, (retryCount - 1) * 20) } + if consts.IsDebugMode { log.Printf("[TeamSpeak] connect failed [%s], retry in %ds", tsErr, (retryCount - 1) * 20) } } } } @@ -318,7 +320,7 @@ func checkOnlineClientChange(before []string) []string { olClient, err := tsClient.Server.ClientList() if err != nil { - log.Println("[teamspeak] get online client error:", err) + log.Println("[TeamSpeak] get online client error:", err) isCanListening = false privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ ChatID: privateOpts.Update.Message.Chat.ID, @@ -332,7 +334,7 @@ func checkOnlineClientChange(before []string) []string { added, removed := DiffSlices(before, nowOnlineClient) if len(added) + len(removed) > 0 { if consts.IsDebugMode { - log.Printf("[teamspeak] online client change: added %v, removed %v", added, removed) + log.Printf("[TeamSpeak] online client change: added %v, removed %v", added, removed) } notifyClientChange(privateOpts, tsServerQuery.GroupID, added, removed) } diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index a010271..19807a4 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -5,11 +5,13 @@ import ( "io" "log" "os" + "path/filepath" "strconv" "strings" "time" "trbot/utils" + "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/plugin_utils" @@ -19,10 +21,10 @@ import ( "gopkg.in/yaml.v3" ) -var UdoneseData *Udonese -var UdoneseErr error +var UdoneseData Udonese +var UdoneseErr error -var Udonese_path string = consts.DB_path + "udonese/" +var Udonese_path string = filepath.Join(consts.YAMLDataBasePath, "udonese/") var UdonGroupID int64 = -1002205667779 var UdoneseManagerIDs []int64 = []int64{ 872082796, // akaudon @@ -136,14 +138,14 @@ type UdoneseMeaning struct { } func ReadUdonese() { - var udonese *Udonese + var udonese Udonese - file, err := os.Open(Udonese_path + consts.MetadataFileName) + file, err := os.Open(filepath.Join(Udonese_path, consts.YAMLFileName)) if err != nil { // 如果是找不到目录,新建一个 - log.Println("[Udonese]: Not found database file. Created new one") + log.Println("[Udonese] Not found database file. Created new one") SaveUdonese() - UdoneseData, UdoneseErr = &Udonese{}, err + UdoneseData, UdoneseErr = Udonese{}, err return } defer file.Close() @@ -152,13 +154,13 @@ func ReadUdonese() { err = decoder.Decode(&udonese) if err != nil { if err == io.EOF { - log.Println("[Udonese]: Udonese list looks empty. now format it") + log.Println("[Udonese] Udonese list looks empty. now format it") SaveUdonese() - UdoneseData, UdoneseErr = &Udonese{}, nil + UdoneseData, UdoneseErr = Udonese{}, nil return } - log.Println("(func)ReadUdonese:", err) - UdoneseData, UdoneseErr = &Udonese{}, err + log.Println("[Udonese] (func)ReadUdonese:", err) + UdoneseData, UdoneseErr = Udonese{}, err return } UdoneseData, UdoneseErr = udonese, nil @@ -174,22 +176,22 @@ func SaveUdonese() error { } } - if _, err := os.Stat(Udonese_path + consts.MetadataFileName); os.IsNotExist(err) { - _, err := os.Create(Udonese_path + consts.MetadataFileName) + if _, err := os.Stat(filepath.Join(Udonese_path, consts.YAMLFileName)); os.IsNotExist(err) { + _, err := os.Create(filepath.Join(Udonese_path, consts.YAMLFileName)) if err != nil { return err } } - return os.WriteFile(Udonese_path + consts.MetadataFileName, data, 0644) + return os.WriteFile(filepath.Join(Udonese_path, consts.YAMLFileName), data, 0644) } // 如果要添加的意思重复,返回对应意思的单个词结构体指针,否则返回空指针 // 设计之初可以添加多个意思,但现在不推荐这样 -func addUdonese(udonese *Udonese, params *UdoneseWord) *UdoneseWord { - for wordIndex, savedList := range udonese.List { +func addUdonese(params *UdoneseWord) *UdoneseWord { + for wordIndex, savedList := range UdoneseData.List { if strings.EqualFold(savedList.Word, params.Word){ - log.Printf("发现已存在的词 [%s],正在检查是否有新增的意思", savedList.Word) + log.Printf("[Udonese] 发现已存在的词 [%s],正在检查是否有新增的意思", savedList.Word) for _, newMeaning := range params.MeaningList { var isreallynew bool = true for _, oldmeanlist := range savedList.MeaningList { @@ -198,19 +200,19 @@ func addUdonese(udonese *Udonese, params *UdoneseWord) *UdoneseWord { } } if isreallynew { - udonese.List[wordIndex].MeaningList = append(udonese.List[wordIndex].MeaningList, newMeaning) - log.Printf("正在为 [%s] 添加 [%s] 意思", udonese.List[wordIndex].Word, newMeaning.Meaning) + UdoneseData.List[wordIndex].MeaningList = append(UdoneseData.List[wordIndex].MeaningList, newMeaning) + log.Printf("[Udonese] 正在为 [%s] 添加 [%s] 意思", UdoneseData.List[wordIndex].Word, newMeaning.Meaning) } else { - log.Println("存在的意思,跳过", newMeaning) + log.Println("[Udonese] 存在的意思,跳过", newMeaning) return &savedList } } return nil } } - log.Printf("发现新的词 [%s],正在添加 %v", params.Word, params.MeaningList) - udonese.List = append(udonese.List, *params) - udonese.Count++ + log.Printf("[Udonese] 发现新的词 [%s],正在添加 %v", params.Word, params.MeaningList) + UdoneseData.List = append(UdoneseData.List, *params) + UdoneseData.Count++ return nil } @@ -231,7 +233,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, }) if err != nil { - log.Println("error sending /udonese not allowed group:", err) + log.Println("[Udonese] error sending /udonese not allowed group:", err) } return } @@ -265,7 +267,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, }) if err != nil { - log.Println("error sending /udonese word not found:", err) + log.Println("[Udonese] error sending /udonese word not found:", err) } return } @@ -382,7 +384,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { var pendingMessage string var botMessage *models.Message - oldMeaning := addUdonese(UdoneseData, &UdoneseWord{ + oldMeaning := addUdonese(&UdoneseWord{ Word: opts.Fields[1], MeaningList: []UdoneseMeaning{{ Meaning: meaning, @@ -570,7 +572,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { } if UdoneseErr != nil { - log.Println("some error in while read udonese list: ", UdoneseErr) + log.Println("[Udonese] some error in while read udonese list: ", UdoneseErr) ReadUdonese() } @@ -580,9 +582,8 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { UdoneseData.List[i].Used++ err := SaveUdonese() if err != nil { - log.Println("get some error when add udonese used count:", err) + log.Println("[Udonese] get some error when add udonese used count:", err) } - // fmt.Println(udon.List[i].Word, "+1", udon.List[i].Used) } } @@ -597,7 +598,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "点击浏览全部词与意思", - SwitchInlineQueryCurrentChat: consts.InlineSubCommandSymbol + "sms ", + SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "sms ", }}}}, }) return @@ -614,7 +615,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, }) if err != nil { - log.Println("get some error when answer udonese meaning:", err) + log.Println("[Udonese] get some error when answer udonese meaning:", err) } return } @@ -743,7 +744,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { wordAndIndexList := strings.Split(wordAndIndex, "_") meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) if err != nil { - log.Println("covert meanning index error:", err) + log.Println("[Udonese] covert meanning index error:", err) } var targetMeaning UdoneseMeaning @@ -799,7 +800,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { }}}, }) if err != nil { - log.Println(err) + log.Println("[Udonese] error when editing message:",err) } return @@ -808,7 +809,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { wordAndIndexList := strings.Split(wordAndIndex, "_") meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) if err != nil { - log.Println("covert meanning index error:", err) + log.Println("[Udonese] covert meanning index error:", err) } var newMeaningList []UdoneseMeaning var targetWord UdoneseWord @@ -838,7 +839,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), }) if err != nil { - log.Println("error when edit deleted meaning keyboard:", err) + log.Println("[Udonese] error when edit deleted meaning keyboard:", err) } SaveUdonese() @@ -866,7 +867,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { }}}}, }) if err != nil { - log.Println("error when edit deleted word message:", err) + log.Println("[Udonese] error when edit deleted word message:", err) } SaveUdonese() diff --git a/plugins/plugin_voicelist.go b/plugins/plugin_voicelist.go index 66f5962..c7ae710 100644 --- a/plugins/plugin_voicelist.go +++ b/plugins/plugin_voicelist.go @@ -8,6 +8,7 @@ import ( "strings" "time" "trbot/utils" + "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/plugin_utils" @@ -20,7 +21,7 @@ import ( var VoiceLists []VoicePack var VoiceListErr error -var VoiceList_path string = consts.DB_path + "voices/" +var VoiceList_path string = filepath.Join(consts.YAMLDataBasePath, "voices/") func init() { ReadVoicePackFromPath() @@ -50,7 +51,7 @@ func ReadVoicePackFromPath() { var packs []VoicePack if _, err := os.Stat(VoiceList_path); os.IsNotExist(err) { - log.Printf("No voices dir, create a new one: %s", VoiceList_path) + log.Printf("[VoiceList] No voices dir, create a new one: %s", VoiceList_path) if err := os.MkdirAll(VoiceList_path, 0755); err != nil { VoiceLists, VoiceListErr = nil, err return @@ -61,13 +62,13 @@ func ReadVoicePackFromPath() { if err != nil { return err } if strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml") { file, err := os.Open(path) - if err != nil { log.Println("(func)readVoicesFromDir:", err) } + if err != nil { log.Println("[VoiceList] (func)readVoicesFromDir:", err) } defer file.Close() var singlePack VoicePack decoder := yaml.NewDecoder(file) err = decoder.Decode(&singlePack) - if err != nil { log.Println("(func)readVoicesFromDir:", err) } + if err != nil { log.Println("[VoiceList] (func)readVoicesFromDir:", err) } packs = append(packs, singlePack) } return nil @@ -85,9 +86,9 @@ func VoiceListHandler(opts *handler_structs.SubHandlerParams) []models.InlineQue var results []models.InlineQueryResult if VoiceLists == nil { - log.Printf("No voices file in voices_path: %s", VoiceList_path) + log.Printf("[VoiceList] No voices file in voices_path: %s", VoiceList_path) opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: consts.LogChat_ID, + ChatID: configs.BotConfig.LogChatID, Text: fmt.Sprintf("%s\nInline Mode: some user can't load voices", time.Now().Format(time.RFC3339)), }) return []models.InlineQueryResult{&models.InlineQueryResultVoice{ diff --git a/plugins/saved_message/functions.go b/plugins/saved_message/functions.go index c196fd4..48ae491 100644 --- a/plugins/saved_message/functions.go +++ b/plugins/saved_message/functions.go @@ -5,6 +5,7 @@ import ( "log" "reflect" "trbot/utils" + "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/plugin_utils" @@ -23,7 +24,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "点击浏览您的收藏", - SwitchInlineQueryCurrentChat: consts.InlineSubCommandSymbol + "saved ", + SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", }}}}, } @@ -708,7 +709,7 @@ func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) { ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "点击浏览您的收藏", - SwitchInlineQueryCurrentChat: consts.InlineSubCommandSymbol + "saved ", + SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", }}}}, }) if err != nil { @@ -762,7 +763,7 @@ func Init() { ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{ {{ Text: "点击浏览您的收藏", - SwitchInlineQueryCurrentChat: consts.InlineSubCommandSymbol + "saved ", + SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", }}, {{ Text: "将此功能设定为您的默认 inline 命令", diff --git a/plugins/saved_message/utils.go b/plugins/saved_message/utils.go index ab0932b..3583fb1 100644 --- a/plugins/saved_message/utils.go +++ b/plugins/saved_message/utils.go @@ -5,6 +5,7 @@ import ( "io" "log" "os" + "path/filepath" "strconv" "trbot/utils" "trbot/utils/consts" @@ -17,7 +18,7 @@ import ( var SavedMessageSet map[int64]SavedMessage var SavedMessageErr error -var SavedMessage_path string = consts.DB_path + "savedmessage/" +var SavedMessage_path string = filepath.Join(consts.YAMLDataBasePath, "savedmessage/") var textExpandableLength int = 150 @@ -43,20 +44,20 @@ func SaveSavedMessageList() error { } } - if _, err := os.Stat(SavedMessage_path + consts.MetadataFileName); os.IsNotExist(err) { - _, err := os.Create(SavedMessage_path + consts.MetadataFileName) + if _, err := os.Stat(filepath.Join(SavedMessage_path, consts.YAMLFileName)); os.IsNotExist(err) { + _, err := os.Create(filepath.Join(SavedMessage_path, consts.YAMLFileName)) if err != nil { return err } } - return os.WriteFile(SavedMessage_path + consts.MetadataFileName, data, 0644) + return os.WriteFile(filepath.Join(SavedMessage_path, consts.YAMLFileName), data, 0644) } func ReadSavedMessageList() { var SavedMessages map[int64]SavedMessage - file, err := os.Open(SavedMessage_path + consts.MetadataFileName) + file, err := os.Open(filepath.Join(SavedMessage_path, consts.YAMLFileName)) if err != nil { // 如果是找不到目录,新建一个 log.Println("[SavedMessage]: Not found database file. Created new one") diff --git a/utils/configs/config.go b/utils/configs/config.go new file mode 100644 index 0000000..130366c --- /dev/null +++ b/utils/configs/config.go @@ -0,0 +1,85 @@ +package configs + +import ( + "log" + "strings" + + "github.com/go-telegram/bot" + "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" +) + +// default "./config.yaml", can be changed by env +var ConfigPath string = "./config.yaml" + +type config struct { + // bot config + BotToken string `yaml:"BotToken"` + WebhookURL string `yaml:"WebhookURL"` + + // log + LogLevel string `yaml:"LogLevel"` // `trace` `debug` `info` `warn` `error` `fatal` `panic`, default "info" + LogChatID int64 `yaml:"LogChatID"` + + // admin + AdminIDs []int64 `yaml:"AdminIDs"` + + // redis database + RedisURL string `yaml:"RedisURL"` + RedisPassword string `yaml:"RedisPassword"` + RedisMainDB int `yaml:"RedisMainDB"` + RedisUserInfoDB int `yaml:"RedisUserInfoDB"` + RedisSubDB int `yaml:"RedisSubDB"` + + // inline mode config + InlineDefaultHandler string `yaml:"InlineDefaultHandler"` // Leave empty to show inline menu + InlineSubCommandSymbol string `yaml:"InlineSubCommandSymbol"` // default is "+" + InlinePaginationSymbol string `yaml:"InlinePaginationSymbol"` // default is "-" + InlineResultsPerPage int `yaml:"InlineResultsPerPage"` // default 50, maxinum 50, see https://core.telegram.org/bots/api#answerinlinequery + + AllowedUpdates bot.AllowedUpdates `yaml:"AllowedUpdates"` +} + +func (c config)LevelForZeroLog() zerolog.Level { + switch strings.ToLower(c.LogLevel) { + case "trace": + return zerolog.TraceLevel + case "debug": + return zerolog.DebugLevel + case "info": + return zerolog.InfoLevel + case "warn": + return zerolog.WarnLevel + case "error": + return zerolog.ErrorLevel + case "fatal": + return zerolog.FatalLevel + case "panic": + return zerolog.PanicLevel + default: + log.Printf("Unknown log level [ %s ], using info level", c.LogLevel) + return zerolog.InfoLevel + } +} + +func CreateDefaultConfig() config { + return config{ + BotToken: "REPLACE_THIS_USE_YOUR_BOT_TOKEN", + LogLevel: "info", + + InlineSubCommandSymbol: "+", + InlinePaginationSymbol: "-", + InlineResultsPerPage: 50, + AllowedUpdates: bot.AllowedUpdates{ + models.AllowedUpdateMessage, + models.AllowedUpdateEditedMessage, + models.AllowedUpdateChannelPost, + models.AllowedUpdateEditedChannelPost, + models.AllowedUpdateInlineQuery, + models.AllowedUpdateChosenInlineResult, + models.AllowedUpdateCallbackQuery, + }, + } +} + +var BotConfig config diff --git a/utils/configs/init.go b/utils/configs/init.go new file mode 100644 index 0000000..9b59f48 --- /dev/null +++ b/utils/configs/init.go @@ -0,0 +1,225 @@ +package configs + +import ( + "context" + "fmt" + "os" + "path/filepath" + "trbot/utils/yaml" + "unicode" + + "github.com/joho/godotenv" + "github.com/rs/zerolog" +) + +func InitBot(ctx context.Context) error { + var initFuncs = []func(ctx context.Context)error{ + readConfig, + readBotToken, + readEnvironment, + } + + for _, initfunc := range initFuncs { + err := initfunc(ctx) + if err != nil { return err } + } + + return nil +} + +// 从 yaml 文件读取配置文件 +func readConfig(ctx context.Context) error { + logger := zerolog.Ctx(ctx) + godotenv.Load() + // 先检查一下环境变量里有没有指定配置目录 + configPathToFile := os.Getenv("CONFIG_PATH_TO_FILE") + configDirectory := os.Getenv("CONFIG_DIRECTORY") + + if configPathToFile != "" { + // 检查配置文件是否存在 + if _, err := os.Stat(configPathToFile); err != nil { + if os.IsNotExist(err) { + // 如果配置文件不存在,就以默认配置的方式创建一份 + logger.Warn(). + Str("configPathToFile", configPathToFile). + Msg("The config file does not exist. creating...") + err = yaml.SaveYAML(configPathToFile, CreateDefaultConfig()) + if err != nil { + logger.Error(). + Err(err). + Msg("Create default config failed") + return err + } else { + logger.Warn(). + Str("configPathToFile", configPathToFile). + Msg("The config file is created, please fill the bot token and restart.") + // 创建完成目录就跳到下方读取配置文件 + // 默认配置文件没 bot token 的错误就留后面处理 + } + } else { + // 读取配置文件时的其他错误 + logger.Error(). + Err(err). + Str("configPathToFile", configPathToFile). + Msg("Read config file failed") + return err + } + } + + // 读取配置文件 + ConfigPath = configPathToFile + logger.Info(). + Msg("Read config success from `CONFIG_PATH_TO_FILE` environment") + return yaml.LoadYAML(configPathToFile, &BotConfig) + } else if configDirectory != "" { + // 检查目录是否存在 + if _, err := os.Stat(configDirectory); err != nil { + if os.IsNotExist(err) { + // 目录不存在则创建 + logger.Warn(). + Str("configDirectory", configDirectory). + Msg("Config directory does not exist, creating...") + err = os.MkdirAll(configDirectory, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("configDirectory", configDirectory). + Msg("Create config directory failed") + return err + } + // 如果不出错,到这里会跳到下方的读取配置文件部分 + } else { + // 读取目录时的其他错误 + logger.Error(). + Err(err). + Str("configDirectory", configDirectory). + Msg("Read config directory failed") + return err + } + } + + // 使用默认的配置文件名,把目标配置文件路径补全 + targetConfigPath := filepath.Join(configDirectory, "config.yaml") + + // 检查目录配置文件是否存在 + if _, err := os.Stat(targetConfigPath); err != nil { + if os.IsNotExist(err) { + // 用户指定目录的话,还是不创建配置文件了,提示用户想要自定义配置文件名的话,需要设定另外一个环境变量 + logger.Warn(). + Str("configDirectory", configDirectory). + Msg("No configuration file named `config.yaml` was found in this directory, If you want to set a specific config file name, set the `CONFIG_PATH_TO_FILE` environment variable") + return err + } else { + // 读取目标配置文件路径时的其他错误 + logger.Error(). + Err(err). + Str("targetConfigPath", targetConfigPath). + Msg("Read target config file path failed") + return err + } + } + + // 读取配置文件 + ConfigPath = configPathToFile + logger.Info(). + Msg("Read config path success from `CONFIG_DIRECTORY` environment") + return yaml.LoadYAML(targetConfigPath, &BotConfig) + } else { + // 没有指定任何环境变量,就读取默认的路径 + if _, err := os.Stat(ConfigPath); err != nil { + if os.IsNotExist(err) { + // 如果配置文件不存在,就以默认配置的方式创建一份 + logger.Warn(). + Str("defaultConfigPath", ConfigPath). + Msg("The default config file does not exist. creating...") + err = yaml.SaveYAML(ConfigPath, CreateDefaultConfig()) + if err != nil { + logger.Error(). + Err(err). + Str("defaultConfigPath", ConfigPath). + Msg("Create default config file failed") + return err + } else { + logger.Warn(). + Str("defaultConfigPath", ConfigPath). + Msg("Default config file is created, please fill the bot token and restart.") + // 创建完成目录就跳到下方读取配置文件 + // 默认配置文件没 bot token 的错误就留后面处理 + } + } else { + // 读取配置文件时的其他错误 + logger.Error(). + Err(err). + Str("defaultConfigPath", ConfigPath). + Msg("Read default config file failed") + return err + } + } + + logger.Info(). + Str("defaultConfigPath", ConfigPath). + Msg("Read config file from default path") + return yaml.LoadYAML(ConfigPath, &BotConfig) + } +} + +// 查找 bot token,优先级为 环境变量 > .env 文件 > 配置文件 +func readBotToken(ctx context.Context) error { + logger := zerolog.Ctx(ctx) + // 通过 godotenv 库读取 .env 文件后再尝试读取 + godotenv.Load() + botToken := os.Getenv("BOT_TOKEN") + if botToken != "" { + BotConfig.BotToken = botToken + logger.Info(). + Str("botTokenID", showBotID()). + Msg("Get token from environment or .env file") + return nil + } + + // 从 yaml 配置文件中读取 + if BotConfig.BotToken != "" { + logger.Info(). + Str("botTokenID", showBotID()). + Msg("Get token from config file") + return nil + } + + // 都不存在,提示错误 + logger.Warn(). + Msg("No bot token in environment, .env file and yaml config file, try create a bot from https://t.me/@botfather https://core.telegram.org/bots/tutorial#obtain-your-bot-token and fill it") + return fmt.Errorf("no bot token") + +} + +func readEnvironment(ctx context.Context) error { + logger := zerolog.Ctx(ctx) + godotenv.Load() + if os.Getenv("DEBUG") != "" { + BotConfig.LogLevel = "debug" + logger.Warn(). + Msg("DEBUG environment variable is set, set log level to debug") + } + + logLevel := os.Getenv("LOG_LEVEL") + if logLevel != "" { + BotConfig.LogLevel = logLevel + logger.Warn(). + Str("logLevel", logLevel). + Msg("Get log level from environment") + } + + return nil +} + +func showBotID() string { + var botID string + for _, char := range BotConfig.BotToken { + if unicode.IsDigit(char) { + botID += string(char) + } else { + break // 遇到非数字字符停止 + } + } + return botID +} diff --git a/utils/configs/webhook.go b/utils/configs/webhook.go new file mode 100644 index 0000000..da4653f --- /dev/null +++ b/utils/configs/webhook.go @@ -0,0 +1,105 @@ +package configs + +import ( + "context" + "os" + + "github.com/go-telegram/bot" + "github.com/go-telegram/bot/models" + "github.com/joho/godotenv" + "github.com/rs/zerolog" +) + +// 通过是否设定环境变量和配置文件中的 webhook URL 来决定是否使用 webhook 模式 +func IsUsingWebhook(ctx context.Context) bool { + logger := zerolog.Ctx(ctx) + // 通过 godotenv 库读取 .env 文件后再尝试读取 + godotenv.Load() + webhookURL := os.Getenv("WEBHOOK_URL") + if webhookURL != "" { + BotConfig.WebhookURL = webhookURL + logger.Info(). + Str("Webhook URL", BotConfig.WebhookURL). + Msg("Get Webhook URL from environment or .env file") + return true + } + + // 从 yaml 配置文件中读取 + if BotConfig.WebhookURL != "" { + logger.Info(). + Str("Webhook URL", BotConfig.WebhookURL). + Msg("Get Webhook URL from config file") + return true + } + + logger.Info(). + Msg("No Webhook URL in environment and .env file, using getUpdate mode") + return false +} + +func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookParams) bool { + logger := zerolog.Ctx(ctx) + webHookInfo, err := thebot.GetWebhookInfo(ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Get WebHook info error") + } + if webHookInfo != nil && webHookInfo.URL != params.URL { + if webHookInfo.URL == "" { + logger.Info(). + Msg("Webhook not set, setting it now...") + } else { + logger.Warn(). + Str("Remote URL", webHookInfo.URL). + Str("Local URL", params.URL). + Msg("The remote webhook URL conflicts with the local one, saving and overwriting the remote URL") + } + success, err := thebot.SetWebhook(ctx, params) + if err != nil { + logger.Error(). + Err(err). + Msg("Set WebHook URL failed") + return false + } + if success { + logger.Info(). + Str("Webhook URL", params.URL). + Msg("Set Webhook URL success") + return true + } + } else { + logger.Info(). + Str("Webhook URL", params.URL). + Msg("Webhook URL is already set") + return true + } + + return false +} + +func SaveAndCleanRemoteWebhookURL(ctx context.Context, thebot *bot.Bot) *models.WebhookInfo { + logger := zerolog.Ctx(ctx) + webHookInfo, err := thebot.GetWebhookInfo(ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Get WebHook info error") + } + if webHookInfo != nil && webHookInfo.URL != "" { + logger.Warn(). + Str("Remote URL", webHookInfo.URL). + Msg("There is a webhook URL remotely, saving and clearing it to use the getUpdate mode") + ok, err := thebot.DeleteWebhook(ctx, &bot.DeleteWebhookParams{ + DropPendingUpdates: false, + }) + if !ok { + logger.Error(). + Err(err). + Msg("Delete Webhook URL failed") + } + return webHookInfo + } + + return nil +} diff --git a/utils/consts/consts.go b/utils/consts/consts.go index 827439d..3370da6 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -4,50 +4,14 @@ import ( "github.com/go-telegram/bot/models" ) -var BotToken string // 全局 bot token - -var WebhookURL string // Webhook 运行模式下接受请求的 URL 地址 -var WebhookPort string = "localhost:2847" // Webhook 运行模式下监听的端口 - -var LogChat_ID int64 = -1002499888124 // 用于接收日志的聊天 ID,可以是 用户 群聊 频道 -var LogMan_IDs []int64 = []int64{ // 拥有查看日志权限的用户,可设定多个 - 1086395364, - 2074319561, -} - -var MetadataFileName string = "metadata.yaml" - -var RedisURL string = "localhost:6379" -var RedisPassword string = "" -var RedisMainDB int = 0 -var RedisUserInfoDB int = 1 -var RedisSubDB int = 2 - -var DB_path string = "./db_yaml/" -var LogFile_path string = DB_path + "log.txt" - var IsDebugMode bool -var Private_log bool = false + +var WebhookListenPort string = "localhost:2847" + +var YAMLDataBasePath string = "./DB_yaml/" +var YAMLFileName string = "metadata.yaml" + +var CacheDirectory string = "./cache/" +var LogFilePath string = YAMLDataBasePath + "log.txt" var BotMe *models.User // 用于存储 bot 信息 - -var InlineDefaultHandler string = "voice" // 默认的 inline 命令,设为 "" 会显示进入 inline 命令菜单的提示 -var InlineSubCommandSymbol string = "+" -var InlinePaginationSymbol string = "-" -var InlineResultsPerPage int = 50 // maxinum is 50, see https://core.telegram.org/bots/api#answerinlinequery - -var Cache_path string = "./cache/" - -type SignalChannel struct { - Database_save chan bool - PluginDB_save chan bool - PluginDB_reload chan bool - WorkDone chan bool -} - -var SignalsChannel = SignalChannel{ - Database_save: make(chan bool), - PluginDB_save: make(chan bool), - PluginDB_reload: make(chan bool), - WorkDone: make(chan bool), -} diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go index 2819eb2..2354518 100644 --- a/utils/internal_plugin/register.go +++ b/utils/internal_plugin/register.go @@ -9,10 +9,12 @@ import ( "trbot/database/db_struct" "trbot/plugins" "trbot/utils" + "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/mess" "trbot/utils/plugin_utils" + "trbot/utils/signals" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" @@ -144,7 +146,7 @@ func Register() { Handler: func(opts *handler_structs.SubHandlerParams) { opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, - Text: fmt.Sprintf("选择一个 Inline 模式下的默认命令
由于缓存原因,您可能需要等一会才能看到更新后的结果无论您是否设定了默认命令,您始终都可以在 inline 模式下输入
%s 号来查看全部可用的命令", consts.InlineSubCommandSymbol),
+ Text: fmt.Sprintf("选择一个 Inline 模式下的默认命令由于缓存原因,您可能需要等一会才能看到更新后的结果无论您是否设定了默认命令,您始终都可以在 inline 模式下输入
%s 号来查看全部可用的命令", configs.BotConfig.InlineSubCommandSymbol),
ParseMode: models.ParseModeHTML,
ReplyMarkup: utils.BuildDefaultInlineCommandSelectKeyboard(opts.ChatInfo),
ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID },
@@ -194,7 +196,7 @@ func Register() {
}
}
- consts.SignalsChannel.Database_save <- true
+ signals.SIGNALS.Database_save <- true
},
},
{
@@ -276,7 +278,7 @@ func Register() {
&models.InlineQueryResultArticle{
ID: "error",
Title: "参数过多,请注意空格",
- Description: fmt.Sprintf("使用方法:@%s %suaav <单个音频链接>", consts.BotMe.Username, consts.InlineSubCommandSymbol),
+ Description: fmt.Sprintf("使用方法:@%s %suaav <单个音频链接>", consts.BotMe.Username, configs.BotConfig.InlineSubCommandSymbol),
InputMessageContent: &models.InputTextMessageContent{
MessageText: "由于在使用 inline 模式时没有正确填写参数,无法完成消息",
ParseMode: models.ParseModeMarkdownV1,
@@ -341,7 +343,7 @@ func Register() {
IsOnlyAllowAdmin: true,
},
Handler: func(opts *handler_structs.SubHandlerParams) {
- consts.SignalsChannel.PluginDB_reload <- true
+ signals.SIGNALS.PluginDB_reload <- true
_, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
InlineQueryID: opts.Update.InlineQuery.ID,
Results: []models.InlineQueryResult{
@@ -372,7 +374,7 @@ func Register() {
IsOnlyAllowAdmin: true,
},
Handler: func(opts *handler_structs.SubHandlerParams) {
- consts.SignalsChannel.PluginDB_save <- true
+ signals.SIGNALS.PluginDB_save <- true
_, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
InlineQueryID: opts.Update.InlineQuery.ID,
Results: []models.InlineQueryResult{
@@ -403,7 +405,7 @@ func Register() {
IsOnlyAllowAdmin: true,
},
Handler: func(opts *handler_structs.SubHandlerParams) {
- consts.SignalsChannel.Database_save <- true
+ signals.SIGNALS.Database_save <- true
_, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
InlineQueryID: opts.Update.InlineQuery.ID,
Results: []models.InlineQueryResult{
diff --git a/utils/mess/mess.go b/utils/mess/mess.go
index 2a5bbb2..7aa9bde 100644
--- a/utils/mess/mess.go
+++ b/utils/mess/mess.go
@@ -8,104 +8,20 @@ import (
"os"
"os/exec"
"runtime"
- "time"
- "unicode"
+ "trbot/utils/configs"
"trbot/utils/consts"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
- "github.com/joho/godotenv"
)
-// 查找 bot token,优先级为 环境变量 > .env 文件
-func WhereIsBotToken() string {
- consts.BotToken = os.Getenv("BOT_TOKEN")
- if consts.BotToken == "" {
- // log.Printf("No bot token in environment, trying to read it from the .env file")
- godotenv.Load()
- consts.BotToken = os.Getenv("BOT_TOKEN")
- if consts.BotToken == "" {
- log.Fatalln("No bot token in environment and .env file, try create a bot from https://t.me/@botfather https://core.telegram.org/bots/tutorial#obtain-your-bot-token")
- }
- log.Printf("Get token from .env file: %s", ShowBotID())
- } else {
- log.Printf("Get token from environment: %s", ShowBotID())
- }
- return consts.BotToken
-}
-// 输出 bot 的 ID
-func ShowBotID() string {
- var botID string
- for _, char := range consts.BotToken {
- if unicode.IsDigit(char) {
- botID += string(char)
- } else {
- break // 遇到非数字字符停止
- }
- }
- return botID
-}
-
-func UsingWebhook() bool {
- consts.WebhookURL = os.Getenv("WEBHOOK_URL")
- if consts.WebhookURL == "" {
- // 到这里可能变量没在环境里,试着读一下 .env 文件
- godotenv.Load()
- consts.WebhookURL = os.Getenv("WEBHOOK_URL")
- if consts.WebhookURL == "" {
- // 到这里就是 .env 文件里也没有,不启用
- log.Printf("No Webhook URL in environment and .env file, using getUpdate")
- return false
- }
- // 从 .env 文件中获取到了 URL,启用 Webhook
- log.Printf("Get Webhook URL from .env file: %s", consts.WebhookURL)
- return true
- } else {
- // 从环境变量中获取到了 URL,启用 Webhook
- log.Printf("Get Webhook URL from environment: %s", consts.WebhookURL)
- return true
- }
-}
-
-func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookParams) {
- webHookInfo, err := thebot.GetWebhookInfo(ctx)
- if err != nil { log.Println(err) }
- if webHookInfo.URL != params.URL {
- if webHookInfo.URL == "" {
- log.Println("Webhook not set, setting it now...")
- } else {
- log.Printf("unsame Webhook URL [%s], save it and setting up new URL...", webHookInfo.URL)
- PrintLogAndSave(time.Now().Format(time.RFC3339) + " (unsame) old Webhook URL: " + webHookInfo.URL)
- }
- success, err := thebot.SetWebhook(ctx, params)
- if err != nil { log.Panicln("Set Webhook URL err:", err) }
- if success { log.Println("Webhook setup successfully:", params.URL) }
-
- } else {
- log.Println("Webhook is already set:", webHookInfo.URL)
- }
-}
-
-func SaveAndCleanRemoteWebhookURL(ctx context.Context, thebot *bot.Bot) *models.WebhookInfo {
- webHookInfo, err := thebot.GetWebhookInfo(ctx)
- if err != nil { log.Println(err) }
- if webHookInfo.URL != "" {
- log.Printf("found Webhook URL [%s] set at api server, save and clean it...", webHookInfo.URL)
- PrintLogAndSave(time.Now().Format(time.RFC3339) + " (remote) old Webhook URL: " + webHookInfo.URL)
- thebot.DeleteWebhook(ctx, &bot.DeleteWebhookParams{
- DropPendingUpdates: false,
- })
- return webHookInfo
- }
- return nil
-}
func PrintLogAndSave(message string) {
log.Println(message)
// 打开日志文件,如果不存在则创建
- file, err := os.OpenFile(consts.LogFile_path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
return
@@ -123,7 +39,7 @@ func PrintLogAndSave(message string) {
// 从 log.txt 读取文件
func ReadLog() []string {
// 打开日志文件
- file, err := os.Open(consts.LogFile_path)
+ file, err := os.Open(consts.LogFilePath)
if err != nil {
log.Println(err)
return nil
@@ -146,7 +62,7 @@ func ReadLog() []string {
func PrivateLogToChat(ctx context.Context, thebot *bot.Bot, update *models.Update) {
thebot.SendMessage(ctx, &bot.SendMessageParams{
- ChatID: consts.LogChat_ID,
+ ChatID: configs.BotConfig.LogChatID,
Text: fmt.Sprintf("[%s %s](t.me/@id%d) say: \n%s", update.Message.From.FirstName, update.Message.From.LastName, update.Message.Chat.ID, update.Message.Text),
ParseMode: models.ParseModeMarkdownV1,
})
diff --git a/utils/signals/signals.go b/utils/signals/signals.go
index 423d5f8..a6861af 100644
--- a/utils/signals/signals.go
+++ b/utils/signals/signals.go
@@ -2,46 +2,67 @@ package signals
import (
"context"
- "fmt"
- "log"
+ "os"
"time"
+ "trbot/database"
"trbot/database/yaml_db"
- "trbot/utils/consts"
"trbot/utils/mess"
"trbot/utils/plugin_utils"
+
+ "github.com/rs/zerolog"
)
-func SignalsHandler(ctx context.Context, SIGNAL consts.SignalChannel) {
+type SignalChannel struct {
+ Database_save chan bool
+ PluginDB_save chan bool
+ PluginDB_reload chan bool
+}
+
+var SIGNALS = SignalChannel{
+ Database_save: make(chan bool),
+ PluginDB_save: make(chan bool),
+ PluginDB_reload: make(chan bool),
+}
+
+func SignalsHandler(ctx context.Context) {
+ logger := zerolog.Ctx(ctx)
every10Min := time.NewTicker(10 * time.Minute)
defer every10Min.Stop()
-
- // additional.AdditionalDatas = additional.ReadAdditionalDatas(consts.AdditionalDatas_paths)
+ var saveDatabaseRetryCount int = 0
+ var saveDatabaseRetryMax int = 10
for {
select {
case <-every10Min.C: // 每次 Ticker 触发时执行任务
yaml_db.AutoSaveDatabaseHandler()
case <-ctx.Done():
- log.Println("Cancle signal received")
- yaml_db.AutoSaveDatabaseHandler()
- log.Println("Database saved")
- SIGNAL.WorkDone <- true
- return
- case <-SIGNAL.Database_save:
- yaml_db.Database.UpdateTimestamp = time.Now().Unix()
- err := yaml_db.SaveYamlDB(consts.DB_path, consts.MetadataFileName, yaml_db.Database)
+ if saveDatabaseRetryCount == 0 { logger.Warn().Msg("Cancle signal received") }
+ err := database.SaveDatabase(ctx)
if err != nil {
- mess.PrintLogAndSave(fmt.Sprintln("some issues happend when some function call save database now:", err))
- } else {
- mess.PrintLogAndSave("save at " + time.Now().Format(time.RFC3339))
+ saveDatabaseRetryCount++
+ logger.Error().
+ Err(err).
+ Int("retryCount", saveDatabaseRetryCount).
+ Int("maxRetry", saveDatabaseRetryMax).
+ Msg("Save database failed")
+ time.Sleep(2 * time.Second)
+ if saveDatabaseRetryCount >= saveDatabaseRetryMax {
+ logger.Error().Msg("Save database failed too many times, exiting")
+ os.Exit(1)
+ }
+ continue
}
- case <-SIGNAL.PluginDB_reload:
+ logger.Info().Msg("Database saved")
+ time.Sleep(1 * time.Second)
+ logger.Warn().Msg("manually stopped")
+ os.Exit(0)
+ case <-SIGNALS.Database_save:
+ database.SaveDatabase(ctx)
+ case <-SIGNALS.PluginDB_reload:
plugin_utils.ReloadPluginsDatabase()
- log.Println("Plugin Database reloaded")
- case <-SIGNAL.PluginDB_save:
+ logger.Info().Msg("Plugin Database reloaded")
+ case <-SIGNALS.PluginDB_save:
mess.PrintLogAndSave(plugin_utils.SavePluginsDatabase())
- // log.Println("Plugin Database saved")
}
-
}
}
diff --git a/utils/utils.go b/utils/utils.go
index 044e36b..3c9a361 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -4,13 +4,12 @@ import (
"context"
"fmt"
"log"
- "os"
- "path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"trbot/database/db_struct"
+ "trbot/utils/configs"
"trbot/utils/consts"
"trbot/utils/mess"
"trbot/utils/plugin_utils"
@@ -18,13 +17,19 @@ import (
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
- "gopkg.in/yaml.v3"
)
// 如果 target 是 candidates 的一部分, 返回 true
// 常规类型会判定值是否相等,字符串如果包含也符合条件,例如 "bc" 在 "abcd" 中
+// this is a bad function
func AnyContains(target any, candidates ...any) bool {
for _, candidate := range candidates {
+ switch c := candidate.(type) {
+ case reflect.Kind:
+ if len(c.String()) == 0 { continue }
+ case []int64:
+ if len(c) == 0 { continue }
+ }
if candidate == nil { continue }
// fmt.Println(reflect.ValueOf(target).Kind(), reflect.ValueOf(candidate).Kind(), reflect.Array, reflect.Slice)
targetKind := reflect.ValueOf(target).Kind()
@@ -174,10 +179,10 @@ func UserHavePermissionDeleteMessage(ctx context.Context, thebot *bot.Bot, chatI
func InlineResultPagination(queryFields []string, results []models.InlineQueryResult) []models.InlineQueryResult {
// 当 result 的数量超过 InlineResultsPerPage 时,进行分页
// fmt.Println(len(results), InlineResultsPerPage)
- if len(results) > consts.InlineResultsPerPage {
+ if len(results) > configs.BotConfig.InlineResultsPerPage {
// 获取 update.InlineQuery.Query 末尾的 `<分页符号><数字>` 来选择输出第几页
var pageNow int = 1
- var pageSize = (consts.InlineResultsPerPage - 1)
+ var pageSize = (configs.BotConfig.InlineResultsPerPage - 1)
pageNow, err := InlineExtractPageNumber(queryFields)
// 读取页码发生错误
@@ -198,7 +203,7 @@ func InlineResultPagination(queryFields []string, results []models.InlineQueryRe
return []models.InlineQueryResult{&models.InlineQueryResultArticle{
ID: "noThisOperation",
Title: "无效的操作",
- Description: fmt.Sprintf("若您想翻页查看,请尝试输入 `%s2` 来查看第二页", consts.InlinePaginationSymbol),
+ Description: fmt.Sprintf("若您想翻页查看,请尝试输入 `%s2` 来查看第二页", configs.BotConfig.InlinePaginationSymbol),
InputMessageContent: &models.InputTextMessageContent{
MessageText: "用户在尝试进行分页时输入了错误的页码并点击了分页提示...",
ParseMode: models.ParseModeMarkdownV1,
@@ -233,7 +238,7 @@ func InlineResultPagination(queryFields []string, results []models.InlineQueryRe
pageResults = append(pageResults, &models.InlineQueryResultArticle{
ID: "paginationPage",
Title: fmt.Sprintf("当前您在第 %d 页", pageNow),
- Description: fmt.Sprintf("后面还有 %d 页内容,输入 %s%d 查看下一页", totalPages-pageNow, consts.InlinePaginationSymbol, pageNow+1),
+ Description: fmt.Sprintf("后面还有 %d 页内容,输入 %s%d 查看下一页", totalPages-pageNow, configs.BotConfig.InlinePaginationSymbol, pageNow+1),
InputMessageContent: &models.InputTextMessageContent{
MessageText: "用户在挑选内容时点击了分页提示...",
ParseMode: models.ParseModeMarkdownV1,
@@ -252,7 +257,7 @@ func InlineResultPagination(queryFields []string, results []models.InlineQueryRe
}
return pageResults
- } else if len(queryFields) > 0 && strings.HasPrefix(queryFields[len(queryFields)-1], consts.InlinePaginationSymbol) {
+ } else if len(queryFields) > 0 && strings.HasPrefix(queryFields[len(queryFields)-1], configs.BotConfig.InlinePaginationSymbol) {
return []models.InlineQueryResult{&models.InlineQueryResultArticle{
ID: "noNeedPagination",
Title: "没有多余的内容",
@@ -274,8 +279,8 @@ func InlineExtractSubCommand(fields []string) string {
}
// 判断是不是子命令
- if strings.HasPrefix(fields[0], consts.InlineSubCommandSymbol) {
- return strings.TrimPrefix(fields[0], consts.InlineSubCommandSymbol)
+ if strings.HasPrefix(fields[0], configs.BotConfig.InlineSubCommandSymbol) {
+ return strings.TrimPrefix(fields[0], configs.BotConfig.InlineSubCommandSymbol)
}
return ""
}
@@ -287,11 +292,11 @@ func InlineExtractKeywords(fields []string) []string {
}
// 判断是不是子命令
- if strings.HasPrefix(fields[0], consts.InlineSubCommandSymbol) {
+ if strings.HasPrefix(fields[0], configs.BotConfig.InlineSubCommandSymbol) {
fields = fields[1:]
}
// 判断有没有分页符号
- if len(fields) > 0 && strings.HasPrefix(fields[len(fields)-1], consts.InlinePaginationSymbol) {
+ if len(fields) > 0 && strings.HasPrefix(fields[len(fields)-1], configs.BotConfig.InlinePaginationSymbol) {
fields = fields[:len(fields)-1]
}
@@ -305,7 +310,7 @@ func InlineExtractPageNumber(fields []string) (int, error) {
}
// 判断有没有分页符号
- if strings.HasPrefix(fields[len(fields)-1], consts.InlinePaginationSymbol) {
+ if strings.HasPrefix(fields[len(fields)-1], configs.BotConfig.InlinePaginationSymbol) {
return strconv.Atoi(fields[len(fields)-1][1:])
}
return 1, nil
@@ -382,12 +387,12 @@ func BuildDefaultInlineCommandSelectKeyboard(chatInfo *db_struct.ChatInfo) model
}
if chatInfo.DefaultInlinePlugin == v.Command {
inlinePlugins = append(inlinePlugins, []models.InlineKeyboardButton{{
- Text: fmt.Sprintf("✅ [ %s%s ] - %s", consts.InlineSubCommandSymbol, v.Command, v.Description),
+ Text: fmt.Sprintf("✅ [ %s%s ] - %s", configs.BotConfig.InlineSubCommandSymbol, v.Command, v.Description),
CallbackData: "inline_default_" + v.Command,
}})
} else {
inlinePlugins = append(inlinePlugins, []models.InlineKeyboardButton{{
- Text: fmt.Sprintf("[ %s%s ] - %s", consts.InlineSubCommandSymbol, v.Command, v.Description),
+ Text: fmt.Sprintf("[ %s%s ] - %s", configs.BotConfig.InlineSubCommandSymbol, v.Command, v.Description),
CallbackData: "inline_default_" + v.Command,
}})
}
@@ -455,39 +460,6 @@ func GetMessageFromHyperLink(msg *models.Message, ParseMode models.ParseMode) st
return senderLink
}
-// 一个通用的 yaml 结构体读取函数
-func LoadYAML(pathToFile string, out interface{}) error {
- file, err := os.ReadFile(pathToFile)
- if err != nil {
- return fmt.Errorf("读取文件失败: %w", err)
- }
-
- if err := yaml.Unmarshal(file, out); err != nil {
- return fmt.Errorf("解析 YAML 失败: %w", err)
- }
-
- return nil
-}
-
-// 一个通用的 yaml 结构体保存函数,目录和文件不存在则创建,并以结构体类型保存
-func SaveYAML(pathToFile string, data interface{}) error {
- out, err := yaml.Marshal(data)
- if err != nil {
- return fmt.Errorf("编码 YAML 失败: %w", err)
- }
-
- dir := filepath.Dir(pathToFile)
- if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("创建目录失败: %w", err)
- }
-
- if err := os.WriteFile(pathToFile, out, 0644); err != nil {
- return fmt.Errorf("写入文件失败: %w", err)
- }
-
- return nil
-}
-
// https://jasonkayzk.github.io/2021/09/26/在Golang发生Panic后打印出堆栈信息/
func getCurrentGoroutineStack() string {
var buf [4096]byte
diff --git a/utils/yaml/yaml.go b/utils/yaml/yaml.go
new file mode 100644
index 0000000..2af998d
--- /dev/null
+++ b/utils/yaml/yaml.go
@@ -0,0 +1,42 @@
+package yaml
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "gopkg.in/yaml.v3"
+)
+
+// 一个通用的 yaml 结构体读取函数
+func LoadYAML(pathToFile string, out interface{}) error {
+ file, err := os.ReadFile(pathToFile)
+ if err != nil {
+ return fmt.Errorf("read file failed: %w", err)
+ }
+
+ if err := yaml.Unmarshal(file, out); err != nil {
+ return fmt.Errorf("decode yaml failed: %w", err)
+ }
+
+ return nil
+}
+
+// 一个通用的 yaml 结构体保存函数,目录和文件不存在则创建,并以结构体类型保存
+func SaveYAML(pathToFile string, data interface{}) error {
+ out, err := yaml.Marshal(data)
+ if err != nil {
+ return fmt.Errorf("failed to marshal YAML: %w", err)
+ }
+
+ dir := filepath.Dir(pathToFile)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return fmt.Errorf("create directory failed: %w", err)
+ }
+
+ if err := os.WriteFile(pathToFile, out, 0644); err != nil {
+ return fmt.Errorf("write data failed: %w", err)
+ }
+
+ return nil
+}
--
2.49.1
From d1972da685b4f486c4640f7cf1096f3883cf5476 Mon Sep 17 00:00:00 2001
From: Hubert Chen <01@trle5.xyz>
Date: Fri, 30 May 2025 12:58:13 +0800
Subject: [PATCH 02/27] save changes
---
.../plugin_detect_keyword.go | 612 +++++++++++++++---
.../plugin_limit_message.go | 0
{plugins => bad_plugins}/plugin_sticker.go | 0
{plugins => bad_plugins}/plugin_teamspeak3.go | 0
{plugins => bad_plugins}/plugin_udonese.go | 0
.../saved_message/functions.go | 108 +++-
.../saved_message/item_structs.go | 0
.../saved_message/utils.go | 104 ++-
handlers.go | 82 ++-
main.go | 4 +-
plugins/plugin_voicelist.go | 77 ++-
plugins/sub_package_plugin.go | 4 +-
utils/consts/consts.go | 2 +-
utils/internal_plugin/handler.go | 175 +++--
utils/internal_plugin/register.go | 258 ++++++--
utils/mess/mess.go | 10 +-
utils/plugin_utils/handler_by_chatid.go | 2 +-
utils/plugin_utils/handler_by_message_type.go | 84 ++-
utils/plugin_utils/handler_callback_query.go | 2 +-
utils/plugin_utils/handler_custom_symbol.go | 2 +-
utils/plugin_utils/handler_inline.go | 63 +-
utils/plugin_utils/handler_slash_start.go | 6 +-
utils/plugin_utils/handler_slash_symbol.go | 2 +-
utils/plugin_utils/handler_suffix.go | 2 +-
utils/plugin_utils/plugin_database_handler.go | 70 +-
utils/plugin_utils/plugin_initializer.go | 59 ++
utils/plugin_utils/plugin_type.go | 1 +
utils/signals/signals.go | 6 +-
utils/utils.go | 65 +-
29 files changed, 1426 insertions(+), 374 deletions(-)
rename {plugins => bad_plugins}/plugin_detect_keyword.go (66%)
rename {plugins => bad_plugins}/plugin_limit_message.go (100%)
rename {plugins => bad_plugins}/plugin_sticker.go (100%)
rename {plugins => bad_plugins}/plugin_teamspeak3.go (100%)
rename {plugins => bad_plugins}/plugin_udonese.go (100%)
rename {plugins => bad_plugins}/saved_message/functions.go (92%)
rename {plugins => bad_plugins}/saved_message/item_structs.go (100%)
rename {plugins => bad_plugins}/saved_message/utils.go (86%)
create mode 100644 utils/plugin_utils/plugin_initializer.go
diff --git a/plugins/plugin_detect_keyword.go b/bad_plugins/plugin_detect_keyword.go
similarity index 66%
rename from plugins/plugin_detect_keyword.go
rename to bad_plugins/plugin_detect_keyword.go
index 2b7a7b6..94ab5b3 100644
--- a/plugins/plugin_detect_keyword.go
+++ b/bad_plugins/plugin_detect_keyword.go
@@ -1,9 +1,9 @@
package plugins
import (
+ "context"
"fmt"
"io"
- "log"
"os"
"path/filepath"
"strconv"
@@ -16,6 +16,7 @@ import (
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
+ "github.com/rs/zerolog"
"gopkg.in/yaml.v3"
)
@@ -27,7 +28,10 @@ var KeywordDataErr error
var KeywordData_path string = filepath.Join(consts.YAMLDataBasePath, "detectkeyword/")
func init() {
- ReadKeywordList()
+ plugin_utils.AddInitializer(plugin_utils.Initializer{
+ Name: "Detect Keyword",
+ Func: ReadKeywordList,
+ })
plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{
Name: "Detect Keyword",
Loader: ReadKeywordList,
@@ -149,58 +153,161 @@ type ChatForUser struct {
Keyword []string `yaml:"Keyword"`
}
-func ReadKeywordList() {
+func ReadKeywordList(ctx context.Context) error {
var lists KeywordData
+ logger := zerolog.Ctx(ctx).
+ With().
+ Str("pluginName", "DetectKeyword").
+ Str("funcName", "ReadKeywordList").
+ Logger()
+ // 拼接路径和文件名,打开数据库
file, err := os.Open(filepath.Join(KeywordData_path, consts.YAMLFileName))
if err != nil {
- // 如果是找不到目录,新建一个
- log.Println("[DetectKeyword]: Not found database file. Created new one")
- SaveKeywordList()
- KeywordDataErr = err
- return
+ if os.IsNotExist(err) {
+ // 如果找不到文件,则新建一个
+ logger.Warn().
+ Msg("Not found database file. Create a new one")
+ err = SaveKeywordList(ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Msg("Create empty database file failed")
+ KeywordDataErr = err
+ return err
+ }
+ } else {
+ // 其他错误
+ logger.Error().
+ Err(err).
+ Msg("Open database file failed")
+ KeywordDataErr = err
+ return err
+ }
}
defer file.Close()
+ // 解码 yaml 文件
decoder := yaml.NewDecoder(file)
err = decoder.Decode(&lists)
if err != nil {
if err == io.EOF {
- log.Println("[DetectKeyword]: keyword list looks empty. now format it")
- SaveKeywordList()
- KeywordDataErr = nil
- return
+ // 文件存在,但为空,则用空的数据库覆盖保存
+ logger.Warn().
+ Msg("Keyword list looks empty. now format it")
+ err = SaveKeywordList(ctx)
+ if err != nil {
+ // 保存空的数据库失败
+ logger.Error().
+ Err(err).
+ Msg("Create empty database file failed")
+ KeywordDataErr = err
+ return err
+ }
+ } else {
+ // 其他错误
+ logger.Error().
+ Err(err).
+ Msg("Failed to decode keyword list")
+ KeywordDataErr = err
+ return err
}
- log.Println("(func)ReadKeywordList:", err)
- KeywordDataErr = err
- return
}
- KeywordDataList, KeywordDataErr = lists, nil
+
+ // 一切正常
+ KeywordDataList = lists
+ return nil
}
-func SaveKeywordList() error {
+func SaveKeywordList(ctx context.Context) error {
+ logger := zerolog.Ctx(ctx).
+ With().
+ Str("pluginName", "DetectKeyword").
+ Str("funcName", "SaveKeywordList").
+ Logger()
+
data, err := yaml.Marshal(KeywordDataList)
if err != nil {
+ logger.Error().
+ Err(err).
+ Msg("Failed to marshal keyword list")
+ KeywordDataErr = err
return err
}
- if _, err := os.Stat(KeywordData_path); os.IsNotExist(err) {
- if err := os.MkdirAll(KeywordData_path, 0755); err != nil {
+ _, err = os.Stat(KeywordData_path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ logger.Warn().
+ Str("path", KeywordData_path).
+ Msg("Keyword data directory not exist, now create it")
+ err = os.MkdirAll(KeywordData_path, 0755)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("path", KeywordData_path).
+ Msg("Failed to create keyword data directory")
+ KeywordDataErr = err
+ return err
+ }
+ logger.Trace().
+ Msg("Keyword data directory created successfully")
+ } else {
+ logger.Error().
+ Err(err).
+ Str("path", KeywordData_path).
+ Msg("Open keyword data directory failed")
+ KeywordDataErr = err
return err
}
}
- if _, err := os.Stat(filepath.Join(KeywordData_path, consts.YAMLFileName)); os.IsNotExist(err) {
- _, err := os.Create(filepath.Join(KeywordData_path, consts.YAMLFileName))
- if err != nil {
+ _, err = os.Stat(filepath.Join(KeywordData_path, consts.YAMLFileName))
+ if err != nil {
+ if os.IsNotExist(err) {
+ logger.Warn().
+ Msg("Keyword data file not exist, now create it")
+ _, err := os.Create(filepath.Join(KeywordData_path, consts.YAMLFileName))
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Msg("Failed to create keyword data file")
+ KeywordDataErr = err
+ return err
+ }
+ logger.Trace().
+ Msg("Keyword data file created successfully")
+ } else {
+ logger.Error().
+ Err(err).
+ Msg("Open keyword data file failed")
+ KeywordDataErr = err
return err
}
}
+
- return os.WriteFile(filepath.Join(KeywordData_path, consts.YAMLFileName), data, 0644)
+ err = os.WriteFile(filepath.Join(KeywordData_path, consts.YAMLFileName), data, 0644)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Msg("Failed to write keyword data file")
+ KeywordDataErr = err
+ return err
+ }
+ logger.Trace().
+ Msg("Keyword data file write successfully")
+
+ return nil
}
func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
+ logger := zerolog.Ctx(opts.Ctx).
+ With().
+ Str("pluginName", "DetectKeyword").
+ Str("funcName", "addKeywordHandler").
+ Logger()
+
if opts.Update.Message.Chat.Type != models.ChatTypePrivate {
// 在群组中直接使用 /setkeyword 命令
chat := KeywordDataList.Chats[opts.Update.Message.Chat.ID]
@@ -217,23 +324,33 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
}}}},
})
if err != nil {
- log.Printf("Error response /setkeyword command disabled : %v", err)
+ logger.Error().
+ Err(err).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Msg("Send `function is disabled by admins` message failed")
}
return
} else {
if chat.AddTime == "" {
// 初始化群组
chat = KeywordChatList{
- ChatID: opts.Update.Message.Chat.ID,
- ChatName: opts.Update.Message.Chat.Title,
+ ChatID: opts.Update.Message.Chat.ID,
+ ChatName: opts.Update.Message.Chat.Title,
ChatUsername: opts.Update.Message.Chat.Username,
- ChatType: opts.Update.Message.Chat.Type,
- AddTime: time.Now().Format(time.RFC3339),
- InitByID: opts.Update.Message.From.ID,
- IsDisable: false,
+ ChatType: opts.Update.Message.Chat.Type,
+ AddTime: time.Now().Format(time.RFC3339),
+ InitByID: opts.Update.Message.From.ID,
+ IsDisable: false,
}
KeywordDataList.Chats[opts.Update.Message.Chat.ID] = chat
- SaveKeywordList()
+ err := SaveKeywordList(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Msg("Error init chat and save keyword list")
+ }
}
if len(opts.Fields) == 1 {
// 只有一个 /setkeyword 命令
@@ -254,7 +371,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
}}},
})
if err != nil {
- log.Printf("Error response /setkeyword command: %v", err)
+ logger.Error().
+ Err(err).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Msg("Send /setkeyword command answer failed")
}
} else {
user := KeywordDataList.Users[opts.Update.Message.From.ID]
@@ -268,7 +388,13 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
IsNotInit: true,
}
KeywordDataList.Users[opts.Update.Message.From.ID] = user
- SaveKeywordList()
+ err := SaveKeywordList(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Msg("Error add a not init user and save keyword list")
+ }
}
var isChatAdded bool = false
@@ -284,13 +410,21 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
}
}
if !isChatAdded {
- log.Println("init group", chat.ChatID, "to user", opts.Update.Message.From.ID)
+ logger.Debug().
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Msg("User add a chat to listen list by set keyword in group")
chatForUser = ChatForUser{
ChatID: chat.ChatID,
}
user.ChatsForUser = append(user.ChatsForUser, chatForUser)
KeywordDataList.Users[user.UserID] = user
- SaveKeywordList()
+ err := SaveKeywordList(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Msg("Error add chat to user listen list and save keyword list")
+ }
}
// 限制关键词长度
@@ -302,7 +436,11 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
ParseMode: models.ParseModeHTML,
})
if err != nil {
- log.Printf("Error response /setkeyword command keyword too long: %v", err)
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Msg("Send `keyword is too long` message failed")
}
return
}
@@ -318,11 +456,23 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) {
}
}
if !isKeywordExist {
- log.Println("add keyword", keyword, "to user", opts.Update.Message.From.ID, "chat", chatForUser.ChatID)
+ logger.Debug().
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Int64("chatID", chatForUser.ChatID).
+ Str("keyword", keyword).
+ Msg("User add a keyword to chat")
chatForUser.Keyword = append(chatForUser.Keyword, keyword)
user.ChatsForUser[chatForUserIndex] = chatForUser
KeywordDataList.Users[user.UserID] = user
- SaveKeywordList()
+ err := SaveKeywordList(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Int64("chatID", chatForUser.ChatID).
+ Str("keyword", keyword).
+ Msg("Error add keyword and save keyword list")
+ }
if user.IsNotInit {
pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表\n若要在检测到关键词时收到提醒,请点击下方的按钮来初始化您的账号", strings.ToLower(opts.Fields[1])) } else { @@ -347,10 +497,13 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { }}}}, }) if err != nil { - log.Printf("Error response /setkeyword command: %v", err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Msg("Send `keyword added` message failed") } } - } } else { // 与机器人的私聊对话 @@ -365,7 +518,14 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { IsSilentNotice: false, } KeywordDataList.Users[opts.Update.Message.From.ID] = user - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Error init user and save keyword list") + } + } if len(user.ChatsForUser) == 0 { // 没有添加群组 @@ -376,7 +536,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, }) if err != nil { - log.Printf("Error response /setkeyword command: %v", err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Send `no group for user` message failed") } return } @@ -401,9 +564,16 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, }) if err != nil { - log.Printf("Error response /setkeyword command keyword too long: %v", err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `keyword is too long` message failed") } - return + return } keyword := strings.ToLower(opts.Fields[1]) @@ -421,10 +591,28 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { } } if !isKeywordExist { - log.Println("add global keyword", keyword, "to user", opts.Update.Message.From.ID) + logger.Debug(). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Str("globalKeyword", keyword). + Msg("User add a global keyword") user.GlobalKeyword = append(user.GlobalKeyword, keyword) KeywordDataList.Users[user.UserID] = user - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Str("globalKeyword", keyword). + Msg("Error add global keyword and save keyword list") + } pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ]", opts.Fields[1]) } else { pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", opts.Fields[1]) @@ -441,11 +629,32 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { } } if !isKeywordExist { - log.Println("add keyword", keyword, "to user", opts.Update.Message.From.ID, "chat", chatForUser.ChatID) + logger.Debug(). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("User add a keyword to chat") chatForUser.Keyword = append(chatForUser.Keyword, keyword) user.ChatsForUser[chatForUserIndex] = chatForUser KeywordDataList.Users[user.UserID] = user - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("Error add keyword and save keyword list") + } + pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(opts.Fields[1])) } else { pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", opts.Fields[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) @@ -477,7 +686,14 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: button, }) if err != nil { - log.Printf("Error response /setkeyword command success: %v", err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `keyword added` message failed") } } else { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ @@ -488,7 +704,14 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: user.selectChat(), }) if err != nil { - log.Printf("Error response /setkeyword command: %v", err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `not selected chat yet` message failed") } } } else { @@ -500,7 +723,14 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: buildUserChatList(user), }) if err != nil { - log.Printf("Error response /setkeyword command: %v", err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `user group list keyboart` message failed") } } } @@ -534,7 +764,7 @@ func buildListenList() { } } -func KeywordDetector(opts *handler_structs.SubHandlerParams) { +func KeywordDetector(opts *handler_structs.SubHandlerParams) error { var text string if opts.Update.Message.Caption != "" { text = strings.ToLower(opts.Update.Message.Caption) @@ -580,6 +810,12 @@ func KeywordDetector(opts *handler_structs.SubHandlerParams) { } func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, chatname, keyword, text string, isGlobalKeyword bool) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "DetectKeyword"). + Str("funcName", "notifyUser"). + Logger() + var messageLink string = fmt.Sprintf("https://t.me/c/%s/%d", utils.RemoveIDPrefix(opts.Update.Message.Chat.ID), opts.Update.Message.ID) _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ @@ -596,19 +832,37 @@ func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, ch DisableNotification: user.IsSilentNotice, }) if err != nil { - log.Printf("Error response /setkeyword command: %v", err) + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Int64("userID", user.UserID). + Str("keyword", keyword). + Msg("Send `keyword notice to user` message failed") } user.MentionCount++ KeywordDataList.Users[user.UserID] = user } func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "DetectKeyword"). + Str("funcName", "groupManageCallbackHandler"). + Logger() + if !utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.CallbackQuery.Message.Message.Chat.ID, opts.Update.CallbackQuery.From.ID) { - opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, Text: "您没有权限修改此配置", ShowAlert: true, }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Send `no permission to change group functions` callback answer failed") + } return } @@ -619,7 +873,14 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { chat.IsDisable = !chat.IsDisable KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] = chat buildListenList() - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Send `the group function switch has changed` callback answer failed") + } } _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ @@ -629,11 +890,20 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: buildGroupManageKB(chat), }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Edit message to `group function manager keyboard` failed") } } func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "DetectKeyword"). + Str("funcName", "userManageCallbackHandler"). + Logger() user := KeywordDataList.Users[opts.Update.CallbackQuery.From.ID] switch opts.Update.CallbackQuery.Data { @@ -651,11 +921,22 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { user.AddingChatID = 0 case "detectkw_mng_chatdisablebyadmin": // 目标群组的管理员为群组关闭了此功能 - opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, Text: "此群组的的管理员禁用了此功能,因此,您无法再收到来自该群组的关键词提醒,您可以询问该群组的管理员是否可以重新开启这个功能", ShowAlert: true, }) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Send `this group is disable by admins` callback answer failed") + } + return default: if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_undo_") || strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_delkw_") { // 撤销添加或删除关键词 @@ -668,10 +949,20 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Parse chat ID failed when user undo add or delete a keyword") + return } + // 删除关键词过程 if chatID == user.UserID { + // 全局关键词 var tempKeyword []string for _, keyword := range user.GlobalKeyword { if keyword != chatIDAndKeywordList[1] { @@ -681,6 +972,7 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { user.GlobalKeyword = tempKeyword KeywordDataList.Users[user.UserID] = user } else { + // 群组关键词 for index, chatForUser := range KeywordDataList.Users[user.UserID].ChatsForUser { if chatForUser.ChatID == chatID { var tempKeyword []string @@ -694,7 +986,17 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { KeywordDataList.Users[user.UserID].ChatsForUser[index] = chatForUser } } - SaveKeywordList() + err = SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Error undo add or remove keyword and save keyword list") + } if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_undo_") { _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ @@ -704,7 +1006,14 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Edit message to `add keyword has been canceled` failed") } } else { var buttons [][]models.InlineKeyboardButton @@ -728,6 +1037,8 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { } } + // bug here + var pendingMessage string if chatID == user.UserID { pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前设定了 %d 个全局关键词", chatIDAndKeywordList[1], len(buttons)) @@ -756,7 +1067,14 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { }, }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Edit message to `chat keyword list keyboard with deleted keyword notice` failed") } } @@ -766,13 +1084,32 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_adding_") chatID_int64, err := strconv.ParseInt(chatID, 10, 64) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Parse chat ID failed when user selecting chat to add keyword") + return } user := KeywordDataList.Users[user.UserID] user.AddingChatID = chatID_int64 KeywordDataList.Users[user.UserID] = user buildListenList() - SaveKeywordList() + err = SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Error set a chat ID for user add keyword and save keyword list") + + } var pendingMessage string if chatID_int64 == user.UserID { @@ -788,7 +1125,14 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Edit message to `already to add keyword` failed") } return } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_switch_chat_") { @@ -796,7 +1140,15 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { id := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_switch_chat_") id_int64, err := strconv.ParseInt(id, 10, 64) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Parse chat ID failed when user change the group switch") + return } for index, chat := range KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser { if chat.ChatID == id_int64 { @@ -809,7 +1161,15 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_chat_") chatID_int64, err := strconv.ParseInt(chatID, 10, 64) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Parse chat ID failed when user wanna manage keyword for group") + return } var buttons [][]models.InlineKeyboardButton var tempbutton []models.InlineKeyboardButton @@ -884,14 +1244,32 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { }, }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Edit message to `group keyword list keyboard` failed") } return } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_kw_") { // 删除某个关键词 chatIDAndKeyword := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_kw_") chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") - chatID, _ := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) + chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Parse chat ID failed when user wanna manage a keyword") + return + } var pendingMessage string @@ -901,7 +1279,7 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { pendingMessage = fmt.Sprintf("[ %s ] 是为 %s 群组设定的关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName) } - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.Update.CallbackQuery.Message.Message.ID, Text: pendingMessage, @@ -918,7 +1296,14 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Edit message to `keyword manager keyboard` failed") } return } @@ -931,12 +1316,30 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: buildUserChatList(user), }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Edit message to `main manager keyboard` failed") } KeywordDataList.Users[opts.Update.CallbackQuery.From.ID] = user buildListenList() - SaveKeywordList() + err = SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Str("callbackQuery", opts.Update.CallbackQuery.Data). + Msg("Error add user and save keyword list") + } } func buildGroupManageKB(chat KeywordChatList) models.ReplyMarkup { @@ -953,6 +1356,12 @@ func buildGroupManageKB(chat KeywordChatList) models.ReplyMarkup { } func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "DetectKeyword"). + Str("funcName", "startPrefixAddGroup"). + Logger() + user := KeywordDataList.Users[opts.Update.Message.From.ID] if user.AddTime == "" { // 初始化用户 @@ -964,20 +1373,45 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { IsSilentNotice: false, } KeywordDataList.Users[user.UserID] = user - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Error add user and save keyword list") + } } if user.IsNotInit { - // 用户之前仅在群组内发送了命令,但并没有点击机器人来初始化 + // 用户之前仅在群组内发送命令添加了关键词,但并没有点击机器人来初始化 user.IsNotInit = false KeywordDataList.Users[user.UserID] = user buildListenList() - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Error init user and save keyword list") } if strings.HasPrefix(opts.Fields[1], "detectkw_addgroup_") { groupID := strings.TrimPrefix(opts.Fields[1], "detectkw_addgroup_") groupID_int64, err := strconv.ParseInt(groupID, 10, 64) if err != nil { - fmt.Println("format groupID error:", err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). + Str("username", opts.Update.CallbackQuery.From.Username). + Int64("ID", opts.Update.CallbackQuery.From.ID), + ). + Msg("Parse chat ID failed when user add a group by /start command") return } @@ -992,7 +1426,10 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { } } if !IsAdded { - log.Println("add group", groupID_int64, "to user", opts.Update.Message.From.ID) + logger.Debug(). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("User add a chat to listen list by /start command") user.ChatsForUser = append(user.ChatsForUser, ChatForUser{ ChatID: groupID_int64, }) @@ -1009,10 +1446,27 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { ReplyMarkup: buildUserChatList(user), }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `added group in user list` message failed") } } - SaveKeywordList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Error add group for user and save keyword list failed") + } } diff --git a/plugins/plugin_limit_message.go b/bad_plugins/plugin_limit_message.go similarity index 100% rename from plugins/plugin_limit_message.go rename to bad_plugins/plugin_limit_message.go diff --git a/plugins/plugin_sticker.go b/bad_plugins/plugin_sticker.go similarity index 100% rename from plugins/plugin_sticker.go rename to bad_plugins/plugin_sticker.go diff --git a/plugins/plugin_teamspeak3.go b/bad_plugins/plugin_teamspeak3.go similarity index 100% rename from plugins/plugin_teamspeak3.go rename to bad_plugins/plugin_teamspeak3.go diff --git a/plugins/plugin_udonese.go b/bad_plugins/plugin_udonese.go similarity index 100% rename from plugins/plugin_udonese.go rename to bad_plugins/plugin_udonese.go diff --git a/plugins/saved_message/functions.go b/bad_plugins/saved_message/functions.go similarity index 92% rename from plugins/saved_message/functions.go rename to bad_plugins/saved_message/functions.go index 48ae491..899fb54 100644 --- a/plugins/saved_message/functions.go +++ b/bad_plugins/saved_message/functions.go @@ -13,9 +13,15 @@ import ( "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" ) func saveMessageHandler(opts *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "ReadSavedMessageList"). + Logger() UserSavedMessage := SavedMessageSet[opts.Update.Message.From.ID] messageParams := &bot.SendMessageParams{ @@ -125,7 +131,19 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.OnlyText[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Update user saved `OnlyText` item keyword and save savedmessage list failed") + SavedMessageErr = err + return + } } break } @@ -143,7 +161,19 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Add `OnlyText` item to user saved list and save savedmessage list failed") + SavedMessageErr = err + return + } messageParams.Text = "已保存文本" } } else if opts.Update.Message.ReplyToMessage.Audio != nil { @@ -163,7 +193,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Audio[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -182,7 +212,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存音乐" } } else if opts.Update.Message.ReplyToMessage.Animation != nil { @@ -202,7 +232,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Mpeg4gif[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -220,7 +250,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存 GIF" } } else if opts.Update.Message.ReplyToMessage.Document != nil { @@ -241,7 +271,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Gif[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -258,7 +288,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存 GIF (文件)" } } else { @@ -278,7 +308,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Document[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -296,7 +326,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存文件" } } @@ -317,7 +347,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Photo[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -336,7 +366,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存图片" } } else if opts.Update.Message.ReplyToMessage.Sticker != nil { @@ -356,7 +386,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Sticker[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -388,7 +418,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存贴纸" } @@ -409,7 +439,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Video[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -427,7 +457,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存视频" } @@ -448,7 +478,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.VideoNote[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -464,7 +494,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存圆形视频" } @@ -485,7 +515,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { n.Description = DescriptionText UserSavedMessage.Item.Voice[i] = n SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) } break } @@ -503,7 +533,7 @@ func saveMessageHandler(opts *handler_structs.SubHandlerParams) { UserSavedMessage.Count++ UserSavedMessage.SavedTimes++ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage - SaveSavedMessageList() + err := SaveSavedMessageList(opts.Ctx) messageParams.Text = "已保存语音" } } else { @@ -694,6 +724,12 @@ func SendPrivacyPolicy(opts *handler_structs.SubHandlerParams) { } func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "AgreePrivacyPolicy"). + Logger() + var UserSavedMessage SavedMessage // , ok := consts.Database.Data.SavedMessage[opts.ChatInfo.ID] if len(SavedMessageSet) == 0 { @@ -702,8 +738,21 @@ func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) { } UserSavedMessage.AgreePrivacyPolicy = true SavedMessageSet[opts.ChatInfo.ID] = UserSavedMessage - SaveSavedMessageList() - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + err := SaveSavedMessageList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Change user `AgreePrivacyPolicy` flag to true and save savemessage list failed") + SavedMessageErr = err + return + } + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: "您已成功开启收藏信息功能,回复一条信息的时候发送 /save 来使用收藏功能吧!\n由于服务器性能原因,每个人的收藏数量上限默认为 100 个,您也可以从机器人的个人信息中寻找管理员来申请更高的上限\n点击下方按钮来浏览您的收藏内容", ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, @@ -713,12 +762,23 @@ func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) { }}}}, }) if err != nil { - log.Println("error when send savedmessage_privacy_policy_agree:", err) + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `saved message function enabled` message failed") } } func Init() { - ReadSavedMessageList() + plugin_utils.AddInitializer(plugin_utils.Initializer{ + Name: "Saved Message", + Func: ReadSavedMessageList, + }) + // ReadSavedMessageList() plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ Name: "Saved Message", Saver: SaveSavedMessageList, diff --git a/plugins/saved_message/item_structs.go b/bad_plugins/saved_message/item_structs.go similarity index 100% rename from plugins/saved_message/item_structs.go rename to bad_plugins/saved_message/item_structs.go diff --git a/plugins/saved_message/utils.go b/bad_plugins/saved_message/utils.go similarity index 86% rename from plugins/saved_message/utils.go rename to bad_plugins/saved_message/utils.go index 3583fb1..6d2b800 100644 --- a/plugins/saved_message/utils.go +++ b/bad_plugins/saved_message/utils.go @@ -1,6 +1,7 @@ package saved_message import ( + "context" "fmt" "io" "log" @@ -12,6 +13,7 @@ import ( "trbot/utils/type_utils" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" "gopkg.in/yaml.v3" ) @@ -34,15 +36,46 @@ type SavedMessage struct { Item SavedMessageItems `yaml:"Item,omitempty"` } -func SaveSavedMessageList() error { - data, err := yaml.Marshal(SavedMessageSet) - if err != nil { return err } +func SaveSavedMessageList(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "SaveSavedMessageList"). + Logger() - if _, err := os.Stat(SavedMessage_path); os.IsNotExist(err) { - if err := os.MkdirAll(SavedMessage_path, 0755); err != nil { + data, err := yaml.Marshal(SavedMessageSet) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to marshal keyword list") + SavedMessageErr = err + return err + } + + _, err = os.Stat(SavedMessage_path) + if err != nil { + if os.IsNotExist(err) { + logger.Warn(). + Msg("Savedmessage data directory not exist, now create it") + err = os.MkdirAll(SavedMessage_path, 0755) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to create savedmessage data directory") + SavedMessageErr = err + return err + } + logger.Trace(). + Msg("Savedmessage data directory created successfully") + } else { + logger.Error(). + Err(err). + Msg("Open savedmessage data directory failed") + SavedMessageErr = err return err } } + if _, err := os.Stat(filepath.Join(SavedMessage_path, consts.YAMLFileName)); os.IsNotExist(err) { _, err := os.Create(filepath.Join(SavedMessage_path, consts.YAMLFileName)) @@ -54,33 +87,62 @@ func SaveSavedMessageList() error { return os.WriteFile(filepath.Join(SavedMessage_path, consts.YAMLFileName), data, 0644) } -func ReadSavedMessageList() { - var SavedMessages map[int64]SavedMessage +func ReadSavedMessageList(ctx context.Context) error { + var savedList map[int64]SavedMessage + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "ReadSavedMessageList"). + Logger() file, err := os.Open(filepath.Join(SavedMessage_path, consts.YAMLFileName)) if err != nil { - // 如果是找不到目录,新建一个 - log.Println("[SavedMessage]: Not found database file. Created new one") - SaveSavedMessageList() - SavedMessageSet, SavedMessageErr = map[int64]SavedMessage{}, err - return + if os.IsNotExist(err) { + logger.Warn(). + Msg("Not found database file. Create a new one") + // 如果是找不到目录,新建一个 + err = SaveSavedMessageList(ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Create empty database file failed") + SavedMessageErr = err + return err + } + } else { + logger.Error(). + Err(err). + Msg("Open database file failed") + SavedMessageErr = err + return err + } } defer file.Close() decoder := yaml.NewDecoder(file) - err = decoder.Decode(&SavedMessages) + err = decoder.Decode(&savedList) if err != nil { if err == io.EOF { - log.Println("[SavedMessage]: Saved Message list looks empty. now format it") - SaveSavedMessageList() - SavedMessageSet, SavedMessageErr = map[int64]SavedMessage{}, nil - return + logger.Warn(). + Msg("Saved Message list looks empty. now format it") + err = SaveSavedMessageList(ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Create empty database file failed") + SavedMessageErr = err + return err + } + } else { + logger.Error(). + Err(err). + Msg("Failed to decode savedmessage list") + SavedMessageErr = err + return err } - log.Println("(func)ReadSavedMessageList:", err) - SavedMessageSet, SavedMessageErr = map[int64]SavedMessage{}, err - return } - SavedMessageSet, SavedMessageErr = SavedMessages, nil + SavedMessageSet = savedList + return nil } type sortstruct struct { diff --git a/handlers.go b/handlers.go index ba9ee0d..d65eeb4 100644 --- a/handlers.go +++ b/handlers.go @@ -24,8 +24,10 @@ import ( func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) { defer utils.PanicCatcher("defaultHandler") - logger := zerolog.Ctx(ctx) - + logger := zerolog.Ctx(ctx). + With(). + Str("funcName", "defaultHandler"). + Logger() var err error var opts = handler_structs.SubHandlerParams{ @@ -38,40 +40,58 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) if update.Message != nil { // 正常消息 opts.Fields = strings.Fields(update.Message.Text) - database.InitChat(opts.Ctx, &update.Message.Chat) - database.IncrementalUsageCount(opts.Ctx, update.Message.Chat.ID, db_struct.MessageNormal) - database.RecordLatestData(opts.Ctx, update.Message.Chat.ID, db_struct.LatestMessage, update.Message.Text) + err = database.InitChat(opts.Ctx, &update.Message.Chat) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&update.Message.Chat)). + Msg("Init chat failed") + } + err = database.IncrementalUsageCount(opts.Ctx, update.Message.Chat.ID, db_struct.MessageNormal) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&update.Message.Chat)). + Msg("Incremental usage count failed") + } + err = database.RecordLatestData(opts.Ctx, update.Message.Chat.ID, db_struct.LatestMessage, update.Message.Text) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&update.Message.Chat)). + Msg("Record latest message failed") + } opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, update.Message.Chat.ID) if err != nil { logger.Warn(). Err(err). - Int64("chatID", update.Message.Chat.ID). - Msg("Get chatinfo error") + Dict(utils.GetChatDict(&update.Message.Chat)). + Msg("Get chat info error") } if consts.IsDebugMode { if update.Message.Photo != nil { logger.Debug(). - Str("user", fmt.Sprintf("%s(%s)[%d]", utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID)). - Str("chat", fmt.Sprintf("%s(%s)[%d]", utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID)). + Dict(utils.GetUserDict(update.Message.From)). + Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). Str("caption", update.Message.Caption). - Msg("photo message") + Msg("photoMessage") } else if update.Message.Sticker != nil { logger.Debug(). - Str("user", fmt.Sprintf("%s(%s)[%d]", utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID)). - Str("chat", fmt.Sprintf("%s(%s)[%d]", utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID)). + Dict(utils.GetUserDict(update.Message.From)). + Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). - Str("sticker emoji", update.Message.Sticker.Emoji). - Str("sticker setname", update.Message.Sticker.SetName). - Str("sticker file ID", update.Message.Sticker.FileID). - Msg("sticker message") + Str("stickerEmoji", update.Message.Sticker.Emoji). + Str("stickerSetname", update.Message.Sticker.SetName). + Str("stickerFileID", update.Message.Sticker.FileID). + Msg("stickerMessage") } else { logger.Debug(). - Str("user", fmt.Sprintf("%s(%s)[%d]", utils.ShowUserName(update.Message.From), update.Message.From.Username, update.Message.From.ID)). - Str("chat", fmt.Sprintf("%s(%s)[%d]", utils.ShowChatName(&update.Message.Chat), update.Message.Chat.Username, update.Message.Chat.ID)). + Dict(utils.GetUserDict(update.Message.From)). + Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). Str("text", update.Message.Text). - Msg("message") + Msg("textMessage") } } @@ -79,18 +99,20 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } else if update.EditedMessage != nil { // 私聊或群组消息被编辑 if consts.IsDebugMode { - if update.EditedMessage.Photo != nil { - log.Printf("edited from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], (%d) edited caption to [%s]", - utils.ShowUserName(update.EditedMessage.From), update.EditedMessage.From.Username, update.EditedMessage.From.ID, - utils.ShowChatName(&update.EditedMessage.Chat), update.EditedMessage.Chat.Username, update.EditedMessage.Chat.ID, - update.EditedMessage.ID, update.EditedMessage.Caption, - ) + if update.EditedMessage.Caption != "" { + logger.Debug(). + Dict(utils.GetUserDict(update.EditedMessage.From)). + Dict(utils.GetChatDict(&update.EditedMessage.Chat)). + Int("messageID", update.EditedMessage.ID). + Str("editedCaption", update.EditedMessage.Caption). + Msg("editedMessage") } else { - log.Printf("edited from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], (%d) edited message to [%s]", - utils.ShowUserName(update.EditedMessage.From), update.EditedMessage.From.Username, update.EditedMessage.From.ID, - utils.ShowChatName(&update.EditedMessage.Chat), update.EditedMessage.Chat.Username, update.EditedMessage.Chat.ID, - update.EditedMessage.ID, update.EditedMessage.Text, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.EditedMessage.From)). + Dict(utils.GetChatDict(&update.EditedMessage.Chat)). + Int("messageID", update.EditedMessage.ID). + Str("editedText", update.EditedMessage.Text). + Msg("editedMessage") } } } else if update.InlineQuery != nil { diff --git a/main.go b/main.go index 313d1a3..8a57a69 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,8 @@ func main() { defer cancel() // create a logger and attached it into ctx + // logger := log.Output(os.Stderr) + // log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) ctx = logger.WithContext(ctx) @@ -64,7 +66,7 @@ func main() { go signals.SignalsHandler(ctx) // register plugin (internal first, then external) - internal_plugin.Register() + internal_plugin.Register(ctx) // Select mode by Webhook config if configs.IsUsingWebhook(ctx) { // Webhook diff --git a/plugins/plugin_voicelist.go b/plugins/plugin_voicelist.go index c7ae710..162f117 100644 --- a/plugins/plugin_voicelist.go +++ b/plugins/plugin_voicelist.go @@ -1,6 +1,7 @@ package plugins import ( + "context" "fmt" "log" "os" @@ -15,6 +16,7 @@ import ( "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" "gopkg.in/yaml.v3" ) @@ -24,7 +26,10 @@ var VoiceListErr error var VoiceList_path string = filepath.Join(consts.YAMLDataBasePath, "voices/") func init() { - ReadVoicePackFromPath() + plugin_utils.AddInitializer(plugin_utils.Initializer{ + Name: "Voice List", + Func: ReadVoicePackFromPath, + }) plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ Name: "Voice List", Loader: ReadVoicePackFromPath, @@ -47,38 +52,82 @@ type VoicePack struct { } // 读取指定目录下所有结尾为 .yaml 或 .yml 的语音文件 -func ReadVoicePackFromPath() { +func ReadVoicePackFromPath(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "Voice List"). + Str("funcName", "ReadVoicePackFromPath"). + Logger() + var packs []VoicePack - if _, err := os.Stat(VoiceList_path); os.IsNotExist(err) { - log.Printf("[VoiceList] No voices dir, create a new one: %s", VoiceList_path) - if err := os.MkdirAll(VoiceList_path, 0755); err != nil { - VoiceLists, VoiceListErr = nil, err - return + _, err := os.Stat(VoiceList_path) + if err != nil { + if os.IsNotExist(err) { + logger.Warn(). + Str("path", VoiceList_path). + Msg("VoiceList directory not exist, now create it") + err = os.MkdirAll(VoiceList_path, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("path", VoiceList_path). + Msg("Failed to create VoiceList data directory") + VoiceListErr = err + return err + } + } else { + logger.Error(). + Err(err). + Str("path", VoiceList_path). + Msg("Open VoiceList data directory failed") + VoiceListErr = err + return err } } + - err := filepath.Walk(VoiceList_path, func(path string, info os.FileInfo, err error) error { - if err != nil { return err } + err = filepath.Walk(VoiceList_path, func(path string, info os.FileInfo, err error) error { + if err != nil { + logger.Error(). + Err(err). + Str("path", path). + Msg("Failed to read file use `filepath.Walk()`") + } if strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml") { file, err := os.Open(path) - if err != nil { log.Println("[VoiceList] (func)readVoicesFromDir:", err) } + if err != nil { + logger.Error(). + Err(err). + Str("path", path). + Msg("Failed to open file use `os.Open()`") + } defer file.Close() var singlePack VoicePack decoder := yaml.NewDecoder(file) err = decoder.Decode(&singlePack) - if err != nil { log.Println("[VoiceList] (func)readVoicesFromDir:", err) } + if err != nil { + logger.Error(). + Err(err). + Str("path", path). + Msg("Failed to decode file use `yaml.NewDecoder()`") + } packs = append(packs, singlePack) } return nil }) if err != nil { - VoiceLists, VoiceListErr = nil, err - return + logger.Error(). + Err(err). + Str("path", VoiceList_path). + Msg("Failed to read voice packs in VoiceList path") + VoiceListErr = err + return err } - VoiceLists, VoiceListErr = packs, nil + VoiceLists = packs + return nil } func VoiceListHandler(opts *handler_structs.SubHandlerParams) []models.InlineQueryResult { diff --git a/plugins/sub_package_plugin.go b/plugins/sub_package_plugin.go index 73dae21..5a9f08c 100644 --- a/plugins/sub_package_plugin.go +++ b/plugins/sub_package_plugin.go @@ -1,7 +1,5 @@ package plugins -import "trbot/plugins/saved_message" - /* This `sub_package_plugin.go` file allow you to import other packages. @@ -30,5 +28,5 @@ import "trbot/plugins/saved_message" ``` */ func InitPlugins() { - saved_message.Init() + // saved_message.Init() } diff --git a/utils/consts/consts.go b/utils/consts/consts.go index 3370da6..8d8a3a7 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -8,7 +8,7 @@ var IsDebugMode bool var WebhookListenPort string = "localhost:2847" -var YAMLDataBasePath string = "./DB_yaml/" +var YAMLDataBasePath string = "./db_yaml/" var YAMLFileName string = "metadata.yaml" var CacheDirectory string = "./cache/" diff --git a/utils/internal_plugin/handler.go b/utils/internal_plugin/handler.go index bc405c0..b083436 100644 --- a/utils/internal_plugin/handler.go +++ b/utils/internal_plugin/handler.go @@ -2,7 +2,6 @@ package internal_plugin import ( "fmt" - "log" "strings" "trbot/utils" "trbot/utils/handler_structs" @@ -10,117 +9,195 @@ import ( "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" ) -func startHandler(opts *handler_structs.SubHandlerParams) { +func startHandler(params *handler_structs.SubHandlerParams) error { defer utils.PanicCatcher("startHandler") + logger := zerolog.Ctx(params.Ctx). + With(). + Str("funcName", "startHandler"). + Logger() - if len(opts.Fields) > 1 { + if len(params.Fields) > 1 { for _, n := range plugin_utils.AllPlugins.SlashStart.WithPrefixHandler { - if strings.HasPrefix(opts.Fields[1], n.Prefix) { - inlineArgument := strings.Split(opts.Fields[1], "_") + if strings.HasPrefix(params.Fields[1], n.Prefix) { + inlineArgument := strings.Split(params.Fields[1], "_") if inlineArgument[1] == n.Argument { if n.Handler == nil { + logger.Trace(). + Dict(utils.GetUserDict(params.Update.Message.From)). + Str("handlerPrefix", n.Prefix). + Str("handlerArgument", n.Argument). + Str("handlerName", n.Name). + Str("fullCommand", params.Update.Message.Text). + Msg("tigger start handler by prefix, but this handler function is nil, skip") continue } - n.Handler(opts) - return + err := n.Handler(params) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.Message.From)). + Str("handlerPrefix", n.Prefix). + Str("handlerArgument", n.Argument). + Str("handlerName", n.Name). + Str("fullCommand", params.Update.Message.Text). + Msg("Error in start handler by prefix tigger") + } + return err } } } for _, n := range plugin_utils.AllPlugins.SlashStart.Handler { - if opts.Fields[1] == n.Argument { - n.Handler(opts) - return + if params.Fields[1] == n.Argument { + err := n.Handler(params) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.Message.From)). + Str("handlerArgument", n.Argument). + Str("handlerName", n.Name). + Str("fullCommand", params.Update.Message.Text). + Msg("Error in start handler by argument") + } + return err } } } - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: fmt.Sprintf("Hello, *%s %s*\n\n您可以向此处发送一个贴纸,您会得到一张转换后的 png 图片\n\n您也可以使用 [inline](https://telegram.org/blog/inline-bots?setln=en) 模式进行交互,点击下方的按钮来使用它", opts.Update.Message.From.FirstName, opts.Update.Message.From.LastName), + _, err := params.Thebot.SendMessage(params.Ctx, &bot.SendMessageParams{ + ChatID: params.Update.Message.Chat.ID, + Text: fmt.Sprintf("Hello, *%s %s*\n\n您可以向此处发送一个贴纸,您会得到一张转换后的 png 图片\n\n您也可以使用 [inline](https://telegram.org/blog/inline-bots?setln=en) 模式进行交互,点击下方的按钮来使用它", params.Update.Message.From.FirstName, params.Update.Message.From.LastName), ParseMode: models.ParseModeMarkdownV1, - ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, - LinkPreviewOptions: &models.LinkPreviewOptions{IsDisabled: bot.True()}, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + ReplyParameters: &models.ReplyParameters{ MessageID: params.Update.Message.ID }, + LinkPreviewOptions: &models.LinkPreviewOptions{ IsDisabled: bot.True() }, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "尝试 Inline 模式", SwitchInlineQueryCurrentChat: " ", }}}}, }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Dict(utils.GetUserDict(params.Update.Message.From)). + Msg("Send `bot welcome` message error") + } + + return err } -func helpHandler(opts *handler_structs.SubHandlerParams) { +func helpHandler(params *handler_structs.SubHandlerParams) error { defer utils.PanicCatcher("helpHandler") + logger := zerolog.Ctx(params.Ctx). + With(). + Str("funcName", "helpHandler"). + Logger() - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, + _, err := params.Thebot.SendMessage(params.Ctx, &bot.SendMessageParams{ + ChatID: params.Update.Message.Chat.ID, Text: fmt.Sprintf("当前 bot 中有 %d 个帮助文档", len(plugin_utils.AllPlugins.HandlerHelp)), ParseMode: models.ParseModeMarkdownV1, - ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, + ReplyParameters: &models.ReplyParameters{MessageID: params.Update.Message.ID}, LinkPreviewOptions: &models.LinkPreviewOptions{IsDisabled: bot.True()}, ReplyMarkup: plugin_utils.BuildHandlerHelpKeyboard(), }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Dict(utils.GetUserDict(params.Update.Message.From)). + Msg("Send `bot help keyboard` message error") + } + return err } -func helpCallbackHandler(opts *handler_structs.SubHandlerParams) { - if opts.Update.CallbackQuery.Data == "help-close" { - opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, +func helpCallbackHandler(params *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(params.Ctx). + With(). + Str("funcName", "helpCallbackHandler"). + Logger() + + if params.Update.CallbackQuery.Data == "help-close" { + _, err := params.Thebot.DeleteMessage(params.Ctx, &bot.DeleteMessageParams{ + ChatID: params.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: params.Update.CallbackQuery.Message.Message.ID, }) - return - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "help-handler_") { - handlerName := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "help-handler_") + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Dict(utils.GetChatDict(¶ms.Update.CallbackQuery.Message.Message.Chat)). + Msg("Delete `bot help keyboard` message failed") + } + return err + } else if strings.HasPrefix(params.Update.CallbackQuery.Data, "help-handler_") { + handlerName := strings.TrimPrefix(params.Update.CallbackQuery.Data, "help-handler_") for _, handler := range plugin_utils.AllPlugins.HandlerHelp { if handler.Name == handlerName { var replyMarkup models.ReplyMarkup + // 如果帮助函数有自定的 ReplyMarkup,则使用它,否则显示默认的按钮 if handler.ReplyMarkup != nil { replyMarkup = handler.ReplyMarkup } else { replyMarkup = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "返回", - CallbackData: "help", - }, - { - Text: "关闭", - CallbackData: "help-close", - }, - }}} + { + Text: "返回", + CallbackData: "help", + }, + { + Text: "关闭", + CallbackData: "help-close", + }, + }}} } - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, + _, err := params.Thebot.EditMessageText(params.Ctx, &bot.EditMessageTextParams{ + ChatID: params.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: params.Update.CallbackQuery.Message.Message.ID, Text: handler.Description, ParseMode: handler.ParseMode, ReplyMarkup: replyMarkup, }) if err != nil { - log.Println("[helpCallbackHandler] error when build handler help message:",err) + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("pluginName", handler.Name). + Msg("Edit messag to `plugin help message` failed") } - return + return err } } - _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: opts.Update.CallbackQuery.ID, + _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: params.Update.CallbackQuery.ID, Text: "您请求查看的帮助页面不存在,可能是机器人管理员已经移除了这个插件", ShowAlert: true, }) if err != nil { - log.Println("[helpCallbackHandler] error when send no this plugin message:", err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Send `help page is not exist` answer failed") } } - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, + _, err := params.Thebot.EditMessageText(params.Ctx, &bot.EditMessageTextParams{ + ChatID: params.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: params.Update.CallbackQuery.Message.Message.ID, Text: fmt.Sprintf("当前 bot 中有 %d 个帮助文档", len(plugin_utils.AllPlugins.HandlerHelp)), ReplyMarkup: plugin_utils.BuildHandlerHelpKeyboard(), }) if err != nil { - log.Println("[helpCallbackHandler] error when rebuild help keyboard:",err) + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Edit messag to `bot help keyboard` failed") } + return err } diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go index 2354518..50101db 100644 --- a/utils/internal_plugin/register.go +++ b/utils/internal_plugin/register.go @@ -1,8 +1,8 @@ package internal_plugin import ( + "context" "fmt" - "log" "strings" "time" "trbot/database" @@ -18,13 +18,16 @@ import ( "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" ) // this function run only once in main -func Register() { +func Register(ctx context.Context) { // 初始化 /plugins/ 中的插件 plugins.InitPlugins() + plugin_utils.RunPluginInitializers(ctx) + // 以 `/` 符号开头的命令 plugin_utils.AddSlashSymbolCommandPlugins([]plugin_utils.SlashSymbolCommand{ { @@ -37,32 +40,49 @@ func Register() { }, { SlashCommand: "chatinfo", - Handler: func(opts *handler_structs.SubHandlerParams) { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + Handler: func(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx) + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, Text: fmt.Sprintf("类型: [
%v]\nID: [%v]\n用户名:[%v]", opts.Update.Message.Chat.Type, opts.Update.Message.Chat.ID, opts.Update.Message.Chat.Username),
ParseMode: models.ParseModeHTML,
})
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("command", "/chatinfo").
+ Msg("send `chat info` message failed")
+ }
+ return err
},
},
{
SlashCommand: "test",
- Handler: func(opts *handler_structs.SubHandlerParams) {
- opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
+ Handler: func(opts *handler_structs.SubHandlerParams) error {
+ logger := zerolog.Ctx(opts.Ctx)
+ _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
ChatID: opts.Update.Message.Chat.ID,
Text: "如果您愿意帮忙,请加入测试群组帮助我们完善机器人",
ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID},
- ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
+ ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
Text: "点击加入测试群组",
URL: "https://t.me/+BomkHuFsjqc3ZGE1",
}}}},
})
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("command", "/test").
+ Msg("send `test group invite link` message failed")
+ }
+ return err
},
},
{
SlashCommand: "fileid",
- Handler: func(opts *handler_structs.SubHandlerParams) {
+ Handler: func(opts *handler_structs.SubHandlerParams) error {
+ logger := zerolog.Ctx(opts.Ctx)
var pendingMessage string
if opts.Update.Message.ReplyToMessage != nil {
if opts.Update.Message.ReplyToMessage.Sticker != nil {
@@ -71,7 +91,7 @@ func Register() {
pendingMessage = fmt.Sprintf("Type: [Document] \nFileID: [%v]", opts.Update.Message.ReplyToMessage.Document.FileID)
} else if opts.Update.Message.ReplyToMessage.Photo != nil {
pendingMessage = "Type: [Photo]\n"
- if len(opts.Fields) > 1 && opts.Fields[1] == "all" { // 如果有 all 指示,显示图片所有分辨率的 File ID
+ if len(opts.Fields) > 1 && opts.Fields[1] == "all" { // 如果有 all 参数则显示图片所有分辨率的 File ID
for i, n := range opts.Update.Message.ReplyToMessage.Photo {
pendingMessage += fmt.Sprintf("\nPhotoID_%d: W:%d H:%d Size:%d \n[%s]\n", i, n.Width, n.Height, n.FileSize, n.FileID)
}
@@ -97,38 +117,63 @@ func Register() {
ParseMode: models.ParseModeHTML,
})
if err != nil {
- log.Printf("Error response /fileid command: %v", err)
+ logger.Error().
+ Err(err).
+ Str("command", "/fileid").
+ Msg("send `file ID` message failed")
}
+ return err
},
},
{
SlashCommand: "version",
- Handler: func(opts *handler_structs.SubHandlerParams) {
+ Handler: func(opts *handler_structs.SubHandlerParams) error {
+ logger := zerolog.Ctx(opts.Ctx)
// info, err := opts.Thebot.GetWebhookInfo(ctx)
// fmt.Println(info)
// return
- botMessage, _ := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
+ botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
ChatID: opts.Update.Message.Chat.ID,
Text: mess.OutputVersionInfo(),
ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID},
ParseMode: models.ParseModeMarkdownV1,
})
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("command", "/version").
+ Msg("Send `bot version info` message failed")
+ return err
+ }
time.Sleep(time.Second * 20)
- success, _ := opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{
+ success, err := opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{
ChatID: opts.Update.Message.Chat.ID,
MessageIDs: []int{
opts.Update.Message.ID,
botMessage.ID,
},
})
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("command", "/version").
+ Msg("Delete command message and `bot version info` message failed")
+ }
if !success {
// 如果不能把用户的消息也删了,就单独删 bot 的消息
- opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{
+ _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{
ChatID: opts.Update.Message.Chat.ID,
MessageID: botMessage.ID,
})
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("command", "/version").
+ Msg("Delete `bot version info` message failed")
+ }
}
+ return err
},
},
}...)
@@ -143,41 +188,85 @@ func Register() {
{
Prefix: "via-inline",
Argument: "change-inline-command",
- Handler: func(opts *handler_structs.SubHandlerParams) {
- opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
- ChatID: opts.Update.Message.Chat.ID,
- Text: fmt.Sprintf("选择一个 Inline 模式下的默认命令由于缓存原因,您可能需要等一会才能看到更新后的结果无论您是否设定了默认命令,您始终都可以在 inline 模式下输入
%s 号来查看全部可用的命令", configs.BotConfig.InlineSubCommandSymbol),
- ParseMode: models.ParseModeHTML,
- ReplyMarkup: utils.BuildDefaultInlineCommandSelectKeyboard(opts.ChatInfo),
+ Handler: func(opts *handler_structs.SubHandlerParams) error {
+ logger := zerolog.Ctx(opts.Ctx)
+ _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
+ ChatID: opts.Update.Message.Chat.ID,
+ Text: fmt.Sprintf("选择一个 Inline 模式下的默认命令由于缓存原因,您可能需要等一会才能看到更新后的结果无论您是否设定了默认命令,您始终都可以在 inline 模式下输入
%s 号来查看全部可用的命令", configs.BotConfig.InlineSubCommandSymbol),
+ ParseMode: models.ParseModeHTML,
+ ReplyMarkup: plugin_utils.BuildDefaultInlineCommandSelectKeyboard(opts.ChatInfo),
ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID },
})
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Msg("Send `select inline default command keyboard` message failed")
+ }
+ return err
},
},
}...)
+ // 触发:'/start Error downloading sticker: %s", err), ParseMode: models.ParseModeHTML, }) + if err != nil { + + } } if stickerData == nil || stickerData.Data == nil { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: "未能获取到贴纸", ParseMode: models.ParseModeMarkdownV1, }) - return + if err != nil { + + } + return err } documentParams := &bot.SendDocumentParams{ @@ -482,17 +501,28 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) { documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s.%s", stickerFilePrefix, stickerFileSuffix), Data: stickerData.Data} - opts.Thebot.SendDocument(opts.Ctx, documentParams) + _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) + if err != nil { + return err + } + + return nil } -func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) { - botMessage, _ := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ +func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) error { + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, Text: "已请求下载,请稍候", ParseMode: models.ParseModeMarkdownV1, }) + if err != nil { + return err + } - database.IncrementalUsageCount(opts.Ctx, opts.Update.CallbackQuery.Message.Message.Chat.ID, db_struct.StickerSetDownloaded) + err = database.IncrementalUsageCount(opts.Ctx, opts.Update.CallbackQuery.Message.Message.Chat.ID, db_struct.StickerSetDownloaded) + if err != nil { + return err + } var packName string var isOnlyPNG bool @@ -513,7 +543,7 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Text: fmt.Sprintf("获取贴纸包时发生了一些错误\n
Error getting sticker set: %s", err), ParseMode: models.ParseModeHTML, }) - return + return err } stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) @@ -531,7 +561,7 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Text: "未能获取到压缩包", ParseMode: models.ParseModeMarkdownV1, }) - return + return err } documentParams := &bot.SendDocumentParams{ @@ -547,11 +577,18 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d).zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} } - opts.Thebot.SendDocument(opts.Ctx, documentParams) + _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) + if err != nil { + return err + } - opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: botMessage.ID, }) + if err != nil { + return err + } + return nil } diff --git a/database/operates.go b/database/operates.go index 7cbe4f1..d04e4ee 100644 --- a/database/operates.go +++ b/database/operates.go @@ -3,9 +3,15 @@ package database import ( "context" "fmt" + "strings" + "trbot/database/db_struct" + "trbot/utils" + "trbot/utils/handler_structs" + "trbot/utils/type/update_utils" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" ) func InitChat(ctx context.Context, chat *models.Chat) error { @@ -154,3 +160,145 @@ func ReadDatabase(ctx context.Context) error { } return allErr } + + +func RecordData(params *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(params.Ctx). + With(). + Str("funcName", "RecordData"). + Logger() + + updateType := update_utils.GetUpdateType(params.Update) + + switch { + case updateType.Message: + if params.Update.Message.Text != "" { + params.Fields = strings.Fields(params.Update.Message.Text) + } + err := InitChat(params.Ctx, ¶ms.Update.Message.Chat) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Init chat failed") + } + err = IncrementalUsageCount(params.Ctx, params.Update.Message.Chat.ID, db_struct.MessageNormal) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Incremental message count failed") + } + err = RecordLatestData(params.Ctx, params.Update.Message.Chat.ID, db_struct.LatestMessage, params.Update.Message.Text) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Record latest message failed") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.Message.Chat.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Get chat info failed") + } + case updateType.EditedMessage: + // no ? + case updateType.InlineQuery: + if params.Update.InlineQuery.Query != "" { + params.Fields = strings.Fields(params.Update.InlineQuery.Query) + } + + err := InitUser(params.Ctx, params.Update.InlineQuery.From) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Init user failed") + } + err = IncrementalUsageCount(params.Ctx, params.Update.InlineQuery.From.ID, db_struct.InlineRequest) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Incremental inline request count failed") + } + err = RecordLatestData(params.Ctx, params.Update.InlineQuery.From.ID, db_struct.LatestInlineQuery, params.Update.InlineQuery.Query) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Record latest inline query failed") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.InlineQuery.From.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Get user info failed") + } + case updateType.ChosenInlineResult: + if params.Update.ChosenInlineResult.Query != "" { + params.Fields = strings.Fields(params.Update.ChosenInlineResult.Query) + } + + err := InitUser(params.Ctx, ¶ms.Update.ChosenInlineResult.From) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Init user failed") + } + err = IncrementalUsageCount(params.Ctx, params.Update.ChosenInlineResult.From.ID, db_struct.InlineResult) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Incremental inline result count failed") + } + err = RecordLatestData(params.Ctx, params.Update.ChosenInlineResult.From.ID, db_struct.LatestInlineResult, params.Update.ChosenInlineResult.ResultID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Record latest inline result failed") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.ChosenInlineResult.From.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Get user info failed") + } + case updateType.CallbackQuery: + err := InitUser(params.Ctx, ¶ms.Update.CallbackQuery.From) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Init user failed") + } + err = IncrementalUsageCount(params.Ctx, params.Update.CallbackQuery.From.ID, db_struct.CallbackQuery) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Incremental callback query count failed") + } + err = RecordLatestData(params.Ctx, params.Update.CallbackQuery.From.ID, db_struct.LatestCallbackQueryData, params.Update.CallbackQuery.Data) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Record latest callback query failed") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.CallbackQuery.From.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Get user info failed") + } + } +} diff --git a/handlers.go b/handlers.go index d65eeb4..5f6394e 100644 --- a/handlers.go +++ b/handlers.go @@ -13,9 +13,8 @@ import ( "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" - "trbot/utils/mess" "trbot/utils/plugin_utils" - "trbot/utils/type_utils" + "trbot/utils/type/message_utils" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" @@ -29,45 +28,18 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Str("funcName", "defaultHandler"). Logger() - var err error + // var err error var opts = handler_structs.SubHandlerParams{ Ctx: ctx, Thebot: thebot, Update: update, } + database.RecordData(&opts) + // 需要重写来配合 handler by update type if update.Message != nil { // 正常消息 - opts.Fields = strings.Fields(update.Message.Text) - err = database.InitChat(opts.Ctx, &update.Message.Chat) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(&update.Message.Chat)). - Msg("Init chat failed") - } - err = database.IncrementalUsageCount(opts.Ctx, update.Message.Chat.ID, db_struct.MessageNormal) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(&update.Message.Chat)). - Msg("Incremental usage count failed") - } - err = database.RecordLatestData(opts.Ctx, update.Message.Chat.ID, db_struct.LatestMessage, update.Message.Text) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(&update.Message.Chat)). - Msg("Record latest message failed") - } - opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, update.Message.Chat.ID) - if err != nil { - logger.Warn(). - Err(err). - Dict(utils.GetChatDict(&update.Message.Chat)). - Msg("Get chat info error") - } if consts.IsDebugMode { if update.Message.Photo != nil { logger.Debug(). @@ -117,90 +89,33 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } } else if update.InlineQuery != nil { // inline 查询 - opts.Fields = strings.Fields(update.InlineQuery.Query) - database.InitUser(opts.Ctx, update.InlineQuery.From) - database.IncrementalUsageCount(opts.Ctx, update.InlineQuery.From.ID, db_struct.InlineRequest) - database.RecordLatestData(opts.Ctx, update.InlineQuery.From.ID, db_struct.LatestInlineQuery, update.InlineQuery.Query) - opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, update.InlineQuery.From.ID) - if err != nil { - log.Println(err) - } - log.Printf("inline from: \"%s\"(%s)[%d], query: [%s]", - utils.ShowUserName(update.InlineQuery.From), update.InlineQuery.From.Username, update.InlineQuery.From.ID, - update.InlineQuery.Query, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.InlineQuery.From)). + Str("query", update.InlineQuery.Query). + Msg("inline request") inlineHandler(&opts) } else if update.ChosenInlineResult != nil { // inline 查询结果被选择 - opts.Fields = strings.Fields(update.ChosenInlineResult.Query) - database.InitUser(opts.Ctx, &update.ChosenInlineResult.From) - database.IncrementalUsageCount(opts.Ctx, update.ChosenInlineResult.From.ID, db_struct.InlineResult) - database.RecordLatestData(opts.Ctx, update.ChosenInlineResult.From.ID, db_struct.LatestInlineResult, update.ChosenInlineResult.ResultID) - opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, update.ChosenInlineResult.From.ID) - if err != nil { - log.Println(err) - } + logger.Debug(). + Dict(utils.GetUserDict(&update.ChosenInlineResult.From)). + Str("query", update.ChosenInlineResult.Query). + Str("resultID", update.ChosenInlineResult.ResultID). + Msg("chosen inline result") - log.Printf("chosen inline from \"%s\"(%s)[%d], ID: [%s] query: [%s]", - utils.ShowUserName(&update.ChosenInlineResult.From), update.ChosenInlineResult.From.Username, update.ChosenInlineResult.From.ID, - update.ChosenInlineResult.ResultID, update.ChosenInlineResult.Query, - ) + } else if update.CallbackQuery != nil { // replymarkup 回调 + logger.Debug(). + Dict(utils.GetUserDict(&update.CallbackQuery.From)). + Dict(utils.GetChatDict(&update.CallbackQuery.Message.Message.Chat)). + Str("query", update.CallbackQuery.Data). + Msg("callback query") + + callbackQueryHandler(&opts) + - database.InitUser(opts.Ctx, &update.CallbackQuery.From) - database.IncrementalUsageCount(opts.Ctx, update.CallbackQuery.From.ID, db_struct.CallbackQuery) - database.RecordLatestData(opts.Ctx, update.CallbackQuery.From.ID, db_struct.LatestCallbackQueryData, update.CallbackQuery.Data) - opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, update.CallbackQuery.From.ID) - if err != nil { - log.Println(err) - } - - log.Printf("callback from \"%s\"(%s)[%d] in \"%s\"(%s)[%d] query: [%s]", - utils.ShowUserName(&update.CallbackQuery.From), update.CallbackQuery.From.Username, update.CallbackQuery.From.ID, - utils.ShowChatName(&update.CallbackQuery.Message.Message.Chat), update.CallbackQuery.Message.Message.Chat.Username, update.CallbackQuery.Message.Message.Chat.ID, - update.CallbackQuery.Data, - ) - - // 如果有一个正在处理的请求,且用户再次发送相同的请求,则提示用户等待 - if opts.ChatInfo.HasPendingCallbackQuery && update.CallbackQuery.Data == opts.ChatInfo.LatestCallbackQueryData { - log.Println("same callback query, ignore") - thebot.AnswerCallbackQuery(ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: update.CallbackQuery.ID, - Text: "当前的请求正在处理,请等待处理完成", - ShowAlert: true, - }) - return - } else if opts.ChatInfo.HasPendingCallbackQuery { - // 如果有一个正在处理的请求,用户发送了不同的请求,则提示用户等待 - log.Println("a callback query is pending, ignore") - thebot.AnswerCallbackQuery(ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: update.CallbackQuery.ID, - Text: "请等待上一个请求处理完成再尝试发送新的请求", - ShowAlert: true, - }) - return - } else { - // 如果没有正在处理的请求,则接受新的请求 - log.Println("accept callback query") - opts.ChatInfo.HasPendingCallbackQuery = true - opts.ChatInfo.LatestCallbackQueryData = update.CallbackQuery.Data - // thebot.AnswerCallbackQuery(ctx, &bot.AnswerCallbackQueryParams{ - // CallbackQueryID: update.CallbackQuery.ID, - // Text: "已接受请求", - // ShowAlert: false, - // }) - } - - for _, n := range plugin_utils.AllPlugins.CallbackQuery { - if strings.HasPrefix(update.CallbackQuery.Data, n.CommandChar) { - if n.Handler == nil { continue } - n.Handler(&opts) - break - } - } opts.ChatInfo.HasPendingCallbackQuery = false return @@ -330,132 +245,316 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) // 处理所有信息请求的处理函数,触发条件为任何消息 func messageHandler(opts *handler_structs.SubHandlerParams) { defer utils.PanicCatcher("messageHandler") + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("funcName", "messageHandler"). + Logger() // 检测如果消息开头是 / 符号,作为命令来处理 if strings.HasPrefix(opts.Update.Message.Text, "/") { // 匹配默认的 `/xxx` 命令 for _, plugin := range plugin_utils.AllPlugins.SlashSymbolCommand { if utils.CommandMaybeWithSuffixUsername(opts.Fields, "/" + plugin.SlashCommand) { - if consts.IsDebugMode { - log.Printf("hit slashcommand: /%s", plugin.SlashCommand) + logger.Debug(). + Str("slashCommand", plugin.SlashCommand). + Str("message", opts.Update.Message.Text). + Msg("Hit slash command handler") + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("slashCommand", plugin.SlashCommand). + Str("message", opts.Update.Message.Text). + Msg("Hit slash symbol command handler, but this handler function is nil, skip") + continue + } + err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("slashCommand", plugin.SlashCommand). + Str("message", opts.Update.Message.Text). + Msg("Incremental message command count error") + } + err = plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("slashCommand", plugin.SlashCommand). + Str("message", opts.Update.Message.Text). + Msg("Error in slash symbol command handler") } - if plugin.Handler == nil { continue } - database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) - plugin.Handler(opts) return } } + // 不存在以 `/` 作为前缀的命令 if opts.Update.Message.Chat.Type == models.ChatTypePrivate { // 非冗余条件,在私聊状态下应处理用户发送的所有开头为 / 的命令 // 与群组中不同,群组中命令末尾不指定此 bot 回应的命令无须处理,以防与群组中的其他 bot 冲突 - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, Text: "不存在的命令", }) - database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) - if configs.BotConfig.LogChatID != 0 { mess.PrivateLogToChat(opts.Ctx, opts.Thebot, opts.Update) } + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("message", opts.Update.Message.Text). + Msg("Send `no this command` message failed") + } + err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("message", opts.Update.Message.Text). + Msg("Incremental message command count error") + } + + // if configs.BotConfig.LogChatID != 0 { mess.PrivateLogToChat(opts.Ctx, opts.Thebot, opts.Update) } } else if strings.HasSuffix(opts.Fields[0], "@" + consts.BotMe.Username) { // 当使用一个不存在的命令,但是命令末尾指定为此 bot 处理 // 为防止与其他 bot 的命令冲突,默认不会处理不在命令列表中的命令 // 如果消息以 /xxx@examplebot 的形式指定此 bot 回应,且 /xxx 不在预设的命令中时,才发送该命令不可用的提示 - botMessage, _ := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, Text: "不存在的命令", }) - database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("message", opts.Update.Message.Text). + Msg("Send `no this command` message failed") + } + err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("message", opts.Update.Message.Text). + Msg("Incremental message command count error") + } time.Sleep(time.Second * 10) - opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ + _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ ChatID: opts.Update.Message.Chat.ID, MessageIDs: []int{ opts.Update.Message.ID, botMessage.ID, }, }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("message", opts.Update.Message.Text). + Msg("Delete `no this command` message failed") + } return } } else if len(opts.Update.Message.Text) > 0 { // 没有 `/` 号作为前缀,检查是不是自定义命令 for _, plugin := range plugin_utils.AllPlugins.CustomSymbolCommand { if utils.CommandMaybeWithSuffixUsername(opts.Fields, plugin.FullCommand) { - if consts.IsDebugMode { - log.Printf("hit fullcommand: %s", plugin.FullCommand) + logger.Debug(). + Str("fullCommand", plugin.FullCommand). + Str("message", opts.Update.Message.Text). + Msg("Hit full command handler") + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("fullCommand", plugin.FullCommand). + Str("message", opts.Update.Message.Text). + Msg("Hit full command handler, but this handler function is nil, skip") + continue + } + err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("fullCommand", plugin.FullCommand). + Str("message", opts.Update.Message.Text). + Msg("Incremental message command count error") + } + err = plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("fullCommand", plugin.FullCommand). + Str("message", opts.Update.Message.Text). + Msg("Error in full command handler") } - if plugin.Handler == nil { continue } - database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) - plugin.Handler(opts) return } } // 以后缀来触发的命令 for _, plugin := range plugin_utils.AllPlugins.SuffixCommand { if strings.HasSuffix(opts.Update.Message.Text, plugin.SuffixCommand) { - if consts.IsDebugMode { - log.Printf("hit suffixcommand: %s", plugin.SuffixCommand) + logger.Debug(). + Str("suffixCommand", plugin.SuffixCommand). + Str("message", opts.Update.Message.Text). + Msg("Hit suffix command handler") + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("suffixCommand", plugin.SuffixCommand). + Str("message", opts.Update.Message.Text). + Msg("Hit suffix command handler, but this handler function is nil, skip") + continue + } + err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("suffixCommand", plugin.SuffixCommand). + Str("message", opts.Update.Message.Text). + Msg("Incremental message command count error") + } + err = plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("suffixCommand", plugin.SuffixCommand). + Str("message", opts.Update.Message.Text). + Msg("Error in suffix command handler") } - if plugin.Handler == nil { continue } - database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) - plugin.Handler(opts) return } } } + // 按消息类型来触发的 handler + // handler by message type if plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type] != nil { - msgTypeInString := type_utils.GetMessageType(opts.Update.Message).InString() - var isProcessed bool + msgTypeInString := message_utils.GetMessageType(opts.Update.Message).InString() - // 如果此类型的 handler 数量仅有一个,且允许自动触发 - if len(plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString]) == 1 { - // 虽然是遍历,但实际上只能遍历一次 - for name, handler := range plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString] { - isProcessed = true - if handler.AllowAutoTrigger { - // 允许自动触发的 handler - if consts.IsDebugMode { - log.Printf("trigger handler by message type [%s] plugin [%s] for chat type [%s]", msgTypeInString, name, opts.Update.Message.Chat.Type) + if plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString] != nil { + handlerInThisTypeCount := len(plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString]) + if handlerInThisTypeCount == 1 { + // 虽然是遍历,但实际上只能遍历一次 + for name, handler := range plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString] { + if handler.AllowAutoTrigger { + // 允许自动触发的 handler + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("messageType", string(msgTypeInString)). + Str("handlerName", name). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Msg("trigger handler by message type") + err := handler.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("messageType", string(msgTypeInString)). + Str("handlerName", name). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Msg("Error in handler by message type") + } + } else { + // 此 handler 不允许自动触发,回复一条带按钮的消息让用户手动操作 + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: fmt.Sprintf("请选择一个 [ %s ] 类型消息的功能", msgTypeInString), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ReplyMarkup: plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString].BuildSelectKeyboard(), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("messageType", string(msgTypeInString)). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Int("handlerInThisTypeCount", handlerInThisTypeCount). + Msg("Send `select a handler by message type keyboard` message failed") + } } - handler.Handler(opts) - } else { - // 此 handler 不允许自动触发,回复一条带按钮的消息让用户手动操作 - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: fmt.Sprintf("请选择一个 [ %s ] 类型消息的功能", msgTypeInString), - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ReplyMarkup: plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString].BuildSelectKeyboard(), - }) + } + } else { + // 多个 handler 自动回复一条带按钮的消息让用户手动操作 + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: fmt.Sprintf("请选择一个 [ %s ] 类型消息的功能", msgTypeInString), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ReplyMarkup: plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString].BuildSelectKeyboard(), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("messageType", string(msgTypeInString)). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Int("handlerInThisTypeCount", handlerInThisTypeCount). + Msg("Send `select a handler by message type keyboard` message failed") } } - } else { - // 多个 handler 自动回复一条带按钮的消息让用户手动操作 - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: fmt.Sprintf("请选择一个 [ %s ] 类型消息的功能", msgTypeInString), - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ReplyMarkup: plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString].BuildSelectKeyboard(), - }) - } - - // 仅在 private 对话中显示无默认处理插件的消息 - if !isProcessed && opts.Update.Message.Chat.Type == models.ChatTypePrivate { - // 非命令消息,提示无操作可用 - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + } else if opts.Update.Message.Chat.Type == models.ChatTypePrivate { + // 仅在 private 对话中显示无默认处理插件的消息 + // 如果没有设定任何对于 private 对话按消息来触发的 handler,则代码不会运行到这里 + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: fmt.Sprintf("对于 [ %s ] 类型的消息没有默认处理插件", msgTypeInString), ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("messageType", string(msgTypeInString)). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Msg("Send `no handler by message type plugin for this message type` message failed") + } } } // 最后才运行针对群组 ID 的 handler - ByChatIDHandlers, isExist := plugin_utils.AllPlugins.HandlerByChatID[opts.Update.Message.Chat.ID] - if isExist { - for name, handler := range ByChatIDHandlers { - if consts.IsDebugMode { - log.Printf("trigger handler by chatID [%s] for group [%d]", name, handler.ChatID) + // handler by chat ID + if plugin_utils.AllPlugins.HandlerByChatID[opts.Update.Message.Chat.ID] != nil { + for name, handler := range plugin_utils.AllPlugins.HandlerByChatID[opts.Update.Message.Chat.ID] { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("handlerName", name). + Int64("chatID", handler.ChatID). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Msg("trigger handler by chat ID") + err := handler.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("handlerName", name). + Int64("chatID", handler.ChatID). + Str("chatType", string(opts.Update.Message.Chat.Type)). + Msg("Error in handler by chat ID") } - handler.Handler(opts) } } } @@ -463,6 +562,10 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { // 处理 inline 模式下的请求 func inlineHandler(opts *handler_structs.SubHandlerParams) { defer utils.PanicCatcher("inlineHandler") + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("funcName", "inlineHandler"). + Logger() var IsAdmin bool = utils.AnyContains(opts.Update.InlineQuery.From.ID, configs.BotConfig.AdminIDs) @@ -509,6 +612,10 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { IsPersonal: true, }) if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Msg("Send /setkeyword command answer failed") log.Printf("Error sending inline query response: %v", err) return } @@ -763,3 +870,87 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { } } } + +func callbackQueryHandler(params *handler_structs.SubHandlerParams) { + defer utils.PanicCatcher("callbackQueryHandler") + logger := zerolog.Ctx(params.Ctx). + With(). + Str("funcName", "callbackQueryHandler"). + Logger() + + // 如果有一个正在处理的请求,且用户再次发送相同的请求,则提示用户等待 + if params.ChatInfo.HasPendingCallbackQuery && params.Update.CallbackQuery.Data == params.ChatInfo.LatestCallbackQueryData { + logger.Info(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("query", params.Update.CallbackQuery.Data). + Msg("this callback request is processing, ignore") + _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: params.Update.CallbackQuery.ID, + Text: "当前请求正在处理中,请等待处理完成", + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Send `this callback request is processing` callback answer failed") + } + return + } else if params.ChatInfo.HasPendingCallbackQuery { + // 如果有一个正在处理的请求,用户发送了不同的请求,则提示用户等待 + logger.Info(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("pendingQuery", params.ChatInfo.LatestCallbackQueryData). + Str("query", params.Update.CallbackQuery.Data). + Msg("another callback request is processing, ignore") + _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: params.Update.CallbackQuery.ID, + Text: "请等待上一个请求处理完成后再尝试发送新的请求", + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Send `a callback request is processing, send new request later` callback answer failed") + } + return + } else { + // 如果没有正在处理的请求,则接受新的请求 + logger.Debug(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("query", params.Update.CallbackQuery.Data). + Msg("accept callback query") + + params.ChatInfo.HasPendingCallbackQuery = true + params.ChatInfo.LatestCallbackQueryData = params.Update.CallbackQuery.Data + // params.Thebot.AnswerCallbackQuery(ctx, &bot.AnswerCallbackQueryParams{ + // CallbackQueryID: params.Update.CallbackQuery.ID, + // Text: "已接受请求", + // ShowAlert: false, + // }) + } + + for _, n := range plugin_utils.AllPlugins.CallbackQuery { + if strings.HasPrefix(params.Update.CallbackQuery.Data, n.CommandChar) { + if n.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(params.Update.Message.From)). + Str("handlerPrefix", n.CommandChar). + Str("query", params.Update.CallbackQuery.Data). + Msg("tigger a callback query handler, but this handler function is nil, skip") + continue + } + err := n.Handler(params) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.Message.From)). + Str("handlerPrefix", n.CommandChar). + Str("query", params.Update.CallbackQuery.Data). + Msg("Error in callback query handler") + } + break + } + } +} diff --git a/utils/internal_plugin/handler.go b/utils/internal_plugin/handler.go index b083436..0bbd384 100644 --- a/utils/internal_plugin/handler.go +++ b/utils/internal_plugin/handler.go @@ -26,13 +26,13 @@ func startHandler(params *handler_structs.SubHandlerParams) error { inlineArgument := strings.Split(params.Fields[1], "_") if inlineArgument[1] == n.Argument { if n.Handler == nil { - logger.Trace(). + logger.Debug(). Dict(utils.GetUserDict(params.Update.Message.From)). Str("handlerPrefix", n.Prefix). Str("handlerArgument", n.Argument). Str("handlerName", n.Name). Str("fullCommand", params.Update.Message.Text). - Msg("tigger start handler by prefix, but this handler function is nil, skip") + Msg("tigger /start command handler by prefix, but this handler function is nil, skip") continue } err := n.Handler(params) @@ -44,7 +44,7 @@ func startHandler(params *handler_structs.SubHandlerParams) error { Str("handlerArgument", n.Argument). Str("handlerName", n.Name). Str("fullCommand", params.Update.Message.Text). - Msg("Error in start handler by prefix tigger") + Msg("Error in /start command handler by prefix tigger") } return err } @@ -60,7 +60,7 @@ func startHandler(params *handler_structs.SubHandlerParams) error { Str("handlerArgument", n.Argument). Str("handlerName", n.Name). Str("fullCommand", params.Update.Message.Text). - Msg("Error in start handler by argument") + Msg("Error in /start command handler by argument") } return err } diff --git a/utils/plugin_utils/handler_by_message_type.go b/utils/plugin_utils/handler_by_message_type.go index 0aeacaa..f5f2d6c 100644 --- a/utils/plugin_utils/handler_by_message_type.go +++ b/utils/plugin_utils/handler_by_message_type.go @@ -5,7 +5,7 @@ import ( "strings" "trbot/utils" "trbot/utils/handler_structs" - "trbot/utils/type_utils" + "trbot/utils/type/message_utils" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" @@ -32,7 +32,7 @@ func (funcs HandlerByMessageTypeFunctions) BuildSelectKeyboard() models.ReplyMar type HandlerByMessageType struct { PluginName string ChatType models.ChatType - MessageType type_utils.MessageTypeList + MessageType message_utils.MessageTypeList AllowAutoTrigger bool // Allow auto trigger when there is only one handler of the same type Handler func(*handler_structs.SubHandlerParams) error } @@ -62,11 +62,11 @@ type HandlerByMessageType struct { ``` */ func AddHandlerByMessageTypePlugins(plugins ...HandlerByMessageType) int { - if AllPlugins.HandlerByMessageType == nil { AllPlugins.HandlerByMessageType = map[models.ChatType]map[type_utils.MessageTypeList]HandlerByMessageTypeFunctions{} } + if AllPlugins.HandlerByMessageType == nil { AllPlugins.HandlerByMessageType = map[models.ChatType]map[message_utils.MessageTypeList]HandlerByMessageTypeFunctions{} } var pluginCount int for _, plugin := range plugins { - if AllPlugins.HandlerByMessageType[plugin.ChatType] == nil { AllPlugins.HandlerByMessageType[plugin.ChatType] = map[type_utils.MessageTypeList]HandlerByMessageTypeFunctions{} } + if AllPlugins.HandlerByMessageType[plugin.ChatType] == nil { AllPlugins.HandlerByMessageType[plugin.ChatType] = map[message_utils.MessageTypeList]HandlerByMessageTypeFunctions{} } if AllPlugins.HandlerByMessageType[plugin.ChatType][plugin.MessageType] == nil { AllPlugins.HandlerByMessageType[plugin.ChatType][plugin.MessageType] = HandlerByMessageTypeFunctions{} } _, isExist := AllPlugins.HandlerByMessageType[plugin.ChatType][plugin.MessageType][plugin.PluginName] @@ -79,7 +79,7 @@ func AddHandlerByMessageTypePlugins(plugins ...HandlerByMessageType) int { return pluginCount } -func RemoveHandlerByMessageTypePlugin(chatType models.ChatType, messageType type_utils.MessageTypeList, pluginName string) { +func RemoveHandlerByMessageTypePlugin(chatType models.ChatType, messageType message_utils.MessageTypeList, pluginName string) { if AllPlugins.HandlerByMessageType == nil { return } _, isExist := AllPlugins.HandlerByMessageType[chatType][messageType][pluginName] @@ -114,7 +114,7 @@ func SelectHandlerByMessageTypeHandlerCallback(opts *handler_structs.SubHandlerP return err } chatType, messageType, pluginName = chatTypeAndPluginNameList[0], chatTypeAndPluginNameList[1], chatTypeAndPluginNameList[2] - handler, isExist := AllPlugins.HandlerByMessageType[models.ChatType(chatType)][type_utils.MessageTypeList(messageType)][pluginName] + handler, isExist := AllPlugins.HandlerByMessageType[models.ChatType(chatType)][message_utils.MessageTypeList(messageType)][pluginName] if isExist { logger.Debug(). Dict("user", zerolog.Dict(). diff --git a/utils/plugin_utils/handler_help.go b/utils/plugin_utils/handler_help.go index ebefa2d..7e95061 100644 --- a/utils/plugin_utils/handler_help.go +++ b/utils/plugin_utils/handler_help.go @@ -19,6 +19,9 @@ func BuildHandlerHelpKeyboard() models.ReplyMarkup { }, }) } + if len(button) == 0 { + return nil + } return models.InlineKeyboardMarkup{ InlineKeyboard: button, } diff --git a/utils/plugin_utils/plugin_type.go b/utils/plugin_utils/plugin_type.go index 0a2721f..6ec21f7 100644 --- a/utils/plugin_utils/plugin_type.go +++ b/utils/plugin_utils/plugin_type.go @@ -1,7 +1,7 @@ package plugin_utils import ( - "trbot/utils/type_utils" + "trbot/utils/type/message_utils" "github.com/go-telegram/bot/models" ) @@ -26,7 +26,7 @@ type Plugin_All struct { CallbackQuery []CallbackQuery // 处理 InlineKeyboardMarkup 的 callback 函数 // 根据聊天类型设定的默认处理函数 - HandlerByMessageType map[models.ChatType]map[type_utils.MessageTypeList]HandlerByMessageTypeFunctions + HandlerByMessageType map[models.ChatType]map[message_utils.MessageTypeList]HandlerByMessageTypeFunctions // 以聊天 ID 设定的默认处理函数,第一个 map 为 ID,第二个为 handler 名称 HandlerByChatID map[int64]map[string]HandlerByChatID diff --git a/utils/type_utils/message_attribute.go b/utils/type/message_utils/message_attribute.go similarity index 99% rename from utils/type_utils/message_attribute.go rename to utils/type/message_utils/message_attribute.go index ed75e9d..a48ea83 100644 --- a/utils/type_utils/message_attribute.go +++ b/utils/type/message_utils/message_attribute.go @@ -1,4 +1,4 @@ -package type_utils +package message_utils import "github.com/go-telegram/bot/models" diff --git a/utils/type_utils/message_type.go b/utils/type/message_utils/message_type.go similarity index 99% rename from utils/type_utils/message_type.go rename to utils/type/message_utils/message_type.go index b5f2017..c709521 100644 --- a/utils/type_utils/message_type.go +++ b/utils/type/message_utils/message_type.go @@ -1,4 +1,4 @@ -package type_utils +package message_utils import ( "reflect" diff --git a/utils/type/update_utils/update_type.go b/utils/type/update_utils/update_type.go new file mode 100644 index 0000000..228845b --- /dev/null +++ b/utils/type/update_utils/update_type.go @@ -0,0 +1,147 @@ +package update_utils + +import ( + "reflect" + + "github.com/go-telegram/bot/models" +) + +// 更新类型 +type UpdateType struct { + Message bool `yaml:"Message,omitempty"` // *models.Message + EditedMessage bool `yaml:"EditedMessage,omitempty"` // *models.Message + ChannelPost bool `yaml:"ChannelPost,omitempty"` // *models.Message + EditedChannelPost bool `yaml:"EditedChannelPost,omitempty"` // *models.Message + BusinessConnection bool `yaml:"BusinessConnection,omitempty"` // *models.BusinessConnection + BusinessMessage bool `yaml:"BusinessMessage,omitempty"` // *models.Message + EditedBusinessMessage bool `yaml:"EditedBusinessMessage,omitempty"` // *models.Message + DeletedBusinessMessages bool `yaml:"DeletedBusinessMessages,omitempty"` // *models.BusinessMessagesDeleted + MessageReaction bool `yaml:"MessageReaction,omitempty"` // *models.MessageReactionUpdated + MessageReactionCount bool `yaml:"MessageReactionCount,omitempty"` // *models.MessageReactionCountUpdated + InlineQuery bool `yaml:"InlineQuery,omitempty"` // *models.InlineQuery + ChosenInlineResult bool `yaml:"ChosenInlineResult,omitempty"` // *models.ChosenInlineResult + CallbackQuery bool `yaml:"CallbackQuery,omitempty"` // *models.CallbackQuery + ShippingQuery bool `yaml:"ShippingQuery,omitempty"` // *models.ShippingQuery + PreCheckoutQuery bool `yaml:"PreCheckoutQuery,omitempty"` // *models.PreCheckoutQuery + PurchasedPaidMedia bool `yaml:"PurchasedPaidMedia,omitempty"` // *models.PaidMediaPurchased + Poll bool `yaml:"Poll,omitempty"` // *models.Poll + PollAnswer bool `yaml:"PollAnswer,omitempty"` // *models.PollAnswer + MyChatMember bool `yaml:"MyChatMember,omitempty"` // *models.ChatMemberUpdated + ChatMember bool `yaml:"ChatMember,omitempty"` // *models.ChatMemberUpdated + ChatJoinRequest bool `yaml:"ChatJoinRequest,omitempty"` // *models.ChatJoinRequest + ChatBoost bool `yaml:"ChatBoost,omitempty"` // *models.ChatBoostUpdated + RemovedChatBoost bool `yaml:"RemovedChatBoost,omitempty"` // *models.ChatBoostRemoved +} + +func (mt UpdateType)InString() UpdateTypeList { + val := reflect.ValueOf(mt) + typ := reflect.TypeOf(mt) + + for i := 0; i < val.NumField(); i++ { + if val.Field(i).Bool() { + return UpdateTypeList(typ.Field(i).Name) + } + } + + return "" +} + +type UpdateTypeList string + +const ( + Message UpdateTypeList = "Message" + EditedMessage UpdateTypeList = "EditedMessage" + ChannelPost UpdateTypeList = "ChannelPost" + EditedChannelPost UpdateTypeList = "EditedChannelPost" + BusinessConnection UpdateTypeList = "BusinessConnection" + BusinessMessage UpdateTypeList = "BusinessMessage" + EditedBusinessMessage UpdateTypeList = "EditedBusinessMessage" + DeletedBusinessMessages UpdateTypeList = "DeletedBusinessMessages" + MessageReaction UpdateTypeList = "MessageReaction" + MessageReactionCount UpdateTypeList = "MessageReactionCount" + InlineQuery UpdateTypeList = "InlineQuery" + ChosenInlineResult UpdateTypeList = "ChosenInlineResult" + CallbackQuery UpdateTypeList = "CallbackQuery" + ShippingQuery UpdateTypeList = "ShippingQuery" + PreCheckoutQuery UpdateTypeList = "PreCheckoutQuery" + PurchasedPaidMedia UpdateTypeList = "PurchasedPaidMedia" + Poll UpdateTypeList = "Poll" + PollAnswer UpdateTypeList = "PollAnswer" + MyChatMember UpdateTypeList = "MyChatMember" + ChatMember UpdateTypeList = "ChatMember" + ChatJoinRequest UpdateTypeList = "ChatJoinRequest" + ChatBoost UpdateTypeList = "ChatBoost" + RemovedChatBoost UpdateTypeList = "RemovedChatBoost" +) + +// 判断更新属性 +func GetUpdateType(update *models.Update) UpdateType { + var updateType UpdateType + if update.Message != nil { + updateType.Message = true + } + if update.EditedMessage != nil { + updateType.EditedMessage = true + } + if update.ChannelPost != nil { + updateType.ChannelPost = true + } + if update.EditedChannelPost != nil { + updateType.EditedChannelPost = true + } + if update.BusinessConnection != nil { + updateType.BusinessConnection = true + } + if update.BusinessMessage != nil { + updateType.BusinessMessage = true + } + if update.EditedBusinessMessage != nil { + updateType.EditedBusinessMessage = true + } + if update.MessageReaction != nil { + updateType.MessageReaction = true + } + if update.MessageReactionCount != nil { + updateType.MessageReactionCount = true + } + if update.InlineQuery != nil { + updateType.InlineQuery = true + } + if update.ChosenInlineResult != nil { + updateType.ChosenInlineResult = true + } + if update.CallbackQuery != nil { + updateType.CallbackQuery = true + } + if update.ShippingQuery != nil { + updateType.ShippingQuery = true + } + if update.PreCheckoutQuery != nil { + updateType.PreCheckoutQuery = true + } + if update.PurchasedPaidMedia != nil { + updateType.PurchasedPaidMedia = true + } + if update.Poll != nil { + updateType.Poll = true + } + if update.PollAnswer != nil { + updateType.PollAnswer = true + } + if update.MyChatMember != nil { + updateType.MyChatMember = true + } + if update.ChatMember != nil { + updateType.ChatMember = true + } + if update.ChatJoinRequest != nil { + updateType.ChatJoinRequest = true + } + if update.ChatBoost != nil { + updateType.ChatBoost = true + } + if update.RemovedChatBoost != nil { + updateType.RemovedChatBoost = true + } + return updateType +} diff --git a/utils/type_utils/update_type.go b/utils/type_utils/update_type.go deleted file mode 100644 index e021182..0000000 --- a/utils/type_utils/update_type.go +++ /dev/null @@ -1,102 +0,0 @@ -package type_utils - -import "github.com/go-telegram/bot/models" - -// 更新类型 -type UpdateType struct { - Message bool // *models.Message - EditedMessage bool // *models.Message - ChannelPost bool // *models.Message - EditedChannelPost bool // *models.Message - BusinessConnection bool // *models.BusinessConnection - BusinessMessage bool // *models.Message - EditedBusinessMessage bool // *models.Message - DeletedBusinessMessages bool // *models.BusinessMessagesDeleted - MessageReaction bool // *models.MessageReactionUpdated - MessageReactionCount bool // *models.MessageReactionCountUpdated - InlineQuery bool // *models.InlineQuery - ChosenInlineResult bool // *models.ChosenInlineResult - CallbackQuery bool // *models.CallbackQuery - ShippingQuery bool // *models.ShippingQuery - PreCheckoutQuery bool // *models.PreCheckoutQuery - PurchasedPaidMedia bool // *models.PaidMediaPurchased - Poll bool // *models.Poll - PollAnswer bool // *models.PollAnswer - MyChatMember bool // *models.ChatMemberUpdated - ChatMember bool // *models.ChatMemberUpdated - ChatJoinRequest bool // *models.ChatJoinRequest - ChatBoost bool // *models.ChatBoostUpdated - RemovedChatBoost bool // *models.ChatBoostRemoved -} - -// 判断更新属性 -func GetUpdateType(update *models.Update) UpdateType { - var updateType UpdateType - if update.Message != nil { - updateType.Message = true - } - if update.EditedMessage != nil { - updateType.EditedMessage = true - } - if update.ChannelPost != nil { - updateType.ChannelPost = true - } - if update.EditedChannelPost != nil { - updateType.EditedChannelPost = true - } - if update.BusinessConnection != nil { - updateType.BusinessConnection = true - } - if update.BusinessMessage != nil { - updateType.BusinessMessage = true - } - if update.EditedBusinessMessage != nil { - updateType.EditedBusinessMessage = true - } - if update.MessageReaction != nil { - updateType.MessageReaction = true - } - if update.MessageReactionCount != nil { - updateType.MessageReactionCount = true - } - if update.InlineQuery != nil { - updateType.InlineQuery = true - } - if update.ChosenInlineResult != nil { - updateType.ChosenInlineResult = true - } - if update.CallbackQuery != nil { - updateType.CallbackQuery = true - } - if update.ShippingQuery != nil { - updateType.ShippingQuery = true - } - if update.PreCheckoutQuery != nil { - updateType.PreCheckoutQuery = true - } - if update.PurchasedPaidMedia != nil { - updateType.PurchasedPaidMedia = true - } - if update.Poll != nil { - updateType.Poll = true - } - if update.PollAnswer != nil { - updateType.PollAnswer = true - } - if update.MyChatMember != nil { - updateType.MyChatMember = true - } - if update.ChatMember != nil { - updateType.ChatMember = true - } - if update.ChatJoinRequest != nil { - updateType.ChatJoinRequest = true - } - if update.ChatBoost != nil { - updateType.ChatBoost = true - } - if update.RemovedChatBoost != nil { - updateType.RemovedChatBoost = true - } - return updateType -} diff --git a/utils/utils.go b/utils/utils.go index c3375ae..0a06886 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -11,7 +11,7 @@ import ( "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/mess" - "trbot/utils/type_utils" + "trbot/utils/type/message_utils" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" @@ -400,7 +400,7 @@ func TextForTrueOrFalse(condition bool, tureText, falseText string) string { // 获取消息来源的链接 func GetMessageFromHyperLink(msg *models.Message, ParseMode models.ParseMode) string { var senderLink string - attr := type_utils.GetMessageAttribute(msg) + attr := message_utils.GetMessageAttribute(msg) switch ParseMode { case models.ParseModeHTML: -- 2.49.1 From 456fad99d1f804f40ae2b863754154278b8435f9 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Mon, 2 Jun 2025 00:19:50 +0800 Subject: [PATCH 04/27] save changes fix some redis error database SaveDatabase and ReadDatabase can be nil --- database/initial.go | 48 ++- database/operates.go | 29 +- database/redis_db/redis.go | 10 +- handlers.go | 465 +++++++++++++++-------- main.go | 24 +- utils/configs/webhook.go | 12 +- utils/internal_plugin/handler.go | 4 +- utils/internal_plugin/register.go | 18 +- utils/plugin_utils/plugin_initializer.go | 7 +- utils/utils.go | 16 +- 10 files changed, 434 insertions(+), 199 deletions(-) diff --git a/database/initial.go b/database/initial.go index f563136..bb69edc 100644 --- a/database/initial.go +++ b/database/initial.go @@ -2,12 +2,12 @@ package database import ( "context" - "log" "trbot/database/db_struct" "trbot/database/redis_db" "trbot/database/yaml_db" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" ) type DatabaseBackend struct { @@ -39,7 +39,9 @@ type DatabaseBackend struct { var DBBackends []DatabaseBackend var DBBackends_LowLevel []DatabaseBackend -func AddDatabaseBackend(backends ...DatabaseBackend) int { +func AddDatabaseBackend(ctx context.Context, backends ...DatabaseBackend) int { + logger := zerolog.Ctx(ctx) + if DBBackends == nil { DBBackends = []DatabaseBackend{} } if DBBackends_LowLevel == nil { DBBackends_LowLevel = []DatabaseBackend{} } @@ -52,18 +54,24 @@ func AddDatabaseBackend(backends ...DatabaseBackend) int { } else { DBBackends = append(DBBackends, db) } - log.Printf("Initialized database backend [%s]", db.Name) + logger.Info(). + Str("database", db.Name). + Msg("Database initialized") count++ } else { - log.Printf("Failed to initialize database backend [%s]: %s", db.Name, db.InitializedErr) + logger.Error(). + Err(db.InitializedErr). + Str("database", db.Name). + Msg("Database initialize failed") } } return count } -func InitAndListDatabases() { - AddDatabaseBackend(DatabaseBackend{ +func InitAndListDatabases(ctx context.Context) { + logger := zerolog.Ctx(ctx) + AddDatabaseBackend(ctx, DatabaseBackend{ Name: "redis", Initializer: redis_db.InitializeDB, @@ -76,7 +84,7 @@ func InitAndListDatabases() { SetCustomFlag: redis_db.SetCustomFlag, }) - AddDatabaseBackend(DatabaseBackend{ + AddDatabaseBackend(ctx, DatabaseBackend{ Name: "yaml", IsLowLevel: true, Initializer: yaml_db.InitializeDB, @@ -93,16 +101,26 @@ func InitAndListDatabases() { SetCustomFlag: yaml_db.SetCustomFlag, }) - for _, backend := range DBBackends { - log.Printf("Database backend [%s] is available (High-level)", backend.Name) - } - for _, backend := range DBBackends_LowLevel { - log.Printf("Database backend [%s] is available (Low-level)", backend.Name) - } + // for _, backend := range DBBackends { + // logger.Info(). + // Str("database", backend.Name). + // Str("level", "high"). + // Msg("database initialized") + // } + // for _, backend := range DBBackends_LowLevel { + // logger.Info(). + // Str("database", backend.Name). + // Str("level", "low"). + // Msg("database initialized") + // } if len(DBBackends) + len(DBBackends_LowLevel) == 0 { - log.Fatalln("No database available") + logger.Fatal(). + Msg("No database available") } else { - log.Printf("Available databases: [H: %d, L: %d]", len(DBBackends), len(DBBackends_LowLevel)) + logger.Info(). + Int("High-level", len(DBBackends)). + Int("Low-level", len(DBBackends_LowLevel)). + Msg("Available databases") } } diff --git a/database/operates.go b/database/operates.go index d04e4ee..7f8cc6e 100644 --- a/database/operates.go +++ b/database/operates.go @@ -14,6 +14,8 @@ import ( "github.com/rs/zerolog" ) +// 需要给一些函数加上一个 success 返回值,有时部分数据库不可用,但数据成功保存到了其他数据库 + func InitChat(ctx context.Context, chat *models.Chat) error { var allErr error for _, db := range DBBackends { @@ -48,13 +50,22 @@ func InitUser(ctx context.Context, user *models.User) error { return allErr } -func GetChatInfo(ctx context.Context, chatID int64) (*db_struct.ChatInfo, error) { +func GetChatInfo(ctx context.Context, chatID int64) (data *db_struct.ChatInfo, err error) { // 优先从高优先级数据库获取数据 for _, db := range DBBackends { - return db.GetChatInfo(ctx, chatID) + data, err = db.GetChatInfo(ctx, chatID) + if err == nil { + return + } } for _, db := range DBBackends_LowLevel { - return db.GetChatInfo(ctx, chatID) + data, err = db.GetChatInfo(ctx, chatID) + if err == nil { + return + } + } + if err != nil { + return } return nil, fmt.Errorf("no database available") } @@ -130,12 +141,18 @@ func SetCustomFlag(ctx context.Context, chatID int64, fieldName db_struct.ChatIn func SaveDatabase(ctx context.Context) error { var allErr error for _, db := range DBBackends { + if db.SaveDatabase == nil { + continue + } err := db.SaveDatabase(ctx) if err != nil { allErr = err } } for _, db := range DBBackends_LowLevel { + if db.SaveDatabase == nil { + continue + } err := db.SaveDatabase(ctx) if err != nil { allErr = fmt.Errorf("%s, %s", allErr, err) @@ -147,12 +164,18 @@ func SaveDatabase(ctx context.Context) error { func ReadDatabase(ctx context.Context) error { var allErr error for _, db := range DBBackends { + if db.ReadDatabase == nil { + continue + } err := db.ReadDatabase(ctx) if err != nil { allErr = err } } for _, db := range DBBackends_LowLevel { + if db.ReadDatabase == nil { + continue + } err := db.ReadDatabase(ctx) if err != nil { allErr = fmt.Errorf("%s, %s", allErr, err) diff --git a/database/redis_db/redis.go b/database/redis_db/redis.go index bf15493..0fed5d8 100644 --- a/database/redis_db/redis.go +++ b/database/redis_db/redis.go @@ -166,12 +166,12 @@ func InitChat(ctx context.Context, chat *models.Chat) error { func IncrementalUsageCount(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_UsageCount) error { count, err := UserDB.HGet(ctx, strconv.FormatInt(chatID, 10), string(fieldName)).Int() if err == nil { - err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), fieldName, count + 1).Err() + err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), count + 1).Err() if err == nil { return nil } } else if err == redis.Nil { - err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), fieldName, 1).Err() + err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), 1).Err() if err == nil { log.Printf("[UserDB] Key %s not found, creating in Redis\n", fieldName) return nil @@ -182,7 +182,7 @@ func IncrementalUsageCount(ctx context.Context, chatID int64, fieldName db_struc } func RecordLatestData(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_LatestData, value string) error { - err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), fieldName, value).Err() + err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() if err == nil { return nil } @@ -191,7 +191,7 @@ func RecordLatestData(ctx context.Context, chatID int64, fieldName db_struct.Cha } func UpdateOperationStatus(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_Status, value bool) error { - err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), fieldName, value).Err() + err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() if err == nil { return nil } @@ -200,7 +200,7 @@ func UpdateOperationStatus(ctx context.Context, chatID int64, fieldName db_struc } func SetCustomFlag(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_CustomFlag, value string) error { - err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), fieldName, value).Err() + err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() if err == nil { return nil } diff --git a/handlers.go b/handlers.go index 5f6394e..b7872bc 100644 --- a/handlers.go +++ b/handlers.go @@ -15,6 +15,7 @@ import ( "trbot/utils/handler_structs" "trbot/utils/plugin_utils" "trbot/utils/type/message_utils" + "trbot/utils/type/update_utils" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" @@ -22,7 +23,7 @@ import ( ) func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) { - defer utils.PanicCatcher("defaultHandler") + defer utils.PanicCatcher(ctx, "defaultHandler") logger := zerolog.Ctx(ctx). With(). Str("funcName", "defaultHandler"). @@ -70,22 +71,20 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) messageHandler(&opts) } else if update.EditedMessage != nil { // 私聊或群组消息被编辑 - if consts.IsDebugMode { - if update.EditedMessage.Caption != "" { - logger.Debug(). - Dict(utils.GetUserDict(update.EditedMessage.From)). - Dict(utils.GetChatDict(&update.EditedMessage.Chat)). - Int("messageID", update.EditedMessage.ID). - Str("editedCaption", update.EditedMessage.Caption). - Msg("editedMessage") - } else { - logger.Debug(). - Dict(utils.GetUserDict(update.EditedMessage.From)). - Dict(utils.GetChatDict(&update.EditedMessage.Chat)). - Int("messageID", update.EditedMessage.ID). - Str("editedText", update.EditedMessage.Text). - Msg("editedMessage") - } + if update.EditedMessage.Caption != "" { + logger.Debug(). + Dict(utils.GetUserDict(update.EditedMessage.From)). + Dict(utils.GetChatDict(&update.EditedMessage.Chat)). + Int("messageID", update.EditedMessage.ID). + Str("editedCaption", update.EditedMessage.Caption). + Msg("editedMessage") + } else { + logger.Debug(). + Dict(utils.GetUserDict(update.EditedMessage.From)). + Dict(utils.GetChatDict(&update.EditedMessage.Chat)). + Int("messageID", update.EditedMessage.ID). + Str("editedText", update.EditedMessage.Text). + Msg("editedMessage") } } else if update.InlineQuery != nil { // inline 查询 @@ -125,97 +124,144 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) if len(update.MessageReaction.OldReaction) > 0 { for i, oldReaction := range update.MessageReaction.OldReaction { if oldReaction.ReactionTypeEmoji != nil { - log.Printf("%d remove emoji reaction %s from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], to message [%d]", - i + 1, oldReaction.ReactionTypeEmoji.Emoji, - utils.ShowUserName(update.MessageReaction.User), update.MessageReaction.User.Username, update.MessageReaction.User.ID, - utils.ShowChatName(&update.MessageReaction.Chat), update.MessageReaction.Chat.Username, update.MessageReaction.Chat.ID, - update.MessageReaction.MessageID, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("removedEmoji", oldReaction.ReactionTypeEmoji.Emoji). + Str("emojiType", string(oldReaction.ReactionTypeEmoji.Type)). + Int("count", i + 1). + Msg("removed emoji reaction") } else if oldReaction.ReactionTypeCustomEmoji != nil { - log.Printf("%d remove custom emoji reaction %s from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], to message [%d]", - i + 1, oldReaction.ReactionTypeCustomEmoji.CustomEmojiID, - utils.ShowUserName(update.MessageReaction.User), update.MessageReaction.User.Username, update.MessageReaction.User.ID, - utils.ShowChatName(&update.MessageReaction.Chat), update.MessageReaction.Chat.Username, update.MessageReaction.Chat.ID, - update.MessageReaction.MessageID, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("removedEmojiID", oldReaction.ReactionTypeCustomEmoji.CustomEmojiID). + Str("emojiType", string(oldReaction.ReactionTypeCustomEmoji.Type)). + Int("count", i + 1). + Msg("removed custom emoji reaction") } else if oldReaction.ReactionTypePaid != nil { - log.Printf("%d remove paid reaction from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], to message [%d]", - i + 1, - utils.ShowUserName(update.MessageReaction.User), update.MessageReaction.User.Username, update.MessageReaction.User.ID, - utils.ShowChatName(&update.MessageReaction.Chat), update.MessageReaction.Chat.Username, update.MessageReaction.Chat.ID, - update.MessageReaction.MessageID, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("emojiType", string(oldReaction.ReactionTypePaid.Type)). + Int("count", i + 1). + Msg("removed paid emoji reaction") } } } if len(update.MessageReaction.NewReaction) > 0 { for i, newReaction := range update.MessageReaction.NewReaction { if newReaction.ReactionTypeEmoji != nil { - log.Printf("%d emoji reaction %s from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], to message [%d]", - i + 1, newReaction.ReactionTypeEmoji.Emoji, - utils.ShowUserName(update.MessageReaction.User), update.MessageReaction.User.Username, update.MessageReaction.User.ID, - utils.ShowChatName(&update.MessageReaction.Chat), update.MessageReaction.Chat.Username, update.MessageReaction.Chat.ID, - update.MessageReaction.MessageID, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("addEmoji", newReaction.ReactionTypeEmoji.Emoji). + Str("emojiType", string(newReaction.ReactionTypeEmoji.Type)). + Int("count", i + 1). + Msg("add emoji reaction") } else if newReaction.ReactionTypeCustomEmoji != nil { - log.Printf("%d custom emoji reaction %s from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], to message [%d]", - i + 1, newReaction.ReactionTypeCustomEmoji.CustomEmojiID, - utils.ShowUserName(update.MessageReaction.User), update.MessageReaction.User.Username, update.MessageReaction.User.ID, - utils.ShowChatName(&update.MessageReaction.Chat), update.MessageReaction.Chat.Username, update.MessageReaction.Chat.ID, - update.MessageReaction.MessageID, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("addEmojiID", newReaction.ReactionTypeCustomEmoji.CustomEmojiID). + Str("emojiType", string(newReaction.ReactionTypeCustomEmoji.Type)). + Int("count", i + 1). + Msg("add custom emoji reaction") } else if newReaction.ReactionTypePaid != nil { - log.Printf("%d paid reaction from \"%s\"(%s)[%d] in \"%s\"(%s)[%d], to message [%d]", - i + 1, - utils.ShowUserName(update.MessageReaction.User), update.MessageReaction.User.Username, update.MessageReaction.User.ID, - utils.ShowChatName(&update.MessageReaction.Chat), update.MessageReaction.Chat.Username, update.MessageReaction.Chat.ID, - update.MessageReaction.MessageID, - ) + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("emojiType", string(newReaction.ReactionTypePaid.Type)). + Int("count", i + 1). + Msg("add paid emoji reaction") } } } } } else if update.MessageReactionCount != nil { // 频道消息表情回应数量 - log.Printf("reaction count from in \"%s\"(%s)[%d], to message [%d], reactions: %v", - utils.ShowChatName(&update.MessageReactionCount.Chat), update.MessageReactionCount.Chat.Username, update.MessageReactionCount.Chat.ID, - update.MessageReactionCount.MessageID, update.MessageReactionCount.Reactions, - ) + var emoji = zerolog.Dict() + var customEmoji = zerolog.Dict() + var paid = zerolog.Dict() + for _, n := range update.MessageReactionCount.Reactions { + switch n.Type.Type { + case models.ReactionTypeTypeEmoji: + emoji.Dict(n.Type.ReactionTypeEmoji.Emoji, zerolog.Dict(). + // Str("type", string(n.Type.ReactionTypeEmoji.Type)). + // Str("emoji", n.Type.ReactionTypeEmoji.Emoji). + Int("count", n.TotalCount), + ) + case models.ReactionTypeTypeCustomEmoji: + customEmoji.Dict(n.Type.ReactionTypeCustomEmoji.CustomEmojiID, zerolog.Dict(). + // Str("type", string(n.Type.ReactionTypeCustomEmoji.Type)). + // Str("customEmojiID", n.Type.ReactionTypeCustomEmoji.CustomEmojiID). + Int("count", n.TotalCount), + ) + case models.ReactionTypeTypePaid: + paid.Dict(n.Type.ReactionTypePaid.Type, zerolog.Dict(). + // Str("type", n.Type.ReactionTypePaid.Type). + Int("count", n.TotalCount), + ) + } + + } + + logger.Debug(). + Dict(utils.GetChatDict(&update.MessageReactionCount.Chat)). + Dict("reactions", zerolog.Dict(). + Dict("emoji", emoji). + Dict("customEmoji", customEmoji). + Dict("paid", paid), + ). + Int("messageID", update.MessageReactionCount.MessageID). + Msg("emoji reaction count updated") } else if update.ChannelPost != nil { // 频道信息 if consts.IsDebugMode { - if update.ChannelPost.From != nil { // 在频道中使用户身份发送 + if update.ChannelPost.From != nil { + // 在频道中使用户身份发送 log.Printf("channel post from user \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]", utils.ShowUserName(update.ChannelPost.From), update.ChannelPost.From.Username, update.ChannelPost.From.ID, utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID, update.ChannelPost.ID, update.ChannelPost.Text, ) - } else if update.ChannelPost.SenderBusinessBot != nil { // 在频道中由商业 bot 发送 + } else if update.ChannelPost.SenderBusinessBot != nil { + // 在频道中由商业 bot 发送 log.Printf("channel post from businessbot \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]", utils.ShowUserName(update.ChannelPost.SenderBusinessBot), update.ChannelPost.SenderBusinessBot.Username, update.ChannelPost.SenderBusinessBot.ID, utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID, update.ChannelPost.ID, update.ChannelPost.Text, ) - } else if update.ChannelPost.ViaBot != nil { // 在频道中由 bot 发送 + } else if update.ChannelPost.ViaBot != nil { + // 在频道中由 bot 发送 log.Printf("channel post from bot \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]", utils.ShowUserName(update.ChannelPost.ViaBot), update.ChannelPost.ViaBot.Username, update.ChannelPost.ViaBot.ID, utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID, update.ChannelPost.ID, update.ChannelPost.Text, ) - } else if update.ChannelPost.SenderChat != nil { // 在频道中使用其他频道身份发送 - if update.ChannelPost.SenderChat.ID == update.ChannelPost.Chat.ID { // 在频道中由频道自己发送 + } else if update.ChannelPost.SenderChat != nil { + if update.ChannelPost.SenderChat.ID == update.ChannelPost.Chat.ID { + // 在频道中由频道自己发送 log.Printf("channel post in \"%s\"(%s)[%d], (%d) message [%s]", utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID, update.ChannelPost.ID, update.ChannelPost.Text, ) } else { + // 在频道中使用其他频道身份发送 log.Printf("channel post from another channel \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]", utils.ShowChatName(update.ChannelPost.SenderChat), update.ChannelPost.SenderChat.Username, update.ChannelPost.SenderChat.ID, utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID, update.ChannelPost.ID, update.ChannelPost.Text, ) } - } else { // 没有身份信息 + } else { + // 没有身份信息 log.Printf("channel post from nobody in \"%s\"(%s)[%d], (%d) message [%s]", // utils.ShowUserName(update.ChannelPost.From), update.ChannelPost.From.Username, update.ChannelPost.From.ID, utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID, @@ -226,25 +272,32 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } } else if update.EditedChannelPost != nil { // 频道中编辑过的消息 - if consts.IsDebugMode { - log.Printf("edited channel post in \"%s\"(%s)[%d], message [%s]", - utils.ShowChatName(&update.EditedChannelPost.Chat), update.EditedChannelPost.Chat.Username, update.EditedChannelPost.Chat.ID, - update.EditedChannelPost.Text, - ) + if update.EditedChannelPost.Caption != "" { + logger.Debug(). + Dict(utils.GetUserDict(update.EditedChannelPost.From)). + Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). + Int("messageID", update.EditedChannelPost.ID). + Str("editedCaption", update.EditedChannelPost.Caption). + Msg("edited channel post caption") + } else { + logger.Debug(). + Dict(utils.GetUserDict(update.EditedChannelPost.From)). + Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). + Int("messageID", update.EditedChannelPost.ID). + Str("editedText", update.EditedChannelPost.Text). + Msg("edited channel post") } } else { // 其他没有加入的更新类型 - if consts.IsDebugMode { - log.Printf("unknown update type: %v", update) - // thebot.CopyMessage(ctx, &bot.CopyMessageParams{ - // }) - } + logger.Warn(). + Str("updateType", string(update_utils.GetUpdateType(update).InString())). + Msg("Receive a no tagged update type") } } // 处理所有信息请求的处理函数,触发条件为任何消息 func messageHandler(opts *handler_structs.SubHandlerParams) { - defer utils.PanicCatcher("messageHandler") + defer utils.PanicCatcher(opts.Ctx, "messageHandler") logger := zerolog.Ctx(opts.Ctx). With(). Str("funcName", "messageHandler"). @@ -561,7 +614,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { // 处理 inline 模式下的请求 func inlineHandler(opts *handler_structs.SubHandlerParams) { - defer utils.PanicCatcher("inlineHandler") + defer utils.PanicCatcher(opts.Ctx, "inlineHandler") logger := zerolog.Ctx(opts.Ctx). With(). Str("funcName", "inlineHandler"). @@ -586,25 +639,22 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { }, }) for _, plugin := range plugin_utils.AllPlugins.InlineCommandList { - if !IsAdmin && plugin.Attr.IsHideInCommandList { - continue - } + if !IsAdmin && plugin.Attr.IsHideInCommandList { continue } var command = &models.InlineQueryResultArticle{ - ID: "inlinemenu" + plugin.Command, + ID: "inlineMenu_" + plugin.Command, Title: plugin.Command, Description: plugin.Description, InputMessageContent: &models.InputTextMessageContent{ MessageText: "请不要点击选单中的命令...", }, } - if plugin.Attr.IsHideInCommandList { - command.Description = "隐藏 | " + command.Description - } - if plugin.Attr.IsOnlyAllowAdmin { - command.Description = "管理员 | " + command.Description - } + + if plugin.Attr.IsHideInCommandList { command.Description = "隐藏 | " + command.Description } + if plugin.Attr.IsOnlyAllowAdmin { command.Description = "管理员 | " + command.Description } + results = append(results, command) } + _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, Results: results, @@ -614,9 +664,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Msg("Send /setkeyword command answer failed") - log.Printf("Error sending inline query response: %v", err) + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Msg("Send `bot inline handler list` result failed") return } } else if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol) { @@ -624,11 +673,16 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { // 插件处理完后返回全部列表,由设定好的函数进行分页输出 for _, plugin := range plugin_utils.AllPlugins.InlineHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.Fields[0][1:] == plugin.Command { - if plugin.Handler == nil { continue } + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("handlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Msg("Hit inline handler, but this handler function is nil, skip") + continue + } ResultList := plugin.Handler(opts) _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -637,7 +691,12 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { CacheTime: 30, }) if err != nil { - log.Printf("Error when answering inline [%s] command: %v", plugin.Command, err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("handlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Msg("Send `inline handler` inline result failed") // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } return @@ -645,23 +704,47 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { } // 完全由插件控制输出,若回答请求时列表数量超过 50 项会出错,无法回应用户请求 for _, plugin := range plugin_utils.AllPlugins.InlineManualHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.Fields[0][1:] == plugin.Command { - if plugin.Handler == nil { continue } - plugin.Handler(opts) + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("handlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Msg("Hit inline handler, but this handler function is nil, skip") + continue + } + err := plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Msg("Error in inline handler") + } return } } // 符合命令前缀,完全由插件自行控制输出 for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol + plugin.PrefixCommand) { - if plugin.Handler == nil { continue } - plugin.Handler(opts) + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("handlerCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Msg("Hit inline handler, but this handler function is nil, skip") + continue + } + err := plugin.Handler(opts) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("handlerCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Msg("Error in inline handler") return } } @@ -680,20 +763,29 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { }}, }) if err != nil { - log.Println("Error when answering inline no command", err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Msg("Send `no this inline command` result failed") } return } else { + // inline query 不以命令符号开头 if opts.ChatInfo.DefaultInlinePlugin != "" { // 来自用户设定的默认命令 // 插件处理完后返回全部列表,由设定好的函数进行分页输出 for _, plugin := range plugin_utils.AllPlugins.InlineHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.Command { - if plugin.Handler == nil { continue } + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("userDefaultHandlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Msg("Hit user default inline handler, but this handler function is nil, skip") + continue + } ResultList := plugin.Handler(opts) _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -702,7 +794,12 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { CacheTime: 30, }) if err != nil { - log.Printf("Error when answering inline [%s] command: %v", plugin.Command, err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("userDefaultHandlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Msg("Send `user default inline handler` inline result failed") // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } return @@ -710,24 +807,48 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { } // 完全由插件控制输出,若回答请求时列表数量超过 50 项会出错,无法回应用户请求 for _, plugin := range plugin_utils.AllPlugins.InlineManualHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.Command { - if plugin.Handler == nil { continue } - plugin.Handler(opts) + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("userDefaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Msg("Hit user default inline handler, but this handler function is nil, skip") + continue + } + err := plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("userDefaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Msg("Error in user default inline handler") + } return } } // 符合命令前缀,完全由插件自行控制输出 for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.PrefixCommand { - if plugin.Handler == nil { continue } - plugin.Handler(opts) + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("userDefaultHandlerCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Msg("Hit user inline handler, but this handler function is nil, skip") + continue + } + err := plugin.Handler(opts) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("userDefaultHandlerCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Msg("Error in user inline handler") return } } @@ -750,7 +871,13 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { }, }) if err != nil { - log.Println("Error when answering inline default command invailid:", err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("query", opts.Update.InlineQuery.Query). + Str("userDefaultInlineCommand", opts.ChatInfo.DefaultInlinePlugin). + // Str("result", "invalid user default inline handler"). + Msg("Send `invalid user default inline handler` inline result failed") } return } else if configs.BotConfig.InlineDefaultHandler != "" { @@ -758,11 +885,16 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { // 插件处理完后返回全部列表,由设定好的函数进行分页输出 for _, plugin := range plugin_utils.AllPlugins.InlineHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if configs.BotConfig.InlineDefaultHandler == plugin.Command { - if plugin.Handler == nil { continue } + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Msg("Hit bot default inline handler, but this handler function is nil, skip") + continue + } ResultList := plugin.Handler(opts) _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -770,12 +902,17 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { IsPersonal: true, CacheTime: 30, Button: &models.InlineQueryResultsButton{ - Text: "输入 + 号显示菜单,或点击此处修改默认命令", + Text: fmt.Sprintf("输入 %s 号显示菜单,或点击此处修改默认命令", configs.BotConfig.InlineSubCommandSymbol), StartParameter: "via-inline_change-inline-command", }, }) if err != nil { - log.Printf("Error when answering inline [%s] command: %v", plugin.Command, err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Msg("Send `bot default inline handler` inline result failed") // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } return @@ -783,23 +920,49 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { } // 完全由插件控制输出,若回答请求时列表数量超过 50 项会出错,无法回应用户请求 for _, plugin := range plugin_utils.AllPlugins.InlineManualHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if configs.BotConfig.InlineDefaultHandler == plugin.Command { - if plugin.Handler == nil { continue } - plugin.Handler(opts) + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Msg("Hit bot default inline handler, but this handler function is nil, skip") + continue + } + err := plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Msg("Error in bot default inline handler") + } return } } // 符合命令前缀,完全由插件自行控制输出 for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { - if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { - continue - } + if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.PrefixCommand { - if plugin.Handler == nil { continue } - plugin.Handler(opts) + if plugin.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Msg("Hit bot default inline handler, but this handler function is nil, skip") + continue + } + err := plugin.Handler(opts) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("defaultHandlerCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Msg("Error in bot default inline handler") + } return } } @@ -828,7 +991,11 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { }, }) if err != nil { - log.Printf("Error sending inline query response: %v", err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("query", opts.Update.InlineQuery.Query). + Msg("Send `invalid bot default inline handler` inline result failed") return } return @@ -841,9 +1008,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { } var message string = "可用的 Inline 模式命令:\n\n" for _, command := range plugin_utils.AllPlugins.InlineCommandList { - if command.Attr.IsHideInCommandList { - continue - } + if command.Attr.IsHideInCommandList { continue } message += fmt.Sprintf("命令:
%s%s\n", configs.BotConfig.InlineSubCommandSymbol, command.Command)
if command.Description != "" {
message += fmt.Sprintf("描述: %s\n", command.Description)
@@ -865,18 +1030,22 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Button: inlineButton,
})
if err != nil {
- log.Printf("Error sending inline query no default handler: %v", err)
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
+ Str("query", opts.Update.InlineQuery.Query).
+ Msg("Send `bot no default inline handler` inline result failed")
return
}
}
}
func callbackQueryHandler(params *handler_structs.SubHandlerParams) {
- defer utils.PanicCatcher("callbackQueryHandler")
- logger := zerolog.Ctx(params.Ctx).
- With().
- Str("funcName", "callbackQueryHandler").
- Logger()
+ defer utils.PanicCatcher(params.Ctx, "callbackQueryHandler")
+ logger := zerolog.Ctx(params.Ctx).
+ With().
+ Str("funcName", "callbackQueryHandler").
+ Logger()
// 如果有一个正在处理的请求,且用户再次发送相同的请求,则提示用户等待
if params.ChatInfo.HasPendingCallbackQuery && params.Update.CallbackQuery.Data == params.ChatInfo.LatestCallbackQueryData {
@@ -935,7 +1104,7 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) {
if strings.HasPrefix(params.Update.CallbackQuery.Data, n.CommandChar) {
if n.Handler == nil {
logger.Debug().
- Dict(utils.GetUserDict(params.Update.Message.From)).
+ Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)).
Str("handlerPrefix", n.CommandChar).
Str("query", params.Update.CallbackQuery.Data).
Msg("tigger a callback query handler, but this handler function is nil, skip")
@@ -945,7 +1114,7 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) {
if err != nil {
logger.Error().
Err(err).
- Dict(utils.GetUserDict(params.Update.Message.From)).
+ Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)).
Str("handlerPrefix", n.CommandChar).
Str("query", params.Update.CallbackQuery.Data).
Msg("Error in callback query handler")
diff --git a/main.go b/main.go
index 8a57a69..18cad73 100644
--- a/main.go
+++ b/main.go
@@ -5,9 +5,11 @@ import (
"net/http"
"os"
"os/signal"
+ "runtime"
"time"
"trbot/database"
+ "trbot/utils"
"trbot/utils/configs"
"trbot/utils/consts"
"trbot/utils/internal_plugin"
@@ -27,6 +29,9 @@ func main() {
// log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
ctx = logger.WithContext(ctx)
+ logger.Info().
+ Str("version", runtime.Version()).
+ Msg("trbot")
// read configs
if err := configs.InitBot(ctx); err != nil {
@@ -45,27 +50,26 @@ func main() {
}
thebot, err := bot.New(configs.BotConfig.BotToken, opts...)
- if err != nil { logger.Panic().Err(err).Msg("Failed to initialize bot") }
+ if err != nil { logger.Fatal().Err(err).Msg("Failed to initialize bot") }
consts.BotMe, err = thebot.GetMe(ctx)
- if err != nil { logger.Panic().Err(err).Msg("Failed to get bot info") }
+ if err != nil { logger.Fatal().Err(err).Msg("Failed to get bot info") }
+
logger.Info().
- Str("name", consts.BotMe.FirstName).
- Str("username", consts.BotMe.Username).
- Int64("ID", consts.BotMe.ID).
- Msg("bot initialized")
+ Dict(utils.GetUserDict(consts.BotMe)).
+ Msg("Bot initialized")
if configs.BotConfig.LogChatID != 0 {
logger.Info().
Int64("LogChatID", configs.BotConfig.LogChatID).
Msg("Enabled log to chat")
}
- database.InitAndListDatabases()
+ database.InitAndListDatabases(ctx)
// start handler custom signals
go signals.SignalsHandler(ctx)
- // register plugin (internal first, then external)
+ // register plugin (plugin use `init()` first, then plugin user `InitPlugins` second, and internal last)
internal_plugin.Register(ctx)
// Select mode by Webhook config
@@ -80,9 +84,9 @@ func main() {
go thebot.StartWebhook(ctx)
err := http.ListenAndServe(consts.WebhookListenPort, thebot.WebhookHandler())
if err != nil {
- logger.Panic().
+ logger.Fatal().
Err(err).
- Msg("webhook server failed")
+ Msg("Webhook server failed")
}
} else { // getUpdate, aka Long Polling
// save and clean remove Webhook URL befor using getUpdate https://core.telegram.org/bots/api#getupdates
diff --git a/utils/configs/webhook.go b/utils/configs/webhook.go
index da4653f..0e2807f 100644
--- a/utils/configs/webhook.go
+++ b/utils/configs/webhook.go
@@ -10,7 +10,7 @@ import (
"github.com/rs/zerolog"
)
-// 通过是否设定环境变量和配置文件中的 webhook URL 来决定是否使用 webhook 模式
+// 通过是否设定环境变量和配置文件中的 Webhook URL 来决定是否使用 Webhook 模式
func IsUsingWebhook(ctx context.Context) bool {
logger := zerolog.Ctx(ctx)
// 通过 godotenv 库读取 .env 文件后再尝试读取
@@ -43,7 +43,7 @@ func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookPa
if err != nil {
logger.Error().
Err(err).
- Msg("Get WebHook info error")
+ Msg("Get Webhook info error")
}
if webHookInfo != nil && webHookInfo.URL != params.URL {
if webHookInfo.URL == "" {
@@ -53,13 +53,13 @@ func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookPa
logger.Warn().
Str("Remote URL", webHookInfo.URL).
Str("Local URL", params.URL).
- Msg("The remote webhook URL conflicts with the local one, saving and overwriting the remote URL")
+ Msg("The remote Webhook URL conflicts with the local one, saving and overwriting the remote URL")
}
success, err := thebot.SetWebhook(ctx, params)
if err != nil {
logger.Error().
Err(err).
- Msg("Set WebHook URL failed")
+ Msg("Set Webhook URL failed")
return false
}
if success {
@@ -84,12 +84,12 @@ func SaveAndCleanRemoteWebhookURL(ctx context.Context, thebot *bot.Bot) *models.
if err != nil {
logger.Error().
Err(err).
- Msg("Get WebHook info error")
+ Msg("Get Webhook info error")
}
if webHookInfo != nil && webHookInfo.URL != "" {
logger.Warn().
Str("Remote URL", webHookInfo.URL).
- Msg("There is a webhook URL remotely, saving and clearing it to use the getUpdate mode")
+ Msg("There is a Webhook URL remotely, saving and clearing it to use the getUpdate mode")
ok, err := thebot.DeleteWebhook(ctx, &bot.DeleteWebhookParams{
DropPendingUpdates: false,
})
diff --git a/utils/internal_plugin/handler.go b/utils/internal_plugin/handler.go
index 0bbd384..9408022 100644
--- a/utils/internal_plugin/handler.go
+++ b/utils/internal_plugin/handler.go
@@ -14,7 +14,7 @@ import (
func startHandler(params *handler_structs.SubHandlerParams) error {
- defer utils.PanicCatcher("startHandler")
+ defer utils.PanicCatcher(params.Ctx, "startHandler")
logger := zerolog.Ctx(params.Ctx).
With().
Str("funcName", "startHandler").
@@ -90,7 +90,7 @@ func startHandler(params *handler_structs.SubHandlerParams) error {
}
func helpHandler(params *handler_structs.SubHandlerParams) error {
- defer utils.PanicCatcher("helpHandler")
+ defer utils.PanicCatcher(params.Ctx, "helpHandler")
logger := zerolog.Ctx(params.Ctx).
With().
Str("funcName", "helpHandler").
diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go
index 50101db..21eed6e 100644
--- a/utils/internal_plugin/register.go
+++ b/utils/internal_plugin/register.go
@@ -23,7 +23,7 @@ import (
// this function run only once in main
func Register(ctx context.Context) {
- // 初始化 /plugins/ 中的插件
+ // 初始化 plugins/ 中的插件
plugins.InitPlugins()
plugin_utils.RunPluginInitializers(ctx)
@@ -230,6 +230,14 @@ func Register(ctx context.Context) {
Msg("Remove inline default command flag failed")
return err
}
+ // if chatinfo get from redis database, it won't be the newst data, need reload it from database
+ opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, opts.Update.CallbackQuery.From.ID)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Msg("Get chat info failed")
+ }
_, err = opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{
ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID,
MessageID: opts.Update.CallbackQuery.Message.Message.ID,
@@ -282,6 +290,14 @@ func Register(ctx context.Context) {
Msg("Change inline default command flag failed")
return err
}
+ // if chatinfo get from redis database, it won't be the newst data, need reload it from database
+ opts.ChatInfo, err = database.GetChatInfo(opts.Ctx, opts.Update.CallbackQuery.From.ID)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Msg("Get chat info failed")
+ }
_, err = opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{
ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID,
MessageID: opts.Update.CallbackQuery.Message.Message.ID,
diff --git a/utils/plugin_utils/plugin_initializer.go b/utils/plugin_utils/plugin_initializer.go
index 6147071..39497ac 100644
--- a/utils/plugin_utils/plugin_initializer.go
+++ b/utils/plugin_utils/plugin_initializer.go
@@ -24,10 +24,7 @@ func AddInitializer(initializers ...Initializer) int {
}
func RunPluginInitializers(ctx context.Context) {
- logger := zerolog.Ctx(ctx).
- With().
- Str("funcName", "RunPluginInitializers").
- Logger()
+ logger := zerolog.Ctx(ctx)
count := len(AllPlugins.Initializer)
successCount := 0
@@ -49,7 +46,7 @@ func RunPluginInitializers(ctx context.Context) {
} else {
logger.Info().
Str("pluginName", initializer.Name).
- Msg("Plugin initialized success")
+ Msg("Plugin initialize success")
successCount++
}
diff --git a/utils/utils.go b/utils/utils.go
index 0a06886..ab40f07 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -10,7 +10,6 @@ import (
"strings"
"trbot/utils/configs"
"trbot/utils/consts"
- "trbot/utils/mess"
"trbot/utils/type/message_utils"
"github.com/go-telegram/bot"
@@ -430,10 +429,18 @@ func getCurrentGoroutineStack() string {
return string(buf[:n])
}
-func PanicCatcher(pluginName string) {
+func PanicCatcher(ctx context.Context, pluginName string) {
+ logger := zerolog.Ctx(ctx)
+
panic := recover()
if panic != nil {
- mess.PrintLogAndSave(fmt.Sprintf("recovered panic in [%s]: \"%v\"\nStack: %s", pluginName, panic, getCurrentGoroutineStack()))
+ logger.Error().
+ Interface("panic", panic).
+ // Str("Stack", getCurrentGoroutineStack()).
+ Str("panicFunc", pluginName).
+ Msg("Panic recovered")
+ fmt.Println("Stack", getCurrentGoroutineStack())
+ // mess.PrintLogAndSave(fmt.Sprintf("recovered panic in [%s]: \"%v\"\nStack: %s", pluginName, panic, getCurrentGoroutineStack()))
}
}
@@ -448,7 +455,7 @@ func GetUserDict(user *models.User) (string, *zerolog.Event) {
Int64("ID", user.ID)
}
-// return a "chat" string and a `zerolog.Dict()` with `name`(string), `username`(string), `ID`(int64) *zerolog.Event
+// return a "chat" string and a `zerolog.Dict()` with `name`(string), `username`(string), `ID`(int64), `type`(string) *zerolog.Event
func GetChatDict(chat *models.Chat) (string, *zerolog.Event) {
if chat == nil {
return "chat", zerolog.Dict()
@@ -456,5 +463,6 @@ func GetChatDict(chat *models.Chat) (string, *zerolog.Event) {
return "chat", zerolog.Dict().
Str("name", ShowChatName(chat)).
Str("username", chat.Username).
+ Str("type", string(chat.Type)).
Int64("ID", chat.ID)
}
--
2.49.1
From bddb042f905da126be35970ce77185196f8e50f9 Mon Sep 17 00:00:00 2001
From: Hubert Chen <01@trle5.xyz>
Date: Fri, 6 Jun 2025 01:17:42 +0800
Subject: [PATCH 05/27] save changes
add a `IsFromBusinessBot` flag to `message_attribute` package
add `GetUserOrSenderChatDict()` func in some case to replace `GetUserDict()` func
---
download_database.sh | 2 -
handlers.go | 86 +++++++------------
utils/type/message_utils/message_attribute.go | 4 +
utils/utils.go | 49 +++++++++++
4 files changed, 86 insertions(+), 55 deletions(-)
delete mode 100644 download_database.sh
diff --git a/download_database.sh b/download_database.sh
deleted file mode 100644
index 1f73a7e..0000000
--- a/download_database.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-scp root@server:~/trbot/db_yaml/udonese/metadata.yaml db_yaml/udonese/metadata.yaml
-scp root@server:~/trbot/db_yaml/metadata.yaml db_yaml/metadata.yaml
diff --git a/handlers.go b/handlers.go
index b7872bc..b21103c 100644
--- a/handlers.go
+++ b/handlers.go
@@ -3,7 +3,6 @@ package main
import (
"context"
"fmt"
- "log"
"strings"
"time"
@@ -24,10 +23,11 @@ import (
func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) {
defer utils.PanicCatcher(ctx, "defaultHandler")
- logger := zerolog.Ctx(ctx).
- With().
- Str("funcName", "defaultHandler").
- Logger()
+ logger := zerolog.Ctx(ctx)
+ // logger := zerolog.Ctx(ctx).
+ // With().
+ // Str("funcName", "defaultHandler").
+ // Logger()
// var err error
var opts = handler_structs.SubHandlerParams{
@@ -44,14 +44,14 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
if consts.IsDebugMode {
if update.Message.Photo != nil {
logger.Debug().
- Dict(utils.GetUserDict(update.Message.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.Message)).
Dict(utils.GetChatDict(&update.Message.Chat)).
Int("messageID", update.Message.ID).
Str("caption", update.Message.Caption).
Msg("photoMessage")
} else if update.Message.Sticker != nil {
logger.Debug().
- Dict(utils.GetUserDict(update.Message.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.Message)).
Dict(utils.GetChatDict(&update.Message.Chat)).
Int("messageID", update.Message.ID).
Str("stickerEmoji", update.Message.Sticker.Emoji).
@@ -60,7 +60,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
Msg("stickerMessage")
} else {
logger.Debug().
- Dict(utils.GetUserDict(update.Message.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.Message)).
Dict(utils.GetChatDict(&update.Message.Chat)).
Int("messageID", update.Message.ID).
Str("text", update.Message.Text).
@@ -73,14 +73,14 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
// 私聊或群组消息被编辑
if update.EditedMessage.Caption != "" {
logger.Debug().
- Dict(utils.GetUserDict(update.EditedMessage.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)).
Dict(utils.GetChatDict(&update.EditedMessage.Chat)).
Int("messageID", update.EditedMessage.ID).
Str("editedCaption", update.EditedMessage.Caption).
Msg("editedMessage")
} else {
logger.Debug().
- Dict(utils.GetUserDict(update.EditedMessage.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)).
Dict(utils.GetChatDict(&update.EditedMessage.Chat)).
Int("messageID", update.EditedMessage.ID).
Str("editedText", update.EditedMessage.Text).
@@ -224,49 +224,29 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
} else if update.ChannelPost != nil {
// 频道信息
if consts.IsDebugMode {
- if update.ChannelPost.From != nil {
- // 在频道中使用户身份发送
- log.Printf("channel post from user \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]",
- utils.ShowUserName(update.ChannelPost.From), update.ChannelPost.From.Username, update.ChannelPost.From.ID,
- utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID,
- update.ChannelPost.ID, update.ChannelPost.Text,
- )
- } else if update.ChannelPost.SenderBusinessBot != nil {
- // 在频道中由商业 bot 发送
- log.Printf("channel post from businessbot \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]",
- utils.ShowUserName(update.ChannelPost.SenderBusinessBot), update.ChannelPost.SenderBusinessBot.Username, update.ChannelPost.SenderBusinessBot.ID,
- utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID,
- update.ChannelPost.ID, update.ChannelPost.Text,
- )
- } else if update.ChannelPost.ViaBot != nil {
+ logger.Debug().
+ Dict(utils.GetUserOrSenderChatDict(update.ChannelPost)).
+ Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
+ Str("text", update.ChannelPost.Text).
+ Int("messageID", update.ChannelPost.ID).
+ Msg("channel post")
+ if update.ChannelPost.ViaBot != nil {
// 在频道中由 bot 发送
- log.Printf("channel post from bot \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]",
- utils.ShowUserName(update.ChannelPost.ViaBot), update.ChannelPost.ViaBot.Username, update.ChannelPost.ViaBot.ID,
- utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID,
- update.ChannelPost.ID, update.ChannelPost.Text,
- )
- } else if update.ChannelPost.SenderChat != nil {
- if update.ChannelPost.SenderChat.ID == update.ChannelPost.Chat.ID {
- // 在频道中由频道自己发送
- log.Printf("channel post in \"%s\"(%s)[%d], (%d) message [%s]",
- utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID,
- update.ChannelPost.ID, update.ChannelPost.Text,
- )
- } else {
- // 在频道中使用其他频道身份发送
- log.Printf("channel post from another channel \"%s\"(%s)[%d], in \"%s\"(%s)[%d], (%d) message [%s]",
- utils.ShowChatName(update.ChannelPost.SenderChat), update.ChannelPost.SenderChat.Username, update.ChannelPost.SenderChat.ID,
- utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID,
- update.ChannelPost.ID, update.ChannelPost.Text,
- )
- }
- } else {
+ _, viaBot := utils.GetUserDict(update.ChannelPost.ViaBot)
+ logger.Debug().
+ Dict("viaBot", viaBot).
+ Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
+ Str("text", update.ChannelPost.Text).
+ Int("messageID", update.ChannelPost.ID).
+ Msg("channel post send via bot")
+ }
+ if update.ChannelPost.SenderChat == nil {
// 没有身份信息
- log.Printf("channel post from nobody in \"%s\"(%s)[%d], (%d) message [%s]",
- // utils.ShowUserName(update.ChannelPost.From), update.ChannelPost.From.Username, update.ChannelPost.From.ID,
- utils.ShowChatName(&update.ChannelPost.Chat), update.ChannelPost.Chat.Username, update.ChannelPost.Chat.ID,
- update.ChannelPost.ID, update.ChannelPost.Text,
- )
+ logger.Debug().
+ Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
+ Str("text", update.ChannelPost.Text).
+ Int("messageID", update.ChannelPost.ID).
+ Msg("channel post from nobody")
}
return
}
@@ -274,14 +254,14 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
// 频道中编辑过的消息
if update.EditedChannelPost.Caption != "" {
logger.Debug().
- Dict(utils.GetUserDict(update.EditedChannelPost.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)).
Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)).
Int("messageID", update.EditedChannelPost.ID).
Str("editedCaption", update.EditedChannelPost.Caption).
Msg("edited channel post caption")
} else {
logger.Debug().
- Dict(utils.GetUserDict(update.EditedChannelPost.From)).
+ Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)).
Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)).
Int("messageID", update.EditedChannelPost.ID).
Str("editedText", update.EditedChannelPost.Text).
diff --git a/utils/type/message_utils/message_attribute.go b/utils/type/message_utils/message_attribute.go
index a48ea83..15080d6 100644
--- a/utils/type/message_utils/message_attribute.go
+++ b/utils/type/message_utils/message_attribute.go
@@ -8,6 +8,7 @@ type MessageAttribute struct {
IsFromLinkedChannel bool `yaml:"IsFromLinkedChannel,omitempty"` // is automatic forward post from linked channel
IsUserAsChannel bool `yaml:"IsUserAsChannel,omitempty"` // user selected to send message as a channel
IsHasSenderChat bool `yaml:"IsHasSenderChat,omitempty"` // sender of the message when sent on behalf of a chat, eg current group/supergroup or linked channel
+ IsFromBusinessBot bool `yaml:"IsFromBusinessBot,omitempty"` // the bot that actually sent the message on behalf of the business account
IsChatEnableForum bool `yaml:"IsChatEnableForum,omitempty"` // group or supergroup is enable topic
IsForwardMessage bool `yaml:"IsForwardMessage,omitempty"` // not a origin message, forward from somewhere
IsTopicMessage bool `yaml:"IsTopicMessage,omitempty"` // the message is sent to a forum topic
@@ -49,6 +50,9 @@ func GetMessageAttribute(msg *models.Message) MessageAttribute {
}
}
}
+ if msg.SenderBusinessBot != nil {
+ attribute.IsFromBusinessBot = true
+ }
if msg.Chat.IsForum {
attribute.IsChatEnableForum = true
}
diff --git a/utils/utils.go b/utils/utils.go
index ab40f07..fb95931 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -466,3 +466,52 @@ func GetChatDict(chat *models.Chat) (string, *zerolog.Event) {
Str("type", string(chat.Type)).
Int64("ID", chat.ID)
}
+
+// Can replace GetUserDict(), not for GetChatDict(), and not available for some update type.
+// return a sender type string and a `zerolog.Dict()` to show sender info
+func GetUserOrSenderChatDict(userOrSenderChat *models.Message) (string, *zerolog.Event) {
+ if userOrSenderChat == nil {
+ return "noMessage", zerolog.Dict().Str("error", "no message to check")
+ }
+
+ if userOrSenderChat.From != nil {
+ return "user", zerolog.Dict().
+ Str("name", ShowUserName(userOrSenderChat.From)).
+ Str("username", userOrSenderChat.From.Username).
+ Int64("ID", userOrSenderChat.From.ID)
+ }
+
+ attr := message_utils.GetMessageAttribute(userOrSenderChat)
+
+ if userOrSenderChat.SenderChat != nil {
+ if attr.IsFromAnonymous {
+ return "groupAnonymous", zerolog.Dict().
+ Str("chat", ShowChatName(userOrSenderChat.SenderChat)).
+ Str("username", userOrSenderChat.SenderChat.Username).
+ Int64("ID", userOrSenderChat.SenderChat.ID)
+ } else if attr.IsUserAsChannel {
+ return "userAsChannel", zerolog.Dict().
+ Str("chat", ShowChatName(userOrSenderChat.SenderChat)).
+ Str("username", userOrSenderChat.SenderChat.Username).
+ Int64("ID", userOrSenderChat.SenderChat.ID)
+ } else if attr.IsFromLinkedChannel {
+ return "linkedChannel", zerolog.Dict().
+ Str("chat", ShowChatName(userOrSenderChat.SenderChat)).
+ Str("username", userOrSenderChat.SenderChat.Username).
+ Int64("ID", userOrSenderChat.SenderChat.ID)
+ } else if attr.IsFromBusinessBot {
+ return "businessBot", zerolog.Dict().
+ Str("name", ShowUserName(userOrSenderChat.SenderBusinessBot)).
+ Str("username", userOrSenderChat.SenderBusinessBot.Username).
+ Int64("ID", userOrSenderChat.SenderBusinessBot.ID)
+ } else if attr.IsHasSenderChat && userOrSenderChat.SenderChat.ID != userOrSenderChat.Chat.ID {
+ // use other channel send message in this channel
+ return "senderChat", zerolog.Dict().
+ Str("chat", ShowChatName(userOrSenderChat.SenderChat)).
+ Str("username", userOrSenderChat.SenderChat.Username).
+ Int64("ID", userOrSenderChat.SenderChat.ID)
+ }
+ }
+
+ return "noUserOrSender", zerolog.Dict().Str("error", "no user or sender chat")
+}
--
2.49.1
From 3f72534a99761d37bf970c17eb9a6ccce17f5646 Mon Sep 17 00:00:00 2001
From: Hubert Chen <01@trle5.xyz>
Date: Sun, 8 Jun 2025 03:19:25 +0800
Subject: [PATCH 06/27] save changes
make error log use `Failed to` first
add full log to `plugin_sticker`
---
bad_plugins/plugin_teamspeak3.go | 2 +-
bad_plugins/saved_message/functions.go | 2 +-
handlers.go | 244 +++---
{bad_plugins => plugins}/plugin_sticker.go | 823 ++++++++++++------
utils/internal_plugin/handler.go | 8 +-
utils/internal_plugin/register.go | 60 +-
utils/plugin_utils/handler_by_message_type.go | 4 +-
utils/utils.go | 2 +-
8 files changed, 705 insertions(+), 440 deletions(-)
rename {bad_plugins => plugins}/plugin_sticker.go (51%)
diff --git a/bad_plugins/plugin_teamspeak3.go b/bad_plugins/plugin_teamspeak3.go
index 947869b..52163cf 100644
--- a/bad_plugins/plugin_teamspeak3.go
+++ b/bad_plugins/plugin_teamspeak3.go
@@ -229,7 +229,7 @@ func showStatus(opts *handler_structs.SubHandlerParams) {
pendingMessage += "\n"
}
if userCount == 0 {
- pendingMessage += "当前无用户在线"
+ pendingMessage = "当前无用户在线"
}
}
} else {
diff --git a/bad_plugins/saved_message/functions.go b/bad_plugins/saved_message/functions.go
index 899fb54..724e84f 100644
--- a/bad_plugins/saved_message/functions.go
+++ b/bad_plugins/saved_message/functions.go
@@ -769,7 +769,7 @@ func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) {
Str("username", opts.Update.Message.From.Username).
Int64("ID", opts.Update.Message.From.ID),
).
- Msg("Send `saved message function enabled` message failed")
+ Msg("Failed to send `saved message function enabled` message")
}
}
diff --git a/handlers.go b/handlers.go
index b21103c..a6fd94d 100644
--- a/handlers.go
+++ b/handlers.go
@@ -41,31 +41,29 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
// 需要重写来配合 handler by update type
if update.Message != nil {
// 正常消息
- if consts.IsDebugMode {
- if update.Message.Photo != nil {
- logger.Debug().
- Dict(utils.GetUserOrSenderChatDict(update.Message)).
- Dict(utils.GetChatDict(&update.Message.Chat)).
- Int("messageID", update.Message.ID).
- Str("caption", update.Message.Caption).
- Msg("photoMessage")
- } else if update.Message.Sticker != nil {
- logger.Debug().
- Dict(utils.GetUserOrSenderChatDict(update.Message)).
- Dict(utils.GetChatDict(&update.Message.Chat)).
- Int("messageID", update.Message.ID).
- Str("stickerEmoji", update.Message.Sticker.Emoji).
- Str("stickerSetname", update.Message.Sticker.SetName).
- Str("stickerFileID", update.Message.Sticker.FileID).
- Msg("stickerMessage")
- } else {
- logger.Debug().
- Dict(utils.GetUserOrSenderChatDict(update.Message)).
- Dict(utils.GetChatDict(&update.Message.Chat)).
- Int("messageID", update.Message.ID).
- Str("text", update.Message.Text).
- Msg("textMessage")
- }
+ if update.Message.Photo != nil {
+ logger.Debug().
+ Dict(utils.GetUserOrSenderChatDict(update.Message)).
+ Dict(utils.GetChatDict(&update.Message.Chat)).
+ Int("messageID", update.Message.ID).
+ Str("caption", update.Message.Caption).
+ Msg("photoMessage")
+ } else if update.Message.Sticker != nil {
+ logger.Debug().
+ Dict(utils.GetUserOrSenderChatDict(update.Message)).
+ Dict(utils.GetChatDict(&update.Message.Chat)).
+ Int("messageID", update.Message.ID).
+ Str("stickerEmoji", update.Message.Sticker.Emoji).
+ Str("stickerSetname", update.Message.Sticker.SetName).
+ Str("stickerFileID", update.Message.Sticker.FileID).
+ Msg("stickerMessage")
+ } else {
+ logger.Debug().
+ Dict(utils.GetUserOrSenderChatDict(update.Message)).
+ Dict(utils.GetChatDict(&update.Message.Chat)).
+ Int("messageID", update.Message.ID).
+ Str("text", update.Message.Text).
+ Msg("textMessage")
}
messageHandler(&opts)
@@ -114,73 +112,69 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
callbackQueryHandler(&opts)
-
-
opts.ChatInfo.HasPendingCallbackQuery = false
return
} else if update.MessageReaction != nil {
// 私聊或群组表情回应
- if consts.IsDebugMode {
- if len(update.MessageReaction.OldReaction) > 0 {
- for i, oldReaction := range update.MessageReaction.OldReaction {
- if oldReaction.ReactionTypeEmoji != nil {
- logger.Debug().
- Dict(utils.GetUserDict(update.MessageReaction.User)).
- Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
- Int("messageID", update.MessageReaction.MessageID).
- Str("removedEmoji", oldReaction.ReactionTypeEmoji.Emoji).
- Str("emojiType", string(oldReaction.ReactionTypeEmoji.Type)).
- Int("count", i + 1).
- Msg("removed emoji reaction")
- } else if oldReaction.ReactionTypeCustomEmoji != nil {
- logger.Debug().
- Dict(utils.GetUserDict(update.MessageReaction.User)).
- Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
- Int("messageID", update.MessageReaction.MessageID).
- Str("removedEmojiID", oldReaction.ReactionTypeCustomEmoji.CustomEmojiID).
- Str("emojiType", string(oldReaction.ReactionTypeCustomEmoji.Type)).
- Int("count", i + 1).
- Msg("removed custom emoji reaction")
- } else if oldReaction.ReactionTypePaid != nil {
- logger.Debug().
- Dict(utils.GetUserDict(update.MessageReaction.User)).
- Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
- Int("messageID", update.MessageReaction.MessageID).
- Str("emojiType", string(oldReaction.ReactionTypePaid.Type)).
- Int("count", i + 1).
- Msg("removed paid emoji reaction")
- }
+ if len(update.MessageReaction.OldReaction) > 0 {
+ for i, oldReaction := range update.MessageReaction.OldReaction {
+ if oldReaction.ReactionTypeEmoji != nil {
+ logger.Debug().
+ Dict(utils.GetUserDict(update.MessageReaction.User)).
+ Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
+ Int("messageID", update.MessageReaction.MessageID).
+ Str("removedEmoji", oldReaction.ReactionTypeEmoji.Emoji).
+ Str("emojiType", string(oldReaction.ReactionTypeEmoji.Type)).
+ Int("count", i + 1).
+ Msg("removed emoji reaction")
+ } else if oldReaction.ReactionTypeCustomEmoji != nil {
+ logger.Debug().
+ Dict(utils.GetUserDict(update.MessageReaction.User)).
+ Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
+ Int("messageID", update.MessageReaction.MessageID).
+ Str("removedEmojiID", oldReaction.ReactionTypeCustomEmoji.CustomEmojiID).
+ Str("emojiType", string(oldReaction.ReactionTypeCustomEmoji.Type)).
+ Int("count", i + 1).
+ Msg("removed custom emoji reaction")
+ } else if oldReaction.ReactionTypePaid != nil {
+ logger.Debug().
+ Dict(utils.GetUserDict(update.MessageReaction.User)).
+ Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
+ Int("messageID", update.MessageReaction.MessageID).
+ Str("emojiType", string(oldReaction.ReactionTypePaid.Type)).
+ Int("count", i + 1).
+ Msg("removed paid emoji reaction")
}
}
- if len(update.MessageReaction.NewReaction) > 0 {
- for i, newReaction := range update.MessageReaction.NewReaction {
- if newReaction.ReactionTypeEmoji != nil {
- logger.Debug().
- Dict(utils.GetUserDict(update.MessageReaction.User)).
- Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
- Int("messageID", update.MessageReaction.MessageID).
- Str("addEmoji", newReaction.ReactionTypeEmoji.Emoji).
- Str("emojiType", string(newReaction.ReactionTypeEmoji.Type)).
- Int("count", i + 1).
- Msg("add emoji reaction")
- } else if newReaction.ReactionTypeCustomEmoji != nil {
- logger.Debug().
- Dict(utils.GetUserDict(update.MessageReaction.User)).
- Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
- Int("messageID", update.MessageReaction.MessageID).
- Str("addEmojiID", newReaction.ReactionTypeCustomEmoji.CustomEmojiID).
- Str("emojiType", string(newReaction.ReactionTypeCustomEmoji.Type)).
- Int("count", i + 1).
- Msg("add custom emoji reaction")
- } else if newReaction.ReactionTypePaid != nil {
- logger.Debug().
- Dict(utils.GetUserDict(update.MessageReaction.User)).
- Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
- Int("messageID", update.MessageReaction.MessageID).
- Str("emojiType", string(newReaction.ReactionTypePaid.Type)).
- Int("count", i + 1).
- Msg("add paid emoji reaction")
- }
+ }
+ if len(update.MessageReaction.NewReaction) > 0 {
+ for i, newReaction := range update.MessageReaction.NewReaction {
+ if newReaction.ReactionTypeEmoji != nil {
+ logger.Debug().
+ Dict(utils.GetUserDict(update.MessageReaction.User)).
+ Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
+ Int("messageID", update.MessageReaction.MessageID).
+ Str("addEmoji", newReaction.ReactionTypeEmoji.Emoji).
+ Str("emojiType", string(newReaction.ReactionTypeEmoji.Type)).
+ Int("count", i + 1).
+ Msg("add emoji reaction")
+ } else if newReaction.ReactionTypeCustomEmoji != nil {
+ logger.Debug().
+ Dict(utils.GetUserDict(update.MessageReaction.User)).
+ Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
+ Int("messageID", update.MessageReaction.MessageID).
+ Str("addEmojiID", newReaction.ReactionTypeCustomEmoji.CustomEmojiID).
+ Str("emojiType", string(newReaction.ReactionTypeCustomEmoji.Type)).
+ Int("count", i + 1).
+ Msg("add custom emoji reaction")
+ } else if newReaction.ReactionTypePaid != nil {
+ logger.Debug().
+ Dict(utils.GetUserDict(update.MessageReaction.User)).
+ Dict(utils.GetChatDict(&update.MessageReaction.Chat)).
+ Int("messageID", update.MessageReaction.MessageID).
+ Str("emojiType", string(newReaction.ReactionTypePaid.Type)).
+ Int("count", i + 1).
+ Msg("add paid emoji reaction")
}
}
}
@@ -223,32 +217,29 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update)
Msg("emoji reaction count updated")
} else if update.ChannelPost != nil {
// 频道信息
- if consts.IsDebugMode {
+ logger.Debug().
+ Dict(utils.GetUserOrSenderChatDict(update.ChannelPost)).
+ Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
+ Str("text", update.ChannelPost.Text).
+ Int("messageID", update.ChannelPost.ID).
+ Msg("channel post")
+ if update.ChannelPost.ViaBot != nil {
+ // 在频道中由 bot 发送
+ _, viaBot := utils.GetUserDict(update.ChannelPost.ViaBot)
logger.Debug().
- Dict(utils.GetUserOrSenderChatDict(update.ChannelPost)).
+ Dict("viaBot", viaBot).
Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
Str("text", update.ChannelPost.Text).
Int("messageID", update.ChannelPost.ID).
- Msg("channel post")
- if update.ChannelPost.ViaBot != nil {
- // 在频道中由 bot 发送
- _, viaBot := utils.GetUserDict(update.ChannelPost.ViaBot)
- logger.Debug().
- Dict("viaBot", viaBot).
- Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
- Str("text", update.ChannelPost.Text).
- Int("messageID", update.ChannelPost.ID).
- Msg("channel post send via bot")
- }
- if update.ChannelPost.SenderChat == nil {
- // 没有身份信息
- logger.Debug().
- Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
- Str("text", update.ChannelPost.Text).
- Int("messageID", update.ChannelPost.ID).
- Msg("channel post from nobody")
- }
- return
+ Msg("channel post send via bot")
+ }
+ if update.ChannelPost.SenderChat == nil {
+ // 没有身份信息
+ logger.Debug().
+ Dict(utils.GetChatDict(&update.ChannelPost.Chat)).
+ Str("text", update.ChannelPost.Text).
+ Int("messageID", update.ChannelPost.ID).
+ Msg("channel post from nobody")
}
} else if update.EditedChannelPost != nil {
// 频道中编辑过的消息
@@ -339,7 +330,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetUserDict(opts.Update.Message.From)).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Str("message", opts.Update.Message.Text).
- Msg("Send `no this command` message failed")
+ Msg("Failed to send `no this command` message")
}
err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand)
if err != nil {
@@ -367,7 +358,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetUserDict(opts.Update.Message.From)).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Str("message", opts.Update.Message.Text).
- Msg("Send `no this command` message failed")
+ Msg("Failed to send `no this command` message")
}
err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand)
if err != nil {
@@ -392,7 +383,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetUserDict(opts.Update.Message.From)).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Str("message", opts.Update.Message.Text).
- Msg("Delete `no this command` message failed")
+ Msg("Failed to delete `no this command` message")
}
return
}
@@ -523,7 +514,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Str("messageType", string(msgTypeInString)).
Str("chatType", string(opts.Update.Message.Chat.Type)).
Int("handlerInThisTypeCount", handlerInThisTypeCount).
- Msg("Send `select a handler by message type keyboard` message failed")
+ Msg("Failed to send `select a handler by message type keyboard` message")
}
}
}
@@ -543,7 +534,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Str("messageType", string(msgTypeInString)).
Str("chatType", string(opts.Update.Message.Chat.Type)).
Int("handlerInThisTypeCount", handlerInThisTypeCount).
- Msg("Send `select a handler by message type keyboard` message failed")
+ Msg("Failed to send `select a handler by message type keyboard` message")
}
}
} else if opts.Update.Message.Chat.Type == models.ChatTypePrivate {
@@ -561,7 +552,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Str("messageType", string(msgTypeInString)).
Str("chatType", string(opts.Update.Message.Chat.Type)).
- Msg("Send `no handler by message type plugin for this message type` message failed")
+ Msg("Failed to send `no handler by message type plugin for this message type` message")
}
}
}
@@ -645,8 +636,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
logger.Error().
Err(err).
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
- Msg("Send `bot inline handler list` result failed")
- return
+ Msg("Failed to send `bot inline handler list` inline result")
}
} else if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol) {
// 用户输入了分页符号和一些字符,判断接着的命令是否正确,正确则交给对应的插件处理,否则提示错误
@@ -676,7 +666,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
Str("handlerCommand", plugin.Command).
Str("handlerType", "returnResult").
- Msg("Send `inline handler` inline result failed")
+ Msg("Failed to send `inline handler` inline result")
// 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次
}
return
@@ -719,12 +709,14 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
continue
}
err := plugin.Handler(opts)
- logger.Error().
+ if err != nil {
+ logger.Error().
Err(err).
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
Str("handlerCommand", plugin.PrefixCommand).
Str("handlerType", "manuallyAnswerResult_PrefixCommand").
Msg("Error in inline handler")
+ }
return
}
}
@@ -746,7 +738,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
logger.Error().
Err(err).
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
- Msg("Send `no this inline command` result failed")
+ Msg("Failed to send `no this inline command` inline result")
}
return
} else {
@@ -779,7 +771,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
Str("userDefaultHandlerCommand", plugin.Command).
Str("handlerType", "returnResult").
- Msg("Send `user default inline handler` inline result failed")
+ Msg("Failed to send `user default inline handler result` inline result")
// 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次
}
return
@@ -857,7 +849,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Str("query", opts.Update.InlineQuery.Query).
Str("userDefaultInlineCommand", opts.ChatInfo.DefaultInlinePlugin).
// Str("result", "invalid user default inline handler").
- Msg("Send `invalid user default inline handler` inline result failed")
+ Msg("Failed to send `invalid user default inline handler` inline result")
}
return
} else if configs.BotConfig.InlineDefaultHandler != "" {
@@ -892,7 +884,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
Str("defaultHandlerCommand", plugin.Command).
Str("handlerType", "returnResult").
- Msg("Send `bot default inline handler` inline result failed")
+ Msg("Failed to send `bot default inline handler result` inline result")
// 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次
}
return
@@ -975,7 +967,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Err(err).
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
Str("query", opts.Update.InlineQuery.Query).
- Msg("Send `invalid bot default inline handler` inline result failed")
+ Msg("Failed to send `invalid bot default inline handler` inline result")
return
}
return
@@ -1014,7 +1006,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) {
Err(err).
Dict(utils.GetUserDict(opts.Update.InlineQuery.From)).
Str("query", opts.Update.InlineQuery.Query).
- Msg("Send `bot no default inline handler` inline result failed")
+ Msg("Failed to send `bot no default inline handler` inline result")
return
}
}
@@ -1042,7 +1034,7 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) {
logger.Error().
Err(err).
Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)).
- Msg("Send `this callback request is processing` callback answer failed")
+ Msg("Failed to send `this callback request is processing` callback answer")
}
return
} else if params.ChatInfo.HasPendingCallbackQuery {
@@ -1061,7 +1053,7 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) {
logger.Error().
Err(err).
Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)).
- Msg("Send `a callback request is processing, send new request later` callback answer failed")
+ Msg("Failed to send `a callback request is processing, send new request later` callback answer")
}
return
} else {
diff --git a/bad_plugins/plugin_sticker.go b/plugins/plugin_sticker.go
similarity index 51%
rename from bad_plugins/plugin_sticker.go
rename to plugins/plugin_sticker.go
index b58ac75..239ecb2 100644
--- a/bad_plugins/plugin_sticker.go
+++ b/plugins/plugin_sticker.go
@@ -12,6 +12,7 @@ import (
"strings"
"trbot/database"
"trbot/database/db_struct"
+ "trbot/utils"
"trbot/utils/configs"
"trbot/utils/consts"
"trbot/utils/handler_structs"
@@ -47,11 +48,11 @@ func init() {
ParseMode: models.ParseModeHTML,
})
plugin_utils.AddHandlerByMessageTypePlugins(plugin_utils.HandlerByMessageType{
- PluginName: "StickerDownload",
- ChatType: models.ChatTypePrivate,
- MessageType: message_utils.Sticker,
+ PluginName: "StickerDownload",
+ ChatType: models.ChatTypePrivate,
+ MessageType: message_utils.Sticker,
AllowAutoTrigger: true,
- Handler: EchoStickerHandler,
+ Handler: EchoStickerHandler,
})
}
@@ -64,15 +65,115 @@ type stickerDatas struct {
StickerSetTitle string // 贴纸包名称
}
+func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error {
+ logger := zerolog.Ctx(opts.Ctx).
+ With().
+ Str("pluginName", "StickerDownload").
+ Str("funcName", "EchoStickerHandler").
+ Logger()
+
+ if opts.Update.Message == nil && opts.Update.CallbackQuery != nil && strings.HasPrefix(opts.Update.CallbackQuery.Data, "HBMT_") && opts.Update.CallbackQuery.Message.Message != nil && opts.Update.CallbackQuery.Message.Message.ReplyToMessage != nil {
+ // if this handler tigger by `handler by message type`, copy `update.CallbackQuery.Message.Message.ReplyToMessage` to `update.Message`
+ opts.Update.Message = opts.Update.CallbackQuery.Message.Message.ReplyToMessage
+ logger.Debug().
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Msg("copy `update.CallbackQuery.Message.Message.ReplyToMessage` to `update.Message`")
+ }
+
+ logger.Debug().
+ Str("emoji", opts.Update.Message.Sticker.Emoji).
+ Str("setName", opts.Update.Message.Sticker.SetName).
+ Msg("start download sticker")
+
+ err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.From.ID, db_struct.StickerDownloaded)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Msg("Incremental sticker download count error")
+ }
+
+ stickerData, err := EchoSticker(opts)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Msg("Failed to download sticker")
+
+ _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
+ ChatID: opts.Update.Message.From.ID,
+ Text: fmt.Sprintf("下载贴纸时发生了一些错误\nError downloading sticker: %s", err), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to send `sticker download error` message") + } + return err + } + + documentParams := &bot.SendDocumentParams{ + ChatID: opts.Update.Message.From.ID, + ParseMode: models.ParseModeHTML, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + DisableNotification: true, + } + + var stickerFilePrefix, stickerFileSuffix string + + if opts.Update.Message.Sticker.IsVideo { + documentParams.Caption = "
see wikipedia/WebM" + stickerFileSuffix = "webm" + } else if opts.Update.Message.Sticker.IsAnimated { + documentParams.Caption = "
see stickers/animated-stickers" + stickerFileSuffix = "tgs.file" + } else { + stickerFileSuffix = "png" + } + + if stickerData.IsCustomSticker { + stickerFilePrefix = "sticker" + } else { + stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) + + // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 + documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) + documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ + { + { Text: "下载贴纸包中的静态贴纸", CallbackData: fmt.Sprintf("S_%s", opts.Update.Message.Sticker.SetName) }, + }, + { + { Text: "下载整个贴纸包(不转换格式)", CallbackData: fmt.Sprintf("s_%s", opts.Update.Message.Sticker.SetName) }, + }, + }} + } + + documentParams.Document = &models.InputFileUpload{ Filename: fmt.Sprintf("%s.%s", stickerFilePrefix, stickerFileSuffix), Data: stickerData.Data } + + _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to send sticker png file to user") + return err + } + + return nil +} + func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) { - var data stickerDatas logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "StickerDownload"). Str("funcName", "EchoSticker"). Logger() + var data stickerDatas var fileSuffix string // `webp`, `webm`, `tgs` + // 根据贴纸类型设置文件扩展名 if opts.Update.Message.Sticker.IsVideo { fileSuffix = "webm" @@ -90,6 +191,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) // 获取贴纸包信息 stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{ Name: opts.Update.Message.Sticker.SetName }) if err != nil { + // this sticker has a setname, but that sticker set has been deleted + // it may also be because of an error in obtaining sticker there are no static stickers in the sticker set information, so just let the user try again. logger.Warn(). Err(err). Str("setName", opts.Update.Message.Sticker.SetName). @@ -100,6 +203,7 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) stickerSetNamePrivate = opts.Update.Message.Sticker.SetName stickerFileNameWithDot = fmt.Sprintf("%s %d %s.", opts.Update.Message.Sticker.SetName, -1, opts.Update.Message.Sticker.FileID) } else { + // sticker is in a sticker set data.StickerCount = len(stickerSet.Stickers) data.StickerSetName = stickerSet.Name data.StickerSetTitle = stickerSet.Title @@ -117,6 +221,7 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) stickerFileNameWithDot = fmt.Sprintf("%s %d %s.", opts.Update.Message.Sticker.SetName, data.StickerIndex, opts.Update.Message.Sticker.FileID) } } else { + // this sticker doesn't have a setname, so it is a custom sticker // 自定义贴纸,防止与普通贴纸包冲突,将贴纸包名设置为 `-custom`,文件名仅有 FileID 用于辨识 data.IsCustomSticker = true stickerSetNamePrivate = "-custom" @@ -126,39 +231,81 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) var filePath string = StickerCache_path + stickerSetNamePrivate + "/" // 保存贴纸源文件的目录 .cache/sticker/setName/ var originFullPath string = filePath + stickerFileNameWithDot + fileSuffix // 到贴纸文件的完整目录 .cache/sticker/setName/stickerFileName.webp - var filePathPNG string = StickerCachePNG_path + stickerSetNamePrivate + "/" // 转码后为 png 格式的目录 .cache/sticker_png/setName/ - var toPNGFullPath string = filePathPNG + stickerFileNameWithDot + "png" // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png + var PNGFilePath string = StickerCachePNG_path + stickerSetNamePrivate + "/" // 转码后为 png 格式的目录 .cache/sticker_png/setName/ + var toPNGFullPath string = PNGFilePath + stickerFileNameWithDot + "png" // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png _, err := os.Stat(originFullPath) // 检查贴纸源文件是否已缓存 - // 如果文件不存在,进行下载,否则跳过 - if os.IsNotExist(err) { - // 从服务器获取文件信息 - fileinfo, err := opts.Thebot.GetFile(opts.Ctx, &bot.GetFileParams{ FileID: opts.Update.Message.Sticker.FileID }) - if err != nil { return nil, fmt.Errorf("error getting fileinfo %s: %v", opts.Update.Message.Sticker.FileID, err) } + if err != nil { + // 如果文件不存在,进行下载,否则返回错误 + if os.IsNotExist(err) { + // 日志提示该文件没被缓存,正在下载 + logger.Trace(). + Str("originFullPath", originFullPath). + Msg("sticker file not cached, downloading") - // 日志提示该文件没被缓存,正在下载 - if consts.IsDebugMode { log.Printf("file [%s] doesn't exist, downloading %s", originFullPath, fileinfo.FilePath) } + // 从服务器获取文件信息 + fileinfo, err := opts.Thebot.GetFile(opts.Ctx, &bot.GetFileParams{ FileID: opts.Update.Message.Sticker.FileID }) + if err != nil { + logger.Error(). + Err(err). + Str("fileID", opts.Update.Message.Sticker.FileID). + Msg("error getting sticker file info") + return nil, fmt.Errorf("error getting sticker fileinfo %s: %v", opts.Update.Message.Sticker.FileID, err) + } - // 组合链接下载贴纸源文件 - resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", configs.BotConfig.BotToken, fileinfo.FilePath)) - if err != nil { return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) } - defer resp.Body.Close() + // 组合链接下载贴纸源文件 + resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", configs.BotConfig.BotToken, fileinfo.FilePath)) + if err != nil { + logger.Error(). + Err(err). + Str("filePath", fileinfo.FilePath). + Msg("error downloading sticker file") + return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) + } + defer resp.Body.Close() - // 创建保存贴纸的目录 - err = os.MkdirAll(filePath, 0755) - if err != nil { return nil, fmt.Errorf("error creating directory %s: %w", filePath, err) } + // 创建保存贴纸的目录 + err = os.MkdirAll(filePath, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("filePath", filePath). + Msg("error creating directory") + return nil, fmt.Errorf("error creating directory %s: %w", filePath, err) + } - // 创建贴纸空文件 - downloadedSticker, err := os.Create(originFullPath) - if err != nil { return nil, fmt.Errorf("error creating file %s: %w", originFullPath, err) } - defer downloadedSticker.Close() + // 创建贴纸空文件 + downloadedSticker, err := os.Create(originFullPath) + if err != nil { + logger.Error(). + Err(err). + Str("originFullPath", originFullPath). + Msg("error creating file") + return nil, fmt.Errorf("error creating file %s: %w", originFullPath, err) + } + defer downloadedSticker.Close() - // 将下载的原贴纸写入空文件 - _, err = io.Copy(downloadedSticker, resp.Body) - if err != nil { return nil, fmt.Errorf("error writing to file %s: %w", originFullPath, err) } - } else if consts.IsDebugMode { + // 将下载的原贴纸写入空文件 + _, err = io.Copy(downloadedSticker, resp.Body) + if err != nil { + logger.Error(). + Err(err). + Str("originFullPath", originFullPath). + Msg("error writing sticker data to file") + return nil, fmt.Errorf("error writing to file %s: %w", originFullPath, err) + } + } else { + logger.Error(). + Err(err). + Str("originFullPath", originFullPath). + Msg("error when reading cached file info") + return nil, fmt.Errorf("error when reading cached file info: %w", err) + } + } else { // 文件已存在,跳过下载 - log.Printf("file %s already exists", originFullPath) + logger.Trace(). + Str("originFullPath", originFullPath). + Msg("sticker file already cached") } var finalFullPath string // 存放最后读取并发送的文件完整目录 .cache/sticker/setName/stickerFileName.webp @@ -166,21 +313,46 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) // 如果贴纸类型不是视频和矢量,进行转换 if !opts.Update.Message.Sticker.IsVideo && !opts.Update.Message.Sticker.IsAnimated { _, err = os.Stat(toPNGFullPath) // 使用目录提前检查一下是否已经转换过 - // 如果提示不存在,进行转换 - if os.IsNotExist(err) { - // 日志提示该文件没转换,正在转换 - if consts.IsDebugMode { log.Printf("file [%s] does not exist, converting", toPNGFullPath) } + if err != nil { + // 如果提示不存在,进行转换 + if os.IsNotExist(err) { + // 日志提示该文件没转换,正在转换 + logger.Trace(). + Str("toPNGFullPath", toPNGFullPath). + Msg("file does not convert, converting") - // 创建保存贴纸的目录 - err = os.MkdirAll(filePathPNG, 0755) - if err != nil { return nil, fmt.Errorf("error creating directory %s: %w", filePathPNG, err) } + // 创建保存贴纸的目录 + err = os.MkdirAll(PNGFilePath, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("PNGFilePath", PNGFilePath). + Msg("error creating directory to convert file") + return nil, fmt.Errorf("error creating directory to convert file %s: %w", PNGFilePath, err) + } - // 读取原贴纸文件,转码后存储到 png 格式贴纸的完整目录 - err = ConvertWebPToPNG(originFullPath, toPNGFullPath) - if err != nil { return nil, fmt.Errorf("error converting webp to png %s: %w", originFullPath, err) } - } else if consts.IsDebugMode { + // 读取原贴纸文件,转码后存储到 png 格式贴纸的完整目录 + err = convertWebPToPNG(originFullPath, toPNGFullPath) + if err != nil { + logger.Error(). + Err(err). + Str("originFullPath", originFullPath). + Msg("error converting webp to png") + return nil, fmt.Errorf("error converting webp to png %s: %w", originFullPath, err) + } + } else { + // 其他错误 + logger.Error(). + Err(err). + Str("toPNGFullPath", toPNGFullPath). + Msg("error when reading converted file info") + return nil, fmt.Errorf("error when reading converted file info: %w", err) + } + } else { // 文件存在,跳过转换 - log.Printf("file [%s] already converted", toPNGFullPath) + logger.Trace(). + Str("toPNGFullPath", toPNGFullPath). + Msg("file already converted") } // 处理完成,将最后要读取的目录设为转码后 png 格式贴纸的完整目录 finalFullPath = toPNGFullPath @@ -191,22 +363,156 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) // 逻辑完成,读取最后的文件,返回给上一级函数 data.Data, err = os.Open(finalFullPath) - if err != nil { return nil, fmt.Errorf("error opening file %s: %w", finalFullPath, err) } - - if data.IsCustomSticker { log.Printf("sticker [%s] is downloaded", finalFullPath) } + if err != nil { + logger.Error(). + Err(err). + Str("finalFullPath", finalFullPath). + Msg("error opening sticker file") + } return &data, nil } + +func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "StickerDownload"). + Str("funcName", "DownloadStickerPackCallBackHandler"). + Logger() + + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + Text: "已请求下载,请稍候", + ParseMode: models.ParseModeMarkdownV1, + DisableNotification: true, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to send `start download stickerset` message") + } + + err = database.IncrementalUsageCount(opts.Ctx, opts.Update.CallbackQuery.Message.Message.Chat.ID, db_struct.StickerSetDownloaded) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Incremental sticker set download count error") + } + + var packName string + var isOnlyPNG bool + if opts.Update.CallbackQuery.Data[0:2] == "S_" { + packName = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "S_") + isOnlyPNG = true + } else { + packName = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "s_") + isOnlyPNG = false + } + + // 通过贴纸的 packName 获取贴纸集 + stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{ Name: packName }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to get sticker set info") + + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + Text: fmt.Sprintf("获取贴纸包时发生了一些错误\n
Failed to get sticker set info: %s", err), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to send `get sticker set info error` message") + } + return err + } + + stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to download sticker set") + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + Text: fmt.Sprintf("下载贴纸包时发生了一些错误\n
Failed to download sticker set: %s", err), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to send `download sticker set error` message") + } + return err + } + + documentParams := &bot.SendDocumentParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + ParseMode: models.ParseModeMarkdownV1, + } + + if isOnlyPNG { + documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸(仅转换后的 PNG 格式)", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) + documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d)_png.zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} + } else { + documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) + documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d).zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} + } + + _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to send sticker set zip file to user") + } + + _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: botMessage.ID, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to delete `start download stickerset` message") + } + + return nil +} + func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.StickerSet, isOnlyPNG bool) (*stickerDatas, error) { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "StickerDownload"). + Str("funcName", "getStickerPack"). + Logger() + var data stickerDatas = stickerDatas{ IsCustomSticker: false, StickerSetName: stickerSet.Name, StickerSetTitle: stickerSet.Title, } + logger.Debug(). + Dict("stickerSet", zerolog.Dict(). + Str("title", data.StickerSetTitle). + Str("name", data.StickerSetName). + Int("allCount", len(stickerSet.Stickers)), + ). + Msg("start download sticker set") + filePath := StickerCache_path + stickerSet.Name + "/" - filePathPNG := StickerCachePNG_path + stickerSet.Name + "/" + PNGFilePath := StickerCachePNG_path + stickerSet.Name + "/" var allCached bool = true var allConverted bool = true @@ -232,61 +538,133 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S } var originFullPath string = filePath + stickerfileName + fileSuffix - var toPNGFullPath string = filePathPNG + stickerfileName + "png" + var toPNGFullPath string = PNGFilePath + stickerfileName + "png" _, err := os.Stat(originFullPath) // 检查单个贴纸是否已缓存 - if os.IsNotExist(err) { - allCached = false + if err != nil { + if os.IsNotExist(err) { + allCached = false + logger.Trace(). + Str("originFullPath", originFullPath). + Str("stickerSetName", data.StickerSetName). + Int("stickerIndex", i). + Msg("sticker file not cached, downloading") - // 从服务器获取文件内容 - fileinfo, err := opts.Thebot.GetFile(opts.Ctx, &bot.GetFileParams{ FileID: sticker.FileID }) - if err != nil { return nil, fmt.Errorf("error getting file info %s: %v", sticker.FileID, err) } + // 从服务器获取文件内容 + fileinfo, err := opts.Thebot.GetFile(opts.Ctx, &bot.GetFileParams{ FileID: sticker.FileID }) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("fileID", opts.Update.Message.Sticker.FileID). + Msg("error getting sticker file info") + return nil, fmt.Errorf("error getting file info %s: %v", sticker.FileID, err) + } - if consts.IsDebugMode { - log.Printf("file [%s] does not exist, downloading %s", originFullPath, fileinfo.FilePath) + // 下载贴纸文件 + resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", configs.BotConfig.BotToken, fileinfo.FilePath)) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("filePath", fileinfo.FilePath). + Msg("error downloading sticker file") + return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) + } + defer resp.Body.Close() + + err = os.MkdirAll(filePath, 0755) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("filePath", filePath). + Msg("error creating directory") + return nil, fmt.Errorf("error creating directory %s: %w", filePath, err) + } + + // 创建文件并保存 + downloadedSticker, err := os.Create(originFullPath) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("originFullPath", originFullPath). + Msg("error creating file") + return nil, fmt.Errorf("error creating file %s: %w", originFullPath, err) + } + defer downloadedSticker.Close() + + // 将下载的内容写入文件 + _, err = io.Copy(downloadedSticker, resp.Body) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("originFullPath", originFullPath). + Msg("error writing sticker data to file") + + return nil, fmt.Errorf("error writing to file %s: %w", originFullPath, err) + } + } else { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("originFullPath", originFullPath). + Msg("error when reading cached file info") + return nil, fmt.Errorf("error when reading cached file info: %w", err) } - - // 下载贴纸文件 - resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", configs.BotConfig.BotToken, fileinfo.FilePath)) - if err != nil { return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) } - defer resp.Body.Close() - - err = os.MkdirAll(filePath, 0755) - if err != nil { return nil, fmt.Errorf("error creating directory %s: %w", filePath, err) } - - // 创建文件并保存 - downloadedSticker, err := os.Create(originFullPath) - if err != nil { return nil, fmt.Errorf("error creating file %s: %w", originFullPath, err) } - defer downloadedSticker.Close() - - // 将下载的内容写入文件 - _, err = io.Copy(downloadedSticker, resp.Body) - if err != nil { return nil, fmt.Errorf("error writing to file %s: %w", originFullPath, err) } - } else if consts.IsDebugMode { + } else { // 存在跳过下载过程 - log.Printf("file [%s] already exists", originFullPath) + logger.Trace(). + Int("stickerIndex", i). + Str("originFullPath", originFullPath). + Msg("sticker file already exists") } // 仅需要 PNG 格式时进行转换 if isOnlyPNG && !sticker.IsVideo && !sticker.IsAnimated { _, err = os.Stat(toPNGFullPath) - if os.IsNotExist(err) { - allConverted = false - if consts.IsDebugMode { - log.Printf("file [%s] does not exist, converting", toPNGFullPath) + if err != nil { + if os.IsNotExist(err) { + allConverted = false + logger.Trace(). + Int("stickerIndex", i). + Str("toPNGFullPath", toPNGFullPath). + Msg("file does not convert, converting") + // 创建保存贴纸的目录 + err = os.MkdirAll(PNGFilePath, 0755) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("PNGFilePath", PNGFilePath). + Msg("error creating directory to convert file") + return nil, fmt.Errorf("error creating directory %s: %w", PNGFilePath, err) + } + // 将 webp 转换为 png + err = convertWebPToPNG(originFullPath, toPNGFullPath) + if err != nil { + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("originFullPath", originFullPath). + Msg("error converting webp to png") + return nil, fmt.Errorf("error converting webp to png %s: %w", originFullPath, err) + } + } else { + // 其他错误 + logger.Error(). + Err(err). + Int("stickerIndex", i). + Str("toPNGFullPath", toPNGFullPath). + Msg("error when reading converted file info") + return nil, fmt.Errorf("error when reading converted file info: %w", err) } - // 创建保存贴纸的目录 - err = os.MkdirAll(filePathPNG, 0755) - if err != nil { - return nil, fmt.Errorf("error creating directory %s: %w", filePathPNG, err) - } - // 将 webp 转换为 png - err = ConvertWebPToPNG(originFullPath, toPNGFullPath) - if err != nil { - return nil, fmt.Errorf("error converting webp to png %s: %w", originFullPath, err) - } - } else if consts.IsDebugMode { - log.Printf("file [%s] already converted", toPNGFullPath) + } else { + logger.Trace(). + Str("toPNGFullPath", toPNGFullPath). + Msg("file already converted") } } } @@ -299,11 +677,19 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S // 根据要下载的类型设置压缩包的文件名和路径以及压缩包中的贴纸数量 if isOnlyPNG { if stickerCount_webp == 0 { - return nil, fmt.Errorf("there are no static stickers in the sticker pack") + logger.Warn(). + Dict("stickerSet", zerolog.Dict(). + Str("stickerSetName", stickerSet.Name). + Int("WebP", stickerCount_webp). + Int("tgs", stickerCount_tgs). + Int("WebM", stickerCount_webm), + ). + Msg("there are no static stickers in the sticker set") + return nil, fmt.Errorf("there are no static stickers in the sticker set") } data.StickerCount = stickerCount_webp zipFileName = fmt.Sprintf("%s(%d)_png.zip", stickerSet.Name, data.StickerCount) - compressFolderPath = filePathPNG + compressFolderPath = PNGFilePath } else { data.StickerCount = stickerCount_webp + stickerCount_webm + stickerCount_tgs zipFileName = fmt.Sprintf("%s(%d).zip", stickerSet.Name, data.StickerCount) @@ -311,42 +697,94 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S } _, err := os.Stat(StickerCacheZip_path + zipFileName) // 检查压缩包文件是否存在 - if os.IsNotExist(err) { - isZiped = false - err = os.MkdirAll(StickerCacheZip_path, 0755) - if err != nil { - return nil, fmt.Errorf("error creating directory %s: %w", StickerCacheZip_path, err) + if err != nil { + if os.IsNotExist(err) { + isZiped = false + err = os.MkdirAll(StickerCacheZip_path, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("zipFilePath", StickerCacheZip_path). + Msg("error creating zip file directory") + return nil, fmt.Errorf("error creating zip file directory %s: %w", StickerCacheZip_path, err) + } + err = zipFolder(compressFolderPath, StickerCacheZip_path + zipFileName) + if err != nil { + logger.Error(). + Err(err). + Str("zipFilePath", StickerCacheZip_path + zipFileName). + Msg("error zipping sticker folder") + return nil, fmt.Errorf("error zipping folder %s: %w", compressFolderPath, err) + } + logger.Trace(). + Str("zipFilePath", StickerCacheZip_path + zipFileName). + Msg("successfully zipped folder") + } else { + logger.Error(). + Err(err). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Msg("error when reading sticker set zip file info") + return nil, fmt.Errorf("error when reading sticker set zip file info: %w", err) } - err = zipFolder(compressFolderPath, StickerCacheZip_path + zipFileName) - if err != nil { - return nil, fmt.Errorf("error zipping folder %s: %w", compressFolderPath, err) - } else if consts.IsDebugMode { - log.Println("successfully zipped folder", StickerCacheZip_path + zipFileName) - } - } else if consts.IsDebugMode { - log.Println("zip file already exists", StickerCacheZip_path + zipFileName) + } else { + logger.Trace(). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Msg("sticker set zip file already cached") } // 读取压缩后的贴纸包 data.Data, err = os.Open(StickerCacheZip_path + zipFileName) if err != nil { + logger.Error(). + Err(err). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Msg("error opening zip file") return nil, fmt.Errorf("error opening zip file %s: %w", StickerCacheZip_path + zipFileName, err) } if isZiped { // 存在已经完成压缩的贴纸包 - log.Printf("sticker pack \"%s\"[%s](%d) is already zipped", stickerSet.Title, stickerSet.Name, data.StickerCount) + logger.Info(). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Dict("stickerSet", zerolog.Dict(). + Str("title", data.StickerSetTitle). + Str("name", data.StickerSetName). + Int("count", data.StickerCount), + ). + Msg("sticker set already zipped") } else if isOnlyPNG && allConverted { // 仅需要 PNG 格式,且贴纸包完全转换成 PNG 格式,但尚未压缩 - log.Printf("sticker pack \"%s\"[%s](%d) is already converted", stickerSet.Title, stickerSet.Name, data.StickerCount) + logger.Info(). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Dict("stickerSet", zerolog.Dict(). + Str("title", data.StickerSetTitle). + Str("name", data.StickerSetName). + Int("count", data.StickerCount), + ). + Msg("sticker set already converted") } else if allCached { // 贴纸包中的贴纸已经全部缓存了 - log.Printf("sticker pack \"%s\"[%s](%d) is already cached", stickerSet.Title, stickerSet.Name, data.StickerCount) + logger.Info(). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Dict("stickerSet", zerolog.Dict(). + Str("title", data.StickerSetTitle). + Str("name", data.StickerSetName). + Int("count", data.StickerCount), + ). + Msg("sticker set already cached") + log.Printf("sticker set \"%s\"[%s](%d) is already cached", stickerSet.Title, stickerSet.Name, data.StickerCount) } else { // 新下载的贴纸包(如果有部分已经下载了也是这个) - log.Printf("sticker pack \"%s\"[%s](%d) is downloaded", stickerSet.Title, stickerSet.Name, data.StickerCount) + logger.Info(). + Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Dict("stickerSet", zerolog.Dict(). + Str("title", data.StickerSetTitle). + Str("name", data.StickerSetName). + Int("count", data.StickerCount), + ). + Msg("sticker set already downloaded") } return &data, nil } -func ConvertWebPToPNG(webpPath, pngPath string) error { +func convertWebPToPNG(webpPath, pngPath string) error { // 打开 WebP 文件 webpFile, err := os.Open(webpPath) if err != nil { @@ -425,170 +863,3 @@ func zipFolder(srcDir, zipFile string) error { return err } - -func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { - if opts.Update.Message == nil && opts.Update.CallbackQuery != nil && strings.HasPrefix(opts.Update.CallbackQuery.Data, "HBMT_") && opts.Update.CallbackQuery.Message.Message != nil && opts.Update.CallbackQuery.Message.Message.ReplyToMessage != nil { - opts.Update.Message = opts.Update.CallbackQuery.Message.Message.ReplyToMessage - } - - // 下载 webp 格式的贴纸 - if consts.IsDebugMode { - fmt.Println(opts.Update.Message.Sticker) - } - - err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.StickerDownloaded) - if err != nil { - - } - - stickerData, err := EchoSticker(opts) - if err != nil { - _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: fmt.Sprintf("下载贴纸时发生了一些错误\n
Error downloading sticker: %s", err), - ParseMode: models.ParseModeHTML, - }) - if err != nil { - - } - } - - if stickerData == nil || stickerData.Data == nil { - _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "未能获取到贴纸", - ParseMode: models.ParseModeMarkdownV1, - }) - if err != nil { - - } - return err - } - - documentParams := &bot.SendDocumentParams{ - ChatID: opts.Update.Message.Chat.ID, - ParseMode: models.ParseModeHTML, - ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, - } - - var stickerFilePrefix, stickerFileSuffix string - - if opts.Update.Message.Sticker.IsVideo { - documentParams.Caption = "
see wikipedia/WebM" - stickerFileSuffix = "webm" - } else if opts.Update.Message.Sticker.IsAnimated { - documentParams.Caption = "
see stickers/animated-stickers" - stickerFileSuffix = "tgs.file" - } else { - stickerFileSuffix = "png" - } - - if stickerData.IsCustomSticker { - stickerFilePrefix = "sticker" - } else { - stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) - // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 - documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) - documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{ - { - {Text: "下载贴纸包中的静态贴纸", CallbackData: fmt.Sprintf("S_%s", opts.Update.Message.Sticker.SetName)}, - }, - { - {Text: "下载整个贴纸包(不转换格式)", CallbackData: fmt.Sprintf("s_%s", opts.Update.Message.Sticker.SetName)}, - }, - }} - } - - documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s.%s", stickerFilePrefix, stickerFileSuffix), Data: stickerData.Data} - - _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) - if err != nil { - return err - } - - return nil -} - -func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) error { - botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - Text: "已请求下载,请稍候", - ParseMode: models.ParseModeMarkdownV1, - }) - if err != nil { - return err - } - - err = database.IncrementalUsageCount(opts.Ctx, opts.Update.CallbackQuery.Message.Message.Chat.ID, db_struct.StickerSetDownloaded) - if err != nil { - return err - } - - var packName string - var isOnlyPNG bool - if opts.Update.CallbackQuery.Data[0:2] == "S_" { - packName = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "S_") - isOnlyPNG = true - } else { - packName = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "s_") - isOnlyPNG = false - } - - // 通过贴纸的 packName 获取贴纸集 - stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{ Name: packName }) - if err != nil { - log.Printf("error getting sticker set: %v", err) - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.CallbackQuery.From.ID, - Text: fmt.Sprintf("获取贴纸包时发生了一些错误\n
Error getting sticker set: %s", err), - ParseMode: models.ParseModeHTML, - }) - return err - } - - stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) - if err != nil { - log.Println("Error downloading sticker:", err) - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.CallbackQuery.From.ID, - Text: fmt.Sprintf("下载贴纸包时发生了一些错误\n
Error download sticker set: %s", err), - ParseMode: models.ParseModeHTML, - }) - } - if stickerData == nil || stickerData.Data == nil { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.CallbackQuery.From.ID, - Text: "未能获取到压缩包", - ParseMode: models.ParseModeMarkdownV1, - }) - return err - } - - documentParams := &bot.SendDocumentParams{ - ChatID: opts.Update.CallbackQuery.From.ID, - ParseMode: models.ParseModeMarkdownV1, - } - - if isOnlyPNG { - documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸(仅转换后的 PNG 格式)", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) - documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d)_png.zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} - } else { - documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) - documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d).zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} - } - - _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) - if err != nil { - return err - } - - _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: botMessage.ID, - }) - if err != nil { - return err - } - - return nil -} diff --git a/utils/internal_plugin/handler.go b/utils/internal_plugin/handler.go index 9408022..b42aa6f 100644 --- a/utils/internal_plugin/handler.go +++ b/utils/internal_plugin/handler.go @@ -83,7 +83,7 @@ func startHandler(params *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). Dict(utils.GetUserDict(params.Update.Message.From)). - Msg("Send `bot welcome` message error") + Msg("Failed to send `bot welcome` message") } return err @@ -109,7 +109,7 @@ func helpHandler(params *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). Dict(utils.GetUserDict(params.Update.Message.From)). - Msg("Send `bot help keyboard` message error") + Msg("Failed to send `bot help keyboard` message") } return err } @@ -130,7 +130,7 @@ func helpCallbackHandler(params *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). Dict(utils.GetChatDict(¶ms.Update.CallbackQuery.Message.Message.Chat)). - Msg("Delete `bot help keyboard` message failed") + Msg("Failed to delete `bot help keyboard` message") } return err } else if strings.HasPrefix(params.Update.CallbackQuery.Data, "help-handler_") { @@ -182,7 +182,7 @@ func helpCallbackHandler(params *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Send `help page is not exist` answer failed") + Msg("Failed to send `help page is not exist` callback answer") } } diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go index 21eed6e..3d1dd44 100644 --- a/utils/internal_plugin/register.go +++ b/utils/internal_plugin/register.go @@ -142,7 +142,7 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Str("command", "/version"). - Msg("Send `bot version info` message failed") + Msg("Failed to send `bot version info` message") return err } time.Sleep(time.Second * 20) @@ -157,7 +157,7 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Str("command", "/version"). - Msg("Delete command message and `bot version info` message failed") + Msg("Failed to delete `command message and bot version info` message") } if !success { // 如果不能把用户的消息也删了,就单独删 bot 的消息 @@ -169,7 +169,7 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Str("command", "/version"). - Msg("Delete `bot version info` message failed") + Msg("Failed to delete `bot version info` message") } } @@ -200,7 +200,7 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Msg("Send `select inline default command keyboard` message failed") + Msg("Failed to send `select inline default command keyboard` message") } return err }, @@ -272,7 +272,7 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Send `inline command changed` callback answer failed") + Msg("Failed to send `inline command changed` callback answer") return err } break @@ -344,7 +344,7 @@ func Register(ctx context.Context) { InlineQueryID: opts.Update.InlineQuery.ID, Results: []models.InlineQueryResult{ &models.InlineQueryResultArticle{ - ID: "custom voices", + ID: "custom_voices", Title: "URL as a voice", Description: "接着输入一个音频 URL 来其作为语音样式发送(不会转换格式)", InputMessageContent: &models.InputTextMessageContent{ @@ -358,8 +358,7 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Str("command", "uaav"). - Str("result", "command usage tips"). - Msg("Send inline command result failed") + Msg("Failed to send `usage tips` inline result") return err } } else if len(keywords) == 1 { @@ -378,10 +377,9 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Str("callbackQuery", opts.Update.CallbackQuery.Data). + Str("query", opts.Update.InlineQuery.Query). Str("command", "uaav"). - Str("result", "valid voice url"). - Msg("Send inline command result failed") + Msg("Failed to send `valid voice url` inline result") return err } } else { @@ -402,10 +400,9 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Str("callbackQuery", opts.Update.CallbackQuery.Data). + Str("query", opts.Update.InlineQuery.Query). Str("command", "uaav"). - Str("result", "URL invalid"). - Msg("Send inline command result failed") + Msg("Failed to send `URL invalid` inline result") return err } } @@ -427,10 +424,9 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Str("callbackQuery", opts.Update.CallbackQuery.Data). + Str("query", opts.Update.InlineQuery.Query). Str("command", "uaav"). - Str("result", "argumunt too much"). - Msg("Send inline command result failed") + Msg("Failed to send `too much argumunt` inline result") return err } } @@ -454,7 +450,8 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Str("callbackQuery", opts.Update.CallbackQuery.Data). + Str("query", opts.Update.InlineQuery.Query). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("command", "log"). Msg("Read log by inline command failed") return err @@ -483,8 +480,10 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("command", "log"). - Msg("Send inline command result failed") + Msg("Failed to send `log info` inline result") + return err } } @@ -506,8 +505,8 @@ func Register(ctx context.Context) { InlineQueryID: opts.Update.InlineQuery.ID, Results: []models.InlineQueryResult{ &models.InlineQueryResultArticle{ - ID: "reload", - Title: "已请求更新", + ID: "reloadpdb-back", + Title: "已请求重新加载插件数据库", Description: fmt.Sprintf("last update at %s", time.Now().Format(time.RFC3339)), InputMessageContent: &models.InputTextMessageContent{ MessageText: "???", @@ -521,8 +520,9 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Str("command", "reload"). - Msg("Send inline command result failed") + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("command", "reloadpdb"). + Msg("Failed to send `reload plugin database info` inline result") } return err }, @@ -542,8 +542,8 @@ func Register(ctx context.Context) { InlineQueryID: opts.Update.InlineQuery.ID, Results: []models.InlineQueryResult{ &models.InlineQueryResultArticle{ - ID: "reload", - Title: "已请求保存", + ID: "savepdb-back", + Title: "已请求保存插件数据库", Description: fmt.Sprintf("last save at %s", time.Now().Format(time.RFC3339)), InputMessageContent: &models.InputTextMessageContent{ MessageText: "???", @@ -557,8 +557,9 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("command", "savepdb"). - Msg("Send inline command result failed") + Msg("Failed to send `save plugin database info` inline result") } return err }, @@ -578,8 +579,8 @@ func Register(ctx context.Context) { InlineQueryID: opts.Update.InlineQuery.ID, Results: []models.InlineQueryResult{ &models.InlineQueryResultArticle{ - ID: "savedb", - Title: "已请求保存", + ID: "savedb-back", + Title: "已请求保存数据库", Description: fmt.Sprintf("last update at %s", time.Now().Format(time.RFC3339)), InputMessageContent: &models.InputTextMessageContent{ MessageText: "???", @@ -593,8 +594,9 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("command", "savedb"). - Msg("Send inline command result failed") + Msg("Failed to send `save database info` inline result") } return err }, diff --git a/utils/plugin_utils/handler_by_message_type.go b/utils/plugin_utils/handler_by_message_type.go index f5f2d6c..4b9f403 100644 --- a/utils/plugin_utils/handler_by_message_type.go +++ b/utils/plugin_utils/handler_by_message_type.go @@ -150,7 +150,7 @@ func SelectHandlerByMessageTypeHandlerCallback(opts *handler_structs.SubHandlerP Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). - Msg("Delete `select handler by message type keyboard` message failed") + Msg("Failed to delete `select handler by message type keyboard` message") return err } } else { @@ -164,7 +164,7 @@ func SelectHandlerByMessageTypeHandlerCallback(opts *handler_structs.SubHandlerP Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). - Msg("Send `handler by message type is not exist` callback answer failed") + Msg("Failed to send `handler by message type is not exist` callback answer") return err } } diff --git a/utils/utils.go b/utils/utils.go index fb95931..ff8e292 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -513,5 +513,5 @@ func GetUserOrSenderChatDict(userOrSenderChat *models.Message) (string, *zerolog } } - return "noUserOrSender", zerolog.Dict().Str("error", "no user or sender chat") + return "noUserOrSender", zerolog.Dict().Str("warn", "no user or sender chat") } -- 2.49.1 From 139171e68959930c5980797bd1238cd70da2b2ae Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sun, 8 Jun 2025 03:40:53 +0800 Subject: [PATCH 07/27] save changes --- database/initial.go | 2 ++ handlers.go | 3 ++- logstruct.txt | 4 ++++ utils/plugin_utils/plugin_database_handler.go | 7 +++---- utils/signals/signals.go | 4 ++-- utils/type/message_utils/message_attribute.go | 2 +- utils/type/message_utils/message_type.go | 1 + utils/type/update_utils/update_type.go | 9 +++++---- 8 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 logstruct.txt diff --git a/database/initial.go b/database/initial.go index bb69edc..ca70230 100644 --- a/database/initial.go +++ b/database/initial.go @@ -5,6 +5,7 @@ import ( "trbot/database/db_struct" "trbot/database/redis_db" "trbot/database/yaml_db" + "trbot/utils" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" @@ -56,6 +57,7 @@ func AddDatabaseBackend(ctx context.Context, backends ...DatabaseBackend) int { } logger.Info(). Str("database", db.Name). + Str("databaseLevel", utils.TextForTrueOrFalse(db.IsLowLevel, "low", "high")). Msg("Database initialized") count++ } else { diff --git a/handlers.go b/handlers.go index a6fd94d..aca0085 100644 --- a/handlers.go +++ b/handlers.go @@ -63,7 +63,8 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). Str("text", update.Message.Text). - Msg("textMessage") + Str("type", string(message_utils.GetMessageType(update.Message).InString())). + Msg("message") } messageHandler(&opts) diff --git a/logstruct.txt b/logstruct.txt new file mode 100644 index 0000000..62fae90 --- /dev/null +++ b/logstruct.txt @@ -0,0 +1,4 @@ +bot.SendMessage: Failed to send `` message +bot.DeleteMessages: Failed to delete `` message +bot.AnswerInlineQuery: Failed to send `` inline result (sub handler can add a `Str("command", "log")` ) +bot.AnswerCallbackQuery: Failed to send `` callback answer diff --git a/utils/plugin_utils/plugin_database_handler.go b/utils/plugin_utils/plugin_database_handler.go index 7c426cc..a8961e6 100644 --- a/utils/plugin_utils/plugin_database_handler.go +++ b/utils/plugin_utils/plugin_database_handler.go @@ -2,7 +2,6 @@ package plugin_utils import ( "context" - "fmt" "github.com/rs/zerolog" ) @@ -25,7 +24,7 @@ func AddDataBaseHandler(InlineHandlerPlugins ...DatabaseHandler) int { return pluginCount } -func ReloadPluginsDatabase(ctx context.Context) string { +func ReloadPluginsDatabase(ctx context.Context) { logger := zerolog.Ctx(ctx). With(). Str("funcName", "ReloadPluginsDatabase"). @@ -50,8 +49,8 @@ func ReloadPluginsDatabase(ctx context.Context) string { successCount++ } } - return fmt.Sprintf("Reload (%d/%d) plugins database", successCount, dbCount) + logger.Info().Msgf("Reloaded (%d/%d) plugins database", successCount, dbCount) } func SavePluginsDatabase(ctx context.Context) { @@ -81,5 +80,5 @@ func SavePluginsDatabase(ctx context.Context) { } } - logger.Info().Msgf("[plugin_utils] Saved (%d/%d) plugins database", successCount, dbCount) + logger.Info().Msgf("Saved (%d/%d) plugins database", successCount, dbCount) } diff --git a/utils/signals/signals.go b/utils/signals/signals.go index c0b3c11..2280d55 100644 --- a/utils/signals/signals.go +++ b/utils/signals/signals.go @@ -43,10 +43,10 @@ func SignalsHandler(ctx context.Context) { Err(err). Int("retryCount", saveDatabaseRetryCount). Int("maxRetry", saveDatabaseRetryMax). - Msg("Save database failed") + Msg("Failed to save database, retrying...") time.Sleep(2 * time.Second) if saveDatabaseRetryCount >= saveDatabaseRetryMax { - logger.Error().Msg("Save database failed too many times, exiting") + logger.Error().Msg("Failed to save database too many times, exiting") os.Exit(1) } continue diff --git a/utils/type/message_utils/message_attribute.go b/utils/type/message_utils/message_attribute.go index 15080d6..1338472 100644 --- a/utils/type/message_utils/message_attribute.go +++ b/utils/type/message_utils/message_attribute.go @@ -31,7 +31,7 @@ type MessageAttribute struct { IsHasInlineKeyboard bool `yaml:"IsHasInlineKeyboard,omitempty"` // message has inline keyboard } -// 判断消息属性 +// 判断消息的属性 func GetMessageAttribute(msg *models.Message) MessageAttribute { var attribute MessageAttribute if msg.SenderChat != nil { diff --git a/utils/type/message_utils/message_type.go b/utils/type/message_utils/message_type.go index c709521..f38abd2 100644 --- a/utils/type/message_utils/message_type.go +++ b/utils/type/message_utils/message_type.go @@ -31,6 +31,7 @@ type MessageType struct { Giveaway bool `yaml:"Giveaway,omitempty"` } +// 将消息类型结构体转换为 MessageTypeList(string) 类型 func (mt MessageType)InString() MessageTypeList { val := reflect.ValueOf(mt) typ := reflect.TypeOf(mt) diff --git a/utils/type/update_utils/update_type.go b/utils/type/update_utils/update_type.go index 228845b..797fb3e 100644 --- a/utils/type/update_utils/update_type.go +++ b/utils/type/update_utils/update_type.go @@ -33,9 +33,10 @@ type UpdateType struct { RemovedChatBoost bool `yaml:"RemovedChatBoost,omitempty"` // *models.ChatBoostRemoved } -func (mt UpdateType)InString() UpdateTypeList { - val := reflect.ValueOf(mt) - typ := reflect.TypeOf(mt) +// 将消息类型结构体转换为 UpdateTypeList(string) 类型 +func (ut UpdateType)InString() UpdateTypeList { + val := reflect.ValueOf(ut) + typ := reflect.TypeOf(ut) for i := 0; i < val.NumField(); i++ { if val.Field(i).Bool() { @@ -74,7 +75,7 @@ const ( RemovedChatBoost UpdateTypeList = "RemovedChatBoost" ) -// 判断更新属性 +// 判断更新的类型 func GetUpdateType(update *models.Update) UpdateType { var updateType UpdateType if update.Message != nil { -- 2.49.1 From c3c3e6de4507a7864cd5fe1c588c4766d12c1914 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sun, 8 Jun 2025 21:06:27 +0800 Subject: [PATCH 08/27] save change format redis database logs only need single redis database --- database/db_struct/struct.go | 1 - database/initial.go | 38 +++------ database/operates.go | 147 --------------------------------- database/redis_db/redis.go | 103 +++++++++--------------- database/utils.go | 152 +++++++++++++++++++++++++++++++++++ database/yaml_db/yaml.go | 6 +- utils/configs/config.go | 4 +- 7 files changed, 208 insertions(+), 243 deletions(-) create mode 100644 database/utils.go diff --git a/database/db_struct/struct.go b/database/db_struct/struct.go index 50cb534..d02ef70 100644 --- a/database/db_struct/struct.go +++ b/database/db_struct/struct.go @@ -49,7 +49,6 @@ const ( LatestInlineResult ChatInfoField_LatestData = "LatestInlineResult" LatestCallbackQueryData ChatInfoField_LatestData = "LatestCallbackQueryData" - ) type ChatInfoField_UsageCount string diff --git a/database/initial.go b/database/initial.go index ca70230..273c166 100644 --- a/database/initial.go +++ b/database/initial.go @@ -18,10 +18,9 @@ type DatabaseBackend struct { // 数据库等级,低优先级的数据库不会实时同步更改,程序仅会在高优先级数据库不可用才会尝试使用其中的数据 IsLowLevel bool - // 是否已被成功初始化 - Initializer func() (bool, error) - IsInitialized bool - InitializedErr error + Initializer func() (bool, error) // 数据库初始化函数 + IsInitialized bool // 是否已被成功初始化 + InitializedErr error // 初始化错误 // 数据库保存和读取函数 SaveDatabase func(ctx context.Context) error @@ -40,7 +39,7 @@ type DatabaseBackend struct { var DBBackends []DatabaseBackend var DBBackends_LowLevel []DatabaseBackend -func AddDatabaseBackend(ctx context.Context, backends ...DatabaseBackend) int { +func AddDatabaseBackends(ctx context.Context, backends ...DatabaseBackend) int { logger := zerolog.Ctx(ctx) if DBBackends == nil { DBBackends = []DatabaseBackend{} } @@ -64,7 +63,7 @@ func AddDatabaseBackend(ctx context.Context, backends ...DatabaseBackend) int { logger.Error(). Err(db.InitializedErr). Str("database", db.Name). - Msg("Database initialize failed") + Msg("Failed to initialize database") } } @@ -73,7 +72,7 @@ func AddDatabaseBackend(ctx context.Context, backends ...DatabaseBackend) int { func InitAndListDatabases(ctx context.Context) { logger := zerolog.Ctx(ctx) - AddDatabaseBackend(ctx, DatabaseBackend{ + AddDatabaseBackends(ctx, DatabaseBackend{ Name: "redis", Initializer: redis_db.InitializeDB, @@ -86,7 +85,7 @@ func InitAndListDatabases(ctx context.Context) { SetCustomFlag: redis_db.SetCustomFlag, }) - AddDatabaseBackend(ctx, DatabaseBackend{ + AddDatabaseBackends(ctx, DatabaseBackend{ Name: "yaml", IsLowLevel: true, Initializer: yaml_db.InitializeDB, @@ -103,26 +102,13 @@ func InitAndListDatabases(ctx context.Context) { SetCustomFlag: yaml_db.SetCustomFlag, }) - // for _, backend := range DBBackends { - // logger.Info(). - // Str("database", backend.Name). - // Str("level", "high"). - // Msg("database initialized") - // } - // for _, backend := range DBBackends_LowLevel { - // logger.Info(). - // Str("database", backend.Name). - // Str("level", "low"). - // Msg("database initialized") - // } - if len(DBBackends) + len(DBBackends_LowLevel) == 0 { logger.Fatal(). Msg("No database available") - } else { - logger.Info(). - Int("High-level", len(DBBackends)). - Int("Low-level", len(DBBackends_LowLevel)). - Msg("Available databases") } + + logger.Info(). + Int("highLevel", len(DBBackends)). + Int("lowLevel", len(DBBackends_LowLevel)). + Msg("Available databases") } diff --git a/database/operates.go b/database/operates.go index 7f8cc6e..f51c640 100644 --- a/database/operates.go +++ b/database/operates.go @@ -3,15 +3,10 @@ package database import ( "context" "fmt" - "strings" "trbot/database/db_struct" - "trbot/utils" - "trbot/utils/handler_structs" - "trbot/utils/type/update_utils" "github.com/go-telegram/bot/models" - "github.com/rs/zerolog" ) // 需要给一些函数加上一个 success 返回值,有时部分数据库不可用,但数据成功保存到了其他数据库 @@ -183,145 +178,3 @@ func ReadDatabase(ctx context.Context) error { } return allErr } - - -func RecordData(params *handler_structs.SubHandlerParams) { - logger := zerolog.Ctx(params.Ctx). - With(). - Str("funcName", "RecordData"). - Logger() - - updateType := update_utils.GetUpdateType(params.Update) - - switch { - case updateType.Message: - if params.Update.Message.Text != "" { - params.Fields = strings.Fields(params.Update.Message.Text) - } - err := InitChat(params.Ctx, ¶ms.Update.Message.Chat) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). - Msg("Init chat failed") - } - err = IncrementalUsageCount(params.Ctx, params.Update.Message.Chat.ID, db_struct.MessageNormal) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). - Msg("Incremental message count failed") - } - err = RecordLatestData(params.Ctx, params.Update.Message.Chat.ID, db_struct.LatestMessage, params.Update.Message.Text) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). - Msg("Record latest message failed") - } - params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.Message.Chat.ID) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). - Msg("Get chat info failed") - } - case updateType.EditedMessage: - // no ? - case updateType.InlineQuery: - if params.Update.InlineQuery.Query != "" { - params.Fields = strings.Fields(params.Update.InlineQuery.Query) - } - - err := InitUser(params.Ctx, params.Update.InlineQuery.From) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(params.Update.InlineQuery.From)). - Msg("Init user failed") - } - err = IncrementalUsageCount(params.Ctx, params.Update.InlineQuery.From.ID, db_struct.InlineRequest) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(params.Update.InlineQuery.From)). - Msg("Incremental inline request count failed") - } - err = RecordLatestData(params.Ctx, params.Update.InlineQuery.From.ID, db_struct.LatestInlineQuery, params.Update.InlineQuery.Query) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(params.Update.InlineQuery.From)). - Msg("Record latest inline query failed") - } - params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.InlineQuery.From.ID) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(params.Update.InlineQuery.From)). - Msg("Get user info failed") - } - case updateType.ChosenInlineResult: - if params.Update.ChosenInlineResult.Query != "" { - params.Fields = strings.Fields(params.Update.ChosenInlineResult.Query) - } - - err := InitUser(params.Ctx, ¶ms.Update.ChosenInlineResult.From) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). - Msg("Init user failed") - } - err = IncrementalUsageCount(params.Ctx, params.Update.ChosenInlineResult.From.ID, db_struct.InlineResult) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). - Msg("Incremental inline result count failed") - } - err = RecordLatestData(params.Ctx, params.Update.ChosenInlineResult.From.ID, db_struct.LatestInlineResult, params.Update.ChosenInlineResult.ResultID) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). - Msg("Record latest inline result failed") - } - params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.ChosenInlineResult.From.ID) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). - Msg("Get user info failed") - } - case updateType.CallbackQuery: - err := InitUser(params.Ctx, ¶ms.Update.CallbackQuery.From) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Init user failed") - } - err = IncrementalUsageCount(params.Ctx, params.Update.CallbackQuery.From.ID, db_struct.CallbackQuery) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Incremental callback query count failed") - } - err = RecordLatestData(params.Ctx, params.Update.CallbackQuery.From.ID, db_struct.LatestCallbackQueryData, params.Update.CallbackQuery.Data) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Record latest callback query failed") - } - params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.CallbackQuery.From.ID) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). - Msg("Get user info failed") - } - } -} diff --git a/database/redis_db/redis.go b/database/redis_db/redis.go index 0fed5d8..6d489da 100644 --- a/database/redis_db/redis.go +++ b/database/redis_db/redis.go @@ -3,7 +3,6 @@ package redis_db import ( "context" "fmt" - "log" "reflect" "strconv" "time" @@ -16,33 +15,19 @@ import ( "github.com/redis/go-redis/v9" ) -var MainDB *redis.Client // 配置文件 var UserDB *redis.Client // 用户数据 -var ctxbg = context.Background() - func InitializeDB() (bool, error) { if configs.BotConfig.RedisURL != "" { - if configs.BotConfig.RedisMainDB != -1 { - MainDB = redis.NewClient(&redis.Options{ - Addr: configs.BotConfig.RedisURL, - Password: configs.BotConfig.RedisPassword, - DB: configs.BotConfig.RedisMainDB, - }) - err := PingRedis(ctxbg, MainDB) - if err != nil { - return false, fmt.Errorf("error ping Redis MainDB: %s", err) - } - } - if configs.BotConfig.RedisUserInfoDB != -1 { + if configs.BotConfig.RedisDatabaseID != -1 { UserDB = redis.NewClient(&redis.Options{ Addr: configs.BotConfig.RedisURL, Password: configs.BotConfig.RedisPassword, - DB: configs.BotConfig.RedisUserInfoDB, + DB: configs.BotConfig.RedisDatabaseID, }) - err := PingRedis(ctxbg, UserDB) + err := UserDB.Ping(context.Background()).Err() if err != nil { - return false, fmt.Errorf("error ping Redis UserDB: %s", err) + return false, fmt.Errorf("failed to ping Redis [%d] database: %w", configs.BotConfig.RedisDatabaseID, err) } } @@ -52,19 +37,14 @@ func InitializeDB() (bool, error) { } } -func PingRedis(ctx context.Context, db *redis.Client) error { - _, err := db.Ping(ctx).Result() - return err -} - // 保存用户信息 func SaveChatInfo(ctx context.Context, chatInfo *db_struct.ChatInfo) error { if chatInfo == nil { - return fmt.Errorf("chatInfo 不能为空") + return fmt.Errorf("failed to save chat info: chatInfo is nil") } key := strconv.FormatInt(chatInfo.ID, 10) - v := reflect.ValueOf(*chatInfo) // 解除指针获取值 + v := reflect.ValueOf(*chatInfo) t := reflect.TypeOf(*chatInfo) data := make(map[string]interface{}) @@ -82,7 +62,7 @@ func GetChatInfo(ctx context.Context, chatID int64) (*db_struct.ChatInfo, error) key := strconv.FormatInt(chatID, 10) data, err := UserDB.HGetAll(ctx, key).Result() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get chat info: %w", err) } if len(data) == 0 { return nil, nil @@ -116,7 +96,7 @@ func GetChatInfo(ctx context.Context, chatID int64) (*db_struct.ChatInfo, error) func InitUser(ctx context.Context, user *models.User) error { chatData, err := GetChatInfo(ctx, user.ID) if err != nil { - return fmt.Errorf("[UserDB] Error getting chat info from Redis: %v", err) + return fmt.Errorf("failed to get chat info: %w", err) } if chatData == nil { var newUser = db_struct.ChatInfo{ @@ -128,82 +108,77 @@ func InitUser(ctx context.Context, user *models.User) error { err = SaveChatInfo(ctx, &newUser) if err != nil { - return fmt.Errorf("[UserDB] Error saving user info to Redis: %v", err) + return fmt.Errorf("failed to init new user: %w", err) } - log.Printf("newUser: \"%s\"(%d)\n", newUser.ChatName, user.ID) - return nil - } else { - log.Printf("oldUser: \"%s\"(%d)\n", chatData.ChatName, chatData.ID) - return nil } + return nil } func InitChat(ctx context.Context, chat *models.Chat) error { chatData, err := GetChatInfo(ctx, chat.ID) if err != nil { - return fmt.Errorf("[UserDB] Error getting chat info from Redis: %v", err) + return fmt.Errorf("failed to get chat info: %w", err) } if chatData == nil { var newChat = db_struct.ChatInfo{ ID: chat.ID, ChatName: utils.ShowChatName(chat), - ChatType: models.ChatTypePrivate, + ChatType: chat.Type, AddTime: time.Now().Format(time.RFC3339), } err = SaveChatInfo(ctx, &newChat) if err != nil { - return fmt.Errorf("[UserDB] Error saving chat info to Redis: %v", err) + return fmt.Errorf("failed to init new chat: %w", err) } - log.Printf("newChat: \"%s\"(%d)\n", newChat.ChatName, newChat.ID) - return nil - } else { - log.Printf("oldChat: \"%s\"(%d)\n", chatData.ChatName, chatData.ID) - return nil } + return nil } func IncrementalUsageCount(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_UsageCount) error { count, err := UserDB.HGet(ctx, strconv.FormatInt(chatID, 10), string(fieldName)).Int() - if err == nil { - err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), count + 1).Err() - if err == nil { - return nil - } - } else if err == redis.Nil { - err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), 1).Err() - if err == nil { - log.Printf("[UserDB] Key %s not found, creating in Redis\n", fieldName) - return nil + if err != nil { + if err == redis.Nil { + err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), 0).Err() + if err != nil { + return fmt.Errorf("failed to create empty [%s] key: %w", string(fieldName), err) + } + } else { + return fmt.Errorf("failed to get [%s] usage count: %w", string(fieldName), err) } } - return fmt.Errorf("[UserDB] Error incrementing usage count to Redis: %v", err) + err = UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), count + 1).Err() + if err != nil { + return fmt.Errorf("failed to incrementing [%s] usage count: %w", string(fieldName), err) + } + + return nil } func RecordLatestData(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_LatestData, value string) error { err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() - if err == nil { - return nil + if err != nil { + return fmt.Errorf("failed to record latest [%s] data: %w", string(fieldName), err) } - - return fmt.Errorf("[UserDB] Error saving chat info to Redis: %v", err) + + return nil } func UpdateOperationStatus(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_Status, value bool) error { err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() - if err == nil { - return nil + if err != nil { + return fmt.Errorf("failed to update operation [%s] status: %w", string(fieldName), err) } - return fmt.Errorf("[UserDB] Error update operation status to Redis: %v", err) + return nil } func SetCustomFlag(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_CustomFlag, value string) error { - err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() - if err == nil { - return nil + err := UserDB.HSet(ctx, strconv.FormatInt(chatID, 10), string(fieldName), value).Err() + if err != nil { + return fmt.Errorf("failed to set custom [%s] flag: %w", string(fieldName), err) } - return fmt.Errorf("[UserDB] Error setting custom flag to Redis: %v", err) + return nil } diff --git a/database/utils.go b/database/utils.go new file mode 100644 index 0000000..dfc39e7 --- /dev/null +++ b/database/utils.go @@ -0,0 +1,152 @@ +package database + +import ( + "strings" + "trbot/database/db_struct" + "trbot/utils" + "trbot/utils/handler_structs" + "trbot/utils/type/update_utils" + + "github.com/rs/zerolog" +) + +func RecordData(params *handler_structs.SubHandlerParams) { + logger := zerolog.Ctx(params.Ctx). + With(). + Str("funcName", "RecordData"). + Logger() + + updateType := update_utils.GetUpdateType(params.Update) + + switch { + case updateType.Message: + if params.Update.Message.Text != "" { + params.Fields = strings.Fields(params.Update.Message.Text) + } + err := InitChat(params.Ctx, ¶ms.Update.Message.Chat) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Failed to init chat") + } + err = IncrementalUsageCount(params.Ctx, params.Update.Message.Chat.ID, db_struct.MessageNormal) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Failed to incremental `message` usage count") + } + err = RecordLatestData(params.Ctx, params.Update.Message.Chat.ID, db_struct.LatestMessage, params.Update.Message.Text) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Failed to record latest `message text` data") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.Message.Chat.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(¶ms.Update.Message.Chat)). + Msg("Failed to get chat info") + } + case updateType.EditedMessage: + // no ? + case updateType.InlineQuery: + if params.Update.InlineQuery.Query != "" { + params.Fields = strings.Fields(params.Update.InlineQuery.Query) + } + + err := InitUser(params.Ctx, params.Update.InlineQuery.From) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Failed to init user") + } + err = IncrementalUsageCount(params.Ctx, params.Update.InlineQuery.From.ID, db_struct.InlineRequest) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Failed to incremental `inline request` usage count") + } + err = RecordLatestData(params.Ctx, params.Update.InlineQuery.From.ID, db_struct.LatestInlineQuery, params.Update.InlineQuery.Query) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Failed to record latest `inline query` data") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.InlineQuery.From.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(params.Update.InlineQuery.From)). + Msg("Failed to get user info") + } + case updateType.ChosenInlineResult: + if params.Update.ChosenInlineResult.Query != "" { + params.Fields = strings.Fields(params.Update.ChosenInlineResult.Query) + } + + err := InitUser(params.Ctx, ¶ms.Update.ChosenInlineResult.From) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Failed to init user") + } + err = IncrementalUsageCount(params.Ctx, params.Update.ChosenInlineResult.From.ID, db_struct.InlineResult) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Failed to incremental `inline result` usage count") + } + err = RecordLatestData(params.Ctx, params.Update.ChosenInlineResult.From.ID, db_struct.LatestInlineResult, params.Update.ChosenInlineResult.ResultID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("failed to record latest `inline result` data") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.ChosenInlineResult.From.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Failed to get user info") + } + case updateType.CallbackQuery: + err := InitUser(params.Ctx, ¶ms.Update.CallbackQuery.From) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Failed to init user") + } + err = IncrementalUsageCount(params.Ctx, params.Update.CallbackQuery.From.ID, db_struct.CallbackQuery) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Failed to incremental `callback query` usage count") + } + err = RecordLatestData(params.Ctx, params.Update.CallbackQuery.From.ID, db_struct.LatestCallbackQueryData, params.Update.CallbackQuery.Data) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Failed to record latest `callback query` data") + } + params.ChatInfo, err = GetChatInfo(params.Ctx, params.Update.CallbackQuery.From.ID) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.ChosenInlineResult.From)). + Msg("Failed get user info") + } + } +} diff --git a/database/yaml_db/yaml.go b/database/yaml_db/yaml.go index 0a4b94c..ffe3c77 100644 --- a/database/yaml_db/yaml.go +++ b/database/yaml_db/yaml.go @@ -20,6 +20,8 @@ import ( var Database DataBaseYaml +// 需要重构,错误信息不足 + type DataBaseYaml struct { // 如果运行中希望程序强制读取新数据,在 YAML 数据库文件的开头添加 FORCEOVERWRITE: true 即可 ForceOverwrite bool `yaml:"FORCEOVERWRITE,omitempty"` @@ -35,11 +37,11 @@ func InitializeDB() (bool, error) { var err error Database, err = ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)) if err != nil { - return false, fmt.Errorf("read yaml db error: %s", err) + return false, fmt.Errorf("failed to read yaml databse: %s", err) } return true, nil } else { - return false, fmt.Errorf("DB path is empty") + return false, fmt.Errorf("yaml database path is empty") } } diff --git a/utils/configs/config.go b/utils/configs/config.go index 130366c..ac6554d 100644 --- a/utils/configs/config.go +++ b/utils/configs/config.go @@ -27,9 +27,7 @@ type config struct { // redis database RedisURL string `yaml:"RedisURL"` RedisPassword string `yaml:"RedisPassword"` - RedisMainDB int `yaml:"RedisMainDB"` - RedisUserInfoDB int `yaml:"RedisUserInfoDB"` - RedisSubDB int `yaml:"RedisSubDB"` + RedisDatabaseID int `yaml:"RedisDatabaseID"` // inline mode config InlineDefaultHandler string `yaml:"InlineDefaultHandler"` // Leave empty to show inline menu -- 2.49.1 From 60e3c958285df64f2fe9746d53affa72aa577081 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Tue, 10 Jun 2025 00:17:57 +0800 Subject: [PATCH 09/27] save changes add convert webm to gif use external ffmpeg use `update_utils.GetUpdateType()` get update type then run handlers remove `IsDebugMode` flag --- .gitignore | 1 + database/yaml_db/yaml.go | 4 +- handlers.go | 457 +++++++++++++++++++------------------- main.go | 11 +- plugins/plugin_sticker.go | 241 ++++++++++++-------- utils/configs/init.go | 2 +- utils/configs/webhook.go | 2 +- utils/consts/consts.go | 2 - 8 files changed, 389 insertions(+), 331 deletions(-) diff --git a/.gitignore b/.gitignore index ed735e5..b058890 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ __debug_bin* /db_yaml/detectkeyword /db_yaml/teamspeak config.yaml +/ffmpeg diff --git a/database/yaml_db/yaml.go b/database/yaml_db/yaml.go index ffe3c77..779eedc 100644 --- a/database/yaml_db/yaml.go +++ b/database/yaml_db/yaml.go @@ -128,7 +128,7 @@ func AutoSaveDatabaseHandler() { } } // 没有修改就跳过保存 - if reflect.DeepEqual(savedDatabase, Database) && consts.IsDebugMode { + if reflect.DeepEqual(savedDatabase, Database) { log.Println("looks Database no any change, skip autosave this time") } else { // 如果数据库文件中有设定专用的 `FORCEOVERWRITE: true` 覆写标记,无论任何修改,先保存程序中的数据,再读取新的数据替换掉当前的数据并保存 @@ -188,7 +188,7 @@ func AutoSaveDatabaseHandler() { err := SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) if err != nil { mess.PrintLogAndSave(fmt.Sprintln("some issues happend when auto saving Database:", err)) - } else if consts.IsDebugMode { + } else { mess.PrintLogAndSave("auto save at " + time.Now().Format(time.RFC3339)) } } diff --git a/handlers.go b/handlers.go index aca0085..c103c0e 100644 --- a/handlers.go +++ b/handlers.go @@ -24,247 +24,250 @@ import ( func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) { defer utils.PanicCatcher(ctx, "defaultHandler") logger := zerolog.Ctx(ctx) - // logger := zerolog.Ctx(ctx). - // With(). - // Str("funcName", "defaultHandler"). - // Logger() - // var err error var opts = handler_structs.SubHandlerParams{ Ctx: ctx, Thebot: thebot, Update: update, } + // Debug level or Trace Level + if zerolog.GlobalLevel() <= zerolog.DebugLevel { + if update.Message != nil { + // 正常消息 + if update.Message.Photo != nil { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.Message)). + Dict(utils.GetChatDict(&update.Message.Chat)). + Int("messageID", update.Message.ID). + Str("caption", update.Message.Caption). + Msg("photoMessage") + } else if update.Message.Sticker != nil { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.Message)). + Dict(utils.GetChatDict(&update.Message.Chat)). + Int("messageID", update.Message.ID). + Str("stickerEmoji", update.Message.Sticker.Emoji). + Str("stickerSetname", update.Message.Sticker.SetName). + Str("stickerFileID", update.Message.Sticker.FileID). + Msg("stickerMessage") + } else { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.Message)). + Dict(utils.GetChatDict(&update.Message.Chat)). + Int("messageID", update.Message.ID). + Str("text", update.Message.Text). + Str("type", string(message_utils.GetMessageType(update.Message).InString())). + Msg("message") + } + } else if update.EditedMessage != nil { + // 私聊或群组消息被编辑 + if update.EditedMessage.Caption != "" { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)). + Dict(utils.GetChatDict(&update.EditedMessage.Chat)). + Int("messageID", update.EditedMessage.ID). + Str("editedCaption", update.EditedMessage.Caption). + Msg("editedMessage") + } else { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)). + Dict(utils.GetChatDict(&update.EditedMessage.Chat)). + Int("messageID", update.EditedMessage.ID). + Str("editedText", update.EditedMessage.Text). + Msg("editedMessage") + } + } else if update.InlineQuery != nil { + // inline 查询 + logger.Debug(). + Dict(utils.GetUserDict(update.InlineQuery.From)). + Str("query", update.InlineQuery.Query). + Msg("inline request") + + } else if update.ChosenInlineResult != nil { + // inline 查询结果被选择 + logger.Debug(). + Dict(utils.GetUserDict(&update.ChosenInlineResult.From)). + Str("query", update.ChosenInlineResult.Query). + Str("resultID", update.ChosenInlineResult.ResultID). + Msg("chosen inline result") + + } else if update.CallbackQuery != nil { + // replymarkup 回调 + logger.Debug(). + Dict(utils.GetUserDict(&update.CallbackQuery.From)). + Dict(utils.GetChatDict(&update.CallbackQuery.Message.Message.Chat)). + Str("query", update.CallbackQuery.Data). + Msg("callback query") + + return + } else if update.MessageReaction != nil { + // 私聊或群组表情回应 + if len(update.MessageReaction.OldReaction) > 0 { + for i, oldReaction := range update.MessageReaction.OldReaction { + if oldReaction.ReactionTypeEmoji != nil { + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("removedEmoji", oldReaction.ReactionTypeEmoji.Emoji). + Str("emojiType", string(oldReaction.ReactionTypeEmoji.Type)). + Int("count", i + 1). + Msg("removed emoji reaction") + } else if oldReaction.ReactionTypeCustomEmoji != nil { + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("removedEmojiID", oldReaction.ReactionTypeCustomEmoji.CustomEmojiID). + Str("emojiType", string(oldReaction.ReactionTypeCustomEmoji.Type)). + Int("count", i + 1). + Msg("removed custom emoji reaction") + } else if oldReaction.ReactionTypePaid != nil { + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("emojiType", string(oldReaction.ReactionTypePaid.Type)). + Int("count", i + 1). + Msg("removed paid emoji reaction") + } + } + } + if len(update.MessageReaction.NewReaction) > 0 { + for i, newReaction := range update.MessageReaction.NewReaction { + if newReaction.ReactionTypeEmoji != nil { + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("addEmoji", newReaction.ReactionTypeEmoji.Emoji). + Str("emojiType", string(newReaction.ReactionTypeEmoji.Type)). + Int("count", i + 1). + Msg("add emoji reaction") + } else if newReaction.ReactionTypeCustomEmoji != nil { + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("addEmojiID", newReaction.ReactionTypeCustomEmoji.CustomEmojiID). + Str("emojiType", string(newReaction.ReactionTypeCustomEmoji.Type)). + Int("count", i + 1). + Msg("add custom emoji reaction") + } else if newReaction.ReactionTypePaid != nil { + logger.Debug(). + Dict(utils.GetUserDict(update.MessageReaction.User)). + Dict(utils.GetChatDict(&update.MessageReaction.Chat)). + Int("messageID", update.MessageReaction.MessageID). + Str("emojiType", string(newReaction.ReactionTypePaid.Type)). + Int("count", i + 1). + Msg("add paid emoji reaction") + } + } + } + } else if update.MessageReactionCount != nil { + // 频道消息表情回应数量 + var emoji = zerolog.Dict() + var customEmoji = zerolog.Dict() + var paid = zerolog.Dict() + for _, n := range update.MessageReactionCount.Reactions { + switch n.Type.Type { + case models.ReactionTypeTypeEmoji: + emoji.Dict(n.Type.ReactionTypeEmoji.Emoji, zerolog.Dict(). + // Str("type", string(n.Type.ReactionTypeEmoji.Type)). + // Str("emoji", n.Type.ReactionTypeEmoji.Emoji). + Int("count", n.TotalCount), + ) + case models.ReactionTypeTypeCustomEmoji: + customEmoji.Dict(n.Type.ReactionTypeCustomEmoji.CustomEmojiID, zerolog.Dict(). + // Str("type", string(n.Type.ReactionTypeCustomEmoji.Type)). + // Str("customEmojiID", n.Type.ReactionTypeCustomEmoji.CustomEmojiID). + Int("count", n.TotalCount), + ) + case models.ReactionTypeTypePaid: + paid.Dict(n.Type.ReactionTypePaid.Type, zerolog.Dict(). + // Str("type", n.Type.ReactionTypePaid.Type). + Int("count", n.TotalCount), + ) + } + + } + + logger.Debug(). + Dict(utils.GetChatDict(&update.MessageReactionCount.Chat)). + Dict("reactions", zerolog.Dict(). + Dict("emoji", emoji). + Dict("customEmoji", customEmoji). + Dict("paid", paid), + ). + Int("messageID", update.MessageReactionCount.MessageID). + Msg("emoji reaction count updated") + } else if update.ChannelPost != nil { + // 频道信息 + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.ChannelPost)). + Dict(utils.GetChatDict(&update.ChannelPost.Chat)). + Str("text", update.ChannelPost.Text). + Int("messageID", update.ChannelPost.ID). + Msg("channel post") + if update.ChannelPost.ViaBot != nil { + // 在频道中由 bot 发送 + _, viaBot := utils.GetUserDict(update.ChannelPost.ViaBot) + logger.Debug(). + Dict("viaBot", viaBot). + Dict(utils.GetChatDict(&update.ChannelPost.Chat)). + Str("text", update.ChannelPost.Text). + Int("messageID", update.ChannelPost.ID). + Msg("channel post send via bot") + } + if update.ChannelPost.SenderChat == nil { + // 没有身份信息 + logger.Debug(). + Dict(utils.GetChatDict(&update.ChannelPost.Chat)). + Str("text", update.ChannelPost.Text). + Int("messageID", update.ChannelPost.ID). + Msg("channel post from nobody") + } + } else if update.EditedChannelPost != nil { + // 频道中编辑过的消息 + if update.EditedChannelPost.Caption != "" { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)). + Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). + Int("messageID", update.EditedChannelPost.ID). + Str("editedCaption", update.EditedChannelPost.Caption). + Msg("edited channel post caption") + } else { + logger.Debug(). + Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)). + Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). + Int("messageID", update.EditedChannelPost.ID). + Str("editedText", update.EditedChannelPost.Text). + Msg("edited channel post") + } + } else { + // 其他没有加入的更新类型 + logger.Warn(). + Str("updateType", string(update_utils.GetUpdateType(update).InString())). + Msg("Receive a no tagged update type") + } + } + + // 记录数据和读取信息 database.RecordData(&opts) - // 需要重写来配合 handler by update type - if update.Message != nil { - // 正常消息 - if update.Message.Photo != nil { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.Message)). - Dict(utils.GetChatDict(&update.Message.Chat)). - Int("messageID", update.Message.ID). - Str("caption", update.Message.Caption). - Msg("photoMessage") - } else if update.Message.Sticker != nil { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.Message)). - Dict(utils.GetChatDict(&update.Message.Chat)). - Int("messageID", update.Message.ID). - Str("stickerEmoji", update.Message.Sticker.Emoji). - Str("stickerSetname", update.Message.Sticker.SetName). - Str("stickerFileID", update.Message.Sticker.FileID). - Msg("stickerMessage") - } else { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.Message)). - Dict(utils.GetChatDict(&update.Message.Chat)). - Int("messageID", update.Message.ID). - Str("text", update.Message.Text). - Str("type", string(message_utils.GetMessageType(update.Message).InString())). - Msg("message") - } - + updateType := update_utils.GetUpdateType(update) + switch { + case updateType.Message: messageHandler(&opts) - } else if update.EditedMessage != nil { - // 私聊或群组消息被编辑 - if update.EditedMessage.Caption != "" { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)). - Dict(utils.GetChatDict(&update.EditedMessage.Chat)). - Int("messageID", update.EditedMessage.ID). - Str("editedCaption", update.EditedMessage.Caption). - Msg("editedMessage") - } else { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)). - Dict(utils.GetChatDict(&update.EditedMessage.Chat)). - Int("messageID", update.EditedMessage.ID). - Str("editedText", update.EditedMessage.Text). - Msg("editedMessage") - } - } else if update.InlineQuery != nil { - // inline 查询 - - logger.Debug(). - Dict(utils.GetUserDict(update.InlineQuery.From)). - Str("query", update.InlineQuery.Query). - Msg("inline request") - + case updateType.InlineQuery: inlineHandler(&opts) - } else if update.ChosenInlineResult != nil { - // inline 查询结果被选择 - logger.Debug(). - Dict(utils.GetUserDict(&update.ChosenInlineResult.From)). - Str("query", update.ChosenInlineResult.Query). - Str("resultID", update.ChosenInlineResult.ResultID). - Msg("chosen inline result") - - - } else if update.CallbackQuery != nil { - // replymarkup 回调 - logger.Debug(). - Dict(utils.GetUserDict(&update.CallbackQuery.From)). - Dict(utils.GetChatDict(&update.CallbackQuery.Message.Message.Chat)). - Str("query", update.CallbackQuery.Data). - Msg("callback query") - + case updateType.CallbackQuery: callbackQueryHandler(&opts) - opts.ChatInfo.HasPendingCallbackQuery = false - return - } else if update.MessageReaction != nil { - // 私聊或群组表情回应 - if len(update.MessageReaction.OldReaction) > 0 { - for i, oldReaction := range update.MessageReaction.OldReaction { - if oldReaction.ReactionTypeEmoji != nil { - logger.Debug(). - Dict(utils.GetUserDict(update.MessageReaction.User)). - Dict(utils.GetChatDict(&update.MessageReaction.Chat)). - Int("messageID", update.MessageReaction.MessageID). - Str("removedEmoji", oldReaction.ReactionTypeEmoji.Emoji). - Str("emojiType", string(oldReaction.ReactionTypeEmoji.Type)). - Int("count", i + 1). - Msg("removed emoji reaction") - } else if oldReaction.ReactionTypeCustomEmoji != nil { - logger.Debug(). - Dict(utils.GetUserDict(update.MessageReaction.User)). - Dict(utils.GetChatDict(&update.MessageReaction.Chat)). - Int("messageID", update.MessageReaction.MessageID). - Str("removedEmojiID", oldReaction.ReactionTypeCustomEmoji.CustomEmojiID). - Str("emojiType", string(oldReaction.ReactionTypeCustomEmoji.Type)). - Int("count", i + 1). - Msg("removed custom emoji reaction") - } else if oldReaction.ReactionTypePaid != nil { - logger.Debug(). - Dict(utils.GetUserDict(update.MessageReaction.User)). - Dict(utils.GetChatDict(&update.MessageReaction.Chat)). - Int("messageID", update.MessageReaction.MessageID). - Str("emojiType", string(oldReaction.ReactionTypePaid.Type)). - Int("count", i + 1). - Msg("removed paid emoji reaction") - } - } - } - if len(update.MessageReaction.NewReaction) > 0 { - for i, newReaction := range update.MessageReaction.NewReaction { - if newReaction.ReactionTypeEmoji != nil { - logger.Debug(). - Dict(utils.GetUserDict(update.MessageReaction.User)). - Dict(utils.GetChatDict(&update.MessageReaction.Chat)). - Int("messageID", update.MessageReaction.MessageID). - Str("addEmoji", newReaction.ReactionTypeEmoji.Emoji). - Str("emojiType", string(newReaction.ReactionTypeEmoji.Type)). - Int("count", i + 1). - Msg("add emoji reaction") - } else if newReaction.ReactionTypeCustomEmoji != nil { - logger.Debug(). - Dict(utils.GetUserDict(update.MessageReaction.User)). - Dict(utils.GetChatDict(&update.MessageReaction.Chat)). - Int("messageID", update.MessageReaction.MessageID). - Str("addEmojiID", newReaction.ReactionTypeCustomEmoji.CustomEmojiID). - Str("emojiType", string(newReaction.ReactionTypeCustomEmoji.Type)). - Int("count", i + 1). - Msg("add custom emoji reaction") - } else if newReaction.ReactionTypePaid != nil { - logger.Debug(). - Dict(utils.GetUserDict(update.MessageReaction.User)). - Dict(utils.GetChatDict(&update.MessageReaction.Chat)). - Int("messageID", update.MessageReaction.MessageID). - Str("emojiType", string(newReaction.ReactionTypePaid.Type)). - Int("count", i + 1). - Msg("add paid emoji reaction") - } - } - } - } else if update.MessageReactionCount != nil { - // 频道消息表情回应数量 - var emoji = zerolog.Dict() - var customEmoji = zerolog.Dict() - var paid = zerolog.Dict() - for _, n := range update.MessageReactionCount.Reactions { - switch n.Type.Type { - case models.ReactionTypeTypeEmoji: - emoji.Dict(n.Type.ReactionTypeEmoji.Emoji, zerolog.Dict(). - // Str("type", string(n.Type.ReactionTypeEmoji.Type)). - // Str("emoji", n.Type.ReactionTypeEmoji.Emoji). - Int("count", n.TotalCount), - ) - case models.ReactionTypeTypeCustomEmoji: - customEmoji.Dict(n.Type.ReactionTypeCustomEmoji.CustomEmojiID, zerolog.Dict(). - // Str("type", string(n.Type.ReactionTypeCustomEmoji.Type)). - // Str("customEmojiID", n.Type.ReactionTypeCustomEmoji.CustomEmojiID). - Int("count", n.TotalCount), - ) - case models.ReactionTypeTypePaid: - paid.Dict(n.Type.ReactionTypePaid.Type, zerolog.Dict(). - // Str("type", n.Type.ReactionTypePaid.Type). - Int("count", n.TotalCount), - ) - } - - } - - logger.Debug(). - Dict(utils.GetChatDict(&update.MessageReactionCount.Chat)). - Dict("reactions", zerolog.Dict(). - Dict("emoji", emoji). - Dict("customEmoji", customEmoji). - Dict("paid", paid), - ). - Int("messageID", update.MessageReactionCount.MessageID). - Msg("emoji reaction count updated") - } else if update.ChannelPost != nil { - // 频道信息 - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.ChannelPost)). - Dict(utils.GetChatDict(&update.ChannelPost.Chat)). - Str("text", update.ChannelPost.Text). - Int("messageID", update.ChannelPost.ID). - Msg("channel post") - if update.ChannelPost.ViaBot != nil { - // 在频道中由 bot 发送 - _, viaBot := utils.GetUserDict(update.ChannelPost.ViaBot) - logger.Debug(). - Dict("viaBot", viaBot). - Dict(utils.GetChatDict(&update.ChannelPost.Chat)). - Str("text", update.ChannelPost.Text). - Int("messageID", update.ChannelPost.ID). - Msg("channel post send via bot") - } - if update.ChannelPost.SenderChat == nil { - // 没有身份信息 - logger.Debug(). - Dict(utils.GetChatDict(&update.ChannelPost.Chat)). - Str("text", update.ChannelPost.Text). - Int("messageID", update.ChannelPost.ID). - Msg("channel post from nobody") - } - } else if update.EditedChannelPost != nil { - // 频道中编辑过的消息 - if update.EditedChannelPost.Caption != "" { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)). - Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). - Int("messageID", update.EditedChannelPost.ID). - Str("editedCaption", update.EditedChannelPost.Caption). - Msg("edited channel post caption") - } else { - logger.Debug(). - Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)). - Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). - Int("messageID", update.EditedChannelPost.ID). - Str("editedText", update.EditedChannelPost.Text). - Msg("edited channel post") - } - } else { - // 其他没有加入的更新类型 - logger.Warn(). - Str("updateType", string(update_utils.GetUpdateType(update).InString())). - Msg("Receive a no tagged update type") } + + } // 处理所有信息请求的处理函数,触发条件为任何消息 diff --git a/main.go b/main.go index 18cad73..39caa5a 100644 --- a/main.go +++ b/main.go @@ -29,9 +29,6 @@ func main() { // log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) ctx = logger.WithContext(ctx) - logger.Info(). - Str("version", runtime.Version()). - Msg("trbot") // read configs if err := configs.InitBot(ctx); err != nil { @@ -40,9 +37,11 @@ func main() { // set log level from config zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog()) - if zerolog.GlobalLevel() == zerolog.DebugLevel { - consts.IsDebugMode = true - } + + logger.Warn(). + Str("runtime", runtime.Version()). + Str("logLevel", zerolog.GlobalLevel().String()). + Msg("trbot") opts := []bot.Option{ bot.WithDefaultHandler(defaultHandler), diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index 239ecb2..765b0d4 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -5,9 +5,9 @@ import ( "fmt" "image/png" "io" - "log" "net/http" "os" + "os/exec" "path/filepath" "strings" "trbot/database" @@ -27,6 +27,7 @@ import ( var StickerCache_path string = filepath.Join(consts.CacheDirectory, "sticker/") var StickerCachePNG_path string = filepath.Join(consts.CacheDirectory, "sticker_png/") +var StickerCacheGIF_path string = filepath.Join(consts.CacheDirectory, "sticker_gif/") var StickerCacheZip_path string = filepath.Join(consts.CacheDirectory, "sticker_zip/") func init() { @@ -58,6 +59,7 @@ func init() { type stickerDatas struct { Data io.Reader + IsConverted bool IsCustomSticker bool StickerCount int StickerIndex int @@ -102,7 +104,7 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.From.ID, - Text: fmt.Sprintf("下载贴纸时发生了一些错误\n
Error downloading sticker: %s", err), + Text: fmt.Sprintf("下载贴纸时发生了一些错误\n
Failed to download sticker: %s", err), ParseMode: models.ParseModeHTML, }) if err != nil { @@ -126,6 +128,7 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { if opts.Update.Message.Sticker.IsVideo { documentParams.Caption = "
see wikipedia/WebM" stickerFileSuffix = "webm" + // stickerFileSuffix = "gif" } else if opts.Update.Message.Sticker.IsAnimated { documentParams.Caption = "
see stickers/animated-stickers" stickerFileSuffix = "tgs.file" @@ -157,7 +160,7 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Failed to send sticker png file to user") + Msg("Failed to send sticker file to user") return err } @@ -196,7 +199,7 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Warn(). Err(err). Str("setName", opts.Update.Message.Sticker.SetName). - Msg("Get sticker set failed, download it as a custom sticker") + Msg("Failed to get sticker set info, download it as a custom sticker") // 到这里是因为用户发送的贴纸对应的贴纸包已经被删除了,但贴纸中的信息还有对应的 SetName,会触发查询,但因为贴纸包被删了就查不到,将 index 值设为 -1,缓存后当作自定义贴纸继续 data.IsCustomSticker = true @@ -228,11 +231,14 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) stickerFileNameWithDot = fmt.Sprintf("%s.", opts.Update.Message.Sticker.FileID) } - var filePath string = StickerCache_path + stickerSetNamePrivate + "/" // 保存贴纸源文件的目录 .cache/sticker/setName/ - var originFullPath string = filePath + stickerFileNameWithDot + fileSuffix // 到贴纸文件的完整目录 .cache/sticker/setName/stickerFileName.webp + var filePath string = filepath.Join(StickerCache_path, stickerSetNamePrivate) // 保存贴纸源文件的目录 .cache/sticker/setName/ + var originFullPath string = filepath.Join(filePath, stickerFileNameWithDot + fileSuffix) // 到贴纸文件的完整目录 .cache/sticker/setName/stickerFileName.webp - var PNGFilePath string = StickerCachePNG_path + stickerSetNamePrivate + "/" // 转码后为 png 格式的目录 .cache/sticker_png/setName/ - var toPNGFullPath string = PNGFilePath + stickerFileNameWithDot + "png" // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png + var PNGFilePath string = filepath.Join(StickerCachePNG_path, stickerSetNamePrivate) // 转码后为 png 格式的目录 .cache/sticker_png/setName/ + var toPNGFullPath string = filepath.Join(PNGFilePath, stickerFileNameWithDot + "png") // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png + + var GIFFilePath string = filepath.Join(StickerCacheGIF_path, stickerSetNamePrivate) // 转码后为 png 格式的目录 .cache/sticker_png/setName/ + var toGIFFullPath string = filepath.Join(GIFFilePath, stickerFileNameWithDot + "gif") // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png _, err := os.Stat(originFullPath) // 检查贴纸源文件是否已缓存 if err != nil { @@ -249,8 +255,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("fileID", opts.Update.Message.Sticker.FileID). - Msg("error getting sticker file info") - return nil, fmt.Errorf("error getting sticker fileinfo %s: %v", opts.Update.Message.Sticker.FileID, err) + Msg("Failed to get sticker file info") + return nil, fmt.Errorf("failed to get sticker file [%s] info: %w", opts.Update.Message.Sticker.FileID, err) } // 组合链接下载贴纸源文件 @@ -259,8 +265,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("filePath", fileinfo.FilePath). - Msg("error downloading sticker file") - return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) + Msg("Failed to download sticker file") + return nil, fmt.Errorf("failed to download sticker file [%s]: %w", fileinfo.FilePath, err) } defer resp.Body.Close() @@ -270,8 +276,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("filePath", filePath). - Msg("error creating directory") - return nil, fmt.Errorf("error creating directory %s: %w", filePath, err) + Msg("Failed to create sticker directory to save sticker") + return nil, fmt.Errorf("failed to create directory [%s] to save sticker: %w", filePath, err) } // 创建贴纸空文件 @@ -280,8 +286,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("originFullPath", originFullPath). - Msg("error creating file") - return nil, fmt.Errorf("error creating file %s: %w", originFullPath, err) + Msg("Failed to create sticker file") + return nil, fmt.Errorf("failed to create sticker file [%s]: %w", originFullPath, err) } defer downloadedSticker.Close() @@ -291,15 +297,15 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("originFullPath", originFullPath). - Msg("error writing sticker data to file") - return nil, fmt.Errorf("error writing to file %s: %w", originFullPath, err) + Msg("Failed to writing sticker data to file") + return nil, fmt.Errorf("failed to writing sticker data to file [%s]: %w", originFullPath, err) } } else { logger.Error(). Err(err). Str("originFullPath", originFullPath). - Msg("error when reading cached file info") - return nil, fmt.Errorf("error when reading cached file info: %w", err) + Msg("Failed to read cached sticker file info") + return nil, fmt.Errorf("failed to read cached sticker file [%s] info: %w", originFullPath, err) } } else { // 文件已存在,跳过下载 @@ -310,8 +316,58 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) var finalFullPath string // 存放最后读取并发送的文件完整目录 .cache/sticker/setName/stickerFileName.webp - // 如果贴纸类型不是视频和矢量,进行转换 - if !opts.Update.Message.Sticker.IsVideo && !opts.Update.Message.Sticker.IsAnimated { + if opts.Update.Message.Sticker.IsAnimated { + // tgs + // 不需要转码,直接读取原贴纸文件 + finalFullPath = originFullPath + } else if opts.Update.Message.Sticker.IsVideo { + // webm, convert to gif + _, err = os.Stat(toGIFFullPath) // 使用目录提前检查一下是否已经转换过 + if err != nil { + // 如果提示不存在,进行转换 + if os.IsNotExist(err) { + // 日志提示该文件没转换,正在转换 + logger.Trace(). + Str("toGIFFullPath", toGIFFullPath). + Msg("sticker file does not convert, converting") + + // 创建保存贴纸的目录 + err = os.MkdirAll(GIFFilePath, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("GIFFilePath", GIFFilePath). + Msg("Failed to create directory to convert file") + return nil, fmt.Errorf("failed to create directory [%s] to convert sticker file: %w", GIFFilePath, err) + } + + // 读取原贴纸文件,转码后存储到 png 格式贴纸的完整目录 + err = convertWebmToGif(originFullPath, toGIFFullPath) + if err != nil { + logger.Error(). + Err(err). + Str("originFullPath", originFullPath). + Msg("Failed to convert webm to gif") + return nil, fmt.Errorf("failed to convert webm [%s] to gif: %w", originFullPath, err) + } + } else { + // 其他错误 + logger.Error(). + Err(err). + Str("toGIFFullPath", toGIFFullPath). + Msg("Failed to read converted file info") + return nil, fmt.Errorf("failed to read converted sticker file [%s] info: %w", toGIFFullPath, err) + } + } else { + // 文件存在,跳过转换 + logger.Trace(). + Str("toGIFFullPath", toGIFFullPath). + Msg("sticker file already converted to gif") + } + // 处理完成,将最后要读取的目录设为转码后 gif 格式贴纸的完整目录 + finalFullPath = toGIFFullPath + } else { + // webp, need convert to png _, err = os.Stat(toPNGFullPath) // 使用目录提前检查一下是否已经转换过 if err != nil { // 如果提示不存在,进行转换 @@ -319,7 +375,7 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) // 日志提示该文件没转换,正在转换 logger.Trace(). Str("toPNGFullPath", toPNGFullPath). - Msg("file does not convert, converting") + Msg("sticker file does not convert, converting") // 创建保存贴纸的目录 err = os.MkdirAll(PNGFilePath, 0755) @@ -327,8 +383,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("PNGFilePath", PNGFilePath). - Msg("error creating directory to convert file") - return nil, fmt.Errorf("error creating directory to convert file %s: %w", PNGFilePath, err) + Msg("Failed to create directory to convert sticker") + return nil, fmt.Errorf("failed to create directory [%s] to convert sticker: %w", PNGFilePath, err) } // 读取原贴纸文件,转码后存储到 png 格式贴纸的完整目录 @@ -337,28 +393,25 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("originFullPath", originFullPath). - Msg("error converting webp to png") - return nil, fmt.Errorf("error converting webp to png %s: %w", originFullPath, err) + Msg("Failed to convert webp to png") + return nil, fmt.Errorf("failed to convert webp [%s] to png: %w", originFullPath, err) } } else { // 其他错误 logger.Error(). Err(err). Str("toPNGFullPath", toPNGFullPath). - Msg("error when reading converted file info") - return nil, fmt.Errorf("error when reading converted file info: %w", err) + Msg("Failed to read converted sticker file info") + return nil, fmt.Errorf("failed to read converted png sticker file [%s] info : %w", toPNGFullPath, err) } } else { // 文件存在,跳过转换 logger.Trace(). Str("toPNGFullPath", toPNGFullPath). - Msg("file already converted") + Msg("sticker file already converted to png") } // 处理完成,将最后要读取的目录设为转码后 png 格式贴纸的完整目录 finalFullPath = toPNGFullPath - } else { - // 不需要转码,直接读取原贴纸文件 - finalFullPath = originFullPath } // 逻辑完成,读取最后的文件,返回给上一级函数 @@ -367,7 +420,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("finalFullPath", finalFullPath). - Msg("error opening sticker file") + Msg("Failed to open sticker file") + return nil, fmt.Errorf("failed to open sticker file [%s]: %w", finalFullPath, err) } return &data, nil @@ -511,8 +565,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S ). Msg("start download sticker set") - filePath := StickerCache_path + stickerSet.Name + "/" - PNGFilePath := StickerCachePNG_path + stickerSet.Name + "/" + filePath := filepath.Join(StickerCache_path, stickerSet.Name) + PNGFilePath := filepath.Join(StickerCachePNG_path, stickerSet.Name) var allCached bool = true var allConverted bool = true @@ -537,8 +591,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S stickerCount_webp++ } - var originFullPath string = filePath + stickerfileName + fileSuffix - var toPNGFullPath string = PNGFilePath + stickerfileName + "png" + var originFullPath string = filepath.Join(filePath, stickerfileName + fileSuffix) + var toPNGFullPath string = filepath.Join(PNGFilePath, stickerfileName + "png") _, err := os.Stat(originFullPath) // 检查单个贴纸是否已缓存 if err != nil { @@ -579,8 +633,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("filePath", filePath). - Msg("error creating directory") - return nil, fmt.Errorf("error creating directory %s: %w", filePath, err) + Msg("Failed to creat directory to save sticker") + return nil, fmt.Errorf("failed to create directory [%s] to save sticker: %w", filePath, err) } // 创建文件并保存 @@ -590,8 +644,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("originFullPath", originFullPath). - Msg("error creating file") - return nil, fmt.Errorf("error creating file %s: %w", originFullPath, err) + Msg("Failed to create sticker file") + return nil, fmt.Errorf("failed to create sticker file [%s]: %w", originFullPath, err) } defer downloadedSticker.Close() @@ -602,17 +656,17 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("originFullPath", originFullPath). - Msg("error writing sticker data to file") + Msg("Failed to writing sticker data to file") - return nil, fmt.Errorf("error writing to file %s: %w", originFullPath, err) + return nil, fmt.Errorf("failed to writing sticker data to file [%s]: %w", originFullPath, err) } } else { logger.Error(). Err(err). Int("stickerIndex", i). Str("originFullPath", originFullPath). - Msg("error when reading cached file info") - return nil, fmt.Errorf("error when reading cached file info: %w", err) + Msg("Failed to read cached sticker file info") + return nil, fmt.Errorf("failed to read cached sticker file [%s] info: %w", originFullPath, err) } } else { // 存在跳过下载过程 @@ -696,7 +750,9 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S compressFolderPath = filePath } - _, err := os.Stat(StickerCacheZip_path + zipFileName) // 检查压缩包文件是否存在 + var zipFileFullPath string = filepath.Join(StickerCacheZip_path, zipFileName) + + _, err := os.Stat(zipFileFullPath) // 检查压缩包文件是否存在 if err != nil { if os.IsNotExist(err) { isZiped = false @@ -704,75 +760,79 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S if err != nil { logger.Error(). Err(err). - Str("zipFilePath", StickerCacheZip_path). - Msg("error creating zip file directory") - return nil, fmt.Errorf("error creating zip file directory %s: %w", StickerCacheZip_path, err) + Str("StickerCacheZip_path", StickerCacheZip_path). + Msg("Failed to create zip file directory") + return nil, fmt.Errorf("failed to create zip file directory [%s]: %w", StickerCacheZip_path, err) } - err = zipFolder(compressFolderPath, StickerCacheZip_path + zipFileName) + err = zipFolder(compressFolderPath, zipFileFullPath) if err != nil { logger.Error(). Err(err). - Str("zipFilePath", StickerCacheZip_path + zipFileName). - Msg("error zipping sticker folder") - return nil, fmt.Errorf("error zipping folder %s: %w", compressFolderPath, err) + Str("compressFolderPath", compressFolderPath). + Msg("Failed to compress sticker folder") + return nil, fmt.Errorf("failed to compress sticker folder [%s]: %w", compressFolderPath, err) } logger.Trace(). - Str("zipFilePath", StickerCacheZip_path + zipFileName). - Msg("successfully zipped folder") + Str("compressFolderPath", compressFolderPath). + Str("zipFileFullPath", zipFileFullPath). + Msg("Compress sticker folder successfully") } else { logger.Error(). Err(err). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). - Msg("error when reading sticker set zip file info") - return nil, fmt.Errorf("error when reading sticker set zip file info: %w", err) + Str("zipFileFullPath", zipFileFullPath). + Msg("Failed to read compressed sticker set zip file info") + return nil, fmt.Errorf("failed to read compressed sticker set zip file [%s] info: %w", zipFileFullPath, err) } } else { logger.Trace(). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). - Msg("sticker set zip file already cached") + Str("zipFileFullPath", zipFileFullPath). + Msg("sticker set zip file already compressed") } // 读取压缩后的贴纸包 - data.Data, err = os.Open(StickerCacheZip_path + zipFileName) + data.Data, err = os.Open(zipFileFullPath) if err != nil { logger.Error(). Err(err). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). - Msg("error opening zip file") - return nil, fmt.Errorf("error opening zip file %s: %w", StickerCacheZip_path + zipFileName, err) + Str("zipFileFullPath", zipFileFullPath). + Msg("Failed to open compressed sticker set zip file") + return nil, fmt.Errorf("failed to open compressed sticker set zip file [%s]: %w", zipFileFullPath, err) } - if isZiped { // 存在已经完成压缩的贴纸包 + if isZiped { + // 存在已经完成压缩的贴纸包(原始格式或已转换) logger.Info(). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Str("zipFileFullPath", zipFileFullPath). Dict("stickerSet", zerolog.Dict(). Str("title", data.StickerSetTitle). Str("name", data.StickerSetName). Int("count", data.StickerCount), ). Msg("sticker set already zipped") - } else if isOnlyPNG && allConverted { // 仅需要 PNG 格式,且贴纸包完全转换成 PNG 格式,但尚未压缩 + } else if isOnlyPNG && allConverted { + // 仅需要 PNG 格式,且贴纸包完全转换成 PNG 格式,但尚未压缩 logger.Info(). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Str("zipFileFullPath", zipFileFullPath). Dict("stickerSet", zerolog.Dict(). Str("title", data.StickerSetTitle). Str("name", data.StickerSetName). Int("count", data.StickerCount), ). Msg("sticker set already converted") - } else if allCached { // 贴纸包中的贴纸已经全部缓存了 + } else if allCached { + // 贴纸包中的贴纸已经全部缓存了 logger.Info(). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Str("zipFileFullPath", zipFileFullPath). Dict("stickerSet", zerolog.Dict(). Str("title", data.StickerSetTitle). Str("name", data.StickerSetName). Int("count", data.StickerCount), ). Msg("sticker set already cached") - log.Printf("sticker set \"%s\"[%s](%d) is already cached", stickerSet.Title, stickerSet.Name, data.StickerCount) - } else { // 新下载的贴纸包(如果有部分已经下载了也是这个) + } else { + // 新下载的贴纸包(如果有部分已经下载了也是这个) logger.Info(). - Str("stickerSetZipPath", StickerCacheZip_path + zipFileName). + Str("zipFileFullPath", zipFileFullPath). Dict("stickerSet", zerolog.Dict(). Str("title", data.StickerSetTitle). Str("name", data.StickerSetName). @@ -814,12 +874,19 @@ func convertWebPToPNG(webpPath, pngPath string) error { return nil } -func zipFolder(srcDir, zipFile string) error { - // 创建 ZIP 文件 - outFile, err := os.Create(zipFile) +func convertWebmToGif(webmPath, gifPath string) error { + cmd := exec.Command("./ffmpeg/bin/ffmpeg.exe", "-i", webmPath, "-vf", "fps=10", gifPath) + err := cmd.Run() if err != nil { return err } + return nil +} + +func zipFolder(srcDir, zipFile string) error { + // 创建 ZIP 文件 + outFile, err := os.Create(zipFile) + if err != nil { return err } defer outFile.Close() // 创建 ZIP 写入器 @@ -828,33 +895,23 @@ func zipFolder(srcDir, zipFile string) error { // 遍历文件夹并添加文件到 ZIP err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } + if err != nil { return err } // 计算文件在 ZIP 中的相对路径 relPath, err := filepath.Rel(srcDir, path) - if err != nil { - return err - } + if err != nil { return err } // 如果是目录,则跳过 - if info.IsDir() { - return nil - } + if info.IsDir() { return nil } // 打开文件 file, err := os.Open(path) - if err != nil { - return err - } + if err != nil { return err } defer file.Close() // 创建 ZIP 内的文件 zipFileWriter, err := zipWriter.Create(relPath) - if err != nil { - return err - } + if err != nil { return err } // 复制文件内容到 ZIP _, err = io.Copy(zipFileWriter, file) diff --git a/utils/configs/init.go b/utils/configs/init.go index 9b59f48..4538b29 100644 --- a/utils/configs/init.go +++ b/utils/configs/init.go @@ -198,7 +198,7 @@ func readEnvironment(ctx context.Context) error { if os.Getenv("DEBUG") != "" { BotConfig.LogLevel = "debug" logger.Warn(). - Msg("DEBUG environment variable is set, set log level to debug") + Msg("The DEBUG environment variable is set") } logLevel := os.Getenv("LOG_LEVEL") diff --git a/utils/configs/webhook.go b/utils/configs/webhook.go index 0e2807f..19e3232 100644 --- a/utils/configs/webhook.go +++ b/utils/configs/webhook.go @@ -32,7 +32,7 @@ func IsUsingWebhook(ctx context.Context) bool { return true } - logger.Info(). + logger.Warn(). Msg("No Webhook URL in environment and .env file, using getUpdate mode") return false } diff --git a/utils/consts/consts.go b/utils/consts/consts.go index 8d8a3a7..06656e3 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -4,8 +4,6 @@ import ( "github.com/go-telegram/bot/models" ) -var IsDebugMode bool - var WebhookListenPort string = "localhost:2847" var YAMLDataBasePath string = "./db_yaml/" -- 2.49.1 From 7a0e719c88c56fdd68233a832326b4f6bb675bc6 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Wed, 11 Jun 2025 01:58:07 +0800 Subject: [PATCH 10/27] save changes --- bad_plugins/plugin_detect_keyword.go | 576 ++++++++++++++------------- logstruct.txt | 8 +- 2 files changed, 299 insertions(+), 285 deletions(-) diff --git a/bad_plugins/plugin_detect_keyword.go b/bad_plugins/plugin_detect_keyword.go index 94ab5b3..a73ff6d 100644 --- a/bad_plugins/plugin_detect_keyword.go +++ b/bad_plugins/plugin_detect_keyword.go @@ -24,8 +24,9 @@ var KeywordDataList KeywordData = KeywordData{ Chats: map[int64]KeywordChatList{}, Users: map[int64]KeywordUserList{}, } -var KeywordDataErr error -var KeywordData_path string = filepath.Join(consts.YAMLDataBasePath, "detectkeyword/") +var KeywordDataErr error +var KeywordDataDir string = filepath.Join(consts.YAMLDataBasePath, "detectkeyword/") +var KeywordDataPath string = filepath.Join(KeywordDataDir, consts.YAMLFileName) func init() { plugin_utils.AddInitializer(plugin_utils.Initializer{ @@ -149,7 +150,7 @@ func (user KeywordUserList)selectChat() models.ReplyMarkup { type ChatForUser struct { ChatID int64 `yaml:"ChatID"` IsDisable bool `yaml:"IsDisable,omitempty"` - IsConfirmDelete bool `yaml:"IsConfirmDelete,omitempty"` + IsConfirmDelete bool `yaml:"IsConfirmDelete,omitempty"` // todo Keyword []string `yaml:"Keyword"` } @@ -161,56 +162,60 @@ func ReadKeywordList(ctx context.Context) error { Str("funcName", "ReadKeywordList"). Logger() - // 拼接路径和文件名,打开数据库 - file, err := os.Open(filepath.Join(KeywordData_path, consts.YAMLFileName)) + // 打开数据库文件 + file, err := os.Open(KeywordDataPath) if err != nil { if os.IsNotExist(err) { // 如果找不到文件,则新建一个 logger.Warn(). - Msg("Not found database file. Create a new one") + Str("path", KeywordDataPath). + Msg("Not found keyword data file. Create a new one") err = SaveKeywordList(ctx) if err != nil { logger.Error(). Err(err). - Msg("Create empty database file failed") - KeywordDataErr = err - return err + Str("path", KeywordDataPath). + Msg("Failed to create empty keyword data file") + KeywordDataErr = fmt.Errorf("failed to create empty keyword data file: %w", err) + return KeywordDataErr } } else { // 其他错误 logger.Error(). Err(err). - Msg("Open database file failed") - KeywordDataErr = err - return err + Str("path", KeywordDataPath). + Msg("Failed to open keyword data file") + KeywordDataErr = fmt.Errorf("failed to open keyword data file: %w", err) + return KeywordDataErr } } defer file.Close() // 解码 yaml 文件 - decoder := yaml.NewDecoder(file) - err = decoder.Decode(&lists) + err = yaml.NewDecoder(file).Decode(&lists) if err != nil { if err == io.EOF { // 文件存在,但为空,则用空的数据库覆盖保存 logger.Warn(). - Msg("Keyword list looks empty. now format it") + Str("path", KeywordDataPath). + Msg("Keyword data file looks empty. now format it") err = SaveKeywordList(ctx) if err != nil { // 保存空的数据库失败 logger.Error(). Err(err). - Msg("Create empty database file failed") - KeywordDataErr = err - return err + Str("path", KeywordDataPath). + Msg("Failed to create empty keyword data file") + KeywordDataErr = fmt.Errorf("failed to create empty keyword data file: %w", err) + return KeywordDataErr } } else { // 其他错误 logger.Error(). Err(err). - Msg("Failed to decode keyword list") - KeywordDataErr = err - return err + Msg("Failed to decode keyword data list") + KeywordDataErr = fmt.Errorf("failed to decode keyword data list: %w", err) + return KeywordDataErr } } @@ -230,85 +235,321 @@ func SaveKeywordList(ctx context.Context) error { if err != nil { logger.Error(). Err(err). - Msg("Failed to marshal keyword list") - KeywordDataErr = err - return err + Msg("Failed to marshal keyword data list") + KeywordDataErr = fmt.Errorf("failed to marshal keyword data list: %w", err) + return KeywordDataErr } - _, err = os.Stat(KeywordData_path) + // 检查数据库目录是否存在,不然则创建 + _, err = os.Stat(KeywordDataDir) if err != nil { if os.IsNotExist(err) { logger.Warn(). - Str("path", KeywordData_path). - Msg("Keyword data directory not exist, now create it") - err = os.MkdirAll(KeywordData_path, 0755) + Str("directory", KeywordDataDir). + Msg("Not found keyword data directory, now create it") + err = os.MkdirAll(KeywordDataDir, 0755) if err != nil { logger.Error(). Err(err). - Str("path", KeywordData_path). + Str("directory", KeywordDataDir). Msg("Failed to create keyword data directory") - KeywordDataErr = err - return err + KeywordDataErr = fmt.Errorf("failed to create keyword data directory: %s", err) + return KeywordDataErr } logger.Trace(). - Msg("Keyword data directory created successfully") + Str("directory", KeywordDataDir). + Msg("Create keyword data directory success") } else { logger.Error(). Err(err). - Str("path", KeywordData_path). - Msg("Open keyword data directory failed") - KeywordDataErr = err - return err + Str("directory", KeywordDataDir). + Msg("Failed to open keyword data directory") + KeywordDataErr = fmt.Errorf("failed to open keyword data directory: %s", err) + return KeywordDataErr } } - _, err = os.Stat(filepath.Join(KeywordData_path, consts.YAMLFileName)) + // 检查数据库文件是否存在,不然则创建 + _, err = os.Stat(KeywordDataPath) if err != nil { if os.IsNotExist(err) { logger.Warn(). - Msg("Keyword data file not exist, now create it") - _, err := os.Create(filepath.Join(KeywordData_path, consts.YAMLFileName)) + Str("path", KeywordDataPath). + Msg("Not found keyword data file. Create a new one") + _, err := os.Create(KeywordDataPath) if err != nil { logger.Error(). Err(err). Msg("Failed to create keyword data file") - KeywordDataErr = err - return err + KeywordDataErr = fmt.Errorf("failed to create keyword data file: %w", err) + return KeywordDataErr } logger.Trace(). - Msg("Keyword data file created successfully") + Str("path", KeywordDataPath). + Msg("Created keyword data file success") } else { logger.Error(). Err(err). - Msg("Open keyword data file failed") - KeywordDataErr = err - return err + Str("path", KeywordDataPath). + Msg("Failed to open keyword data file") + KeywordDataErr = fmt.Errorf("failed to open keyword data file: %w", err) + return KeywordDataErr } } - err = os.WriteFile(filepath.Join(KeywordData_path, consts.YAMLFileName), data, 0644) + err = os.WriteFile(KeywordDataPath, data, 0644) if err != nil { logger.Error(). Err(err). - Msg("Failed to write keyword data file") - KeywordDataErr = err - return err + Str("path", KeywordDataPath). + Msg("Failed to write keyword data list into file") + KeywordDataErr = fmt.Errorf("failed to write keyword data list into file: %w", err) + return KeywordDataErr } logger.Trace(). - Msg("Keyword data file write successfully") - + Str("path", KeywordDataPath). + Msg("Save keyword data success") return nil } -func addKeywordHandler(opts *handler_structs.SubHandlerParams) { +func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str("funcName", "addKeywordHandler"). Logger() - if opts.Update.Message.Chat.Type != models.ChatTypePrivate { + if opts.Update.Message.Chat.Type == models.ChatTypePrivate { + // 与机器人的私聊对话 + user := KeywordDataList.Users[opts.Update.Message.From.ID] + if user.AddTime == "" { + // 初始化用户 + user = KeywordUserList{ + UserID: opts.Update.Message.From.ID, + AddTime: time.Now().Format(time.RFC3339), + Limit: 50, + IsDisable: false, + IsSilentNotice: false, + } + KeywordDataList.Users[opts.Update.Message.From.ID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to init user and save keyword list") + return nil + } + } + + // 用户没有添加任何群组 + if len(user.ChatsForUser) == 0 { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "您还没有添加任何群组,请在群组中使用 `/setkeyword` 命令来记录群组\n若发送信息后没有回应,请检查机器人是否在对应群组中", + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to send `no group for user` message") + } + return nil + } + + if len(opts.Fields) > 1 { + if user.AddingChatID != 0 { + var chatForUser ChatForUser + var chatForUserIndex int + for i, c := range user.ChatsForUser { + if c.ChatID == user.AddingChatID { + chatForUser = c + chatForUserIndex = i + } + } + + // 限制关键词长度 + if len(opts.Fields[1]) > 30 { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "抱歉,单个关键词长度不能超过 30 个字符", + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `keyword is too long` message failed") + } + return nil + } + + keyword := strings.ToLower(opts.Fields[1]) + var pendingMessage string + var button models.ReplyMarkup + var isKeywordExist bool + + // 判断是全局关键词还是群组关键词 + if user.AddingChatID == user.UserID { + // 全局关键词 + for _, k := range user.GlobalKeyword { + if k == keyword { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Str("globalKeyword", keyword). + Msg("User add a global keyword") + user.GlobalKeyword = append(user.GlobalKeyword, keyword) + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Str("globalKeyword", keyword). + Msg("Failed to add global keyword and save keyword list") + } + pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ]", opts.Fields[1]) + } else { + pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", opts.Fields[1]) + } + + } else { + targetChat := KeywordDataList.Chats[chatForUser.ChatID] + + // 群组关键词 + for _, k := range chatForUser.Keyword { + if k == keyword { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("User add a keyword to chat") + chatForUser.Keyword = append(chatForUser.Keyword, keyword) + user.ChatsForUser[chatForUserIndex] = chatForUser + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("Error add keyword and save keyword list") + } + + pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(opts.Fields[1])) + } else { + pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", opts.Fields[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) + } + } + if isKeywordExist { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "完成", + CallbackData: "detectkw_mng_finish", + }}}} + } else { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "撤销操作", + CallbackData: fmt.Sprintf("detectkw_mng_undo_%d_%s", user.AddingChatID, opts.Fields[1]), + }, + { + Text: "完成", + CallbackData: "detectkw_mng_finish", + }, + }}} + } + + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: pendingMessage, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: button, + }) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `keyword added` message failed") + } + } else { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "您还没有选定要将关键词添加到哪个群组,请在下方挑选一个您已经添加的群组", + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: user.selectChat(), + }) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `not selected chat yet` message failed") + } + } + } else { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: user.userStatus(), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: buildUserChatList(user), + }) + if err != nil { + logger.Error(). + Err(err). + Dict("user", zerolog.Dict(). + Str("name", utils.ShowUserName(opts.Update.Message.From)). + Str("username", opts.Update.Message.From.Username). + Int64("ID", opts.Update.Message.From.ID), + ). + Msg("Send `user group list keyboart` message failed") + } + } + } else { // 在群组中直接使用 /setkeyword 命令 chat := KeywordDataList.Chats[opts.Update.Message.Chat.ID] if chat.IsDisable { @@ -505,235 +746,8 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) { } } } - } else { - // 与机器人的私聊对话 - user := KeywordDataList.Users[opts.Update.Message.From.ID] - if user.AddTime == "" { - // 初始化用户 - user = KeywordUserList{ - UserID: opts.Update.Message.From.ID, - AddTime: time.Now().Format(time.RFC3339), - Limit: 50, - IsDisable: false, - IsSilentNotice: false, - } - KeywordDataList.Users[opts.Update.Message.From.ID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Error init user and save keyword list") - } - - } - if len(user.ChatsForUser) == 0 { - // 没有添加群组 - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "您还没有添加任何群组,请在群组中使用 `/setkeyword` 命令来记录群组\n若发送信息后没有回应,请检查机器人是否在对应群组中", - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Send `no group for user` message failed") - } - return - } - - if len(opts.Fields) > 1 { - if user.AddingChatID != 0 { - var chatForUser ChatForUser - var chatForUserIndex int - for i, c := range user.ChatsForUser { - if c.ChatID == user.AddingChatID { - chatForUser = c - chatForUserIndex = i - } - } - - // 限制关键词长度 - if len(opts.Fields[1]) > 30 { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "抱歉,单个关键词长度不能超过 30 个字符", - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - }) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Send `keyword is too long` message failed") - } - return - } - - keyword := strings.ToLower(opts.Fields[1]) - var pendingMessage string - var button models.ReplyMarkup - var isKeywordExist bool - - // 判断是全局关键词还是群组关键词 - if user.AddingChatID == user.UserID { - // 全局关键词 - for _, k := range user.GlobalKeyword { - if k == keyword { - isKeywordExist = true - break - } - } - if !isKeywordExist { - logger.Debug(). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Str("globalKeyword", keyword). - Msg("User add a global keyword") - user.GlobalKeyword = append(user.GlobalKeyword, keyword) - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Str("globalKeyword", keyword). - Msg("Error add global keyword and save keyword list") - } - pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ]", opts.Fields[1]) - } else { - pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", opts.Fields[1]) - } - - } else { - targetChat := KeywordDataList.Chats[chatForUser.ChatID] - - // 群组关键词 - for _, k := range chatForUser.Keyword { - if k == keyword { - isKeywordExist = true - break - } - } - if !isKeywordExist { - logger.Debug(). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Int64("chatID", chatForUser.ChatID). - Str("keyword", keyword). - Msg("User add a keyword to chat") - chatForUser.Keyword = append(chatForUser.Keyword, keyword) - user.ChatsForUser[chatForUserIndex] = chatForUser - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Int64("chatID", chatForUser.ChatID). - Str("keyword", keyword). - Msg("Error add keyword and save keyword list") - } - - pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(opts.Fields[1])) - } else { - pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", opts.Fields[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) - } - } - if isKeywordExist { - button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "完成", - CallbackData: "detectkw_mng_finish", - }}}} - } else { - button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "撤销操作", - CallbackData: fmt.Sprintf("detectkw_mng_undo_%d_%s", user.AddingChatID, opts.Fields[1]), - }, - { - Text: "完成", - CallbackData: "detectkw_mng_finish", - }, - }}} - } - - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: pendingMessage, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: button, - }) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Send `keyword added` message failed") - } - } else { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "您还没有选定要将关键词添加到哪个群组,请在下方挑选一个您已经添加的群组", - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: user.selectChat(), - }) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Send `not selected chat yet` message failed") - } - } - } else { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: user.userStatus(), - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: buildUserChatList(user), - }) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Send `user group list keyboart` message failed") - } - } } + return nil } func buildListenList() { diff --git a/logstruct.txt b/logstruct.txt index 62fae90..4f459a6 100644 --- a/logstruct.txt +++ b/logstruct.txt @@ -1,4 +1,4 @@ -bot.SendMessage: Failed to send `` message -bot.DeleteMessages: Failed to delete `` message -bot.AnswerInlineQuery: Failed to send `` inline result (sub handler can add a `Str("command", "log")` ) -bot.AnswerCallbackQuery: Failed to send `` callback answer +bot.SendMessage: Failed to send [%s] message +bot.DeleteMessages: Failed to delete [%s] message +bot.AnswerInlineQuery: Failed to send [%s] inline result (sub handler can add a `Str("command", "log")` ) +bot.AnswerCallbackQuery: Failed to send [%s] callback answer -- 2.49.1 From 7ebd6a911f2cab6ab9dad6451c4d9b1f49e2f270 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sat, 14 Jun 2025 08:14:27 +0800 Subject: [PATCH 11/27] save changes use mult writer and filter to save different level log to log file add makefile to build and inject version info use `github.com/pkg/errors` and zerolog to print stack and save it --- Makefile | 13 +++++++++++++ go.mod | 1 + go.sum | 1 + main.go | 33 +++++++++++++++++++++++++++++---- utils/consts/consts.go | 6 ++++++ utils/mess/mess.go | 2 -- utils/utils.go | 8 ++++---- 7 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bc52a1f --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +VERSION := $(shell git describe --tags --always) +COMMIT := $(shell git rev-parse HEAD) +CHANGES := $(shell git status -s | wc -l) +TIME := $(shell date --rfc-3339=seconds) +HOSTNAME := $(shell hostname) +LDFLAGS := -X 'trbot/utils/consts.Version=$(VERSION)' \ + -X 'trbot/utils/consts.Commit=$(COMMIT)' \ + -X 'trbot/utils/consts.Changes=$(CHANGES)' \ + -X 'trbot/utils/consts.BuildTime=$(TIME)' \ + -X 'trbot/utils/consts.BuildMachine=$(HOSTNAME)' + +build: + go build -ldflags "$(LDFLAGS)" diff --git a/go.mod b/go.mod index b4152ca..d29e9d7 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/sys v0.32.0 // indirect ) diff --git a/go.sum b/go.sum index cdcf385..dc8aed1 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,7 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/multiplay/go-ts3 v1.2.0 h1:LaN6iz9TZjHXxhLwfU0gjUgDxX0Hq7BCbuyuRhYMl3U= github.com/multiplay/go-ts3 v1.2.0/go.mod h1:OdNmiO3uV++4SldaJDQTIGg8gNAu5MOiccZiAqVqUZA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/main.go b/main.go index 39caa5a..bc92588 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ import ( "github.com/go-telegram/bot" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/rs/zerolog/pkgerrors" ) func main() { @@ -25,9 +26,28 @@ func main() { defer cancel() // create a logger and attached it into ctx - // logger := log.Output(os.Stderr) - // log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + var logger zerolog.Logger + file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + fileWriter := &zerolog.FilteredLevelWriter{ + Writer: zerolog.MultiLevelWriter(file), + Level: zerolog.WarnLevel, + } + + multWriter := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stdout}, fileWriter) + logger = zerolog.New(multWriter).With().Timestamp().Logger() + logger.Info(). + Str("logFile", consts.LogFilePath). + Str("levelForLogFile", zerolog.WarnLevel.String()). + Msg("Use mult log writer") + } else { + logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + logger.Error(). + Err(err). + Str("logFile", consts.LogFilePath). + Msg("Failed to open log file, use console writer only") + } + ctx = logger.WithContext(ctx) // read configs @@ -37,8 +57,13 @@ func main() { // set log level from config zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog()) + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack logger.Warn(). + Str("version", consts.Version). + Str("commit", consts.Commit[:13]). + Str("buildTime", consts.BuildTime). + Str("changes", consts.Changes). Str("runtime", runtime.Version()). Str("logLevel", zerolog.GlobalLevel().String()). Msg("trbot") @@ -68,7 +93,7 @@ func main() { // start handler custom signals go signals.SignalsHandler(ctx) - // register plugin (plugin use `init()` first, then plugin user `InitPlugins` second, and internal last) + // register plugin (plugin use `init()` first, then plugin use `InitPlugins` second, and internal last) internal_plugin.Register(ctx) // Select mode by Webhook config diff --git a/utils/consts/consts.go b/utils/consts/consts.go index 06656e3..c1200d5 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -13,3 +13,9 @@ var CacheDirectory string = "./cache/" var LogFilePath string = YAMLDataBasePath + "log.txt" var BotMe *models.User // 用于存储 bot 信息 + +var Version string = "unknownVersion" +var Commit string = "unknownCommit" +var BuildTime string = "unknownTime" +var BuildMachine string = "unknownMachine" +var Changes string = "noChanges" diff --git a/utils/mess/mess.go b/utils/mess/mess.go index b1c55c4..2348264 100644 --- a/utils/mess/mess.go +++ b/utils/mess/mess.go @@ -16,8 +16,6 @@ import ( "github.com/go-telegram/bot/models" ) - - func PrintLogAndSave(message string) { log.Println(message) // 打开日志文件,如果不存在则创建 diff --git a/utils/utils.go b/utils/utils.go index ff8e292..ece3d16 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -14,6 +14,7 @@ import ( "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" + "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -435,11 +436,10 @@ func PanicCatcher(ctx context.Context, pluginName string) { panic := recover() if panic != nil { logger.Error(). - Interface("panic", panic). - // Str("Stack", getCurrentGoroutineStack()). - Str("panicFunc", pluginName). + Stack(). + Err(errors.WithStack(fmt.Errorf("%v", panic))). + Str("catchFunc", pluginName). Msg("Panic recovered") - fmt.Println("Stack", getCurrentGoroutineStack()) // mess.PrintLogAndSave(fmt.Sprintf("recovered panic in [%s]: \"%v\"\nStack: %s", pluginName, panic, getCurrentGoroutineStack())) } } -- 2.49.1 From 985f0c88dc3a00e0caca9728e807f3423460ed63 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sun, 15 Jun 2025 00:59:52 +0800 Subject: [PATCH 12/27] save change update some log's level show error when build without info add commit tag to panic catcher --- Makefile | 8 +- go.mod | 2 +- go.sum | 2 - handlers.go | 216 ++++++++++++++++++++++++++--------------- main.go | 25 +++-- utils/consts/consts.go | 11 ++- utils/mess/mess.go | 32 +++--- utils/utils.go | 1 + 8 files changed, 189 insertions(+), 108 deletions(-) diff --git a/Makefile b/Makefile index bc52a1f..c674ca9 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,12 @@ -VERSION := $(shell git describe --tags --always) COMMIT := $(shell git rev-parse HEAD) +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +VERSION := $(shell git describe --tags --always) CHANGES := $(shell git status -s | wc -l) TIME := $(shell date --rfc-3339=seconds) HOSTNAME := $(shell hostname) -LDFLAGS := -X 'trbot/utils/consts.Version=$(VERSION)' \ - -X 'trbot/utils/consts.Commit=$(COMMIT)' \ +LDFLAGS := -X 'trbot/utils/consts.Commit=$(COMMIT)' \ + -X 'trbot/utils/consts.Branch=$(BRANCH)' \ + -X 'trbot/utils/consts.Version=$(VERSION)' \ -X 'trbot/utils/consts.Changes=$(CHANGES)' \ -X 'trbot/utils/consts.BuildTime=$(TIME)' \ -X 'trbot/utils/consts.BuildMachine=$(HOSTNAME)' diff --git a/go.mod b/go.mod index d29e9d7..64ccd8b 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-telegram/bot v1.15.0 github.com/joho/godotenv v1.5.1 github.com/multiplay/go-ts3 v1.2.0 + github.com/pkg/errors v0.9.1 github.com/redis/go-redis/v9 v9.7.1 github.com/rs/zerolog v1.34.0 golang.org/x/image v0.23.0 @@ -20,7 +21,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pkg/errors v0.9.1 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/sys v0.32.0 // indirect ) diff --git a/go.sum b/go.sum index dc8aed1..7b6cb20 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/go-telegram/bot v1.14.0 h1:qknBErnf5O1CTWZDdDK/qqV8f7wWTf98gFIVW42m6dk= -github.com/go-telegram/bot v1.14.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= github.com/go-telegram/bot v1.15.0 h1:/ba5pp084MUhjR5sQDymQ7JNZ001CQa7QjtxLWcuGpg= github.com/go-telegram/bot v1.15.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= diff --git a/handlers.go b/handlers.go index c103c0e..eab75c9 100644 --- a/handlers.go +++ b/handlers.go @@ -32,18 +32,18 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } // Debug level or Trace Level - if zerolog.GlobalLevel() <= zerolog.DebugLevel { + if zerolog.GlobalLevel() <= zerolog.InfoLevel { if update.Message != nil { // 正常消息 if update.Message.Photo != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.Message)). Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). Str("caption", update.Message.Caption). Msg("photoMessage") } else if update.Message.Sticker != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.Message)). Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). @@ -52,7 +52,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Str("stickerFileID", update.Message.Sticker.FileID). Msg("stickerMessage") } else { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.Message)). Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). @@ -63,14 +63,14 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } else if update.EditedMessage != nil { // 私聊或群组消息被编辑 if update.EditedMessage.Caption != "" { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)). Dict(utils.GetChatDict(&update.EditedMessage.Chat)). Int("messageID", update.EditedMessage.ID). Str("editedCaption", update.EditedMessage.Caption). Msg("editedMessage") } else { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.EditedMessage)). Dict(utils.GetChatDict(&update.EditedMessage.Chat)). Int("messageID", update.EditedMessage.ID). @@ -79,14 +79,14 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } } else if update.InlineQuery != nil { // inline 查询 - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.InlineQuery.From)). Str("query", update.InlineQuery.Query). Msg("inline request") } else if update.ChosenInlineResult != nil { // inline 查询结果被选择 - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(&update.ChosenInlineResult.From)). Str("query", update.ChosenInlineResult.Query). Str("resultID", update.ChosenInlineResult.ResultID). @@ -94,7 +94,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } else if update.CallbackQuery != nil { // replymarkup 回调 - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(&update.CallbackQuery.From)). Dict(utils.GetChatDict(&update.CallbackQuery.Message.Message.Chat)). Str("query", update.CallbackQuery.Data). @@ -106,7 +106,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) if len(update.MessageReaction.OldReaction) > 0 { for i, oldReaction := range update.MessageReaction.OldReaction { if oldReaction.ReactionTypeEmoji != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.MessageReaction.User)). Dict(utils.GetChatDict(&update.MessageReaction.Chat)). Int("messageID", update.MessageReaction.MessageID). @@ -115,7 +115,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Int("count", i + 1). Msg("removed emoji reaction") } else if oldReaction.ReactionTypeCustomEmoji != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.MessageReaction.User)). Dict(utils.GetChatDict(&update.MessageReaction.Chat)). Int("messageID", update.MessageReaction.MessageID). @@ -124,7 +124,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Int("count", i + 1). Msg("removed custom emoji reaction") } else if oldReaction.ReactionTypePaid != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.MessageReaction.User)). Dict(utils.GetChatDict(&update.MessageReaction.Chat)). Int("messageID", update.MessageReaction.MessageID). @@ -137,7 +137,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) if len(update.MessageReaction.NewReaction) > 0 { for i, newReaction := range update.MessageReaction.NewReaction { if newReaction.ReactionTypeEmoji != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.MessageReaction.User)). Dict(utils.GetChatDict(&update.MessageReaction.Chat)). Int("messageID", update.MessageReaction.MessageID). @@ -146,7 +146,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Int("count", i + 1). Msg("add emoji reaction") } else if newReaction.ReactionTypeCustomEmoji != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.MessageReaction.User)). Dict(utils.GetChatDict(&update.MessageReaction.Chat)). Int("messageID", update.MessageReaction.MessageID). @@ -155,7 +155,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Int("count", i + 1). Msg("add custom emoji reaction") } else if newReaction.ReactionTypePaid != nil { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(update.MessageReaction.User)). Dict(utils.GetChatDict(&update.MessageReaction.Chat)). Int("messageID", update.MessageReaction.MessageID). @@ -193,7 +193,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } - logger.Debug(). + logger.Info(). Dict(utils.GetChatDict(&update.MessageReactionCount.Chat)). Dict("reactions", zerolog.Dict(). Dict("emoji", emoji). @@ -204,7 +204,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Msg("emoji reaction count updated") } else if update.ChannelPost != nil { // 频道信息 - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.ChannelPost)). Dict(utils.GetChatDict(&update.ChannelPost.Chat)). Str("text", update.ChannelPost.Text). @@ -213,7 +213,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) if update.ChannelPost.ViaBot != nil { // 在频道中由 bot 发送 _, viaBot := utils.GetUserDict(update.ChannelPost.ViaBot) - logger.Debug(). + logger.Info(). Dict("viaBot", viaBot). Dict(utils.GetChatDict(&update.ChannelPost.Chat)). Str("text", update.ChannelPost.Text). @@ -222,7 +222,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } if update.ChannelPost.SenderChat == nil { // 没有身份信息 - logger.Debug(). + logger.Info(). Dict(utils.GetChatDict(&update.ChannelPost.Chat)). Str("text", update.ChannelPost.Text). Int("messageID", update.ChannelPost.ID). @@ -231,14 +231,14 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) } else if update.EditedChannelPost != nil { // 频道中编辑过的消息 if update.EditedChannelPost.Caption != "" { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)). Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). Int("messageID", update.EditedChannelPost.ID). Str("editedCaption", update.EditedChannelPost.Caption). Msg("edited channel post caption") } else { - logger.Debug(). + logger.Info(). Dict(utils.GetUserOrSenderChatDict(update.EditedChannelPost)). Dict(utils.GetChatDict(&update.EditedChannelPost.Chat)). Int("messageID", update.EditedChannelPost.ID). @@ -267,28 +267,24 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) opts.ChatInfo.HasPendingCallbackQuery = false } - } // 处理所有信息请求的处理函数,触发条件为任何消息 func messageHandler(opts *handler_structs.SubHandlerParams) { defer utils.PanicCatcher(opts.Ctx, "messageHandler") - logger := zerolog.Ctx(opts.Ctx). - With(). - Str("funcName", "messageHandler"). - Logger() + logger := zerolog.Ctx(opts.Ctx) // 检测如果消息开头是 / 符号,作为命令来处理 if strings.HasPrefix(opts.Update.Message.Text, "/") { // 匹配默认的 `/xxx` 命令 for _, plugin := range plugin_utils.AllPlugins.SlashSymbolCommand { if utils.CommandMaybeWithSuffixUsername(opts.Fields, "/" + plugin.SlashCommand) { - logger.Debug(). + logger.Info(). Str("slashCommand", plugin.SlashCommand). Str("message", opts.Update.Message.Text). Msg("Hit slash command handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("slashCommand", plugin.SlashCommand). @@ -298,7 +294,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { } err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { - logger.Error(). + logger.Warn(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). @@ -338,7 +334,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { } err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { - logger.Error(). + logger.Warn(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). @@ -366,7 +362,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { } err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { - logger.Error(). + logger.Warn(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). @@ -395,12 +391,12 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { // 没有 `/` 号作为前缀,检查是不是自定义命令 for _, plugin := range plugin_utils.AllPlugins.CustomSymbolCommand { if utils.CommandMaybeWithSuffixUsername(opts.Fields, plugin.FullCommand) { - logger.Debug(). + logger.Info(). Str("fullCommand", plugin.FullCommand). Str("message", opts.Update.Message.Text). Msg("Hit full command handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("fullCommand", plugin.FullCommand). @@ -410,7 +406,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { } err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { - logger.Error(). + logger.Warn(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). @@ -434,12 +430,12 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { // 以后缀来触发的命令 for _, plugin := range plugin_utils.AllPlugins.SuffixCommand { if strings.HasSuffix(opts.Update.Message.Text, plugin.SuffixCommand) { - logger.Debug(). + logger.Info(). Str("suffixCommand", plugin.SuffixCommand). Str("message", opts.Update.Message.Text). Msg("Hit suffix command handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("suffixCommand", plugin.SuffixCommand). @@ -449,7 +445,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { } err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { - logger.Error(). + logger.Warn(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). @@ -484,13 +480,13 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { for name, handler := range plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString] { if handler.AllowAutoTrigger { // 允许自动触发的 handler - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("messageType", string(msgTypeInString)). Str("handlerName", name). Str("chatType", string(opts.Update.Message.Chat.Type)). - Msg("trigger handler by message type") + Msg("Hit handler by message type") err := handler.Handler(opts) if err != nil { logger.Error(). @@ -565,13 +561,13 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { // handler by chat ID if plugin_utils.AllPlugins.HandlerByChatID[opts.Update.Message.Chat.ID] != nil { for name, handler := range plugin_utils.AllPlugins.HandlerByChatID[opts.Update.Message.Chat.ID] { - logger.Debug(). + logger.Info(). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("handlerName", name). Int64("chatID", handler.ChatID). Str("chatType", string(opts.Update.Message.Chat.Type)). - Msg("trigger handler by chat ID") + Msg("Hit handler by chat ID") err := handler.Handler(opts) if err != nil { logger.Error(). @@ -590,10 +586,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { // 处理 inline 模式下的请求 func inlineHandler(opts *handler_structs.SubHandlerParams) { defer utils.PanicCatcher(opts.Ctx, "inlineHandler") - logger := zerolog.Ctx(opts.Ctx). - With(). - Str("funcName", "inlineHandler"). - Logger() + logger := zerolog.Ctx(opts.Ctx) var IsAdmin bool = utils.AnyContains(opts.Update.InlineQuery.From.ID, configs.BotConfig.AdminIDs) @@ -640,6 +633,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("query", opts.Update.InlineQuery.Query). Msg("Failed to send `bot inline handler list` inline result") } } else if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol) { @@ -649,11 +643,17 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlineHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.Fields[0][1:] == plugin.Command { + logger.Info(). + Str("handlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit inline handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("handlerCommand", plugin.Command). Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). Msg("Hit inline handler, but this handler function is nil, skip") continue } @@ -670,6 +670,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("handlerCommand", plugin.Command). Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). Msg("Failed to send `inline handler` inline result") // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } @@ -680,12 +681,18 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlineManualHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.Fields[0][1:] == plugin.Command { + logger.Info(). + Str("handlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit inline manual answer handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("handlerCommand", plugin.Command). Str("handlerType", "manuallyAnswerResult"). - Msg("Hit inline handler, but this handler function is nil, skip") + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit inline manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) @@ -695,7 +702,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("defaultHandlerCommand", plugin.Command). Str("handlerType", "manuallyAnswerResult"). - Msg("Error in inline handler") + Str("query", opts.Update.InlineQuery.Query). + Msg("Error in inline manual answer handler") } return } @@ -704,12 +712,18 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol + plugin.PrefixCommand) { + logger.Info(). + Str("handlerPrefixCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit inline prefix manual answer handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("handlerCommand", plugin.PrefixCommand). + Str("handlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). - Msg("Hit inline handler, but this handler function is nil, skip") + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit inline prefix manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) @@ -717,14 +731,20 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("handlerCommand", plugin.PrefixCommand). + Str("handlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). - Msg("Error in inline handler") + Str("query", opts.Update.InlineQuery.Query). + Msg("Error in inline prefix manual answer handler") } return } } + // 没有触发任何 handler + logger.Debug(). + Str("query", opts.Update.InlineQuery.Query). + Msg("No any handler is hit") + // 没有匹配到任何命令 _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -742,6 +762,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("query", opts.Update.InlineQuery.Query). Msg("Failed to send `no this inline command` inline result") } return @@ -754,11 +775,17 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlineHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.Command { + logger.Info(). + Str("userDefaultHandlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit user default inline handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("userDefaultHandlerCommand", plugin.Command). Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). Msg("Hit user default inline handler, but this handler function is nil, skip") continue } @@ -775,6 +802,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("userDefaultHandlerCommand", plugin.Command). Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). Msg("Failed to send `user default inline handler result` inline result") // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } @@ -785,12 +813,18 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlineManualHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.Command { + logger.Info(). + Str("userDefaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit user default inline manual answer handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("userDefaultHandlerCommand", plugin.Command). Str("handlerType", "manuallyAnswerResult"). - Msg("Hit user default inline handler, but this handler function is nil, skip") + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit user default inline manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) @@ -800,7 +834,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("userDefaultHandlerCommand", plugin.Command). Str("handlerType", "manuallyAnswerResult"). - Msg("Error in user default inline handler") + Str("query", opts.Update.InlineQuery.Query). + Msg("Error in user default inline manual answer handler") } return } @@ -810,12 +845,18 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.PrefixCommand { + logger.Info(). + Str("userDefaultHandlerPrefixCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit user default inline prefix manual answer handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("userDefaultHandlerCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). - Msg("Hit user inline handler, but this handler function is nil, skip") + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit user inline prefix manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) @@ -824,7 +865,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("userDefaultHandlerCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). - Msg("Error in user inline handler") + Str("query", opts.Update.InlineQuery.Query). + Msg("Error in user inline prefix manual answer handler") return } } @@ -863,11 +905,17 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlineHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if configs.BotConfig.InlineDefaultHandler == plugin.Command { + logger.Info(). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit bot default inline handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("defaultHandlerCommand", plugin.Command). Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). Msg("Hit bot default inline handler, but this handler function is nil, skip") continue } @@ -888,6 +936,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("defaultHandlerCommand", plugin.Command). Str("handlerType", "returnResult"). + Str("query", opts.Update.InlineQuery.Query). Msg("Failed to send `bot default inline handler result` inline result") // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } @@ -898,12 +947,18 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlineManualHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if configs.BotConfig.InlineDefaultHandler == plugin.Command { + logger.Info(). + Str("defaultHandlerCommand", plugin.Command). + Str("handlerType", "manuallyAnswerResult"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit bot default inline manual answer handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("defaultHandlerCommand", plugin.Command). Str("handlerType", "manuallyAnswerResult"). - Msg("Hit bot default inline handler, but this handler function is nil, skip") + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit bot default inline manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) @@ -913,7 +968,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("defaultHandlerCommand", plugin.Command). Str("handlerType", "manuallyAnswerResult"). - Msg("Error in bot default inline handler") + Str("query", opts.Update.InlineQuery.Query). + Msg("Error in bot default inline manual answer handler") } return } @@ -922,12 +978,17 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } if opts.ChatInfo.DefaultInlinePlugin == plugin.PrefixCommand { + logger.Info(). + Str("defaultHandlerPrefixCommand", plugin.PrefixCommand). + Str("handlerType", "manuallyAnswerResult_PrefixCommand"). + Str("query", opts.Update.InlineQuery.Query). + Msg("Hit bot default inline prefix manual answer handler") if plugin.Handler == nil { - logger.Debug(). + logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("defaultHandlerCommand", plugin.PrefixCommand). + Str("defaultHandlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). - Msg("Hit bot default inline handler, but this handler function is nil, skip") + Msg("Hit bot default inline prefix manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) @@ -935,9 +996,9 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("defaultHandlerCommand", plugin.PrefixCommand). + Str("defaultHandlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). - Msg("Error in bot default inline handler") + Msg("Error in bot default inline prefix manual answer handler") } return } @@ -1018,10 +1079,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { func callbackQueryHandler(params *handler_structs.SubHandlerParams) { defer utils.PanicCatcher(params.Ctx, "callbackQueryHandler") - logger := zerolog.Ctx(params.Ctx). - With(). - Str("funcName", "callbackQueryHandler"). - Logger() + logger := zerolog.Ctx(params.Ctx) // 如果有一个正在处理的请求,且用户再次发送相同的请求,则提示用户等待 if params.ChatInfo.HasPendingCallbackQuery && params.Update.CallbackQuery.Data == params.ChatInfo.LatestCallbackQueryData { @@ -1078,12 +1136,16 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) { for _, n := range plugin_utils.AllPlugins.CallbackQuery { if strings.HasPrefix(params.Update.CallbackQuery.Data, n.CommandChar) { + logger.Info(). + Str("handlerPrefix", n.CommandChar). + Str("callbackData", params.Update.CallbackQuery.Data). + Msg("Hit callback query handler") if n.Handler == nil { logger.Debug(). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). Str("handlerPrefix", n.CommandChar). - Str("query", params.Update.CallbackQuery.Data). - Msg("tigger a callback query handler, but this handler function is nil, skip") + Str("callbackData", params.Update.CallbackQuery.Data). + Msg("Hit callback query handler, but this handler function is nil, skip") continue } err := n.Handler(params) @@ -1092,7 +1154,7 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). Str("handlerPrefix", n.CommandChar). - Str("query", params.Update.CallbackQuery.Data). + Str("callbackData", params.Update.CallbackQuery.Data). Msg("Error in callback query handler") } break diff --git a/main.go b/main.go index bc92588..9c5b2cd 100644 --- a/main.go +++ b/main.go @@ -59,14 +59,23 @@ func main() { zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog()) zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack - logger.Warn(). - Str("version", consts.Version). - Str("commit", consts.Commit[:13]). - Str("buildTime", consts.BuildTime). - Str("changes", consts.Changes). - Str("runtime", runtime.Version()). - Str("logLevel", zerolog.GlobalLevel().String()). - Msg("trbot") + if consts.BuildTime == "" { + logger.Warn(). + Str("runtime", runtime.Version()). + Str("logLevel", zerolog.GlobalLevel().String()). + Str("error", "Remind: You are using a version without build info"). + Msg("trbot") + } else { + logger.Warn(). + Str("commit", consts.Commit). + Str("branch", consts.Branch). + Str("version", consts.Version). + Str("buildTime", consts.BuildTime). + Str("changes", consts.Changes). + Str("runtime", runtime.Version()). + Str("logLevel", zerolog.GlobalLevel().String()). + Msg("trbot") + } opts := []bot.Option{ bot.WithDefaultHandler(defaultHandler), diff --git a/utils/consts/consts.go b/utils/consts/consts.go index c1200d5..0b076df 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -14,8 +14,9 @@ var LogFilePath string = YAMLDataBasePath + "log.txt" var BotMe *models.User // 用于存储 bot 信息 -var Version string = "unknownVersion" -var Commit string = "unknownCommit" -var BuildTime string = "unknownTime" -var BuildMachine string = "unknownMachine" -var Changes string = "noChanges" +var Commit string +var Branch string +var Version string +var BuildTime string +var BuildMachine string +var Changes string diff --git a/utils/mess/mess.go b/utils/mess/mess.go index 2348264..f9e7944 100644 --- a/utils/mess/mess.go +++ b/utils/mess/mess.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "os" - "os/exec" "runtime" "trbot/utils/configs" @@ -65,15 +64,24 @@ func PrivateLogToChat(ctx context.Context, thebot *bot.Bot, update *models.Updat } func OutputVersionInfo() string { - // 获取 git sha 和 commit 时间 - c, _ := exec.Command("git", "rev-parse", "HEAD").Output() - // 获取 git 分支 - b, _ := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output() - // 获取 commit 说明 - m, _ := exec.Command("git", "log", "-1", "--pretty=%s").Output() - r := runtime.Version() - grs := runtime.NumGoroutine() - h, _ := os.Hostname() - info := fmt.Sprintf("Branch: %sCommit: [%s - %s](https://gitea.trle5.xyz/trle5/trbot/commit/%s)\nRuntime: %s\nGoroutine: %d\nHostname: %s", b, m, c[:10], c, r, grs, h) - return info + hostname, _ := os.Hostname() + var gitURL string = "https://gitea.trle5.xyz/trle5/trbot/commit/" + var info string + if consts.BuildTime != "" { + info += fmt.Sprintf("`Version: `%s\n", consts.Version) + info += fmt.Sprintf("`Branch: `%s\n", consts.Branch) + info += fmt.Sprintf("`Commit: `[%s](%s%s) (%s)\n", consts.Commit[:10], gitURL, consts.Commit, consts.Changes) + info += fmt.Sprintf("`BuildTime: `%s\n", consts.BuildTime) + info += fmt.Sprintf("`BuildMachine: `%s\n", consts.BuildMachine) + info += fmt.Sprintf("`Runtime: `%s\n", runtime.Version()) + info += fmt.Sprintf("`Goroutine: `%d\n", runtime.NumGoroutine()) + info += fmt.Sprintf("`Hostname: `%s\n", hostname) + return info + } + return fmt.Sprintln( + "Warning: No build info\n", + "\n`Runtime: `", runtime.Version(), + "\n`Goroutine: `", runtime.NumGoroutine(), + "\n`Hostname: `", hostname, + ) } diff --git a/utils/utils.go b/utils/utils.go index ece3d16..4df10dd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -437,6 +437,7 @@ func PanicCatcher(ctx context.Context, pluginName string) { if panic != nil { logger.Error(). Stack(). + Str("commit", consts.Commit). Err(errors.WithStack(fmt.Errorf("%v", panic))). Str("catchFunc", pluginName). Msg("Panic recovered") -- 2.49.1 From 827bc3fb68bdbb493516b1a635a5643b8f7c696b Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Fri, 20 Jun 2025 23:50:01 +0800 Subject: [PATCH 13/27] move some log into func --- example.env => .example.env | 0 main.go | 55 +++++++++++-------------------- utils/configs/init.go | 64 ++++++++++++++++++++++++++++++++++--- utils/configs/webhook.go | 30 +++++++---------- utils/consts/consts.go | 27 +++++++++++++++- utils/mess/mess.go | 2 +- utils/signals/signals.go | 4 ++- 7 files changed, 120 insertions(+), 62 deletions(-) rename example.env => .example.env (100%) diff --git a/example.env b/.example.env similarity index 100% rename from example.env rename to .example.env diff --git a/main.go b/main.go index 9c5b2cd..b1f2c1b 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "net/http" "os" "os/signal" - "runtime" "time" "trbot/database" @@ -25,64 +24,53 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() - // create a logger and attached it into ctx + // create a logger var logger zerolog.Logger file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + // if can create a log file, use mult log writer if err == nil { fileWriter := &zerolog.FilteredLevelWriter{ Writer: zerolog.MultiLevelWriter(file), Level: zerolog.WarnLevel, } - multWriter := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stdout}, fileWriter) logger = zerolog.New(multWriter).With().Timestamp().Logger() logger.Info(). - Str("logFile", consts.LogFilePath). + Str("logFilePath", consts.LogFilePath). Str("levelForLogFile", zerolog.WarnLevel.String()). Msg("Use mult log writer") } else { logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) logger.Error(). Err(err). - Str("logFile", consts.LogFilePath). - Msg("Failed to open log file, use console writer only") + Str("logFilePath", consts.LogFilePath). + Msg("Failed to open log file, use console log writer only") } + // attach logger into ctx ctx = logger.WithContext(ctx) - // read configs + // read bot configs if err := configs.InitBot(ctx); err != nil { logger.Fatal().Err(err).Msg("Failed to read bot configs") } // set log level from config zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog()) + // set stack trace func zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack - if consts.BuildTime == "" { - logger.Warn(). - Str("runtime", runtime.Version()). - Str("logLevel", zerolog.GlobalLevel().String()). - Str("error", "Remind: You are using a version without build info"). - Msg("trbot") - } else { - logger.Warn(). - Str("commit", consts.Commit). - Str("branch", consts.Branch). - Str("version", consts.Version). - Str("buildTime", consts.BuildTime). - Str("changes", consts.Changes). - Str("runtime", runtime.Version()). - Str("logLevel", zerolog.GlobalLevel().String()). - Msg("trbot") - } + // show build info + consts.ShowConsts(ctx) - opts := []bot.Option{ + // show configs + configs.ShowConfigs(ctx) + + + thebot, err := bot.New(configs.BotConfig.BotToken, []bot.Option{ bot.WithDefaultHandler(defaultHandler), bot.WithAllowedUpdates(configs.BotConfig.AllowedUpdates), - } - - thebot, err := bot.New(configs.BotConfig.BotToken, opts...) + }...) if err != nil { logger.Fatal().Err(err).Msg("Failed to initialize bot") } consts.BotMe, err = thebot.GetMe(ctx) @@ -91,18 +79,13 @@ func main() { logger.Info(). Dict(utils.GetUserDict(consts.BotMe)). Msg("Bot initialized") - if configs.BotConfig.LogChatID != 0 { - logger.Info(). - Int64("LogChatID", configs.BotConfig.LogChatID). - Msg("Enabled log to chat") - } database.InitAndListDatabases(ctx) // start handler custom signals go signals.SignalsHandler(ctx) - // register plugin (plugin use `init()` first, then plugin use `InitPlugins` second, and internal last) + // register plugin (plugin use `init()` first, then plugin use `InitPlugins` second, and internal is the last) internal_plugin.Register(ctx) // Select mode by Webhook config @@ -121,7 +104,7 @@ func main() { Err(err). Msg("Webhook server failed") } - } else { // getUpdate, aka Long Polling + } else /* getUpdate, aka Long Polling */ { // save and clean remove Webhook URL befor using getUpdate https://core.telegram.org/bots/api#getupdates configs.SaveAndCleanRemoteWebhookURL(ctx, thebot) logger.Info(). @@ -136,7 +119,7 @@ func main() { // Webhook won't reach here, http.ListenAndServe() will keep program running till exit. // They use the same code to exit, this loop is to give some time to save the database when receive exit signal. for { + time.Sleep(5 * time.Second) logger.Info().Msg("still waiting...") - time.Sleep(2 * time.Second) } } diff --git a/utils/configs/init.go b/utils/configs/init.go index 4538b29..e0f2501 100644 --- a/utils/configs/init.go +++ b/utils/configs/init.go @@ -19,6 +19,7 @@ func InitBot(ctx context.Context) error { readEnvironment, } + godotenv.Load() for _, initfunc := range initFuncs { err := initfunc(ctx) if err != nil { return err } @@ -30,7 +31,6 @@ func InitBot(ctx context.Context) error { // 从 yaml 文件读取配置文件 func readConfig(ctx context.Context) error { logger := zerolog.Ctx(ctx) - godotenv.Load() // 先检查一下环境变量里有没有指定配置目录 configPathToFile := os.Getenv("CONFIG_PATH_TO_FILE") configDirectory := os.Getenv("CONFIG_DIRECTORY") @@ -52,7 +52,7 @@ func readConfig(ctx context.Context) error { } else { logger.Warn(). Str("configPathToFile", configPathToFile). - Msg("The config file is created, please fill the bot token and restart.") + Msg("The config file is created, please fill the bot token and restart") // 创建完成目录就跳到下方读取配置文件 // 默认配置文件没 bot token 的错误就留后面处理 } @@ -166,8 +166,6 @@ func readConfig(ctx context.Context) error { // 查找 bot token,优先级为 环境变量 > .env 文件 > 配置文件 func readBotToken(ctx context.Context) error { logger := zerolog.Ctx(ctx) - // 通过 godotenv 库读取 .env 文件后再尝试读取 - godotenv.Load() botToken := os.Getenv("BOT_TOKEN") if botToken != "" { BotConfig.BotToken = botToken @@ -194,7 +192,6 @@ func readBotToken(ctx context.Context) error { func readEnvironment(ctx context.Context) error { logger := zerolog.Ctx(ctx) - godotenv.Load() if os.Getenv("DEBUG") != "" { BotConfig.LogLevel = "debug" logger.Warn(). @@ -223,3 +220,60 @@ func showBotID() string { } return botID } + +func ShowConfigs(ctx context.Context) { + logger := zerolog.Ctx(ctx) + + if len(BotConfig.AllowedUpdates) != 0 { + logger.Info(). + Strs("allowedUpdates", BotConfig.AllowedUpdates). + Msg("Allowed updates list is set") + } + + if len(BotConfig.AdminIDs) != 0 { + logger.Info(). + Ints64("AdminIDs", BotConfig.AdminIDs). + Msg("Admin list is set") + } + + if BotConfig.LogChatID != 0 { + logger.Info(). + Int64("LogChatID", BotConfig.LogChatID). + Msg("Enabled log to chat") + } + + if BotConfig.InlineDefaultHandler == "" { + logger.Info(). + Msg("Inline default handler is not set, default show all commands") + } + + if BotConfig.InlineSubCommandSymbol == "" { + BotConfig.InlineSubCommandSymbol = "+" + logger.Info(). + Msg("Inline sub command symbol is not set, set it to `+` (plus sign)") + } + + if BotConfig.InlinePaginationSymbol == "" { + BotConfig.InlinePaginationSymbol = "-" + logger.Info(). + Msg("Inline pagination symbol is not set, set it to `-` (minus sign)") + } + + if BotConfig.InlineResultsPerPage == 0 { + BotConfig.InlineResultsPerPage = 50 + logger.Info(). + Msg("Inline results per page number is not set, set it to 50") + } else if BotConfig.InlineResultsPerPage < 1 || BotConfig.InlineResultsPerPage > 50 { + logger.Warn(). + Int("invalidNumber", BotConfig.InlineResultsPerPage). + Msg("Inline results per page number is invalid, set it to 50") + BotConfig.InlineResultsPerPage = 50 + } + + logger.Info(). + Str("DefaultHandler", BotConfig.InlineDefaultHandler). + Str("SubCommandSymbol", BotConfig.InlineSubCommandSymbol). + Str("PaginationSymbol", BotConfig.InlinePaginationSymbol). + Int("ResultsPerPage", BotConfig.InlineResultsPerPage). + Msg("Inline mode config has been read") +} diff --git a/utils/configs/webhook.go b/utils/configs/webhook.go index 19e3232..d45a4dc 100644 --- a/utils/configs/webhook.go +++ b/utils/configs/webhook.go @@ -5,21 +5,17 @@ import ( "os" "github.com/go-telegram/bot" - "github.com/go-telegram/bot/models" - "github.com/joho/godotenv" "github.com/rs/zerolog" ) // 通过是否设定环境变量和配置文件中的 Webhook URL 来决定是否使用 Webhook 模式 func IsUsingWebhook(ctx context.Context) bool { logger := zerolog.Ctx(ctx) - // 通过 godotenv 库读取 .env 文件后再尝试读取 - godotenv.Load() webhookURL := os.Getenv("WEBHOOK_URL") if webhookURL != "" { BotConfig.WebhookURL = webhookURL logger.Info(). - Str("Webhook URL", BotConfig.WebhookURL). + Str("WebhookURL", BotConfig.WebhookURL). Msg("Get Webhook URL from environment or .env file") return true } @@ -27,12 +23,12 @@ func IsUsingWebhook(ctx context.Context) bool { // 从 yaml 配置文件中读取 if BotConfig.WebhookURL != "" { logger.Info(). - Str("Webhook URL", BotConfig.WebhookURL). + Str("WebhookURL", BotConfig.WebhookURL). Msg("Get Webhook URL from config file") return true } - logger.Warn(). + logger.Info(). Msg("No Webhook URL in environment and .env file, using getUpdate mode") return false } @@ -51,8 +47,8 @@ func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookPa Msg("Webhook not set, setting it now...") } else { logger.Warn(). - Str("Remote URL", webHookInfo.URL). - Str("Local URL", params.URL). + Str("remoteURL", webHookInfo.URL). + Str("localURL", params.URL). Msg("The remote Webhook URL conflicts with the local one, saving and overwriting the remote URL") } success, err := thebot.SetWebhook(ctx, params) @@ -64,13 +60,13 @@ func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookPa } if success { logger.Info(). - Str("Webhook URL", params.URL). + Str("WebhookURL", params.URL). Msg("Set Webhook URL success") return true } } else { logger.Info(). - Str("Webhook URL", params.URL). + Str("WebhookURL", params.URL). Msg("Webhook URL is already set") return true } @@ -78,17 +74,18 @@ func SetUpWebhook(ctx context.Context, thebot *bot.Bot, params *bot.SetWebhookPa return false } -func SaveAndCleanRemoteWebhookURL(ctx context.Context, thebot *bot.Bot) *models.WebhookInfo { +func SaveAndCleanRemoteWebhookURL(ctx context.Context, thebot *bot.Bot) { logger := zerolog.Ctx(ctx) webHookInfo, err := thebot.GetWebhookInfo(ctx) if err != nil { logger.Error(). Err(err). - Msg("Get Webhook info error") + Msg("Failed to get Webhook info") + return } if webHookInfo != nil && webHookInfo.URL != "" { logger.Warn(). - Str("Remote URL", webHookInfo.URL). + Str("remoteURL", webHookInfo.URL). Msg("There is a Webhook URL remotely, saving and clearing it to use the getUpdate mode") ok, err := thebot.DeleteWebhook(ctx, &bot.DeleteWebhookParams{ DropPendingUpdates: false, @@ -96,10 +93,7 @@ func SaveAndCleanRemoteWebhookURL(ctx context.Context, thebot *bot.Bot) *models. if !ok { logger.Error(). Err(err). - Msg("Delete Webhook URL failed") + Msg("Failed to delete Webhook URL") } - return webHookInfo } - - return nil } diff --git a/utils/consts/consts.go b/utils/consts/consts.go index 0b076df..da624ad 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -1,7 +1,11 @@ package consts import ( + "context" + "runtime" + "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" ) var WebhookListenPort string = "localhost:2847" @@ -19,4 +23,25 @@ var Branch string var Version string var BuildTime string var BuildMachine string -var Changes string +var Changes string // uncommit files when build + +func ShowConsts(ctx context.Context) { + logger := zerolog.Ctx(ctx) + if BuildTime == "" { + logger.Warn(). + Str("runtime", runtime.Version()). + Str("logLevel", zerolog.GlobalLevel().String()). + Str("error", "Remind: You are using a version without build info"). + Msg("trbot") + } else { + logger.Info(). + Str("commit", Commit). + Str("branch", Branch). + Str("version", Version). + Str("buildTime", BuildTime). + Str("changes", Changes). + Str("runtime", runtime.Version()). + Str("logLevel", zerolog.GlobalLevel().String()). + Msg("trbot") + } +} diff --git a/utils/mess/mess.go b/utils/mess/mess.go index f9e7944..ba25571 100644 --- a/utils/mess/mess.go +++ b/utils/mess/mess.go @@ -66,7 +66,7 @@ func PrivateLogToChat(ctx context.Context, thebot *bot.Bot, update *models.Updat func OutputVersionInfo() string { hostname, _ := os.Hostname() var gitURL string = "https://gitea.trle5.xyz/trle5/trbot/commit/" - var info string + var info string if consts.BuildTime != "" { info += fmt.Sprintf("`Version: `%s\n", consts.Version) info += fmt.Sprintf("`Branch: `%s\n", consts.Branch) diff --git a/utils/signals/signals.go b/utils/signals/signals.go index 2280d55..5c6b4f2 100644 --- a/utils/signals/signals.go +++ b/utils/signals/signals.go @@ -46,7 +46,9 @@ func SignalsHandler(ctx context.Context) { Msg("Failed to save database, retrying...") time.Sleep(2 * time.Second) if saveDatabaseRetryCount >= saveDatabaseRetryMax { - logger.Error().Msg("Failed to save database too many times, exiting") + logger.Error(). + Err(err). + Msg("Failed to save database too many times, exiting") os.Exit(1) } continue -- 2.49.1 From 7524f98d59c1fedf75935c057503d04f26912713 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Mon, 23 Jun 2025 09:24:09 +0800 Subject: [PATCH 14/27] save changes add log file level adjust logger create and set level use logger.Fatal() replace os.Exit(1) --- .example.env | 1 + handlers.go | 144 +++++++++++++++++++-------------------- main.go | 48 ++++--------- utils/configs/config.go | 29 +++++--- utils/configs/init.go | 113 ++++++++++++++++++++++++------ utils/configs/webhook.go | 2 +- utils/consts/consts.go | 25 ------- utils/signals/signals.go | 3 +- 8 files changed, 202 insertions(+), 163 deletions(-) diff --git a/.example.env b/.example.env index 0e3a2c3..48dd2fe 100644 --- a/.example.env +++ b/.example.env @@ -2,5 +2,6 @@ BOT_TOKEN="114514:ABCDEFGHIJKLMNOPQRSTUVWXYZ" WEBHOOK_URL="https://api.example.com/telegram-webhook" DEBUG="true" LOG_LEVEL="info" +LOG_FILE_LEVEL="info" CONFIG_PATH_TO_FILE="./path/to/your_config_file.yaml" CONFIG_DIRECTORY="." # if use this, must have a config file name `config.yaml` in directory diff --git a/handlers.go b/handlers.go index eab75c9..b611c27 100644 --- a/handlers.go +++ b/handlers.go @@ -31,7 +31,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Update: update, } - // Debug level or Trace Level + // Debug or Trace Level if zerolog.GlobalLevel() <= zerolog.InfoLevel { if update.Message != nil { // 正常消息 @@ -1082,82 +1082,82 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) { logger := zerolog.Ctx(params.Ctx) // 如果有一个正在处理的请求,且用户再次发送相同的请求,则提示用户等待 - if params.ChatInfo.HasPendingCallbackQuery && params.Update.CallbackQuery.Data == params.ChatInfo.LatestCallbackQueryData { - logger.Info(). + if params.ChatInfo.HasPendingCallbackQuery && params.Update.CallbackQuery.Data == params.ChatInfo.LatestCallbackQueryData { + logger.Info(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("query", params.Update.CallbackQuery.Data). + Msg("this callback request is processing, ignore") + _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: params.Update.CallbackQuery.ID, + Text: "当前请求正在处理中,请等待处理完成", + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Str("query", params.Update.CallbackQuery.Data). - Msg("this callback request is processing, ignore") - _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: params.Update.CallbackQuery.ID, - Text: "当前请求正在处理中,请等待处理完成", - ShowAlert: true, - }) + Msg("Failed to send `this callback request is processing` callback answer") + } + return + } else if params.ChatInfo.HasPendingCallbackQuery { + // 如果有一个正在处理的请求,用户发送了不同的请求,则提示用户等待 + logger.Info(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("pendingQuery", params.ChatInfo.LatestCallbackQueryData). + Str("query", params.Update.CallbackQuery.Data). + Msg("another callback request is processing, ignore") + _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: params.Update.CallbackQuery.ID, + Text: "请等待上一个请求处理完成后再尝试发送新的请求", + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Msg("Failed to send `a callback request is processing, send new request later` callback answer") + } + return + } else { + // 如果没有正在处理的请求,则接受新的请求 + logger.Debug(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("query", params.Update.CallbackQuery.Data). + Msg("accept callback query") + + params.ChatInfo.HasPendingCallbackQuery = true + params.ChatInfo.LatestCallbackQueryData = params.Update.CallbackQuery.Data + // params.Thebot.AnswerCallbackQuery(ctx, &bot.AnswerCallbackQueryParams{ + // CallbackQueryID: params.Update.CallbackQuery.ID, + // Text: "已接受请求", + // ShowAlert: false, + // }) + } + + for _, n := range plugin_utils.AllPlugins.CallbackQuery { + if strings.HasPrefix(params.Update.CallbackQuery.Data, n.CommandChar) { + logger.Info(). + Str("handlerPrefix", n.CommandChar). + Str("callbackData", params.Update.CallbackQuery.Data). + Msg("Hit callback query handler") + if n.Handler == nil { + logger.Debug(). + Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). + Str("handlerPrefix", n.CommandChar). + Str("callbackData", params.Update.CallbackQuery.Data). + Msg("Hit callback query handler, but this handler function is nil, skip") + continue + } + err := n.Handler(params) if err != nil { logger.Error(). Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Failed to send `this callback request is processing` callback answer") - } - return - } else if params.ChatInfo.HasPendingCallbackQuery { - // 如果有一个正在处理的请求,用户发送了不同的请求,则提示用户等待 - logger.Info(). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Str("pendingQuery", params.ChatInfo.LatestCallbackQueryData). - Str("query", params.Update.CallbackQuery.Data). - Msg("another callback request is processing, ignore") - _, err := params.Thebot.AnswerCallbackQuery(params.Ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: params.Update.CallbackQuery.ID, - Text: "请等待上一个请求处理完成后再尝试发送新的请求", - ShowAlert: true, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Failed to send `a callback request is processing, send new request later` callback answer") - } - return - } else { - // 如果没有正在处理的请求,则接受新的请求 - logger.Debug(). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Str("query", params.Update.CallbackQuery.Data). - Msg("accept callback query") - - params.ChatInfo.HasPendingCallbackQuery = true - params.ChatInfo.LatestCallbackQueryData = params.Update.CallbackQuery.Data - // params.Thebot.AnswerCallbackQuery(ctx, &bot.AnswerCallbackQueryParams{ - // CallbackQueryID: params.Update.CallbackQuery.ID, - // Text: "已接受请求", - // ShowAlert: false, - // }) - } - - for _, n := range plugin_utils.AllPlugins.CallbackQuery { - if strings.HasPrefix(params.Update.CallbackQuery.Data, n.CommandChar) { - logger.Info(). - Str("handlerPrefix", n.CommandChar). - Str("callbackData", params.Update.CallbackQuery.Data). - Msg("Hit callback query handler") - if n.Handler == nil { - logger.Debug(). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Str("handlerPrefix", n.CommandChar). - Str("callbackData", params.Update.CallbackQuery.Data). - Msg("Hit callback query handler, but this handler function is nil, skip") - continue - } - err := n.Handler(params) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Str("handlerPrefix", n.CommandChar). - Str("callbackData", params.Update.CallbackQuery.Data). - Msg("Error in callback query handler") - } - break + Str("handlerPrefix", n.CommandChar). + Str("callbackData", params.Update.CallbackQuery.Data). + Msg("Error in callback query handler") } + break } + } } diff --git a/main.go b/main.go index b1f2c1b..5fe0ea3 100644 --- a/main.go +++ b/main.go @@ -24,29 +24,9 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() - // create a logger - var logger zerolog.Logger - file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - // if can create a log file, use mult log writer - if err == nil { - fileWriter := &zerolog.FilteredLevelWriter{ - Writer: zerolog.MultiLevelWriter(file), - Level: zerolog.WarnLevel, - } - multWriter := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stdout}, fileWriter) - logger = zerolog.New(multWriter).With().Timestamp().Logger() - logger.Info(). - Str("logFilePath", consts.LogFilePath). - Str("levelForLogFile", zerolog.WarnLevel.String()). - Msg("Use mult log writer") - } else { - logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - logger.Error(). - Err(err). - Str("logFilePath", consts.LogFilePath). - Msg("Failed to open log file, use console log writer only") - } - + // set stack trace func + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack + var logger zerolog.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) // attach logger into ctx ctx = logger.WithContext(ctx) @@ -55,17 +35,10 @@ func main() { logger.Fatal().Err(err).Msg("Failed to read bot configs") } - // set log level from config - zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog()) - // set stack trace func - zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack - - // show build info - consts.ShowConsts(ctx) - - // show configs - configs.ShowConfigs(ctx) - + // writer log to a file or only display on console + if configs.IsUseMultiLogWriter(&logger) { ctx = logger.WithContext(ctx) } // re-attach logger into ctx + configs.CheckConfig(ctx) // check and auto fill some config + configs.ShowConst(ctx) // show build info thebot, err := bot.New(configs.BotConfig.BotToken, []bot.Option{ bot.WithDefaultHandler(defaultHandler), @@ -82,6 +55,9 @@ func main() { database.InitAndListDatabases(ctx) + // set log level after bot initialized + zerolog.SetGlobalLevel(configs.BotConfig.LevelForZeroLog(false)) + // start handler custom signals go signals.SignalsHandler(ctx) @@ -89,7 +65,7 @@ func main() { internal_plugin.Register(ctx) // Select mode by Webhook config - if configs.IsUsingWebhook(ctx) { // Webhook + if configs.IsUsingWebhook(ctx) /* Webhook */ { configs.SetUpWebhook(ctx, thebot, &bot.SetWebhookParams{ URL: configs.BotConfig.WebhookURL, AllowedUpdates: configs.BotConfig.AllowedUpdates, @@ -114,7 +90,7 @@ func main() { thebot.Start(ctx) } - // a loop wait for getUpdate mode, this program will exit in `utils\signals\signals.go`. + // A loop wait for getUpdate mode, this program will exit in `utils\signals\signals.go`. // This loop will only run when the exit signal is received in getUpdate mode. // Webhook won't reach here, http.ListenAndServe() will keep program running till exit. // They use the same code to exit, this loop is to give some time to save the database when receive exit signal. diff --git a/utils/configs/config.go b/utils/configs/config.go index ac6554d..6aa6897 100644 --- a/utils/configs/config.go +++ b/utils/configs/config.go @@ -11,6 +11,7 @@ import ( // default "./config.yaml", can be changed by env var ConfigPath string = "./config.yaml" +var BotConfig config type config struct { // bot config @@ -18,8 +19,9 @@ type config struct { WebhookURL string `yaml:"WebhookURL"` // log - LogLevel string `yaml:"LogLevel"` // `trace` `debug` `info` `warn` `error` `fatal` `panic`, default "info" - LogChatID int64 `yaml:"LogChatID"` + LogLevel string `yaml:"LogLevel"` // `trace` `debug` `info` `warn` `error` `fatal` `panic`, default "info" + LogFileLevel string `yaml:"LogFileLevel"` + LogChatID int64 `yaml:"LogChatID"` // admin AdminIDs []int64 `yaml:"AdminIDs"` @@ -38,8 +40,15 @@ type config struct { AllowedUpdates bot.AllowedUpdates `yaml:"AllowedUpdates"` } -func (c config)LevelForZeroLog() zerolog.Level { - switch strings.ToLower(c.LogLevel) { +func (c config)LevelForZeroLog(forLogFile bool) zerolog.Level { + var levelText string + if forLogFile { + levelText = c.LogFileLevel + } else { + levelText = c.LogLevel + } + + switch strings.ToLower(levelText) { case "trace": return zerolog.TraceLevel case "debug": @@ -55,8 +64,13 @@ func (c config)LevelForZeroLog() zerolog.Level { case "panic": return zerolog.PanicLevel default: - log.Printf("Unknown log level [ %s ], using info level", c.LogLevel) - return zerolog.InfoLevel + if forLogFile { + log.Printf("Unknown log level [ %s ], using error level for log file", c.LogLevel) + return zerolog.ErrorLevel + } else { + log.Printf("Unknown log level [ %s ], using info level for console", c.LogLevel) + return zerolog.InfoLevel + } } } @@ -64,6 +78,7 @@ func CreateDefaultConfig() config { return config{ BotToken: "REPLACE_THIS_USE_YOUR_BOT_TOKEN", LogLevel: "info", + LogFileLevel: "warn", InlineSubCommandSymbol: "+", InlinePaginationSymbol: "-", @@ -79,5 +94,3 @@ func CreateDefaultConfig() config { }, } } - -var BotConfig config diff --git a/utils/configs/init.go b/utils/configs/init.go index e0f2501..00c0d10 100644 --- a/utils/configs/init.go +++ b/utils/configs/init.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "path/filepath" + "runtime" + "trbot/utils/consts" "trbot/utils/yaml" "unicode" @@ -163,7 +165,7 @@ func readConfig(ctx context.Context) error { } } -// 查找 bot token,优先级为 环境变量 > .env 文件 > 配置文件 +// 查找 bot token func readBotToken(ctx context.Context) error { logger := zerolog.Ctx(ctx) botToken := os.Getenv("BOT_TOKEN") @@ -171,7 +173,7 @@ func readBotToken(ctx context.Context) error { BotConfig.BotToken = botToken logger.Info(). Str("botTokenID", showBotID()). - Msg("Get token from environment or .env file") + Msg("Get token from environment") return nil } @@ -206,6 +208,14 @@ func readEnvironment(ctx context.Context) error { Msg("Get log level from environment") } + logFileLevel := os.Getenv("LOG_FILE_LEVEL") + if logFileLevel != "" { + BotConfig.LogFileLevel = logFileLevel + logger.Warn(). + Str("logFileLevel", logFileLevel). + Msg("Get log file level from environment") + } + return nil } @@ -221,25 +231,47 @@ func showBotID() string { return botID } -func ShowConfigs(ctx context.Context) { +func IsUseMultiLogWriter(logger *zerolog.Logger) bool { + file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + multLogger := zerolog.New(zerolog.MultiLevelWriter( + zerolog.ConsoleWriter{Out: os.Stdout}, + &zerolog.FilteredLevelWriter{ + Writer: zerolog.MultiLevelWriter(file), + Level: BotConfig.LevelForZeroLog(true), + }, + )).With().Timestamp().Logger() + + *logger = multLogger + + logger.Info(). + Str("logFilePath", consts.LogFilePath). + Str("logFileLevel", BotConfig.LogFileLevel). + Msg("Use mult log writer") + return true + } else { + logger.Error(). + Err(err). + Str("logFilePath", consts.LogFilePath). + Msg("Failed to open log file, use console log writer only") + return false + } +} + +// 只检查部分必要但可以留空的配置 +func CheckConfig(ctx context.Context) error { logger := zerolog.Ctx(ctx) - if len(BotConfig.AllowedUpdates) != 0 { - logger.Info(). - Strs("allowedUpdates", BotConfig.AllowedUpdates). - Msg("Allowed updates list is set") + if BotConfig.LogLevel == "" { + BotConfig.LogLevel = "info" + logger.Warn(). + Msg("LogLevel is not set, use default value: info") } - if len(BotConfig.AdminIDs) != 0 { - logger.Info(). - Ints64("AdminIDs", BotConfig.AdminIDs). - Msg("Admin list is set") - } - - if BotConfig.LogChatID != 0 { - logger.Info(). - Int64("LogChatID", BotConfig.LogChatID). - Msg("Enabled log to chat") + if BotConfig.LogFileLevel == "" { + BotConfig.LogFileLevel = "warn" + logger.Warn(). + Msg("LogFileLevel is not set, use default value: warn") } if BotConfig.InlineDefaultHandler == "" { @@ -250,13 +282,13 @@ func ShowConfigs(ctx context.Context) { if BotConfig.InlineSubCommandSymbol == "" { BotConfig.InlineSubCommandSymbol = "+" logger.Info(). - Msg("Inline sub command symbol is not set, set it to `+` (plus sign)") + Msg("Inline sub command symbol is not set, use default value: `+` (plus sign)") } if BotConfig.InlinePaginationSymbol == "" { BotConfig.InlinePaginationSymbol = "-" logger.Info(). - Msg("Inline pagination symbol is not set, set it to `-` (minus sign)") + Msg("Inline pagination symbol is not set, use default value: `-` (minus sign)") } if BotConfig.InlineResultsPerPage == 0 { @@ -270,10 +302,53 @@ func ShowConfigs(ctx context.Context) { BotConfig.InlineResultsPerPage = 50 } + // 以下为可有可无的配置,主要是提醒下用户 + + if len(BotConfig.AdminIDs) != 0 { + logger.Info(). + Ints64("AdminIDs", BotConfig.AdminIDs). + Msg("Admin list is set") + } + + if len(BotConfig.AllowedUpdates) != 0 { + logger.Info(). + Strs("allowedUpdates", BotConfig.AllowedUpdates). + Msg("Allowed updates list is set") + } + + if BotConfig.LogChatID != 0 { + logger.Info(). + Int64("LogChatID", BotConfig.LogChatID). + Msg("Enabled log to chat") + } + logger.Info(). Str("DefaultHandler", BotConfig.InlineDefaultHandler). Str("SubCommandSymbol", BotConfig.InlineSubCommandSymbol). Str("PaginationSymbol", BotConfig.InlinePaginationSymbol). Int("ResultsPerPage", BotConfig.InlineResultsPerPage). Msg("Inline mode config has been read") + + return nil +} + +func ShowConst(ctx context.Context) { + logger := zerolog.Ctx(ctx) + if consts.BuildTime == "" { + logger.Warn(). + Str("runtime", runtime.Version()). + Str("logLevel", BotConfig.LogLevel). + Str("error", "Remind: You are using a version without build info"). + Msg("trbot") + } else { + logger.Info(). + Str("commit", consts.Commit). + Str("branch", consts.Branch). + Str("version", consts.Version). + Str("buildTime", consts.BuildTime). + Str("changes", consts.Changes). + Str("runtime", runtime.Version()). + Str("logLevel", BotConfig.LogLevel). + Msg("trbot") + } } diff --git a/utils/configs/webhook.go b/utils/configs/webhook.go index d45a4dc..e0d412b 100644 --- a/utils/configs/webhook.go +++ b/utils/configs/webhook.go @@ -16,7 +16,7 @@ func IsUsingWebhook(ctx context.Context) bool { BotConfig.WebhookURL = webhookURL logger.Info(). Str("WebhookURL", BotConfig.WebhookURL). - Msg("Get Webhook URL from environment or .env file") + Msg("Get Webhook URL from environment") return true } diff --git a/utils/consts/consts.go b/utils/consts/consts.go index da624ad..5654c31 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -1,11 +1,7 @@ package consts import ( - "context" - "runtime" - "github.com/go-telegram/bot/models" - "github.com/rs/zerolog" ) var WebhookListenPort string = "localhost:2847" @@ -24,24 +20,3 @@ var Version string var BuildTime string var BuildMachine string var Changes string // uncommit files when build - -func ShowConsts(ctx context.Context) { - logger := zerolog.Ctx(ctx) - if BuildTime == "" { - logger.Warn(). - Str("runtime", runtime.Version()). - Str("logLevel", zerolog.GlobalLevel().String()). - Str("error", "Remind: You are using a version without build info"). - Msg("trbot") - } else { - logger.Info(). - Str("commit", Commit). - Str("branch", Branch). - Str("version", Version). - Str("buildTime", BuildTime). - Str("changes", Changes). - Str("runtime", runtime.Version()). - Str("logLevel", zerolog.GlobalLevel().String()). - Msg("trbot") - } -} diff --git a/utils/signals/signals.go b/utils/signals/signals.go index 5c6b4f2..dc51fb9 100644 --- a/utils/signals/signals.go +++ b/utils/signals/signals.go @@ -46,10 +46,9 @@ func SignalsHandler(ctx context.Context) { Msg("Failed to save database, retrying...") time.Sleep(2 * time.Second) if saveDatabaseRetryCount >= saveDatabaseRetryMax { - logger.Error(). + logger.Fatal(). Err(err). Msg("Failed to save database too many times, exiting") - os.Exit(1) } continue } -- 2.49.1 From 1dc9864f96f6376f9c6cc40bfc99c82df0b41aee Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Tue, 24 Jun 2025 04:16:23 +0800 Subject: [PATCH 15/27] refactor udonese logger use path to run ffmpeg auto convent webm to gif if have ffmpeg path fix callback query just return --- .example.env | 3 +- handlers.go | 1 - plugins/plugin_sticker.go | 115 +++-- {bad_plugins => plugins}/plugin_udonese.go | 566 ++++++++++++++++----- plugins/plugin_voicelist.go | 44 +- utils/configs/config.go | 2 + utils/configs/init.go | 17 +- utils/logt/log_template.go | 10 + utils/plugin_utils/plugin_initializer.go | 2 +- 9 files changed, 553 insertions(+), 207 deletions(-) rename {bad_plugins => plugins}/plugin_udonese.go (62%) create mode 100644 utils/logt/log_template.go diff --git a/.example.env b/.example.env index 48dd2fe..75fc164 100644 --- a/.example.env +++ b/.example.env @@ -2,6 +2,7 @@ BOT_TOKEN="114514:ABCDEFGHIJKLMNOPQRSTUVWXYZ" WEBHOOK_URL="https://api.example.com/telegram-webhook" DEBUG="true" LOG_LEVEL="info" -LOG_FILE_LEVEL="info" +LOG_FILE_LEVEL="warn" CONFIG_PATH_TO_FILE="./path/to/your_config_file.yaml" CONFIG_DIRECTORY="." # if use this, must have a config file name `config.yaml` in directory +FFMPEG_PATH="./ffmpeg/bin/ffmpeg.exe" diff --git a/handlers.go b/handlers.go index b611c27..b5dd8b8 100644 --- a/handlers.go +++ b/handlers.go @@ -100,7 +100,6 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Str("query", update.CallbackQuery.Data). Msg("callback query") - return } else if update.MessageReaction != nil { // 私聊或群组表情回应 if len(update.MessageReaction.OldReaction) > 0 { diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index 765b0d4..e662296 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -102,14 +102,14 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(opts.Update.Message.From)). Msg("Failed to download sticker") - _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, msgerr := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.From.ID, Text: fmt.Sprintf("下载贴纸时发生了一些错误\n
Failed to download sticker: %s", err), ParseMode: models.ParseModeHTML, }) - if err != nil { + if msgerr != nil { logger.Error(). - Err(err). + Err(msgerr). Dict(utils.GetUserDict(opts.Update.Message.From)). Msg("Failed to send `sticker download error` message") } @@ -117,18 +117,22 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { } documentParams := &bot.SendDocumentParams{ - ChatID: opts.Update.Message.From.ID, - ParseMode: models.ParseModeHTML, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - DisableNotification: true, + ChatID: opts.Update.Message.From.ID, + ParseMode: models.ParseModeHTML, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + DisableNotification: true, + DisableContentTypeDetection: true, // Prevent the server convert gif to mp4 } var stickerFilePrefix, stickerFileSuffix string if opts.Update.Message.Sticker.IsVideo { - documentParams.Caption = "
see wikipedia/WebM" - stickerFileSuffix = "webm" - // stickerFileSuffix = "gif" + if stickerData.IsConverted { + stickerFileSuffix = "gif" + } else { + documentParams.Caption = "
see wikipedia/WebM" + stickerFileSuffix = "webm" + } } else if opts.Update.Message.Sticker.IsAnimated { documentParams.Caption = "
see stickers/animated-stickers" stickerFileSuffix = "tgs.file" @@ -321,51 +325,58 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) // 不需要转码,直接读取原贴纸文件 finalFullPath = originFullPath } else if opts.Update.Message.Sticker.IsVideo { - // webm, convert to gif - _, err = os.Stat(toGIFFullPath) // 使用目录提前检查一下是否已经转换过 - if err != nil { - // 如果提示不存在,进行转换 - if os.IsNotExist(err) { - // 日志提示该文件没转换,正在转换 - logger.Trace(). - Str("toGIFFullPath", toGIFFullPath). - Msg("sticker file does not convert, converting") + if configs.BotConfig.FFmpegPath != "" { + // webm, convert to gif + _, err = os.Stat(toGIFFullPath) // 使用目录提前检查一下是否已经转换过 + if err != nil { + // 如果提示不存在,进行转换 + if os.IsNotExist(err) { + // 日志提示该文件没转换,正在转换 + logger.Trace(). + Str("toGIFFullPath", toGIFFullPath). + Msg("sticker file does not convert, converting") - // 创建保存贴纸的目录 - err = os.MkdirAll(GIFFilePath, 0755) - if err != nil { + // 创建保存贴纸的目录 + err = os.MkdirAll(GIFFilePath, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("GIFFilePath", GIFFilePath). + Msg("Failed to create directory to convert file") + return nil, fmt.Errorf("failed to create directory [%s] to convert sticker file: %w", GIFFilePath, err) + } + + // 读取原贴纸文件,转码后存储到 png 格式贴纸的完整目录 + err = convertWebmToGif(originFullPath, toGIFFullPath) + if err != nil { + logger.Error(). + Err(err). + Str("originFullPath", originFullPath). + Msg("Failed to convert webm to gif") + return nil, fmt.Errorf("failed to convert webm [%s] to gif: %w", originFullPath, err) + } + } else { + // 其他错误 logger.Error(). Err(err). - Str("GIFFilePath", GIFFilePath). - Msg("Failed to create directory to convert file") - return nil, fmt.Errorf("failed to create directory [%s] to convert sticker file: %w", GIFFilePath, err) - } - - // 读取原贴纸文件,转码后存储到 png 格式贴纸的完整目录 - err = convertWebmToGif(originFullPath, toGIFFullPath) - if err != nil { - logger.Error(). - Err(err). - Str("originFullPath", originFullPath). - Msg("Failed to convert webm to gif") - return nil, fmt.Errorf("failed to convert webm [%s] to gif: %w", originFullPath, err) + Str("toGIFFullPath", toGIFFullPath). + Msg("Failed to read converted file info") + return nil, fmt.Errorf("failed to read converted sticker file [%s] info: %w", toGIFFullPath, err) } } else { - // 其他错误 - logger.Error(). - Err(err). + // 文件存在,跳过转换 + logger.Trace(). Str("toGIFFullPath", toGIFFullPath). - Msg("Failed to read converted file info") - return nil, fmt.Errorf("failed to read converted sticker file [%s] info: %w", toGIFFullPath, err) + Msg("sticker file already converted to gif") } + + // 处理完成,将最后要读取的目录设为转码后 gif 格式贴纸的完整目录 + data.IsConverted = true + finalFullPath = toGIFFullPath } else { - // 文件存在,跳过转换 - logger.Trace(). - Str("toGIFFullPath", toGIFFullPath). - Msg("sticker file already converted to gif") + // 没有 ffmpeg 能用来转码,直接读取原贴纸文件 + finalFullPath = originFullPath } - // 处理完成,将最后要读取的目录设为转码后 gif 格式贴纸的完整目录 - finalFullPath = toGIFFullPath } else { // webp, need convert to png _, err = os.Stat(toPNGFullPath) // 使用目录提前检查一下是否已经转换过 @@ -410,7 +421,9 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) Str("toPNGFullPath", toPNGFullPath). Msg("sticker file already converted to png") } + // 处理完成,将最后要读取的目录设为转码后 png 格式贴纸的完整目录 + data.IsConverted = true finalFullPath = toPNGFullPath } @@ -427,7 +440,6 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) return &data, nil } - func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx). With(). @@ -474,14 +486,14 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Msg("Failed to get sticker set info") - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, msgerr := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.CallbackQuery.From.ID, Text: fmt.Sprintf("获取贴纸包时发生了一些错误\n
Failed to get sticker set info: %s", err), ParseMode: models.ParseModeHTML, }) - if err != nil { + if msgerr != nil { logger.Error(). - Err(err). + Err(msgerr). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Msg("Failed to send `get sticker set info error` message") } @@ -874,8 +886,9 @@ func convertWebPToPNG(webpPath, pngPath string) error { return nil } +// use ffmpeg func convertWebmToGif(webmPath, gifPath string) error { - cmd := exec.Command("./ffmpeg/bin/ffmpeg.exe", "-i", webmPath, "-vf", "fps=10", gifPath) + cmd := exec.Command(configs.BotConfig.FFmpegPath, "-i", webmPath, "-vf", "fps=10", gifPath) err := cmd.Run() if err != nil { return err diff --git a/bad_plugins/plugin_udonese.go b/plugins/plugin_udonese.go similarity index 62% rename from bad_plugins/plugin_udonese.go rename to plugins/plugin_udonese.go index 19807a4..e132fc9 100644 --- a/bad_plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -1,9 +1,9 @@ package plugins import ( + "context" "fmt" "io" - "log" "os" "path/filepath" "strconv" @@ -14,20 +14,23 @@ import ( "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" + "trbot/utils/logt" "trbot/utils/plugin_utils" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" "gopkg.in/yaml.v3" ) var UdoneseData Udonese var UdoneseErr error -var Udonese_path string = filepath.Join(consts.YAMLDataBasePath, "udonese/") -var UdonGroupID int64 = -1002205667779 +var UdoneseDir string = filepath.Join(consts.YAMLDataBasePath, "udonese/") +var UdonesePath string = filepath.Join(UdoneseDir, consts.YAMLFileName) +var UdonGroupID int64 = -1002205667779 var UdoneseManagerIDs []int64 = []int64{ - 872082796, // akaudon + 872082796, // akaudon 1086395364, // trle5 } @@ -66,7 +69,7 @@ func (list UdoneseWord) OutputMeanings() string { if len(list.MeaningList) != 0 { pendingMessage += "它的意思有\n" } else { - pendingMessage += "还没有添加任何意思\n" + pendingMessage += "它还没有添加任何意思\n" } for i, s := range list.MeaningList { // 先加意思 @@ -137,61 +140,172 @@ type UdoneseMeaning struct { ViaName string `yaml:"ViaName,omitempty"` } -func ReadUdonese() { +func ReadUdonese(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "Udonese"). + Str("funcName", "ReadUdonese"). + Logger() var udonese Udonese - file, err := os.Open(filepath.Join(Udonese_path, consts.YAMLFileName)) + file, err := os.Open(UdonesePath) if err != nil { - // 如果是找不到目录,新建一个 - log.Println("[Udonese] Not found database file. Created new one") - SaveUdonese() - UdoneseData, UdoneseErr = Udonese{}, err - return + if os.IsNotExist(err) { + // 如果是找不到目录,新建一个 + logger.Warn(). + Err(err). + Str("path", UdonesePath). + Msg("Not found udonese list file. Created new one") + err = SaveUdonese(ctx) + if err != nil { + logger.Error(). + Err(err). + Str("path", UdonesePath). + Msg("Failed to create empty udonese list file") + UdoneseErr = fmt.Errorf("failed to create empty udonese list file: %w", err) + return UdoneseErr + } + } else { + // 其他错误 + logger.Error(). + Err(err). + Str("path", UdonesePath). + Msg("Failed to open udonese list file") + UdoneseErr = fmt.Errorf("failed to open udonese list file: %w", err) + return UdoneseErr + } } defer file.Close() - decoder := yaml.NewDecoder(file) - err = decoder.Decode(&udonese) + err = yaml.NewDecoder(file).Decode(&udonese) if err != nil { if err == io.EOF { - log.Println("[Udonese] Udonese list looks empty. now format it") - SaveUdonese() - UdoneseData, UdoneseErr = Udonese{}, nil - return + logger.Warn(). + Str("path", UdonesePath). + Msg("udonese list file looks empty. now format it") + err = SaveUdonese(ctx) + if err != nil { + // 保存空的数据库失败 + logger.Error(). + Err(err). + Str("path", UdonesePath). + Msg("Failed to create empty udonese list file") + UdoneseErr = fmt.Errorf("failed to create empty udonese list file: %w", err) + return UdoneseErr + } + } else { + logger.Error(). + Err(err). + Msg("Failed to decode udonese list") + UdoneseErr = fmt.Errorf("failed to decode udonese list: %w", err) + return UdoneseErr } - log.Println("[Udonese] (func)ReadUdonese:", err) - UdoneseData, UdoneseErr = Udonese{}, err - return } - UdoneseData, UdoneseErr = udonese, nil + + UdoneseData = udonese + return nil } -func SaveUdonese() error { +func SaveUdonese(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "Udonese"). + Str("funcName", "SaveUdonese"). + Logger() + data, err := yaml.Marshal(UdoneseData) - if err != nil { return err } + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to marshal udonese list") + UdoneseErr = fmt.Errorf("failed to marshal udonese list: %w", err) + return UdoneseErr + } - if _, err := os.Stat(Udonese_path); os.IsNotExist(err) { - if err := os.MkdirAll(Udonese_path, 0755); err != nil { - return err + _, err = os.Stat(UdoneseDir) + if err != nil { + if os.IsNotExist(err) { + logger.Warn(). + Str("directory", UdoneseDir). + Msg("Not found udonese list directory, now create it") + err = os.MkdirAll(UdoneseDir, 0755) + if err != nil { + logger.Error(). + Err(err). + Str("directory", UdoneseDir). + Msg("Failed to create udonese list directory") + UdoneseErr = fmt.Errorf("failed to create udonese list directory: %s", err) + return UdoneseErr + } + logger.Trace(). + Str("directory", UdoneseDir). + Msg("Create udonese list directory success") + } else { + logger.Error(). + Err(err). + Str("directory", UdoneseDir). + Msg("Failed to open udonese list directory") + UdoneseErr = fmt.Errorf("failed to open udonese list directory: %s", err) + return UdoneseErr } } - if _, err := os.Stat(filepath.Join(Udonese_path, consts.YAMLFileName)); os.IsNotExist(err) { - _, err := os.Create(filepath.Join(Udonese_path, consts.YAMLFileName)) - if err != nil { - return err + _, err = os.Stat(UdonesePath) + if err != nil { + if os.IsNotExist(err) { + logger.Warn(). + Str("path", UdonesePath). + Msg("Not found udonese list file. Create a new one") + _, err := os.Create(UdonesePath) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to create udonese list file") + UdoneseErr = fmt.Errorf("failed to create udonese list file: %w", err) + return UdoneseErr + } + logger.Trace(). + Str("path", UdonesePath). + Msg("Created udonese list file success") + } else { + logger.Error(). + Err(err). + Str("path", UdonesePath). + Msg("Failed to open udonese list file") + UdoneseErr = fmt.Errorf("failed to open udonese list file: %w", err) + return UdoneseErr } } - return os.WriteFile(filepath.Join(Udonese_path, consts.YAMLFileName), data, 0644) + err = os.WriteFile(UdonesePath, data, 0644) + if err != nil { + logger.Error(). + Err(err). + Str("path", UdonesePath). + Msg("Failed to write udonese list into file") + UdoneseErr = fmt.Errorf("failed to write udonese list into file: %w", err) + return UdoneseErr + } + logger.Trace(). + Str("path", UdonesePath). + Msg("Save udonese list success") + return nil } // 如果要添加的意思重复,返回对应意思的单个词结构体指针,否则返回空指针 // 设计之初可以添加多个意思,但现在不推荐这样 -func addUdonese(params *UdoneseWord) *UdoneseWord { +func addUdonese(ctx context.Context, params *UdoneseWord) *UdoneseWord { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "Udonese"). + Str("funcName", "SaveUdonese"). + Logger() + for wordIndex, savedList := range UdoneseData.List { if strings.EqualFold(savedList.Word, params.Word){ - log.Printf("[Udonese] 发现已存在的词 [%s],正在检查是否有新增的意思", savedList.Word) + logger.Info(). + Str("word", params.Word). + Msg("Found existing word") for _, newMeaning := range params.MeaningList { var isreallynew bool = true for _, oldmeanlist := range savedList.MeaningList { @@ -201,26 +315,39 @@ func addUdonese(params *UdoneseWord) *UdoneseWord { } if isreallynew { UdoneseData.List[wordIndex].MeaningList = append(UdoneseData.List[wordIndex].MeaningList, newMeaning) - log.Printf("[Udonese] 正在为 [%s] 添加 [%s] 意思", UdoneseData.List[wordIndex].Word, newMeaning.Meaning) + logger.Info(). + Str("word", params.Word). + Str("meaning", newMeaning.Meaning). + Msg("Add new meaning") } else { - log.Println("[Udonese] 存在的意思,跳过", newMeaning) + logger.Info(). + Str("word", params.Word). + Str("meaning", newMeaning.Meaning). + Msg("Skip existing meaning") return &savedList } } return nil } } - log.Printf("[Udonese] 发现新的词 [%s],正在添加 %v", params.Word, params.MeaningList) + logger.Info(). + Str("word", params.Word). + Interface("meaningList", params.MeaningList). + Msg("Add new word") UdoneseData.List = append(UdoneseData.List, *params) UdoneseData.Count++ return nil } -func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { +func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { // 不响应来自转发的命令 - if opts.Update.Message.ForwardOrigin != nil { - return - } + if opts.Update.Message.ForwardOrigin != nil { return nil } + + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Udonese"). + Str("funcName", "addUdoneseHandler"). + Logger() isManager := utils.AnyContains(opts.Update.Message.From.ID, UdoneseManagerIDs) @@ -233,22 +360,34 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, }) if err != nil { - log.Println("[Udonese] error sending /udonese not allowed group:", err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese not allowed group"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `/udonese not allowed group` message: %w", err) } - return + return nil } - if isManager && len(opts.Fields) < 3 { if len(opts.Fields) < 2 { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, Text: "使用 `/udonese <词> <单个意思>` 来添加记录\n或使用 `/udonese <词>` 来管理记录", ParseMode: models.ParseModeMarkdownV1, DisableNotification: true, }) - return + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese admin command help"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `/udonese admin command help` message: %w", err) + } + return nil } else { checkWord := opts.Fields[1] var targetWord UdoneseWord @@ -267,14 +406,19 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, }) if err != nil { - log.Println("[Udonese] error sending /udonese word not found:", err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese admin command no this word"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `/udonese admin command no this word` message: %w", err) } - return + return nil } - var pendingMessage string = fmt.Sprintf("词: [ %s ]\n有 %d 个意思\n", targetWord.Word, len(targetWord.MeaningList)) + var pendingMessage string = fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n", targetWord.Word, len(targetWord.MeaningList), targetWord.Used) - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: pendingMessage, ParseMode: models.ParseModeHTML, @@ -282,17 +426,33 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), DisableNotification: true, }) - return + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese manage keyboard"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `/udonese manage keyboard` message: %w", err) + } + return nil } } else if len(opts.Fields) < 3 { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, Text: "使用 `/udonese <词> <单个意思>` 来添加记录", ParseMode: models.ParseModeMarkdownV1, DisableNotification: true, }) - return + if err != nil { + logger.Info(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese command help"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `/udonese command help` message: %w", err) + } + return nil } meaning := strings.TrimSpace(opts.Update.Message.Text[len(opts.Fields[0])+len(opts.Fields[1])+2:]) @@ -384,7 +544,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { var pendingMessage string var botMessage *models.Message - oldMeaning := addUdonese(&UdoneseWord{ + oldMeaning := addUdonese(opts.Ctx, &UdoneseWord{ Word: opts.Fields[1], MeaningList: []UdoneseMeaning{{ Meaning: meaning, @@ -429,7 +589,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { } } } else { - err := SaveUdonese() + err := SaveUdonese(opts.Ctx) if err != nil { pendingMessage += fmt.Sprintln("保存语句时似乎发生了一些错误:\n", err) } else { @@ -468,15 +628,32 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) { ParseMode: models.ParseModeHTML, DisableNotification: true, }) - if err == nil { + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese keyword added"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `/udonese keyword added` message: %w", err) + } else { time.Sleep(time.Second * 10) - opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ + _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ ChatID: opts.Update.Message.Chat.ID, MessageIDs: []int{ opts.Update.Message.ID, botMessage.ID, }, }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Ints("messageIDs", []int{ opts.Update.Message.ID, botMessage.ID }). + Str("content", "/udonese keyword added"). + Msg(logt.DeleteMessages) + return fmt.Errorf("failed to delete `/udonese keyword added` messages: %w", err) + } + return nil } } @@ -561,36 +738,48 @@ func udoneseInlineHandler(opts *handler_structs.SubHandlerParams) []models.Inlin return udoneseResultList } -func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { - // 不响应来自转发的命令 - if opts.Update.Message.ForwardOrigin != nil { - return - } - // 空文本 - if len(opts.Fields) < 1 { - return - } +func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { + // 不响应来自转发的命令和空文本 + if opts.Update.Message.ForwardOrigin != nil || len(opts.Fields) < 1 { return nil } + + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Udonese"). + Str("funcName", "udoneseGroupHandler"). + Logger() if UdoneseErr != nil { - log.Println("[Udonese] some error in while read udonese list: ", UdoneseErr) - ReadUdonese() + logger.Warn(). + Err(UdoneseErr). + Msg("Some error in while read udonese list") + err := ReadUdonese(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to read udonese list") + return err + } } // 统计词使用次数 for i, n := range UdoneseData.OnlyWord() { if n == opts.Update.Message.Text || strings.HasPrefix(opts.Update.Message.Text, n) { UdoneseData.List[i].Used++ - err := SaveUdonese() + err := SaveUdonese(opts.Ctx) if err != nil { - log.Println("[Udonese] get some error when add udonese used count:", err) + logger.Error(). + Err(err). + Msg("Failed to save udonese list after add word usage count") } } } + var needNotice bool + if opts.Fields[0] == "sms" { // 参数过少,提示用法 if len(opts.Fields) < 2 { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, Text: "使用方法:发送 `sms <词>` 来查看对应的意思", @@ -601,12 +790,20 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "sms ", }}}}, }) - return + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "sms command usage"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `sms command usage` message: %w", err) + } + return nil } // 在数据库循环查找这个词 for _, word := range UdoneseData.List { - if strings.EqualFold(word.Word, opts.Fields[1]) && len(word.MeaningList) > 0 { + if strings.EqualFold(word.Word, opts.Fields[1]) { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: word.OutputMeanings(), @@ -615,66 +812,88 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) { DisableNotification: true, }) if err != nil { - log.Println("[Udonese] get some error when answer udonese meaning:", err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "sms keyword meaning"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `sms keyword meaning` message: %w", err) } - return + return nil } } - // 到这里就是没找到,提示没有 - botMessage, _ := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - Text: "这个词还没有记录,使用 `udonese <词> <意思>` 来添加吧", - ParseMode: models.ParseModeMarkdownV1, - DisableNotification: true, - }) - - time.Sleep(time.Second * 10) - opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageIDs: []int{ - botMessage.ID, - }, - }) - - return + needNotice = true } else if len(opts.Fields) > 1 && strings.HasSuffix(opts.Update.Message.Text, "ssm") { // 在数据库循环查找这个词 for _, word := range UdoneseData.List { - if strings.EqualFold(word.Word, opts.Fields[0]) && len(word.MeaningList) > 0 { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + if strings.EqualFold(word.Word, opts.Fields[0]) { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: word.OutputMeanings(), ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, ParseMode: models.ParseModeHTML, DisableNotification: true, }) - return + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "sms keyword meaning"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `sms keyword meaning` message: %w", err) + } + return nil } } + needNotice = true + } + if needNotice { // 到这里就是没找到,提示没有 - botMessage, _ := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, Text: "这个词还没有记录,使用 `udonese <词> <意思>` 来添加吧", ParseMode: models.ParseModeMarkdownV1, DisableNotification: true, }) - - time.Sleep(time.Second * 10) - opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageIDs: []int{ - botMessage.ID, - }, - }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Int("messageID", botMessage.ID). + Str("content", "sms keyword no meaning"). + Msg(logt.SendMessage) + return fmt.Errorf("failed to send `sms keyword no meaning` message: %w", err) + } else { + time.Sleep(time.Second * 10) + _, err := opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ + ChatID: opts.Update.Message.Chat.ID, + MessageIDs: []int{ + botMessage.ID, + }, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Int("messageID", botMessage.ID). + Str("content", "sms keyword no meaning"). + Msg(logt.DeleteMessage) + return fmt.Errorf("failed to delete `sms keyword no meaning` message: %w", err) + } + } } + + return nil } func init() { - ReadUdonese() + plugin_utils.AddInitializer(plugin_utils.Initializer{ + Name: "Udonese", + Func: ReadUdonese, + }) plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ Name: "Udonese", Saver: SaveUdonese, @@ -704,22 +923,43 @@ func init() { // }) } -func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { +func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Udonese"). + Str("funcName", "udoneseCallbackHandler"). + Logger() + if !utils.AnyContains(opts.Update.CallbackQuery.From.ID, UdoneseManagerIDs) { - opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, Text: "不可以!", ShowAlert: true, }) - return + if err != nil { + logger.Error(). + Err(err). + Str("callbackQueryID", opts.Update.CallbackQuery.ID). + Str("content", "udonese no edit permissions"). + Msg(logt.AnswerCallback) + } + return nil } if opts.Update.CallbackQuery.Data == "udonese_done" { - opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.Update.CallbackQuery.Message.Message.ID, }) - return + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese keyword manage keyboard"). + Msg(logt.DeleteMessage) + } + return nil } if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_word_") { @@ -731,20 +971,32 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { } } - opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: fmt.Sprintf("词: %s\n有 %d 个意思\n", targetWord.Word, len(targetWord.MeaningList)), - ParseMode: models.ParseModeMarkdownV1, + Text: fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n", targetWord.Word, len(targetWord.MeaningList), targetWord.Used), + ParseMode: models.ParseModeHTML, ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), }) - return + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese word meaning list"). + Msg(logt.EditMessage) + } + return nil } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") { wordAndIndex := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") wordAndIndexList := strings.Split(wordAndIndex, "_") meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) if err != nil { - log.Println("[Udonese] covert meanning index error:", err) + logger.Error(). + Err(err). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to covert meanning index") + return fmt.Errorf("failed to covert meanning index: %w", err) } var targetMeaning UdoneseMeaning @@ -759,7 +1011,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { } } - var pendingMessage string = fmt.Sprintf("意思 [ %s ]\n", targetMeaning.Meaning) + var pendingMessage string = fmt.Sprintf("意思: [ %s ]\n", targetMeaning.Meaning) // 来源的用户或频道 if targetMeaning.FromUsername != "" { @@ -800,16 +1052,26 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { }}}, }) if err != nil { - log.Println("[Udonese] error when editing message:",err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese meaning manage keyboard"). + Msg(logt.EditMessage) + return fmt.Errorf("failed to edit message to `udonese meaning manage keyboard`: %w", err) } - return + return nil } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_delmeaning_") { wordAndIndex := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_delmeaning_") wordAndIndexList := strings.Split(wordAndIndex, "_") meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) if err != nil { - log.Println("[Udonese] covert meanning index error:", err) + logger.Error(). + Err(err). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to covert meanning index") + return fmt.Errorf("failed to covert meanning index: %w", err) } var newMeaningList []UdoneseMeaning var targetWord UdoneseWord @@ -829,7 +1091,26 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { } } - var pendingMessage string = fmt.Sprintf("词: [ %s ]\n有 %d 个意思\n\n
已删除 [ %s ] 词中的 %s 意思", targetWord.Word, len(targetWord.MeaningList), wordAndIndexList[0], deletedMeaning) + err = SaveUdonese(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to save udonese data after deleting meaning") + _, msgerr := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "删除意思时保存数据库失败,请重试\n" + err.Error(), + ShowAlert: true, + }) + if msgerr != nil { + logger.Error(). + Err(msgerr). + Msg(logt.AnswerCallback) + } + + return fmt.Errorf("failed to save udonese data after delete meaning: %w", err) + } + + var pendingMessage string = fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n
已删除 [ %s ] 词中的 [ %s ] 意思", targetWord.Word, len(targetWord.MeaningList), targetWord.Used, wordAndIndexList[0], deletedMeaning) _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, @@ -839,11 +1120,16 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), }) if err != nil { - log.Println("[Udonese] error when edit deleted meaning keyboard:", err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese meaning manage keyboard after delete meaning"). + Msg(logt.EditMessage) + return fmt.Errorf("failed to edit message to `udonese meaning manage keyboard after delete meaning`: %w", err) } - SaveUdonese() - return + return nil } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_delword_") { word := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_delword_") var newWordList []UdoneseWord @@ -854,9 +1140,27 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { } UdoneseData.List = newWordList + err := SaveUdonese(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to save udonese data after delete word") + _, msgerr := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "删除词时保存数据库失败,请重试\n" + err.Error(), + ShowAlert: true, + }) + if msgerr != nil { + logger.Error(). + Err(msgerr). + Msg(logt.AnswerCallback) + } + return fmt.Errorf("failed to save udonese data after delete meaning: %w", err) + } + var pendingMessage string = fmt.Sprintf("
已删除 [ %s ] 词", word) - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.Update.CallbackQuery.Message.Message.ID, Text: pendingMessage, @@ -867,9 +1171,15 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) { }}}}, }) if err != nil { - log.Println("[Udonese] error when edit deleted word message:", err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese word deleted notice"). + Msg(logt.EditMessage) + return fmt.Errorf("failed to edit message to `udonese word deleted notice`: %w", err) } - - SaveUdonese() } + + return nil } diff --git a/plugins/plugin_voicelist.go b/plugins/plugin_voicelist.go index 162f117..26dc688 100644 --- a/plugins/plugin_voicelist.go +++ b/plugins/plugin_voicelist.go @@ -3,7 +3,6 @@ package plugins import ( "context" "fmt" - "log" "os" "path/filepath" "strings" @@ -13,21 +12,21 @@ import ( "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/plugin_utils" + "trbot/utils/yaml" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" - "gopkg.in/yaml.v3" ) var VoiceLists []VoicePack var VoiceListErr error -var VoiceList_path string = filepath.Join(consts.YAMLDataBasePath, "voices/") +var VoiceListDir string = filepath.Join(consts.YAMLDataBasePath, "voices/") func init() { plugin_utils.AddInitializer(plugin_utils.Initializer{ - Name: "Voice List", + Name: "VoiceList", Func: ReadVoicePackFromPath, }) plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ @@ -61,17 +60,17 @@ func ReadVoicePackFromPath(ctx context.Context) error { var packs []VoicePack - _, err := os.Stat(VoiceList_path) + _, err := os.Stat(VoiceListDir) if err != nil { if os.IsNotExist(err) { logger.Warn(). - Str("path", VoiceList_path). + Str("directory", VoiceListDir). Msg("VoiceList directory not exist, now create it") - err = os.MkdirAll(VoiceList_path, 0755) + err = os.MkdirAll(VoiceListDir, 0755) if err != nil { logger.Error(). Err(err). - Str("path", VoiceList_path). + Str("directory", VoiceListDir). Msg("Failed to create VoiceList data directory") VoiceListErr = err return err @@ -79,7 +78,7 @@ func ReadVoicePackFromPath(ctx context.Context) error { } else { logger.Error(). Err(err). - Str("path", VoiceList_path). + Str("directory", VoiceListDir). Msg("Open VoiceList data directory failed") VoiceListErr = err return err @@ -87,7 +86,7 @@ func ReadVoicePackFromPath(ctx context.Context) error { } - err = filepath.Walk(VoiceList_path, func(path string, info os.FileInfo, err error) error { + err = filepath.Walk(VoiceListDir, func(path string, info os.FileInfo, err error) error { if err != nil { logger.Error(). Err(err). @@ -95,18 +94,9 @@ func ReadVoicePackFromPath(ctx context.Context) error { Msg("Failed to read file use `filepath.Walk()`") } if strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml") { - file, err := os.Open(path) - if err != nil { - logger.Error(). - Err(err). - Str("path", path). - Msg("Failed to open file use `os.Open()`") - } - defer file.Close() - var singlePack VoicePack - decoder := yaml.NewDecoder(file) - err = decoder.Decode(&singlePack) + + err = yaml.LoadYAML(path, &singlePack) if err != nil { logger.Error(). Err(err). @@ -120,8 +110,8 @@ func ReadVoicePackFromPath(ctx context.Context) error { if err != nil { logger.Error(). Err(err). - Str("path", VoiceList_path). - Msg("Failed to read voice packs in VoiceList path") + Str("directory", VoiceListDir). + Msg("Failed to read voice packs in VoiceList directory") VoiceListErr = err return err } @@ -135,7 +125,13 @@ func VoiceListHandler(opts *handler_structs.SubHandlerParams) []models.InlineQue var results []models.InlineQueryResult if VoiceLists == nil { - log.Printf("[VoiceList] No voices file in voices_path: %s", VoiceList_path) + zerolog.Ctx(opts.Ctx). + Warn(). + Str("pluginName", "Voice List"). + Str("funcName", "VoiceListHandler"). + Str("VoiceListDir", VoiceListDir). + Msg("No voices file in VoiceListDir") + opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: configs.BotConfig.LogChatID, Text: fmt.Sprintf("%s\nInline Mode: some user can't load voices", time.Now().Format(time.RFC3339)), diff --git a/utils/configs/config.go b/utils/configs/config.go index 6aa6897..4dafbd4 100644 --- a/utils/configs/config.go +++ b/utils/configs/config.go @@ -38,6 +38,8 @@ type config struct { InlineResultsPerPage int `yaml:"InlineResultsPerPage"` // default 50, maxinum 50, see https://core.telegram.org/bots/api#answerinlinequery AllowedUpdates bot.AllowedUpdates `yaml:"AllowedUpdates"` + + FFmpegPath string `yaml:"FFmpegPath"` } func (c config)LevelForZeroLog(forLogFile bool) zerolog.Level { diff --git a/utils/configs/init.go b/utils/configs/init.go index 00c0d10..44c0d51 100644 --- a/utils/configs/init.go +++ b/utils/configs/init.go @@ -216,6 +216,14 @@ func readEnvironment(ctx context.Context) error { Msg("Get log file level from environment") } + FFmpegPath := os.Getenv("FFMPEG_PATH") + if FFmpegPath != "" { + BotConfig.FFmpegPath = FFmpegPath + logger.Warn(). + Str("FFmpegPath", FFmpegPath). + Msg("Get FFmpegPath from environment") + } + return nil } @@ -258,10 +266,11 @@ func IsUseMultiLogWriter(logger *zerolog.Logger) bool { } } -// 只检查部分必要但可以留空的配置 func CheckConfig(ctx context.Context) error { logger := zerolog.Ctx(ctx) + // 部分必要但可以留空的配置 + if BotConfig.LogLevel == "" { BotConfig.LogLevel = "info" logger.Warn(). @@ -322,6 +331,12 @@ func CheckConfig(ctx context.Context) error { Msg("Enabled log to chat") } + if BotConfig.FFmpegPath != "" { + logger.Info(). + Str("FFmpegPath", BotConfig.FFmpegPath). + Msg("FFmpeg path is set") + } + logger.Info(). Str("DefaultHandler", BotConfig.InlineDefaultHandler). Str("SubCommandSymbol", BotConfig.InlineSubCommandSymbol). diff --git a/utils/logt/log_template.go b/utils/logt/log_template.go new file mode 100644 index 0000000..bdca0bc --- /dev/null +++ b/utils/logt/log_template.go @@ -0,0 +1,10 @@ +package logt + +const ( + // LogTemplate is the template for log messages. + SendMessage string = "Failed to send message" + EditMessage string = "Failed to edit message" + DeleteMessage string = "Failed to delete message" + DeleteMessages string = "Failed to delete messages" + AnswerCallback string = "Failed to answer callback query" +) diff --git a/utils/plugin_utils/plugin_initializer.go b/utils/plugin_utils/plugin_initializer.go index 39497ac..5f50f7f 100644 --- a/utils/plugin_utils/plugin_initializer.go +++ b/utils/plugin_utils/plugin_initializer.go @@ -41,7 +41,7 @@ func RunPluginInitializers(ctx context.Context) { logger.Error(). Err(err). Str("pluginName", initializer.Name). - Msg("Failed to initialize plugin, skipping") + Msg("Failed to initialize plugin") continue } else { logger.Info(). -- 2.49.1 From d5fa515ded95369c44c9e93533c75cadcdb58def Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Tue, 24 Jun 2025 04:24:24 +0800 Subject: [PATCH 16/27] change ffmpeg convert gif flag --- plugins/plugin_sticker.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index e662296..90418a7 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -888,12 +888,7 @@ func convertWebPToPNG(webpPath, pngPath string) error { // use ffmpeg func convertWebmToGif(webmPath, gifPath string) error { - cmd := exec.Command(configs.BotConfig.FFmpegPath, "-i", webmPath, "-vf", "fps=10", gifPath) - err := cmd.Run() - if err != nil { - return err - } - return nil + return exec.Command(configs.BotConfig.FFmpegPath, "-i", webmPath, gifPath).Run() } func zipFolder(srcDir, zipFile string) error { -- 2.49.1 From 39263085fe32b6b81a5d3b0faba914aa6dc14c46 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Tue, 24 Jun 2025 04:49:39 +0800 Subject: [PATCH 17/27] add a handler to show cached packs --- plugins/plugin_sticker.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index 90418a7..3dba2bf 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -55,6 +55,10 @@ func init() { AllowAutoTrigger: true, Handler: EchoStickerHandler, }) + plugin_utils.AddSlashSymbolCommandPlugins(plugin_utils.SlashSymbolCommand{ + SlashCommand: "cachedsticker", + Handler: showCachedStickers, + }) } type stickerDatas struct { @@ -928,3 +932,36 @@ func zipFolder(srcDir, zipFile string) error { return err } + +func showCachedStickers(opts *handler_structs.SubHandlerParams) error { + var button [][]models.InlineKeyboardButton + var tempButtom []models.InlineKeyboardButton + + entries, err := os.ReadDir(StickerCache_path) + if err != nil { return err } + + for _, entry := range entries { + if entry.IsDir() && entry.Name() != "-custom" { + if len(tempButtom) == 4 { + button = append(button, tempButtom) + tempButtom = []models.InlineKeyboardButton{} + } + tempButtom = append(tempButtom, models.InlineKeyboardButton{ + Text: entry.Name(), + URL: "https://t.me/addstickers/" + entry.Name(), + }) + } + } + + if len(tempButtom) > 0 { button = append(button, tempButtom) } + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "请选择要查看的贴纸包", + ReplyMarkup: models.InlineKeyboardMarkup{ + InlineKeyboard: button, + }, + MessageEffectID: "5104841245755180586", + }) + return err +} -- 2.49.1 From baeed5841a1e2d43c8d279e7e15674e3f330a3cc Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Tue, 24 Jun 2025 05:52:52 +0800 Subject: [PATCH 18/27] refactor teamspeak3 logger --- {bad_plugins => plugins}/plugin_teamspeak3.go | 279 +++++++++++++----- 1 file changed, 204 insertions(+), 75 deletions(-) rename {bad_plugins => plugins}/plugin_teamspeak3.go (59%) diff --git a/bad_plugins/plugin_teamspeak3.go b/plugins/plugin_teamspeak3.go similarity index 59% rename from bad_plugins/plugin_teamspeak3.go rename to plugins/plugin_teamspeak3.go index 52163cf..d33e5ef 100644 --- a/bad_plugins/plugin_teamspeak3.go +++ b/plugins/plugin_teamspeak3.go @@ -1,19 +1,21 @@ package plugins import ( + "context" "fmt" - "log" "os" "path/filepath" "time" "trbot/utils/consts" "trbot/utils/handler_structs" + "trbot/utils/logt" "trbot/utils/plugin_utils" "trbot/utils/yaml" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/multiplay/go-ts3" + "github.com/rs/zerolog" ) // loginname serveradmin @@ -24,7 +26,8 @@ import ( var tsClient *ts3.Client var tsErr error -var tsData_path string = filepath.Join(consts.YAMLDataBasePath, "teamspeak/") +var tsDataDir string = filepath.Join(consts.YAMLDataBasePath, "teamspeak/") +var tsDataPath string = filepath.Join(tsDataDir, consts.YAMLFileName) var botNickName string = "trbot_teamspeak_plugin" var isCanReInit bool = true @@ -38,8 +41,8 @@ var hasHandlerByChatID bool var resetListenTicker chan bool = make(chan bool) var pollingInterval time.Duration = time.Second * 5 -var tsServerQuery TSServerQuery -var privateOpts *handler_structs.SubHandlerParams +var tsData TSServerQuery +var privateOpts *handler_structs.SubHandlerParams type TSServerQuery struct { // get Name And Password in TeamSpeak 3 Client -> `Tools`` -> `ServerQuery Login` @@ -50,21 +53,24 @@ type TSServerQuery struct { } func init() { - // 初始化不成功时依然注册 `/ts3` 命令,使用命令式输出初始化时的错误 - if initTeamSpeak() { - isSuccessInit = true - log.Println("[TeamSpeak] plugin loaded") - - // 需要以群组 ID 来触发 handler 来获取 opts - plugin_utils.AddHandlerByChatIDPlugins(plugin_utils.HandlerByChatID{ - ChatID: tsServerQuery.GroupID, - PluginName: "teamspeak_get_opts", - Handler: getOptsHandler, - }) - hasHandlerByChatID = true - } else { - log.Println("[TeamSpeak] plugin loaded failed:", tsErr) - } + plugin_utils.AddInitializer(plugin_utils.Initializer{ + Name: "teamspeak", + Func: func(ctx context.Context) error{ + if initTeamSpeak(ctx) { + isSuccessInit = true + // 需要以群组 ID 来触发 handler 来获取 opts + plugin_utils.AddHandlerByChatIDPlugins(plugin_utils.HandlerByChatID{ + ChatID: tsData.GroupID, + PluginName: "teamspeak_get_opts", + Handler: getOptsHandler, + }) + hasHandlerByChatID = true + return nil + } else { + return tsErr + } + }, + }) plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ Name: "TeamSpeak 检测用户变动", @@ -78,22 +84,40 @@ func init() { }) } -func initTeamSpeak() bool { +func initTeamSpeak(ctx context.Context) bool { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "teamspeak3"). + Str("funcName", "initTeamSpeak"). + Logger() + // 判断配置文件是否存在 - _, err := os.Stat(tsData_path) + _, err := os.Stat(tsDataDir) if err != nil { if os.IsNotExist(err) { // 不存在,创建一份空文件 - err = yaml.SaveYAML(filepath.Join(tsData_path, consts.YAMLFileName), &TSServerQuery{}) + logger.Warn(). + Err(err). + Str("path", tsDataPath). + Msg("Not found config file. Created new one") + err = yaml.SaveYAML(tsDataPath, &TSServerQuery{}) if err != nil { - log.Println("[TeamSpeak] empty config create faild:", err) - } else { - log.Printf("[TeamSpeak] empty config created at [ %s ]", tsData_path) + logger.Error(). + Err(err). + Str("path", tsDataPath). + Msg("Failed to create empty config") + tsErr = fmt.Errorf("failed to create empty config: %w", err) } - tsErr = fmt.Errorf("config file not exist, created empty config file") + logger.Warn(). + Str("path", tsDataPath). + Msg("Empty config file created, please fill in the config") } else { // 文件存在,但是遇到了其他错误 - tsErr = fmt.Errorf("some error when read config file: %w", err) + logger.Error(). + Err(err). + Str("path", tsDataPath). + Msg("Failed to read config file") + tsErr = fmt.Errorf("failed to read config file: %w", err) } // 无法获取到服务器地址和账号,无法初始化并设定不可重新启动 @@ -101,37 +125,54 @@ func initTeamSpeak() bool { return false } - err = yaml.LoadYAML(filepath.Join(tsData_path, consts.YAMLFileName), &tsServerQuery) + err = yaml.LoadYAML(tsDataPath, &tsData) if err != nil { - // if err != nil || tsServerQuery == nil { + logger.Error(). + Err(err). + Str("path", tsDataPath). + Msg("Failed to read config file") // 读取配置文件内容失败也不允许重新启动 - tsErr = fmt.Errorf("read config error: %w", err) + tsErr = fmt.Errorf("failed to read config file: %w", err) isCanReInit = false return false } // 如果服务器地址为空不允许重新启动 - if tsServerQuery.URL == "" { + if tsData.URL == "" { + logger.Error(). + Str("path", tsDataPath). + Msg("No URL in config") tsErr = fmt.Errorf("no URL in config") isCanReInit = false return false } else { if tsClient != nil { tsClient.Close() } - tsClient, tsErr = ts3.NewClient(tsServerQuery.URL) + tsClient, tsErr = ts3.NewClient(tsData.URL) if tsErr != nil { + logger.Error(). + Err(tsErr). + Str("path", tsDataPath). + Msg("Failed to connect to server") tsErr = fmt.Errorf("connect error: %w", tsErr) return false } } // ServerQuery 账号名或密码为空也不允许重新启动 - if tsServerQuery.Name == "" || tsServerQuery.Password == "" { + if tsData.Name == "" || tsData.Password == "" { + logger.Error(). + Str("path", tsDataPath). + Msg("No Name/Password in config") tsErr = fmt.Errorf("no Name/Password in config") isCanReInit = false return false } else { - err = tsClient.Login(tsServerQuery.Name, tsServerQuery.Password) + err = tsClient.Login(tsData.Name, tsData.Password) if err != nil { + logger.Error(). + Err(err). + Str("path", tsDataPath). + Msg("Failed to login to server") tsErr = fmt.Errorf("login error: %w", err) isLoginFailed = true return false @@ -141,7 +182,10 @@ func initTeamSpeak() bool { } // 检查要设定通知的群组 ID 是否存在 - if tsServerQuery.GroupID == 0 { + if tsData.GroupID == 0 { + logger.Error(). + Str("path", tsDataPath). + Msg("No GroupID in config") tsErr = fmt.Errorf("no GroupID in config") isCanReInit = false return false @@ -150,15 +194,26 @@ func initTeamSpeak() bool { // 显示服务端版本测试一下连接 v, err := tsClient.Version() if err != nil { - tsErr = fmt.Errorf("show version error: %w", err) + logger.Error(). + Err(err). + Str("path", tsDataPath). + Msg("Failed to get server version") + tsErr = fmt.Errorf("failed to get server version: %w", err) return false } else { - log.Printf("[TeamSpeak] running: %v", v) + logger.Info(). + Str("version", v.Version). + Str("platform", v.Platform). + Int("build", v.Build). + Msg("TeamSpeak server connected") } // 切换默认虚拟服务器 err = tsClient.Use(1) if err != nil { + logger.Error(). + Err(err). + Msg("Failed to switch server") tsErr = fmt.Errorf("switch server error: %w", err) return false } @@ -166,11 +221,16 @@ func initTeamSpeak() bool { // 改一下 bot 自己的 nickname,使得在检测用户列表时默认不显示自己 m, err := tsClient.Whoami() if err != nil { - tsErr = fmt.Errorf("get my info error: %w", err) + logger.Error(). + Err(err). + Msg("Failed to get bot info") } else if m != nil && m.ClientName != botNickName { // 当 bot 自己的 nickname 不等于配置文件中的 nickname 时,才进行修改 err = tsClient.SetNick(botNickName) if err != nil { + logger.Error(). + Err(err). + Msg("Failed to set bot nickname") tsErr = fmt.Errorf("set nickname error: %w", err) } } @@ -180,30 +240,47 @@ func initTeamSpeak() bool { } // 用于首次初始化成功时只要对应群组有任何消息,都能自动获取 privateOpts 用来定时发送消息,并开启监听协程 -func getOptsHandler(opts *handler_structs.SubHandlerParams) { - if !isListening && isCanReInit && opts.Update.Message.Chat.ID == tsServerQuery.GroupID { +func getOptsHandler(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "teamspeak3"). + Str("funcName", "getOptsHandler"). + Logger() + + if !isListening && isCanReInit && opts.Update.Message.Chat.ID == tsData.GroupID { privateOpts = opts isCanListening = true - if consts.IsDebugMode { log.Println("[TeamSpeak] success get opts by handler") } + logger.Debug(). + Msg("success get opts by handler") if !isLoginFailed { - go listenUserStatus() - if consts.IsDebugMode { log.Println("[TeamSpeak] success start listening") } + go listenUserStatus(opts.Ctx) + logger.Debug(). + Msg("success start listen user status") } } + return nil } -func showStatus(opts *handler_structs.SubHandlerParams) { +func showStatus(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "teamspeak3"). + Str("funcName", "showStatus"). + Logger() + var pendingMessage string // 如果首次初始化没成功,没有添加根据群组 ID 来触发的 handler,用户发送 /ts3 后可以通过这个来自动获取 opts 并启动监听 // if isSuccessInit && !isCanListening && opts.Update != nil && opts.Update.Message != nil && opts.Update.Message.Chat.ID == tsServerQuery.GroupID { - if !isListening && isCanReInit && opts.Update.Message.Chat.ID == tsServerQuery.GroupID { + if !isListening && isCanReInit && opts.Update.Message.Chat.ID == tsData.GroupID { privateOpts = opts isCanListening = true - if consts.IsDebugMode { log.Println("[TeamSpeak] success get opts") } + logger.Debug(). + Msg("success get opts by showStatus") if !isLoginFailed { - go listenUserStatus() - if consts.IsDebugMode { log.Println("[TeamSpeak] success start listening") } + go listenUserStatus(opts.Ctx) + logger.Debug(). + Msg("success start listen user status") } // pendingMessage += fmt.Sprintln("已准备好发送用户状态") } @@ -211,7 +288,9 @@ func showStatus(opts *handler_structs.SubHandlerParams) { if isSuccessInit && isCanListening { olClient, err := tsClient.Server.ClientList() if err != nil { - log.Println("[TeamSpeak] get online client error:", err) + logger.Error(). + Err(err). + Msg("Failed to get online client") pendingMessage = fmt.Sprintf("连接到 teamspeak 服务器发生错误:\n
%s", err) } else { pendingMessage += fmt.Sprintln("在线客户端:") @@ -235,12 +314,12 @@ func showStatus(opts *handler_structs.SubHandlerParams) { } else { pendingMessage = fmt.Sprintf("初始化 teamspeak 插件时发生了一些错误:\n
%s\n\n", tsErr) if isCanReInit { - if initTeamSpeak() { + if initTeamSpeak(opts.Ctx) { isSuccessInit = true - tsErr = fmt.Errorf("") if !isListening && !isLoginFailed { - go listenUserStatus() - if consts.IsDebugMode { log.Println("[TeamSpeak] success start listening") } + go listenUserStatus(opts.Ctx) + logger.Debug(). + Msg("Start listening user status") } resetListenTicker <- true pendingMessage = "尝试重新初始化成功,现可正常运行" @@ -254,7 +333,6 @@ func showStatus(opts *handler_structs.SubHandlerParams) { } } - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: pendingMessage, @@ -262,11 +340,22 @@ func showStatus(opts *handler_structs.SubHandlerParams) { ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, }) if err != nil { - log.Println("[TeamSpeak] can't answer `/ts3` command:",err) + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "teamspeak status"). + Msg(logt.SendMessage) } + return nil } -func listenUserStatus() { +func listenUserStatus(ctx context.Context) { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "teamspeak3"). + Str("funcName", "listenUserStatus"). + Logger() + isListening = true listenTicker := time.NewTicker(pollingInterval) defer listenTicker.Stop() @@ -274,7 +363,7 @@ func listenUserStatus() { if hasHandlerByChatID { hasHandlerByChatID = false // 获取到 privateOpts 后删掉 handler by chatID - plugin_utils.RemoveHandlerByChatIDPlugin(tsServerQuery.GroupID, "teamspeak_get_opts") + plugin_utils.RemoveHandlerByChatIDPlugin(tsData.GroupID, "teamspeak_get_opts") } var retryCount int = 1 @@ -288,55 +377,83 @@ func listenUserStatus() { retryCount = 1 case <-listenTicker.C: if isSuccessInit && isCanListening { - beforeOnlineClient = checkOnlineClientChange(beforeOnlineClient) + beforeOnlineClient = checkOnlineClientChange(ctx, beforeOnlineClient) } else { - if consts.IsDebugMode { log.Println("[TeamSpeak] try reconnect...") } + logger.Info(). + Msg("try reconnect...") // 出现错误时,先降低 ticker 速度,然后尝试重新初始化 listenTicker.Reset(time.Duration(retryCount) * 20 * time.Second) if retryCount < 15 { retryCount++ } - if initTeamSpeak() { + if initTeamSpeak(ctx) { isSuccessInit = true isCanListening = true // 重新初始化成功则恢复 ticker 速度 retryCount = 1 listenTicker.Reset(pollingInterval) - if consts.IsDebugMode { log.Println("[TeamSpeak] reconnect success") } - privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ - ChatID: privateOpts.Update.Message.Chat.ID, + logger.Info(). + Msg("reconnect success") + _, err := privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ + ChatID: tsData.GroupID, Text: "已成功与服务器重新建立连接", ParseMode: models.ParseModeHTML, }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", tsData.GroupID). + Str("content", "success reconnect to server"). + Msg(logt.SendMessage) + } } else { // 无法成功则等待下一个周期继续尝试 - if consts.IsDebugMode { log.Printf("[TeamSpeak] connect failed [%s], retry in %ds", tsErr, (retryCount - 1) * 20) } + logger.Warn(). + Err(tsErr). + Int("retryCount", retryCount). + Int("nextRetry", (retryCount - 1) * 20). + Msg("connect failed") } } } } } -func checkOnlineClientChange(before []string) []string { +func checkOnlineClientChange(ctx context.Context, before []string) []string { var nowOnlineClient []string + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "teamspeak3"). + Str("funcName", "checkOnlineClientChange"). + Logger() olClient, err := tsClient.Server.ClientList() if err != nil { - log.Println("[TeamSpeak] get online client error:", err) + logger.Error(). + Err(err). + Msg("Failed to get online client") isCanListening = false - privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ - ChatID: privateOpts.Update.Message.Chat.ID, + _, err := privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ + ChatID: tsData.GroupID, Text: "已断开与服务器的连接,开始尝试自动重连", ParseMode: models.ParseModeHTML, }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", tsData.GroupID). + Str("content", "disconnect to server"). + Msg(logt.SendMessage) + } } else { for _, n := range olClient { nowOnlineClient = append(nowOnlineClient, n.Nickname) } added, removed := DiffSlices(before, nowOnlineClient) if len(added) + len(removed) > 0 { - if consts.IsDebugMode { - log.Printf("[TeamSpeak] online client change: added %v, removed %v", added, removed) - } - notifyClientChange(privateOpts, tsServerQuery.GroupID, added, removed) + logger.Debug(). + Strs("added", added). + Strs("removed", removed). + Msg("online client change detected") + notifyClientChange(privateOpts, added, removed) } } @@ -364,8 +481,13 @@ func DiffSlices(before, now []string) (added, removed []string) { return } -func notifyClientChange(opts *handler_structs.SubHandlerParams, chatID int64, add, remove []string) { +func notifyClientChange(opts *handler_structs.SubHandlerParams, add, remove []string) { var pendingMessage string + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "teamspeak3"). + Str("funcName", "notifyClientChange"). + Logger() if len(add) > 0 { pendingMessage += fmt.Sprintln("以下用户进入了服务器:") @@ -380,9 +502,16 @@ func notifyClientChange(opts *handler_structs.SubHandlerParams, chatID int64, ad } } - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: chatID, + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: tsData.GroupID, Text: pendingMessage, ParseMode: models.ParseModeHTML, }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", tsData.GroupID). + Str("content", "teamspeak user change notify"). + Msg(logt.SendMessage) + } } -- 2.49.1 From 99118ef71cbf70bd19dc3ecc70d8163ed5c8ccdb Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Wed, 25 Jun 2025 01:00:27 +0800 Subject: [PATCH 19/27] refactor detected keyword logger change /version command not need delete /version message allow select chat after add keyword some user interface change --- Makefile | 4 +- logstruct.txt | 4 - .../plugin_detect_keyword.go | 713 ++++++++++-------- plugins/plugin_udonese.go | 16 +- utils/configs/init.go | 17 +- utils/consts/consts.go | 12 +- utils/internal_plugin/register.go | 32 +- utils/mess/mess.go | 18 +- 8 files changed, 433 insertions(+), 383 deletions(-) delete mode 100644 logstruct.txt rename {bad_plugins => plugins}/plugin_detect_keyword.go (71%) diff --git a/Makefile b/Makefile index c674ca9..f3849f9 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ LDFLAGS := -X 'trbot/utils/consts.Commit=$(COMMIT)' \ -X 'trbot/utils/consts.Branch=$(BRANCH)' \ -X 'trbot/utils/consts.Version=$(VERSION)' \ -X 'trbot/utils/consts.Changes=$(CHANGES)' \ - -X 'trbot/utils/consts.BuildTime=$(TIME)' \ - -X 'trbot/utils/consts.BuildMachine=$(HOSTNAME)' + -X 'trbot/utils/consts.BuildAt=$(TIME)' \ + -X 'trbot/utils/consts.BuildOn=$(HOSTNAME)' build: go build -ldflags "$(LDFLAGS)" diff --git a/logstruct.txt b/logstruct.txt deleted file mode 100644 index 4f459a6..0000000 --- a/logstruct.txt +++ /dev/null @@ -1,4 +0,0 @@ -bot.SendMessage: Failed to send [%s] message -bot.DeleteMessages: Failed to delete [%s] message -bot.AnswerInlineQuery: Failed to send [%s] inline result (sub handler can add a `Str("command", "log")` ) -bot.AnswerCallbackQuery: Failed to send [%s] callback answer diff --git a/bad_plugins/plugin_detect_keyword.go b/plugins/plugin_detect_keyword.go similarity index 71% rename from bad_plugins/plugin_detect_keyword.go rename to plugins/plugin_detect_keyword.go index a73ff6d..ab44c83 100644 --- a/bad_plugins/plugin_detect_keyword.go +++ b/plugins/plugin_detect_keyword.go @@ -12,6 +12,7 @@ import ( "trbot/utils" "trbot/utils/consts" "trbot/utils/handler_structs" + "trbot/utils/logt" "trbot/utils/plugin_utils" "github.com/go-telegram/bot" @@ -44,25 +45,24 @@ func init() { }) plugin_utils.AddCallbackQueryCommandPlugins([]plugin_utils.CallbackQuery{ { - CommandChar: "detectkw_groupmng", + CommandChar: "detectkw_g", Handler: groupManageCallbackHandler, }, { - CommandChar: "detectkw_mng", + CommandChar: "detectkw_u", Handler: userManageCallbackHandler, }, }...) plugin_utils.AddSlashStartWithPrefixCommandPlugins(plugin_utils.SlashStartWithPrefixHandler{ - Prefix: "detectkw", + Prefix: "detectkw", Argument: "addgroup", - Handler: startPrefixAddGroup, + Handler: startPrefixAddGroup, }) plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ Name: "群组关键词检测", Description: "此功能可以检测群组中的每一条信息,当包含设定的关键词时,将会向用户发送提醒\n\n使用方法:\n首先将机器人添加至想要监听关键词的群组中,发送 /setkeyword 命令,等待机器人回应后点击下方的 “设定关键词” 按钮即可为自己添加要监听的群组\n\n设定关键词:您可以在对应的群组中直接发送
/setkeyword 要设定的关键词 来为该群组设定关键词\n或前往机器人聊天页面,发送 /setkeyword 命令后点击对应的群组或全局关键词按钮,根据提示来添加关键词",
ParseMode: models.ParseModeHTML,
})
- buildListenList()
}
type KeywordData struct {
@@ -129,17 +129,17 @@ func (user KeywordUserList)userStatus() string {
return pendingMessage
}
-func (user KeywordUserList)selectChat() models.ReplyMarkup {
+func (user KeywordUserList)selectChat(keyword string) models.ReplyMarkup {
var buttons [][]models.InlineKeyboardButton
buttons = append(buttons, []models.InlineKeyboardButton{{
- Text: "添加为全局关键词",
- CallbackData: "detectkw_mng_globaladding",
+ Text: "🌐 添加为全局关键词",
+ CallbackData: fmt.Sprintf("detectkw_u_add_%d_%s", user.UserID, keyword),
}})
for _, chat := range user.ChatsForUser {
targetChat := KeywordDataList.Chats[chat.ChatID]
buttons = append(buttons, []models.InlineKeyboardButton{{
- Text: targetChat.ChatName,
- CallbackData: fmt.Sprintf("detectkw_mng_adding_%d", targetChat.ChatID),
+ Text: "👥 " + targetChat.ChatName,
+ CallbackData: fmt.Sprintf("detectkw_u_add_%d_%s", targetChat.ChatID, keyword),
}})
}
return &models.InlineKeyboardMarkup{
@@ -206,7 +206,7 @@ func ReadKeywordList(ctx context.Context) error {
Err(err).
Str("path", KeywordDataPath).
Msg("Failed to create empty keyword data file")
- KeywordDataErr = fmt.Errorf("failed to create empty keyword data file: %w", err)
+ KeywordDataErr = fmt.Errorf("failed to create empty keyword data file: %w", err)
return KeywordDataErr
}
} else {
@@ -221,6 +221,7 @@ func ReadKeywordList(ctx context.Context) error {
// 一切正常
KeywordDataList = lists
+ buildListenList()
return nil
}
@@ -296,7 +297,7 @@ func SaveKeywordList(ctx context.Context) error {
return KeywordDataErr
}
}
-
+
err = os.WriteFile(KeywordDataPath, data, 0644)
if err != nil {
@@ -339,7 +340,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
Err(err).
Dict(utils.GetUserDict(opts.Update.Message.From)).
Msg("Failed to init user and save keyword list")
- return nil
+ return fmt.Errorf("failed to init user and save keyword list: %w", err)
}
}
@@ -354,40 +355,33 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Dict(utils.GetUserDict(opts.Update.Message.From)).
- Msg("Failed to send `no group for user` message")
+ Str("content", "no group for user notice").
+ Msg(logt.SendMessage)
}
return nil
}
if len(opts.Fields) > 1 {
if user.AddingChatID != 0 {
- var chatForUser ChatForUser
- var chatForUserIndex int
- for i, c := range user.ChatsForUser {
- if c.ChatID == user.AddingChatID {
- chatForUser = c
- chatForUserIndex = i
- }
- }
// 限制关键词长度
if len(opts.Fields[1]) > 30 {
_, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
ChatID: opts.Update.Message.Chat.ID,
- Text: "抱歉,单个关键词长度不能超过 30 个字符",
+ Text: "抱歉,单个关键词长度不能超过 30 个英文字符",
ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID },
ParseMode: models.ParseModeHTML,
})
if err != nil {
logger.Error().
Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
- Msg("Send `keyword is too long` message failed")
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Int("length", len(opts.Fields[1])).
+ Str("content", "keyword is too long").
+ Msg(logt.SendMessage)
}
return nil
}
@@ -408,11 +402,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
}
if !isKeywordExist {
logger.Debug().
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
Str("globalKeyword", keyword).
Msg("User add a global keyword")
user.GlobalKeyword = append(user.GlobalKeyword, keyword)
@@ -421,13 +411,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
Str("globalKeyword", keyword).
Msg("Failed to add global keyword and save keyword list")
+ return fmt.Errorf("failed to add global keyword and save keyword list: %w", err)
}
pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ]", opts.Fields[1])
} else {
@@ -435,6 +422,14 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
}
} else {
+ var chatForUser ChatForUser
+ var chatForUserIndex int
+ for i, c := range user.ChatsForUser {
+ if c.ChatID == user.AddingChatID {
+ chatForUser = c
+ chatForUserIndex = i
+ }
+ }
targetChat := KeywordDataList.Chats[chatForUser.ChatID]
// 群组关键词
@@ -446,11 +441,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
}
if !isKeywordExist {
logger.Debug().
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
Int64("chatID", chatForUser.ChatID).
Str("keyword", keyword).
Msg("User add a keyword to chat")
@@ -461,16 +452,13 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
Int64("chatID", chatForUser.ChatID).
Str("keyword", keyword).
- Msg("Error add keyword and save keyword list")
+ Msg("Failed to add keyword and save keyword list")
+ return fmt.Errorf("failed to add keyword and save keyword list: %w", err)
}
-
+
pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(opts.Fields[1]))
} else {
pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", opts.Fields[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName)
@@ -478,22 +466,22 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
}
if isKeywordExist {
button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
- Text: "完成",
- CallbackData: "detectkw_mng_finish",
+ Text: "✅ 完成",
+ CallbackData: "detectkw_u_finish",
}}}}
} else {
button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{
{
- Text: "撤销操作",
- CallbackData: fmt.Sprintf("detectkw_mng_undo_%d_%s", user.AddingChatID, opts.Fields[1]),
+ Text: "↩️ 撤销操作",
+ CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", user.AddingChatID, opts.Fields[1]),
},
{
- Text: "完成",
- CallbackData: "detectkw_mng_finish",
+ Text: "✅ 完成",
+ CallbackData: "detectkw_u_finish",
},
}}}
}
-
+
_, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
ChatID: opts.Update.Message.Chat.ID,
Text: pendingMessage,
@@ -504,12 +492,9 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
- Msg("Send `keyword added` message failed")
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Str("content", "keyword added notice").
+ Msg(logt.SendMessage)
}
} else {
_, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
@@ -517,17 +502,14 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
Text: "您还没有选定要将关键词添加到哪个群组,请在下方挑选一个您已经添加的群组",
ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID },
ParseMode: models.ParseModeHTML,
- ReplyMarkup: user.selectChat(),
+ ReplyMarkup: user.selectChat(opts.Fields[1]),
})
if err != nil {
logger.Error().
Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
- Msg("Send `not selected chat yet` message failed")
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Str("content", "not selected chat yet").
+ Msg(logt.SendMessage)
}
}
} else {
@@ -541,12 +523,9 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
- Msg("Send `user group list keyboart` message failed")
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Str("content", "user group list keyboard").
+ Msg(logt.SendMessage)
}
}
} else {
@@ -561,7 +540,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
ParseMode: models.ParseModeHTML,
ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
Text: "管理此功能",
- CallbackData: "detectkw_groupmng",
+ CallbackData: "detectkw_g",
}}}},
})
if err != nil {
@@ -569,9 +548,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
Err(err).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Dict(utils.GetUserDict(opts.Update.Message.From)).
- Msg("Send `function is disabled by admins` message failed")
+ Str("content", "function is disabled by admins").
+ Msg(logt.SendMessage)
}
- return
+ return nil
} else {
if chat.AddTime == "" {
// 初始化群组
@@ -590,7 +570,8 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
logger.Error().
Err(err).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
- Msg("Error init chat and save keyword list")
+ Msg("Failed to init chat and save keyword list")
+ return fmt.Errorf("failed to init chat and save keyword list: %w", err)
}
}
if len(opts.Fields) == 1 {
@@ -607,7 +588,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
},
{
Text: "管理此功能",
- CallbackData: "detectkw_groupmng",
+ CallbackData: "detectkw_g",
},
}}},
})
@@ -615,7 +596,8 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
logger.Error().
Err(err).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
- Msg("Send /setkeyword command answer failed")
+ Str("content", "group record link button").
+ Msg(logt.SendMessage)
}
} else {
user := KeywordDataList.Users[opts.Update.Message.From.ID]
@@ -634,14 +616,15 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
logger.Error().
Err(err).
Dict(utils.GetUserDict(opts.Update.Message.From)).
- Msg("Error add a not init user and save keyword list")
+ Msg("Failed to add a not init user and save keyword list")
+ return fmt.Errorf("failed to add a not init user and save keyword list: %w", err)
}
}
var isChatAdded bool = false
var chatForUser ChatForUser
var chatForUserIndex int
-
+
for index, keyword := range user.ChatsForUser {
if keyword.ChatID == chat.ChatID {
chatForUser = keyword
@@ -664,7 +647,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Msg("Error add chat to user listen list and save keyword list")
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Msg("Failed to add chat to user listen list and save keyword list")
+ return fmt.Errorf("failed to add chat to user listen list and save keyword list: %w", err)
}
}
@@ -672,7 +658,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
if len(opts.Fields[1]) > 30 {
_, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
ChatID: opts.Update.Message.Chat.ID,
- Text: "抱歉,单个关键词长度不能超过 30 个字符",
+ Text: "抱歉,单个关键词长度不能超过 30 个英文字符",
ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID },
ParseMode: models.ParseModeHTML,
})
@@ -681,9 +667,11 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
Err(err).
Dict(utils.GetUserDict(opts.Update.Message.From)).
Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
- Msg("Send `keyword is too long` message failed")
+ Int("keywordLength", len(opts.Fields[1])).
+ Str("content", "keyword is too long").
+ Msg(logt.SendMessage)
}
- return
+ return nil
}
keyword := strings.ToLower(opts.Fields[1])
@@ -710,9 +698,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error {
logger.Error().
Err(err).
Dict(utils.GetUserDict(opts.Update.Message.From)).
- Int64("chatID", chatForUser.ChatID).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Str("keyword", keyword).
- Msg("Error add keyword and save keyword list")
+ Msg("Failed to add keyword and save keyword list")
+ return fmt.Errorf("failed to add keyword and save keyword list: %w", err)
}
if user.IsNotInit {
pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表\n若要在检测到关键词时收到提醒,请点击下方的按钮来初始化您的账号", strings.ToLower(opts.Fields[1])) @@ -729,7 +718,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, - Text: pendingMessage, + Text: pendingMessage, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, ParseMode: models.ParseModeHTML, ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ @@ -742,7 +731,8 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Msg("Send `keyword added` message failed") + Str("content", "keyword added notice"). + Msg(logt.SendMessage) } } } @@ -786,19 +776,16 @@ func KeywordDetector(opts *handler_structs.SubHandlerParams) error { text = strings.ToLower(opts.Update.Message.Text) } - if text == "" { - return - } + // 没有文字直接跳到 + if text == "" { return nil } // 先循环一遍,找出该群组中启用此功能的用户 ID for _, userID := range KeywordDataList.Chats[opts.Update.Message.Chat.ID].UsersID { // 获取用户信息,开始匹配关键词 user := KeywordDataList.Users[userID] if !user.IsDisable && !user.IsNotInit { - if !user.IsIncludeSelf && opts.Update.Message.From.ID == userID { - // 如果用户设定排除了自己发送的消息,则跳过 - continue - } + // 如果用户设定排除了自己发送的消息,则跳过 + if !user.IsIncludeSelf && opts.Update.Message.From.ID == userID { continue } // 用户为单独群组设定的关键词 for _, userKeywordList := range user.ChatsForUser { @@ -821,6 +808,7 @@ func KeywordDetector(opts *handler_structs.SubHandlerParams) error { } } } + return nil } func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, chatname, keyword, text string, isGlobalKeyword bool) { @@ -851,13 +839,14 @@ func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, ch Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Int64("userID", user.UserID). Str("keyword", keyword). - Msg("Send `keyword notice to user` message failed") + Str("content", "keyword detected notice to user"). + Msg(logt.SendMessage) } user.MentionCount++ KeywordDataList.Users[user.UserID] = user } -func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { +func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). @@ -875,14 +864,15 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Send `no permission to change group functions` callback answer failed") + Str("content", "no permission to change group functions"). + Msg(logt.AnswerCallback) } - return + return nil } chat := KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] - if opts.Update.CallbackQuery.Data == "detectkw_groupmng_switch" { + if opts.Update.CallbackQuery.Data == "detectkw_g_switch" { // 群组里的全局开关,是否允许群组内用户使用这个功能,优先级最高 chat.IsDisable = !chat.IsDisable KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] = chat @@ -893,7 +883,8 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Send `the group function switch has changed` callback answer failed") + Msg("Failed to change group switch and save keyword list") + return fmt.Errorf("failed to change group switch and save keyword list: %w", err) } } @@ -908,11 +899,13 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Edit message to `group function manager keyboard` failed") + Str("content", "group function manager keyboard"). + Msg(logt.EditMessage) } + return nil } -func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { +func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). @@ -921,57 +914,51 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { user := KeywordDataList.Users[opts.Update.CallbackQuery.From.ID] switch opts.Update.CallbackQuery.Data { - case "detectkw_mng_globalswitch": + case "detectkw_u_globalswitch": // 功能全局开关 user.IsDisable = !user.IsDisable - case "detectkw_mng_noticeswitch": + case "detectkw_u_noticeswitch": // 是否静默通知 user.IsSilentNotice = !user.IsSilentNotice - case "detectkw_mng_selfswitch": + case "detectkw_u_selfswitch": // 是否检测自己发送的消息 user.IsIncludeSelf = !user.IsIncludeSelf - case "detectkw_mng_finish": + case "detectkw_u_finish": // 停止添加群组关键词 user.AddingChatID = 0 - case "detectkw_mng_chatdisablebyadmin": + case "detectkw_u_chatdisablebyadmin": // 目标群组的管理员为群组关闭了此功能 _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, - Text: "此群组的的管理员禁用了此功能,因此,您无法再收到来自该群组的关键词提醒,您可以询问该群组的管理员是否可以重新开启这个功能", + Text: "此群组中的管理员禁用了此功能,因此,您无法再收到来自该群组的关键词提醒,您可以询问该群组的管理员是否可以重新开启这个功能", ShowAlert: true, }) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Send `this group is disable by admins` callback answer failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "this group is disable by admins"). + Msg(logt.AnswerCallback) } - return + return nil default: - if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_undo_") || strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_delkw_") { + if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") || strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_delkw_") { // 撤销添加或删除关键词 var chatIDAndKeyword string - if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_undo_") { - chatIDAndKeyword = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_undo_") + if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") { + chatIDAndKeyword = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") } else { - chatIDAndKeyword = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_delkw_") + chatIDAndKeyword = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_delkw_") } chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Parse chat ID failed when user undo add or delete a keyword") - return + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse chat ID when user undo add or delete a keyword") + return fmt.Errorf("failed to parse chat ID when user undo add or delete a keyword: %w", err) } // 删除关键词过程 @@ -1004,15 +991,15 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Error undo add or remove keyword and save keyword list") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Int64("chatID", chatID). + Str("keyword", chatIDAndKeywordList[1]). + Msg("Failed to undo add or remove keyword and save keyword list") + return fmt.Errorf("failed to undo add or remove keyword and save keyword list: %w", err) } - if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_undo_") { + if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") { _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.Update.CallbackQuery.Message.Message.ID, @@ -1022,52 +1009,66 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Edit message to `add keyword has been canceled` failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "add keyword has been canceled notice"). + Msg(logt.EditMessage) } } else { var buttons [][]models.InlineKeyboardButton var tempbutton []models.InlineKeyboardButton - for _, chat := range user.ChatsForUser { - if chat.ChatID == chatID { - for index, keyword := range chat.Keyword { - if index % 2 == 0 && index != 0 { - buttons = append(buttons, tempbutton) - tempbutton = []models.InlineKeyboardButton{} - } - tempbutton = append(tempbutton, models.InlineKeyboardButton{ - Text: keyword, - CallbackData: fmt.Sprintf("detectkw_mng_kw_%d_%s", chat.ChatID, keyword), - }) - // buttons = append(buttons, tempbutton) - } - if len(tempbutton) != 0 { + var keywordCount int + var pendingMessage string + + if chatID == user.UserID { + for index, keyword := range user.GlobalKeyword { + if index % 2 == 0 && index != 0 { buttons = append(buttons, tempbutton) + tempbutton = []models.InlineKeyboardButton{} + } + tempbutton = append(tempbutton, models.InlineKeyboardButton{ + Text: keyword, + CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", user.UserID, keyword), + }) + keywordCount++ + // buttons = append(buttons, tempbutton) + } + if len(tempbutton) != 0 { + buttons = append(buttons, tempbutton) + } + pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前设定了 %d 个全局关键词", chatIDAndKeywordList[1], keywordCount) + + } else { + for _, chat := range user.ChatsForUser { + if chat.ChatID == chatID { + for index, keyword := range chat.Keyword { + if index % 2 == 0 && index != 0 { + buttons = append(buttons, tempbutton) + tempbutton = []models.InlineKeyboardButton{} + } + tempbutton = append(tempbutton, models.InlineKeyboardButton{ + Text: keyword, + CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", chat.ChatID, keyword), + }) + keywordCount++ + // buttons = append(buttons, tempbutton) + } + if len(tempbutton) != 0 { + buttons = append(buttons, tempbutton) + } } } - } - - // bug here - - var pendingMessage string - if chatID == user.UserID { - pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前设定了 %d 个全局关键词", chatIDAndKeywordList[1], len(buttons)) - } else { - pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前为 %s 群组设定了 %d 个关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName, len(buttons)) + pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前为 %s 群组设定了 %d 个关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName, keywordCount) } buttons = append(buttons, []models.InlineKeyboardButton{ { - Text: "返回主菜单", - CallbackData: "detectkw_mng", + Text: "⬅️ 返回主菜单", + CallbackData: "detectkw_u", }, { - Text: "添加关键词", - CallbackData: fmt.Sprintf("detectkw_mng_adding_%d", chatID), + Text: "➕ 添加关键词", + CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID), }, }) @@ -1083,30 +1084,25 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Edit message to `chat keyword list keyboard with deleted keyword notice` failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "keyword list keyboard with deleted keyword notice"). + Msg(logt.EditMessage) } } - - return - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_adding_") { - // 设定要往哪个群组里添加关键词 - chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_adding_") + + return nil + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_adding_") { + // 设定要往哪个群组里添加关键词 + chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_adding_") chatID_int64, err := strconv.ParseInt(chatID, 10, 64) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Parse chat ID failed when user selecting chat to add keyword") - return + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse chat ID when user selecting chat to add keyword") + return fmt.Errorf("failed to parse chat ID when user selecting chat to add keyword: %w", err) } user := KeywordDataList.Users[user.UserID] user.AddingChatID = chatID_int64 @@ -1116,13 +1112,11 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Error set a chat ID for user add keyword and save keyword list") - + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Int64("chatID", chatID_int64). + Msg("Failed to set a chat ID for user add keyword and save keyword list") + return fmt.Errorf("failed to set a chat ID for user add keyword and save keyword list: %w", err) } var pendingMessage string @@ -1141,28 +1135,23 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Edit message to `already to add keyword` failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "already to add keyword notice"). + Msg(logt.EditMessage) } - return - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_switch_chat_") { + return nil + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_switch_chat_") { // 启用或禁用某个群组的关键词检测开关 - id := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_switch_chat_") + id := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_switch_chat_") id_int64, err := strconv.ParseInt(id, 10, 64) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Parse chat ID failed when user change the group switch") - return + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse chat ID when user change the group switch") + return fmt.Errorf("failed to parse chat ID when user change the group switch: %w", err) } for index, chat := range KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser { if chat.ChatID == id_int64 { @@ -1170,24 +1159,22 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { } KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser[index] = chat } - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_chat_") { + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_chat_") { // 显示某个群组的关键词列表 - chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_chat_") + chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_chat_") chatID_int64, err := strconv.ParseInt(chatID, 10, 64) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Parse chat ID failed when user wanna manage keyword for group") - return + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse chat ID when user wanna manage keyword for group") + return fmt.Errorf("failed to parse chat ID when user wanna manage keyword for group: %w", err) } var buttons [][]models.InlineKeyboardButton var tempbutton []models.InlineKeyboardButton var pendingMessage string + var keywordCount int if chatID_int64 == user.UserID { // 全局关键词 @@ -1198,8 +1185,9 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { } tempbutton = append(tempbutton, models.InlineKeyboardButton{ Text: keyword, - CallbackData: fmt.Sprintf("detectkw_mng_kw_%d_%s", user.UserID, keyword), + CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", user.UserID, keyword), }) + keywordCount++ } if len(tempbutton) != 0 { buttons = append(buttons, tempbutton) @@ -1207,7 +1195,7 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if len(buttons) == 0 { pendingMessage = "您没有设定任何全局关键词\n点击下方按钮来添加全局关键词" } else { - pendingMessage = fmt.Sprintf("您当前设定了 %d 个全局关键词\n
全局关键词将对您添加的全部群组生效\n但在部分情况下,全局关键词不会生效:\n- 您手动将群组设定为禁用状态\n- 对应群组的管理员为该群组关闭了此功能", len(buttons)) + pendingMessage = fmt.Sprintf("您当前设定了 %d 个全局关键词\n
全局关键词将对您添加的全部群组生效\n但在部分情况下,全局关键词不会生效:\n- 您手动将群组设定为禁用状态\n- 对应群组的管理员为该群组关闭了此功能", keywordCount) } } else { // 为群组设定的关键词 @@ -1220,8 +1208,9 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { } tempbutton = append(tempbutton, models.InlineKeyboardButton{ Text: keyword, - CallbackData: fmt.Sprintf("detectkw_mng_kw_%d_%s", chat.ChatID, keyword), + CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", chat.ChatID, keyword), }) + keywordCount++ // buttons = append(buttons, tempbutton) } if len(tempbutton) != 0 { @@ -1233,18 +1222,18 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if len(buttons) == 0 { pendingMessage = fmt.Sprintf("当前群组 %s 没有关键词\n点击下方按钮来为此群组添加关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName) } else { - pendingMessage = fmt.Sprintf("您当前为 %s 群组设定了 %d 个关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName, len(buttons)) + pendingMessage = fmt.Sprintf("您当前为 %s 群组设定了 %d 个关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName, keywordCount) } } buttons = append(buttons, []models.InlineKeyboardButton{ { - Text: "返回主菜单", - CallbackData: "detectkw_mng", + Text: "⬅️ 返回主菜单", + CallbackData: "detectkw_u", }, { - Text: "添加关键词", - CallbackData: fmt.Sprintf("detectkw_mng_adding_%d", chatID_int64), + Text: "➕ 添加关键词", + CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID_int64), }, }) @@ -1260,29 +1249,24 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Edit message to `group keyword list keyboard` failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "group keyword list keyboard"). + Msg(logt.EditMessage) } - return - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_kw_") { - // 删除某个关键词 - chatIDAndKeyword := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_mng_kw_") + return nil + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_kw_") { + // 管理一个关键词 + chatIDAndKeyword := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_kw_") chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Parse chat ID failed when user wanna manage a keyword") - return + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse chat ID when user wanna manage a keyword") + return fmt.Errorf("failed to parse chat ID when user wanna manage a keyword: %w", err) } var pendingMessage string @@ -1299,12 +1283,12 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { Text: pendingMessage, ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ { - Text: "返回", - CallbackData: "detectkw_mng_chat_" + chatIDAndKeywordList[0], + Text: "⬅️ 返回", + CallbackData: "detectkw_u_chat_" + chatIDAndKeywordList[0], }, { - Text: "删除此关键词", - CallbackData: "detectkw_mng_delkw_" + chatIDAndKeyword, + Text: "❌ 删除此关键词", + CallbackData: "detectkw_u_delkw_" + chatIDAndKeyword, }, }}}, ParseMode: models.ParseModeHTML, @@ -1312,14 +1296,127 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Edit message to `keyword manager keyboard` failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "keyword manager keyboard"). + Msg(logt.EditMessage) } - return + return nil + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_add_") { + chatIDAndKeyword := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_add_") + chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") + chatID, err := strconv.ParseInt(chatIDAndKeywordList[0], 10, 64) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse chat ID when user wanna add a keyword") + return fmt.Errorf("failed to parse chat ID when user wanna add a keyword: %w", err) + } + + var pendingMessage string + var button models.ReplyMarkup + var isKeywordExist bool + + if chatID == user.UserID { + // 全局关键词 + for _, k := range user.GlobalKeyword { + if k == chatIDAndKeywordList[1] { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("globalKeyword", chatIDAndKeywordList[1]). + Msg("User add a global keyword") + user.GlobalKeyword = append(user.GlobalKeyword, chatIDAndKeywordList[1]) + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("globalKeyword", chatIDAndKeywordList[1]). + Msg("Failed to add global keyword and save keyword list") + return fmt.Errorf("failed to add global keyword and save keyword list: %w", err) + } + pendingMessage = fmt.Sprintf("已添加全局关键词 [ %s ]", chatIDAndKeywordList[1]) + } else { + pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", chatIDAndKeywordList[1]) + } + } else { + // 群组关键词 + var chatForUser ChatForUser + var chatForUserIndex int + for i, c := range user.ChatsForUser { + if c.ChatID == chatID { + chatForUser = c + chatForUserIndex = i + } + } + targetChat := KeywordDataList.Chats[chatID] + for _, k := range chatForUser.Keyword { + if k == chatIDAndKeywordList[1] { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("User add a keyword to chat") + chatForUser.Keyword = append(chatForUser.Keyword, chatIDAndKeywordList[1]) + user.ChatsForUser[chatForUserIndex] = chatForUser + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to add keyword and save keyword list") + return fmt.Errorf("failed to add keyword and save keyword list: %w", err) + } + pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ]", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(chatIDAndKeywordList[1])) + } else { + pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) + } + } + + if isKeywordExist { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "✅ 完成", + CallbackData: "detectkw_u", + }}}} + } else { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "↩️ 撤销操作", + CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", chatID, chatIDAndKeywordList[1]), + }, + { + Text: "✅ 完成", + CallbackData: "detectkw_u", + }, + }}} + } + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: pendingMessage, + ReplyMarkup: button, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + fmt.Println(err) + } + return nil } } @@ -1332,12 +1429,10 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Edit message to `main manager keyboard` failed") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "main manager keyboard"). + Msg(logt.EditMessage) } KeywordDataList.Users[opts.Update.CallbackQuery.From.ID] = user @@ -1346,22 +1441,20 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Str("callbackQuery", opts.Update.CallbackQuery.Data). - Msg("Error add user and save keyword list") + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to save keyword list") + return fmt.Errorf("failed to save keyword list: %w", err) } + return nil } func buildGroupManageKB(chat KeywordChatList) models.ReplyMarkup { var buttons [][]models.InlineKeyboardButton buttons = append(buttons, []models.InlineKeyboardButton{{ - Text: "🔄 当前状态: " + utils.TextForTrueOrFalse(chat.IsDisable, "已禁用", "已启用"), - CallbackData: "detectkw_groupmng_switch", + Text: "🔄 当前状态: " + utils.TextForTrueOrFalse(chat.IsDisable, "已禁用 ❌", "已启用 ✅"), + CallbackData: "detectkw_g_switch", }}) return &models.InlineKeyboardMarkup{ @@ -1369,7 +1462,7 @@ func buildGroupManageKB(chat KeywordChatList) models.ReplyMarkup { } } -func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { +func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). @@ -1391,12 +1484,10 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Error add user and save keyword list") + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("messageText", opts.Update.Message.Text). + Msg("Failed to add user and save keyword list") + return fmt.Errorf("failed to add user and save keyword list: %w", err) } } if user.IsNotInit { @@ -1407,12 +1498,10 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { err := SaveKeywordList(opts.Ctx) logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Error init user and save keyword list") + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("messageText", opts.Update.Message.Text). + Msg("Failed to init user and save keyword list") + return fmt.Errorf("failed to init user and save keyword list: %w", err) } if strings.HasPrefix(opts.Fields[1], "detectkw_addgroup_") { groupID := strings.TrimPrefix(opts.Fields[1], "detectkw_addgroup_") @@ -1420,13 +1509,10 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). - Msg("Parse chat ID failed when user add a group by /start command") - return + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("messageText", opts.Update.Message.Text). + Msg("Failed to parse chat ID when user add a group by /start command") + return fmt.Errorf("failed to parse chat ID when user add a group by /start command: %w", err) } chat := KeywordDataList.Chats[groupID_int64] @@ -1462,26 +1548,21 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) { if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Send `added group in user list` message failed") + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "added group in user list"). + Msg(logt.SendMessage) } } err := SaveKeywordList(opts.Ctx) if err != nil { logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Error add group for user and save keyword list failed") + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("messageText", opts.Update.Message.Text). + Msg("Failed to add group for user and save keyword list") + return fmt.Errorf("failed to add group for user and save keyword list: %w", err) } - + return nil } func buildUserChatList(user KeywordUserList) models.ReplyMarkup { @@ -1490,46 +1571,46 @@ func buildUserChatList(user KeywordUserList) models.ReplyMarkup { if !user.IsDisable { buttons = append(buttons, []models.InlineKeyboardButton{{ Text: fmt.Sprintf("全局关键词 %d 个", len(user.GlobalKeyword)), - CallbackData: fmt.Sprintf("detectkw_mng_chat_%d", user.UserID), + CallbackData: fmt.Sprintf("detectkw_u_chat_%d", user.UserID), }}) for _, chat := range user.ChatsForUser { var subchats []models.InlineKeyboardButton var targetChat = KeywordDataList.Chats[chat.ChatID] - + subchats = append(subchats, models.InlineKeyboardButton{ - Text: targetChat.ChatName, - CallbackData: fmt.Sprintf("detectkw_mng_chat_%d", targetChat.ChatID), + Text: fmt.Sprintf("(%d) %s", len(chat.Keyword), targetChat.ChatName), + CallbackData: fmt.Sprintf("detectkw_u_chat_%d", targetChat.ChatID), }) if targetChat.IsDisable { subchats = append(subchats, models.InlineKeyboardButton{ Text: "🚫 查看帮助", - CallbackData: "detectkw_mng_chatdisablebyadmin", + CallbackData: "detectkw_u_chatdisablebyadmin", }) } else { subchats = append(subchats, models.InlineKeyboardButton{ Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsDisable, "当前已禁用 ❌", "当前已启用 ✅"), - CallbackData: fmt.Sprintf("detectkw_mng_switch_chat_%d", targetChat.ChatID), + CallbackData: fmt.Sprintf("detectkw_u_switch_chat_%d", targetChat.ChatID), }) } - + buttons = append(buttons, subchats) } buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🔄 通知偏好:" + utils.TextForTrueOrFalse(user.IsSilentNotice, "🔇 无声通知", "🔉 有声通知"), - CallbackData: "detectkw_mng_noticeswitch", + CallbackData: "detectkw_u_noticeswitch", }}) buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🔄 检测偏好:" + utils.TextForTrueOrFalse(user.IsIncludeSelf, "不排除自己的消息", "排除自己的消息"), - CallbackData: "detectkw_mng_selfswitch", + CallbackData: "detectkw_u_selfswitch", }}) } buttons = append(buttons, []models.InlineKeyboardButton{{ Text: "🔄 全局状态:" + utils.TextForTrueOrFalse(user.IsDisable, "已禁用 ❌", "已启用 ✅"), - CallbackData: "detectkw_mng_globalswitch", + CallbackData: "detectkw_u_globalswitch", }}) return &models.InlineKeyboardMarkup{ diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index e132fc9..220d09c 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -190,7 +190,7 @@ func ReadUdonese(ctx context.Context) error { Err(err). Str("path", UdonesePath). Msg("Failed to create empty udonese list file") - UdoneseErr = fmt.Errorf("failed to create empty udonese list file: %w", err) + UdoneseErr = fmt.Errorf("failed to create empty udonese list file: %w", err) return UdoneseErr } } else { @@ -212,7 +212,7 @@ func SaveUdonese(ctx context.Context) error { Str("pluginName", "Udonese"). Str("funcName", "SaveUdonese"). Logger() - + data, err := yaml.Marshal(UdoneseData) if err != nil { logger.Error(). @@ -945,7 +945,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { } return nil } - + if opts.Update.CallbackQuery.Data == "udonese_done" { _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, @@ -995,8 +995,8 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("Failed to covert meanning index") - return fmt.Errorf("failed to covert meanning index: %w", err) + Msg("Failed to parse meanning index") + return fmt.Errorf("failed to parse meanning index: %w", err) } var targetMeaning UdoneseMeaning @@ -1070,8 +1070,8 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("Failed to covert meanning index") - return fmt.Errorf("failed to covert meanning index: %w", err) + Msg("Failed to parse meanning index") + return fmt.Errorf("failed to parse meanning index: %w", err) } var newMeaningList []UdoneseMeaning var targetWord UdoneseWord @@ -1106,7 +1106,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Err(msgerr). Msg(logt.AnswerCallback) } - + return fmt.Errorf("failed to save udonese data after delete meaning: %w", err) } diff --git a/utils/configs/init.go b/utils/configs/init.go index 44c0d51..e2fe547 100644 --- a/utils/configs/init.go +++ b/utils/configs/init.go @@ -349,7 +349,7 @@ func CheckConfig(ctx context.Context) error { func ShowConst(ctx context.Context) { logger := zerolog.Ctx(ctx) - if consts.BuildTime == "" { + if consts.BuildAt == "" { logger.Warn(). Str("runtime", runtime.Version()). Str("logLevel", BotConfig.LogLevel). @@ -357,13 +357,14 @@ func ShowConst(ctx context.Context) { Msg("trbot") } else { logger.Info(). - Str("commit", consts.Commit). - Str("branch", consts.Branch). - Str("version", consts.Version). - Str("buildTime", consts.BuildTime). - Str("changes", consts.Changes). - Str("runtime", runtime.Version()). - Str("logLevel", BotConfig.LogLevel). + Str("commit", consts.Commit). + Str("branch", consts.Branch). + Str("version", consts.Version). + Str("buildAt", consts.BuildAt). + Str("buildOn", consts.BuildOn). + Str("changes", consts.Changes). + Str("runtime", runtime.Version()). + Str("logLevel", BotConfig.LogLevel). Msg("trbot") } } diff --git a/utils/consts/consts.go b/utils/consts/consts.go index 5654c31..bb52251 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -14,9 +14,9 @@ var LogFilePath string = YAMLDataBasePath + "log.txt" var BotMe *models.User // 用于存储 bot 信息 -var Commit string -var Branch string -var Version string -var BuildTime string -var BuildMachine string -var Changes string // uncommit files when build +var Commit string +var Branch string +var Version string +var BuildAt string +var BuildOn string +var Changes string // uncommit files when build diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go index 3d1dd44..048623a 100644 --- a/utils/internal_plugin/register.go +++ b/utils/internal_plugin/register.go @@ -132,7 +132,7 @@ func Register(ctx context.Context) { // info, err := opts.Thebot.GetWebhookInfo(ctx) // fmt.Println(info) // return - botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, Text: mess.OutputVersionInfo(), ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, @@ -145,35 +145,7 @@ func Register(ctx context.Context) { Msg("Failed to send `bot version info` message") return err } - time.Sleep(time.Second * 20) - success, err := opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageIDs: []int{ - opts.Update.Message.ID, - botMessage.ID, - }, - }) - if err != nil { - logger.Error(). - Err(err). - Str("command", "/version"). - Msg("Failed to delete `command message and bot version info` message") - } - if !success { - // 如果不能把用户的消息也删了,就单独删 bot 的消息 - _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageID: botMessage.ID, - }) - if err != nil { - logger.Error(). - Err(err). - Str("command", "/version"). - Msg("Failed to delete `bot version info` message") - } - } - - return err + return nil }, }, }...) diff --git a/utils/mess/mess.go b/utils/mess/mess.go index ba25571..e46429a 100644 --- a/utils/mess/mess.go +++ b/utils/mess/mess.go @@ -67,15 +67,15 @@ func OutputVersionInfo() string { hostname, _ := os.Hostname() var gitURL string = "https://gitea.trle5.xyz/trle5/trbot/commit/" var info string - if consts.BuildTime != "" { - info += fmt.Sprintf("`Version: `%s\n", consts.Version) - info += fmt.Sprintf("`Branch: `%s\n", consts.Branch) - info += fmt.Sprintf("`Commit: `[%s](%s%s) (%s)\n", consts.Commit[:10], gitURL, consts.Commit, consts.Changes) - info += fmt.Sprintf("`BuildTime: `%s\n", consts.BuildTime) - info += fmt.Sprintf("`BuildMachine: `%s\n", consts.BuildMachine) - info += fmt.Sprintf("`Runtime: `%s\n", runtime.Version()) - info += fmt.Sprintf("`Goroutine: `%d\n", runtime.NumGoroutine()) - info += fmt.Sprintf("`Hostname: `%s\n", hostname) + if consts.BuildAt != "" { + info += fmt.Sprintf("`Version: `%s\n", consts.Version) + info += fmt.Sprintf("`Branch: `%s\n", consts.Branch) + info += fmt.Sprintf("`Commit: `[%s](%s%s) (%s)\n", consts.Commit[:10], gitURL, consts.Commit, consts.Changes) + info += fmt.Sprintf("`BuildAt: `%s\n", consts.BuildAt) + info += fmt.Sprintf("`BuildOn: `%s\n", consts.BuildOn) + info += fmt.Sprintf("`Runtime: `%s\n", runtime.Version()) + info += fmt.Sprintf("`Goroutine: `%d\n", runtime.NumGoroutine()) + info += fmt.Sprintf("`Hostname: `%s\n", hostname) return info } return fmt.Sprintln( -- 2.49.1 From 17fca9af22d68ac2ec7f514ae395a47dc13f3695 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Wed, 25 Jun 2025 07:20:00 +0800 Subject: [PATCH 20/27] refactor udonese mult error add send document template --- logstruct.txt | 5 + plugins/plugin_detect_keyword.go | 7 +- plugins/plugin_sticker.go | 144 +-- plugins/plugin_teamspeak3.go | 10 +- plugins/plugin_udonese.go | 1037 ++++++++--------- plugins/plugin_voicelist.go | 13 +- utils/logt/log_template.go | 1 + utils/mterr/mult_errors.go | 37 + utils/type/message_utils/message_attribute.go | 12 +- utils/utils.go | 2 +- 10 files changed, 646 insertions(+), 622 deletions(-) create mode 100644 logstruct.txt create mode 100644 utils/mterr/mult_errors.go diff --git a/logstruct.txt b/logstruct.txt new file mode 100644 index 0000000..6f7c592 --- /dev/null +++ b/logstruct.txt @@ -0,0 +1,5 @@ +bot.SendMessage: Failed to send [%s] message +bot.EditMessage: Failed to edit message to [%s] +bot.DeleteMessages: Failed to delete [%s] message +bot.AnswerInlineQuery: Failed to send [%s] inline answer (sub handler can add a `Str("command", "log")` ) +bot.AnswerCallbackQuery: Failed to send [%s] callback answer diff --git a/plugins/plugin_detect_keyword.go b/plugins/plugin_detect_keyword.go index ab44c83..d04ba9a 100644 --- a/plugins/plugin_detect_keyword.go +++ b/plugins/plugin_detect_keyword.go @@ -1414,7 +1414,12 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { ParseMode: models.ParseModeHTML, }) if err != nil { - fmt.Println(err) + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "keyword added notice"). + Msg(logt.EditMessage) } return nil } diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index 3dba2bf..6360c82 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -16,6 +16,7 @@ import ( "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" + "trbot/utils/logt" "trbot/utils/plugin_utils" "trbot/utils/type/message_utils" @@ -77,7 +78,7 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { Str("pluginName", "StickerDownload"). Str("funcName", "EchoStickerHandler"). Logger() - + if opts.Update.Message == nil && opts.Update.CallbackQuery != nil && strings.HasPrefix(opts.Update.CallbackQuery.Data, "HBMT_") && opts.Update.CallbackQuery.Message.Message != nil && opts.Update.CallbackQuery.Message.Message.ReplyToMessage != nil { // if this handler tigger by `handler by message type`, copy `update.CallbackQuery.Message.Message.ReplyToMessage` to `update.Message` opts.Update.Message = opts.Update.CallbackQuery.Message.Message.ReplyToMessage @@ -89,7 +90,7 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { logger.Debug(). Str("emoji", opts.Update.Message.Sticker.Emoji). Str("setName", opts.Update.Message.Sticker.SetName). - Msg("start download sticker") + Msg("Start download sticker") err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.From.ID, db_struct.StickerDownloaded) if err != nil { @@ -115,9 +116,10 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(msgerr). Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Failed to send `sticker download error` message") + Str("content", "sticker download error"). + Msg(logt.SendMessage) } - return err + return fmt.Errorf("failed to download sticker: %w", err) } documentParams := &bot.SendDocumentParams{ @@ -148,7 +150,7 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { stickerFilePrefix = "sticker" } else { stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) - + // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ @@ -168,8 +170,9 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Failed to send sticker file to user") - return err + Str("content", "sticker file"). + Msg(logt.SendDocument) + return fmt.Errorf("failed to send sticker file: %w", err) } return nil @@ -208,7 +211,7 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) Err(err). Str("setName", opts.Update.Message.Sticker.SetName). Msg("Failed to get sticker set info, download it as a custom sticker") - + // 到这里是因为用户发送的贴纸对应的贴纸包已经被删除了,但贴纸中的信息还有对应的 SetName,会触发查询,但因为贴纸包被删了就查不到,将 index 值设为 -1,缓存后当作自定义贴纸继续 data.IsCustomSticker = true stickerSetNamePrivate = opts.Update.Message.Sticker.SetName @@ -241,21 +244,16 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) var filePath string = filepath.Join(StickerCache_path, stickerSetNamePrivate) // 保存贴纸源文件的目录 .cache/sticker/setName/ var originFullPath string = filepath.Join(filePath, stickerFileNameWithDot + fileSuffix) // 到贴纸文件的完整目录 .cache/sticker/setName/stickerFileName.webp - - var PNGFilePath string = filepath.Join(StickerCachePNG_path, stickerSetNamePrivate) // 转码后为 png 格式的目录 .cache/sticker_png/setName/ - var toPNGFullPath string = filepath.Join(PNGFilePath, stickerFileNameWithDot + "png") // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png - - var GIFFilePath string = filepath.Join(StickerCacheGIF_path, stickerSetNamePrivate) // 转码后为 png 格式的目录 .cache/sticker_png/setName/ - var toGIFFullPath string = filepath.Join(GIFFilePath, stickerFileNameWithDot + "gif") // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png + var finalFullPath string // 存放最后读取并发送的文件完整目录 .cache/sticker/setName/stickerFileName.webp _, err := os.Stat(originFullPath) // 检查贴纸源文件是否已缓存 if err != nil { // 如果文件不存在,进行下载,否则返回错误 if os.IsNotExist(err) { // 日志提示该文件没被缓存,正在下载 - logger.Trace(). + logger.Debug(). Str("originFullPath", originFullPath). - Msg("sticker file not cached, downloading") + Msg("Sticker file not cached, downloading") // 从服务器获取文件信息 fileinfo, err := opts.Thebot.GetFile(opts.Ctx, &bot.GetFileParams{ FileID: opts.Update.Message.Sticker.FileID }) @@ -317,13 +315,11 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) } } else { // 文件已存在,跳过下载 - logger.Trace(). + logger.Debug(). Str("originFullPath", originFullPath). - Msg("sticker file already cached") + Msg("Sticker file already cached") } - var finalFullPath string // 存放最后读取并发送的文件完整目录 .cache/sticker/setName/stickerFileName.webp - if opts.Update.Message.Sticker.IsAnimated { // tgs // 不需要转码,直接读取原贴纸文件 @@ -331,14 +327,17 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) } else if opts.Update.Message.Sticker.IsVideo { if configs.BotConfig.FFmpegPath != "" { // webm, convert to gif + var GIFFilePath string = filepath.Join(StickerCacheGIF_path, stickerSetNamePrivate) // 转码后为 png 格式的目录 .cache/sticker_png/setName/ + var toGIFFullPath string = filepath.Join(GIFFilePath, stickerFileNameWithDot + "gif") // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png + _, err = os.Stat(toGIFFullPath) // 使用目录提前检查一下是否已经转换过 if err != nil { // 如果提示不存在,进行转换 if os.IsNotExist(err) { // 日志提示该文件没转换,正在转换 - logger.Trace(). + logger.Debug(). Str("toGIFFullPath", toGIFFullPath). - Msg("sticker file does not convert, converting") + Msg("Sticker file does not convert, converting") // 创建保存贴纸的目录 err = os.MkdirAll(GIFFilePath, 0755) @@ -369,9 +368,9 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) } } else { // 文件存在,跳过转换 - logger.Trace(). + logger.Debug(). Str("toGIFFullPath", toGIFFullPath). - Msg("sticker file already converted to gif") + Msg("Sticker file already converted to gif") } // 处理完成,将最后要读取的目录设为转码后 gif 格式贴纸的完整目录 @@ -383,14 +382,17 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) } } else { // webp, need convert to png + var PNGFilePath string = filepath.Join(StickerCachePNG_path, stickerSetNamePrivate) // 转码后为 png 格式的目录 .cache/sticker_png/setName/ + var toPNGFullPath string = filepath.Join(PNGFilePath, stickerFileNameWithDot + "png") // 转码后到 png 格式贴纸的完整目录 .cache/sticker_png/setName/stickerFileName.png + _, err = os.Stat(toPNGFullPath) // 使用目录提前检查一下是否已经转换过 if err != nil { // 如果提示不存在,进行转换 if os.IsNotExist(err) { // 日志提示该文件没转换,正在转换 - logger.Trace(). + logger.Debug(). Str("toPNGFullPath", toPNGFullPath). - Msg("sticker file does not convert, converting") + Msg("Sticker file does not convert, converting") // 创建保存贴纸的目录 err = os.MkdirAll(PNGFilePath, 0755) @@ -421,9 +423,9 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) } } else { // 文件存在,跳过转换 - logger.Trace(). + logger.Debug(). Str("toPNGFullPath", toPNGFullPath). - Msg("sticker file already converted to png") + Msg("Sticker file already converted to png") } // 处理完成,将最后要读取的目录设为转码后 png 格式贴纸的完整目录 @@ -461,7 +463,8 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) logger.Error(). Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to send `start download stickerset` message") + Str("content", "start download stickerset"). + Msg(logt.SendMessage) } err = database.IncrementalUsageCount(opts.Ctx, opts.Update.CallbackQuery.Message.Message.Chat.ID, db_struct.StickerSetDownloaded) @@ -499,9 +502,10 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) logger.Error(). Err(msgerr). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to send `get sticker set info error` message") + Str("content", "get sticker set info error"). + Msg(logt.SendMessage) } - return err + return fmt.Errorf("failed to get sticker set info: %w", err) } stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) @@ -511,18 +515,19 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Msg("Failed to download sticker set") - _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, msgerr := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.CallbackQuery.From.ID, Text: fmt.Sprintf("下载贴纸包时发生了一些错误\n
Failed to download sticker set: %s", err), ParseMode: models.ParseModeHTML, }) - if err != nil { + if msgerr != nil { logger.Error(). - Err(err). + Err(msgerr). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to send `download sticker set error` message") + Str("content", "download sticker set error"). + Msg(logt.SendMessage) } - return err + return fmt.Errorf("failed to download sticker set: %w", err) } documentParams := &bot.SendDocumentParams{ @@ -543,7 +548,9 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) logger.Error(). Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to send sticker set zip file to user") + Str("content", "sticker set zip file"). + Msg(logt.SendDocument) + return fmt.Errorf("failed to send sticker set zip file: %w", err) } _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ @@ -554,7 +561,8 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) logger.Error(). Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to delete `start download stickerset` message") + Str("content", "start download stickerset notice"). + Msg(logt.DeleteMessage) } return nil @@ -579,7 +587,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Str("name", data.StickerSetName). Int("allCount", len(stickerSet.Stickers)), ). - Msg("start download sticker set") + Msg("Start download sticker set") filePath := filepath.Join(StickerCache_path, stickerSet.Name) PNGFilePath := filepath.Join(StickerCachePNG_path, stickerSet.Name) @@ -607,8 +615,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S stickerCount_webp++ } - var originFullPath string = filepath.Join(filePath, stickerfileName + fileSuffix) - var toPNGFullPath string = filepath.Join(PNGFilePath, stickerfileName + "png") + var originFullPath string = filepath.Join(filePath, stickerfileName + fileSuffix) _, err := os.Stat(originFullPath) // 检查单个贴纸是否已缓存 if err != nil { @@ -618,7 +625,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Str("originFullPath", originFullPath). Str("stickerSetName", data.StickerSetName). Int("stickerIndex", i). - Msg("sticker file not cached, downloading") + Msg("Sticker file not cached, downloading") // 从服务器获取文件内容 fileinfo, err := opts.Thebot.GetFile(opts.Ctx, &bot.GetFileParams{ FileID: sticker.FileID }) @@ -627,8 +634,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("fileID", opts.Update.Message.Sticker.FileID). - Msg("error getting sticker file info") - return nil, fmt.Errorf("error getting file info %s: %v", sticker.FileID, err) + Msg("Failed to get sticker file info") + return nil, fmt.Errorf("failed to get file info %s: %w", sticker.FileID, err) } // 下载贴纸文件 @@ -638,8 +645,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("filePath", fileinfo.FilePath). - Msg("error downloading sticker file") - return nil, fmt.Errorf("error downloading file %s: %v", fileinfo.FilePath, err) + Msg("Failed to download sticker file") + return nil, fmt.Errorf("failed to download sticker file %s: %w", fileinfo.FilePath, err) } defer resp.Body.Close() @@ -673,7 +680,6 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Int("stickerIndex", i). Str("originFullPath", originFullPath). Msg("Failed to writing sticker data to file") - return nil, fmt.Errorf("failed to writing sticker data to file [%s]: %w", originFullPath, err) } } else { @@ -689,11 +695,13 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S logger.Trace(). Int("stickerIndex", i). Str("originFullPath", originFullPath). - Msg("sticker file already exists") + Msg("Sticker file already exists") } // 仅需要 PNG 格式时进行转换 if isOnlyPNG && !sticker.IsVideo && !sticker.IsAnimated { + var toPNGFullPath string = filepath.Join(PNGFilePath, stickerfileName + "png") + _, err = os.Stat(toPNGFullPath) if err != nil { if os.IsNotExist(err) { @@ -701,7 +709,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S logger.Trace(). Int("stickerIndex", i). Str("toPNGFullPath", toPNGFullPath). - Msg("file does not convert, converting") + Msg("File does not convert, converting") // 创建保存贴纸的目录 err = os.MkdirAll(PNGFilePath, 0755) if err != nil { @@ -709,8 +717,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("PNGFilePath", PNGFilePath). - Msg("error creating directory to convert file") - return nil, fmt.Errorf("error creating directory %s: %w", PNGFilePath, err) + Msg("Failed to create directory to convert file") + return nil, fmt.Errorf("failed to create directory [%s] to convert file: %w", PNGFilePath, err) } // 将 webp 转换为 png err = convertWebPToPNG(originFullPath, toPNGFullPath) @@ -719,8 +727,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("originFullPath", originFullPath). - Msg("error converting webp to png") - return nil, fmt.Errorf("error converting webp to png %s: %w", originFullPath, err) + Msg("Failed to convert webp to png") + return nil, fmt.Errorf("failed to converting webp to png [%s]: %w", originFullPath, err) } } else { // 其他错误 @@ -728,13 +736,13 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Err(err). Int("stickerIndex", i). Str("toPNGFullPath", toPNGFullPath). - Msg("error when reading converted file info") - return nil, fmt.Errorf("error when reading converted file info: %w", err) + Msg("Failed to read converted file info") + return nil, fmt.Errorf("failed to read converted file info: %w", err) } } else { logger.Trace(). Str("toPNGFullPath", toPNGFullPath). - Msg("file already converted") + Msg("File already converted") } } } @@ -754,8 +762,8 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Int("tgs", stickerCount_tgs). Int("WebM", stickerCount_webm), ). - Msg("there are no static stickers in the sticker set") - return nil, fmt.Errorf("there are no static stickers in the sticker set") + Msg("There are no static stickers in the sticker set") + return nil, fmt.Errorf("there are no static stickers in the sticker set [%s]", stickerSet.Name) } data.StickerCount = stickerCount_webp zipFileName = fmt.Sprintf("%s(%d)_png.zip", stickerSet.Name, data.StickerCount) @@ -788,7 +796,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Msg("Failed to compress sticker folder") return nil, fmt.Errorf("failed to compress sticker folder [%s]: %w", compressFolderPath, err) } - logger.Trace(). + logger.Debug(). Str("compressFolderPath", compressFolderPath). Str("zipFileFullPath", zipFileFullPath). Msg("Compress sticker folder successfully") @@ -800,7 +808,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S return nil, fmt.Errorf("failed to read compressed sticker set zip file [%s] info: %w", zipFileFullPath, err) } } else { - logger.Trace(). + logger.Debug(). Str("zipFileFullPath", zipFileFullPath). Msg("sticker set zip file already compressed") } @@ -824,7 +832,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Str("name", data.StickerSetName). Int("count", data.StickerCount), ). - Msg("sticker set already zipped") + Msg("Sticker set already zipped") } else if isOnlyPNG && allConverted { // 仅需要 PNG 格式,且贴纸包完全转换成 PNG 格式,但尚未压缩 logger.Info(). @@ -834,7 +842,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Str("name", data.StickerSetName). Int("count", data.StickerCount), ). - Msg("sticker set already converted") + Msg("Sticker set already converted") } else if allCached { // 贴纸包中的贴纸已经全部缓存了 logger.Info(). @@ -844,7 +852,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Str("name", data.StickerSetName). Int("count", data.StickerCount), ). - Msg("sticker set already cached") + Msg("Sticker set already cached") } else { // 新下载的贴纸包(如果有部分已经下载了也是这个) logger.Info(). @@ -854,7 +862,7 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S Str("name", data.StickerSetName). Int("count", data.StickerCount), ). - Msg("sticker set already downloaded") + Msg("Sticker set already downloaded") } return &data, nil @@ -864,27 +872,27 @@ func convertWebPToPNG(webpPath, pngPath string) error { // 打开 WebP 文件 webpFile, err := os.Open(webpPath) if err != nil { - return fmt.Errorf("打开 WebP 文件失败: %v", err) + return fmt.Errorf("打开 WebP 文件失败: %w", err) } defer webpFile.Close() // 解码 WebP 图片 img, err := webp.Decode(webpFile) if err != nil { - return fmt.Errorf("解码 WebP 失败: %v", err) + return fmt.Errorf("解码 WebP 失败: %w", err) } // 创建 PNG 文件 pngFile, err := os.Create(pngPath) if err != nil { - return fmt.Errorf("创建 PNG 文件失败: %v", err) + return fmt.Errorf("创建 PNG 文件失败: %w", err) } defer pngFile.Close() // 编码 PNG err = png.Encode(pngFile, img) if err != nil { - return fmt.Errorf("编码 PNG 失败: %v", err) + return fmt.Errorf("编码 PNG 失败: %w", err) } return nil diff --git a/plugins/plugin_teamspeak3.go b/plugins/plugin_teamspeak3.go index d33e5ef..cb34815 100644 --- a/plugins/plugin_teamspeak3.go +++ b/plugins/plugin_teamspeak3.go @@ -90,7 +90,7 @@ func initTeamSpeak(ctx context.Context) bool { Str("pluginName", "teamspeak3"). Str("funcName", "initTeamSpeak"). Logger() - + // 判断配置文件是否存在 _, err := os.Stat(tsDataDir) if err != nil { @@ -153,7 +153,7 @@ func initTeamSpeak(ctx context.Context) bool { Err(tsErr). Str("path", tsDataPath). Msg("Failed to connect to server") - tsErr = fmt.Errorf("connect error: %w", tsErr) + tsErr = fmt.Errorf("failed to connnect to server: %w", tsErr) return false } } @@ -173,7 +173,7 @@ func initTeamSpeak(ctx context.Context) bool { Err(err). Str("path", tsDataPath). Msg("Failed to login to server") - tsErr = fmt.Errorf("login error: %w", err) + tsErr = fmt.Errorf("failed to login to server: %w", err) isLoginFailed = true return false } else { @@ -214,7 +214,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Err(err). Msg("Failed to switch server") - tsErr = fmt.Errorf("switch server error: %w", err) + tsErr = fmt.Errorf("failed to switch server: %w", err) return false } @@ -231,7 +231,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Err(err). Msg("Failed to set bot nickname") - tsErr = fmt.Errorf("set nickname error: %w", err) + tsErr = fmt.Errorf("failed to set nickname: %w", err) } } diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index 220d09c..db5dcfe 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -15,7 +15,9 @@ import ( "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/logt" + "trbot/utils/mterr" "trbot/utils/plugin_utils" + "trbot/utils/type/message_utils" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" @@ -28,7 +30,8 @@ var UdoneseErr error var UdoneseDir string = filepath.Join(consts.YAMLDataBasePath, "udonese/") var UdonesePath string = filepath.Join(UdoneseDir, consts.YAMLFileName) -var UdonGroupID int64 = -1002205667779 +// var UdonGroupID int64 = -1002205667779 +var UdonGroupID int64 = -1002499888124 // trbot var UdoneseManagerIDs []int64 = []int64{ 872082796, // akaudon 1086395364, // trle5 @@ -343,6 +346,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { // 不响应来自转发的命令 if opts.Update.Message.ForwardOrigin != nil { return nil } + var handlerErr mterr.MultiError logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "Udonese"). @@ -365,296 +369,269 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese not allowed group"). Msg(logt.SendMessage) - return fmt.Errorf("failed to send `/udonese not allowed group` message: %w", err) + handlerErr.Addf("failed to send `/udonese not allowed group` message: %w", err) } - return nil - } + } else { + if len(opts.Fields) < 3 { + // 如果是管理员,则显示可以管理词的帮助 + if isManager { + if len(opts.Fields) < 2 { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + Text: "使用 `/udonese <词> <单个意思>` 来添加记录\n或使用 `/udonese <词>` 来管理记录", + ParseMode: models.ParseModeMarkdownV1, + DisableNotification: true, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese admin command help"). + Msg(logt.SendMessage) + handlerErr.Addf("failed to send `/udonese admin command help` message: %w", err) + } + } else /* 词信息 */ { + checkWord := opts.Fields[1] + var targetWord UdoneseWord + for _, wordlist := range UdoneseData.List { + if wordlist.Word == checkWord { + targetWord = wordlist + } + } - if isManager && len(opts.Fields) < 3 { - if len(opts.Fields) < 2 { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, + // 如果词存在,则显示词的信息 + if targetWord.Word == "" { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "似乎没有这个词呢...", + ParseMode: models.ParseModeMarkdownV1, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + DisableNotification: true, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese admin command no this word"). + Msg(logt.SendMessage) + handlerErr.Addf("failed to send `/udonese admin command no this word` message: %w", err) + } + } else { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n", targetWord.Word, len(targetWord.MeaningList), targetWord.Used), + ParseMode: models.ParseModeHTML, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), + DisableNotification: true, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese manage keyboard"). + Msg(logt.SendMessage) + handlerErr.Addf("failed to send `/udonese manage keyboard` message: %w", err) + } + } + } + } else /* 普通用户 */ { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + Text: "使用 `/udonese <词> <单个意思>` 来添加记录", + ParseMode: models.ParseModeMarkdownV1, + DisableNotification: true, + }) + if err != nil { + logger.Info(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "/udonese command help"). + Msg(logt.SendMessage) + handlerErr.Addf("failed to send `/udonese command help` message: %w", err) + } + } + } else { + meaning := strings.TrimSpace(opts.Update.Message.Text[len(opts.Fields[0]) + len(opts.Fields[1]) + 2:]) + + var ( + fromID int64 + fromUsername string + fromName string + viaID int64 + viaUsername string + viaName string + ) + + msgAttr := message_utils.GetMessageAttribute(opts.Update.Message) + + if msgAttr.IsReplyToMessage { + replyAttr := message_utils.GetMessageAttribute(opts.Update.Message.ReplyToMessage) + + if replyAttr.IsUserAsChannel || replyAttr.IsFromLinkedChannel || replyAttr.IsFromAnonymous { + // 回复给一条频道身份的信息 + fromID = opts.Update.Message.ReplyToMessage.SenderChat.ID + fromUsername = opts.Update.Message.ReplyToMessage.SenderChat.Username + fromName = utils.ShowChatName(opts.Update.Message.ReplyToMessage.SenderChat) + } else { + // 回复给普通用户 + fromName = utils.ShowUserName(opts.Update.Message.ReplyToMessage.From) + fromID = opts.Update.Message.ReplyToMessage.From.ID + } + if msgAttr.IsUserAsChannel || msgAttr.IsFromLinkedChannel || msgAttr.IsFromAnonymous { + // 频道身份 + viaID = opts.Update.Message.SenderChat.ID + viaUsername = opts.Update.Message.SenderChat.Username + viaName = utils.ShowChatName(opts.Update.Message.SenderChat) + } else { + // 普通用户身份 + viaID = opts.Update.Message.From.ID + viaName = utils.ShowUserName(opts.Update.Message.From) + } + } else { + if msgAttr.IsUserAsChannel || msgAttr.IsFromAnonymous { + // 频道身份 + fromID = opts.Update.Message.SenderChat.ID + fromUsername = opts.Update.Message.SenderChat.Username + fromName = utils.ShowChatName(opts.Update.Message.SenderChat) + } else { + // 普通用户身份 + fromID = opts.Update.Message.From.ID + fromName = utils.ShowUserName(opts.Update.Message.From) + } + } + + // 来源和经过都是同一位用户,删除 via 信息 + if fromID == viaID { + viaID = 0 + viaUsername = "" + viaName = "" + } + + var pendingMessage string + var err error + + oldMeaning := addUdonese(opts.Ctx, &UdoneseWord{ + Word: opts.Fields[1], + MeaningList: []UdoneseMeaning{{ + Meaning: meaning, + FromID: fromID, + FromUsername: fromUsername, + FromName: fromName, + ViaID: viaID, + ViaUsername: viaUsername, + ViaName: viaName, + }}, + }) + if oldMeaning != nil { + pendingMessage += fmt.Sprintf("[%s] 意思已存在于 [%s] 中:\n", meaning, oldMeaning.Word) + for i, s := range oldMeaning.MeaningList { + if meaning == s.Meaning { + pendingMessage += fmt.Sprintf("
%d. [%s] ", i + 1, s.Meaning)
+
+ // 来源的用户或频道
+ if s.FromUsername != "" {
+ pendingMessage += fmt.Sprintf("From %s ", s.FromUsername, s.FromName)
+ } else if s.FromID != 0 {
+ if s.FromID < 0 {
+ pendingMessage += fmt.Sprintf("From %s ", utils.RemoveIDPrefix(s.FromID), s.FromName)
+ } else {
+ pendingMessage += fmt.Sprintf("From %s ", s.FromID, s.FromName)
+ }
+ }
+
+ // 由其他用户添加时的信息
+ if s.ViaUsername != "" {
+ pendingMessage += fmt.Sprintf("Via %s ", s.ViaUsername, s.ViaName)
+ } else if s.ViaID != 0 {
+ if s.ViaID < 0 {
+ pendingMessage += fmt.Sprintf("Via %s ", utils.RemoveIDPrefix(s.ViaID), s.ViaName)
+ } else {
+ pendingMessage += fmt.Sprintf("Via %s ", s.ViaID, s.ViaName)
+ }
+ }
+
+ // 末尾换行
+ pendingMessage += "\n"
+ }
+ }
+ } else {
+ err = SaveUdonese(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Str("messageText", opts.Update.Message.Text).
+ Msg("Failed to save udonese list after add word")
+ handlerErr.Addf("failed to save udonese list after add word: %w", err)
+
+ pendingMessage += fmt.Sprintln("保存语句时似乎发生了一些错误:\n", err)
+ } else {
+ pendingMessage += fmt.Sprintf("已添加 [%s]\n", opts.Fields[1])
+ pendingMessage += fmt.Sprintf("[%s] ", meaning)
+
+ // 来源的用户或频道
+ if fromUsername != "" {
+ pendingMessage += fmt.Sprintf("From %s ", fromUsername, fromName)
+ } else if fromID != 0 {
+ if fromID < 0 {
+ pendingMessage += fmt.Sprintf("From %s ", utils.RemoveIDPrefix(fromID), fromName)
+ } else {
+ pendingMessage += fmt.Sprintf("From %s ", fromID, fromName)
+ }
+ }
+
+ // 由其他用户添加时的信息
+ if viaUsername != "" {
+ pendingMessage += fmt.Sprintf("Via %s ", viaUsername, viaName)
+ } else if viaID != 0 {
+ if viaID < 0 {
+ pendingMessage += fmt.Sprintf("Via %s ", utils.RemoveIDPrefix(viaID), viaName)
+ } else {
+ pendingMessage += fmt.Sprintf("Via %s ", viaID, viaName)
+ }
+ }
+ }
+ }
+
+ pendingMessage += fmt.Sprintln("发送的消息与此消息将在十秒后删除") + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: pendingMessage, ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - Text: "使用 `/udonese <词> <单个意思>` 来添加记录\n或使用 `/udonese <词>` 来管理记录", - ParseMode: models.ParseModeMarkdownV1, + ParseMode: models.ParseModeHTML, DisableNotification: true, }) if err != nil { logger.Error(). Err(err). Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "/udonese admin command help"). + Str("content", "/udonese keyword added"). Msg(logt.SendMessage) - return fmt.Errorf("failed to send `/udonese admin command help` message: %w", err) - } - return nil - } else { - checkWord := opts.Fields[1] - var targetWord UdoneseWord - for _, wordlist := range UdoneseData.List { - if wordlist.Word == checkWord { - targetWord = wordlist - } - } - - if targetWord.Word == "" { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "似乎没有这个词呢...", - ParseMode: models.ParseModeMarkdownV1, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - DisableNotification: true, + handlerErr.Addf("failed to send `/udonese keyword added` message: %w", err) + } else { + time.Sleep(time.Second * 10) + _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ + ChatID: opts.Update.Message.Chat.ID, + MessageIDs: []int{ + opts.Update.Message.ID, + botMessage.ID, + }, }) if err != nil { logger.Error(). Err(err). Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "/udonese admin command no this word"). - Msg(logt.SendMessage) - return fmt.Errorf("failed to send `/udonese admin command no this word` message: %w", err) - } - return nil - } - - var pendingMessage string = fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n", targetWord.Word, len(targetWord.MeaningList), targetWord.Used) - - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: pendingMessage, - ParseMode: models.ParseModeHTML, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), - DisableNotification: true, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "/udonese manage keyboard"). - Msg(logt.SendMessage) - return fmt.Errorf("failed to send `/udonese manage keyboard` message: %w", err) - } - return nil - } - } else if len(opts.Fields) < 3 { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - Text: "使用 `/udonese <词> <单个意思>` 来添加记录", - ParseMode: models.ParseModeMarkdownV1, - DisableNotification: true, - }) - if err != nil { - logger.Info(). - Err(err). - Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "/udonese command help"). - Msg(logt.SendMessage) - return fmt.Errorf("failed to send `/udonese command help` message: %w", err) - } - return nil - } - - meaning := strings.TrimSpace(opts.Update.Message.Text[len(opts.Fields[0])+len(opts.Fields[1])+2:]) - - var ( - fromID int64 - fromUsername string - fromName string - viaID int64 - viaUsername string - viaName string - - isVia bool - isFromGroup bool - isViaGroup bool - isFromChannel bool - isViaChannel bool - ) - - if opts.Update.Message.ReplyToMessage != nil { - // 有回复一条信息,通过回复消息添加词 - isVia = true - if opts.Update.Message.ReplyToMessage.From.IsBot { - if opts.Update.Message.ReplyToMessage.From.ID == 136817688 { - // 频道身份信息 - isViaChannel = true - } else if opts.Update.Message.ReplyToMessage.From.ID == 1087968824 { - // 群组匿名身份 - isViaGroup = true - } else { - // 有 bot 标识,但不是频道身份也不是群组匿名,则是普通 bot - isVia = false - } - } - } - // 发送命令的人信息 - if opts.Update.Message.From.IsBot { - if opts.Update.Message.From.ID == 136817688 { - // 用频道身份发言 - isFromChannel = true - } else if opts.Update.Message.From.ID == 1087968824 { - // 用群组匿名身份发言 - isFromGroup = true - } - } - - if isVia { - if isViaChannel || isViaGroup { - // 回复给一条频道身份的信息 - fromID = opts.Update.Message.ReplyToMessage.SenderChat.ID - fromUsername = opts.Update.Message.ReplyToMessage.SenderChat.Username - fromName = utils.ShowChatName(opts.Update.Message.ReplyToMessage.SenderChat) - } else { - // 回复给普通用户 - fromName = utils.ShowUserName(opts.Update.Message.ReplyToMessage.From) - fromID = opts.Update.Message.ReplyToMessage.From.ID - } - if isFromChannel || isFromGroup { - // 频道身份 - viaID = opts.Update.Message.SenderChat.ID - viaUsername = opts.Update.Message.SenderChat.Username - viaName = utils.ShowChatName(opts.Update.Message.SenderChat) - } else { - // 普通用户身份 - viaID = opts.Update.Message.From.ID - viaName = utils.ShowUserName(opts.Update.Message.From) - } - } else { - if isFromChannel || isFromGroup { - // 频道身份 - fromID = opts.Update.Message.SenderChat.ID - fromUsername = opts.Update.Message.SenderChat.Username - fromName = utils.ShowChatName(opts.Update.Message.SenderChat) - } else { - // 普通用户身份 - fromID = opts.Update.Message.From.ID - fromName = utils.ShowUserName(opts.Update.Message.From) - } - } - - // 来源和经过都是同一位用户,删除 via 信息 - if fromID == viaID { - isVia = false - viaID = 0 - viaUsername = "" - viaName = "" - } - - var pendingMessage string - var botMessage *models.Message - - oldMeaning := addUdonese(opts.Ctx, &UdoneseWord{ - Word: opts.Fields[1], - MeaningList: []UdoneseMeaning{{ - Meaning: meaning, - FromID: fromID, - FromUsername: fromUsername, - FromName: fromName, - ViaID: viaID, - ViaUsername: viaUsername, - ViaName: viaName, - }}, - }) - if oldMeaning != nil { - pendingMessage += fmt.Sprintf("[%s] 意思已存在于 [%s] 中:\n", meaning, oldMeaning.Word) - for i, s := range oldMeaning.MeaningList { - if meaning == s.Meaning { - pendingMessage += fmt.Sprintf("
%d. [%s] ", i + 1, s.Meaning)
-
- // 来源的用户或频道
- if s.FromUsername != "" {
- pendingMessage += fmt.Sprintf("From %s ", s.FromUsername, s.FromName)
- } else if s.FromID != 0 {
- if s.FromID < 0 {
- pendingMessage += fmt.Sprintf("From %s ", utils.RemoveIDPrefix(s.FromID), s.FromName)
- } else {
- pendingMessage += fmt.Sprintf("From %s ", s.FromID, s.FromName)
- }
- }
-
- // 由其他用户添加时的信息
- if s.ViaUsername != "" {
- pendingMessage += fmt.Sprintf("Via %s ", s.ViaUsername, s.ViaName)
- } else if s.ViaID != 0 {
- if s.ViaID < 0 {
- pendingMessage += fmt.Sprintf("Via %s ", utils.RemoveIDPrefix(s.ViaID), s.ViaName)
- } else {
- pendingMessage += fmt.Sprintf("Via %s ", s.ViaID, s.ViaName)
- }
- }
-
- // 末尾换行
- pendingMessage += "\n"
- }
- }
- } else {
- err := SaveUdonese(opts.Ctx)
- if err != nil {
- pendingMessage += fmt.Sprintln("保存语句时似乎发生了一些错误:\n", err)
- } else {
- pendingMessage += fmt.Sprintf("已添加 [%s]\n", opts.Fields[1])
- pendingMessage += fmt.Sprintf("[%s] ", meaning)
-
- // 来源的用户或频道
- if fromUsername != "" {
- pendingMessage += fmt.Sprintf("From %s ", fromUsername, fromName)
- } else if fromID != 0 {
- if fromID < 0 {
- pendingMessage += fmt.Sprintf("From %s ", utils.RemoveIDPrefix(fromID), fromName)
- } else {
- pendingMessage += fmt.Sprintf("From %s ", fromID, fromName)
- }
- }
-
- // 由其他用户添加时的信息
- if viaUsername != "" {
- pendingMessage += fmt.Sprintf("Via %s ", viaUsername, viaName)
- } else if viaID != 0 {
- if viaID < 0 {
- pendingMessage += fmt.Sprintf("Via %s ", utils.RemoveIDPrefix(viaID), viaName)
- } else {
- pendingMessage += fmt.Sprintf("Via %s ", viaID, viaName)
+ Ints("messageIDs", []int{ opts.Update.Message.ID, botMessage.ID }).
+ Str("content", "/udonese keyword added").
+ Msg(logt.DeleteMessages)
+ handlerErr.Addf("failed to delete `/udonese keyword added` messages: %w", err)
}
}
}
}
-
- pendingMessage += fmt.Sprintln("发送的消息与此消息将在十秒后删除") - botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: pendingMessage, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - DisableNotification: true, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "/udonese keyword added"). - Msg(logt.SendMessage) - return fmt.Errorf("failed to send `/udonese keyword added` message: %w", err) - } else { - time.Sleep(time.Second * 10) - _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageIDs: []int{ - opts.Update.Message.ID, - botMessage.ID, - }, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.Message.Chat.ID). - Ints("messageIDs", []int{ opts.Update.Message.ID, botMessage.ID }). - Str("content", "/udonese keyword added"). - Msg(logt.DeleteMessages) - return fmt.Errorf("failed to delete `/udonese keyword added` messages: %w", err) - } - return nil - } + return handlerErr.Flat() } func udoneseInlineHandler(opts *handler_structs.SubHandlerParams) []models.InlineQueryResult { @@ -742,6 +719,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { // 不响应来自转发的命令和空文本 if opts.Update.Message.ForwardOrigin != nil || len(opts.Fields) < 1 { return nil } + var handlerErr mterr.MultiError logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "Udonese"). @@ -751,13 +729,13 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { if UdoneseErr != nil { logger.Warn(). Err(UdoneseErr). - Msg("Some error in while read udonese list") + Msg("Some error in while read udonese list, try to read again") err := ReadUdonese(opts.Ctx) if err != nil { logger.Error(). Err(err). Msg("Failed to read udonese list") - return err + handlerErr.Addf("failed to read udonese list: %w", err) } } @@ -770,6 +748,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Msg("Failed to save udonese list after add word usage count") + handlerErr.Addf("failed to save udonese list after add word usage count: %w", err) } } } @@ -796,35 +775,32 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "sms command usage"). Msg(logt.SendMessage) - return fmt.Errorf("failed to send `sms command usage` message: %w", err) + handlerErr.Addf("failed to send `sms command usage` message: %w", err) } - return nil - } - - // 在数据库循环查找这个词 - for _, word := range UdoneseData.List { - if strings.EqualFold(word.Word, opts.Fields[1]) { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: word.OutputMeanings(), - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - DisableNotification: true, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "sms keyword meaning"). - Msg(logt.SendMessage) - return fmt.Errorf("failed to send `sms keyword meaning` message: %w", err) + } else { + // 在数据库循环查找这个词 + for _, word := range UdoneseData.List { + if strings.EqualFold(word.Word, opts.Fields[1]) { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: word.OutputMeanings(), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + DisableNotification: true, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.Message.Chat.ID). + Str("content", "sms keyword meaning"). + Msg(logt.SendMessage) + handlerErr.Addf("failed to send `sms keyword meaning` message: %w", err) + } } - return nil } + needNotice = true } - - needNotice = true - } else if len(opts.Fields) > 1 && strings.HasSuffix(opts.Update.Message.Text, "ssm") { + } else if len(opts.Fields) == 2 && strings.HasSuffix(opts.Update.Message.Text, "ssm") { // 在数据库循环查找这个词 for _, word := range UdoneseData.List { if strings.EqualFold(word.Word, opts.Fields[0]) { @@ -841,9 +817,8 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "sms keyword meaning"). Msg(logt.SendMessage) - return fmt.Errorf("failed to send `sms keyword meaning` message: %w", err) + handlerErr.Addf("failed to send `sms keyword meaning` message: %w", err) } - return nil } } needNotice = true @@ -865,14 +840,12 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Int("messageID", botMessage.ID). Str("content", "sms keyword no meaning"). Msg(logt.SendMessage) - return fmt.Errorf("failed to send `sms keyword no meaning` message: %w", err) + handlerErr.Addf("failed to send `sms keyword no meaning` message: %w", err) } else { time.Sleep(time.Second * 10) - _, err := opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ + _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ ChatID: opts.Update.Message.Chat.ID, - MessageIDs: []int{ - botMessage.ID, - }, + MessageIDs: []int{ botMessage.ID }, }) if err != nil { logger.Error(). @@ -881,12 +854,12 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Int("messageID", botMessage.ID). Str("content", "sms keyword no meaning"). Msg(logt.DeleteMessage) - return fmt.Errorf("failed to delete `sms keyword no meaning` message: %w", err) + handlerErr.Addf("failed to delete `sms keyword no meaning` message: %w", err) } } } - return nil + return handlerErr.Flat() } func init() { @@ -895,13 +868,13 @@ func init() { Func: ReadUdonese, }) plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ - Name: "Udonese", - Saver: SaveUdonese, + Name: "Udonese", + Saver: SaveUdonese, Loader: ReadUdonese, }) plugin_utils.AddInlineHandlerPlugins(plugin_utils.InlineHandler{ - Command: "sms", - Handler: udoneseInlineHandler, + Command: "sms", + Handler: udoneseInlineHandler, Description: "查询 Udonese 词典", }) plugin_utils.AddSlashSymbolCommandPlugins(plugin_utils.SlashSymbolCommand{ @@ -909,27 +882,25 @@ func init() { Handler: addUdoneseHandler, }) plugin_utils.AddHandlerByChatIDPlugins(plugin_utils.HandlerByChatID{ - ChatID: UdonGroupID, + ChatID: UdonGroupID, PluginName: "udoneseGroupHandler", - Handler: udoneseGroupHandler, + Handler: udoneseGroupHandler, }) plugin_utils.AddCallbackQueryCommandPlugins(plugin_utils.CallbackQuery{ CommandChar: "udonese", Handler: udoneseCallbackHandler, }) - // plugin_utils.AddSuffixCommandPlugins(plugin_utils.SuffixCommand{ - // SuffixCommand: "ssm", - // Handler: udoneseHandler, - // }) } func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { + var handlerErr mterr.MultiError logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "Udonese"). Str("funcName", "udoneseCallbackHandler"). Logger() + if !utils.AnyContains(opts.Update.CallbackQuery.From.ID, UdoneseManagerIDs) { _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, @@ -942,244 +913,238 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Str("callbackQueryID", opts.Update.CallbackQuery.ID). Str("content", "udonese no edit permissions"). Msg(logt.AnswerCallback) + handlerErr.Addf("failed to send `udonese no edit permissions` inline result: %w", err) } - return nil - } - - if opts.Update.CallbackQuery.Data == "udonese_done" { - _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). - Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). - Str("content", "udonese keyword manage keyboard"). - Msg(logt.DeleteMessage) - } - return nil - } - - if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_word_") { - word := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_word_") - var targetWord UdoneseWord - for _, wordlist := range UdoneseData.List { - if wordlist.Word == word { - targetWord = wordlist + } else { + if opts.Update.CallbackQuery.Data == "udonese_done" { + _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese keyword manage keyboard"). + Msg(logt.DeleteMessage) + handlerErr.Addf("failed to delete `udonese keyword manage keyboard` inline result: %w", err) } - } - - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n", targetWord.Word, len(targetWord.MeaningList), targetWord.Used), - ParseMode: models.ParseModeHTML, - ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). - Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). - Str("content", "udonese word meaning list"). - Msg(logt.EditMessage) - } - return nil - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") { - wordAndIndex := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") - wordAndIndexList := strings.Split(wordAndIndex, "_") - meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) - if err != nil { - logger.Error(). - Err(err). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("Failed to parse meanning index") - return fmt.Errorf("failed to parse meanning index: %w", err) - } - - var targetMeaning UdoneseMeaning - - for _, udonese := range UdoneseData.List { - if udonese.Word == wordAndIndexList[0] { - for i, meaning := range udonese.MeaningList { - if i == meanningIndex { - targetMeaning = meaning - } + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_word_") { + word := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_word_") + var targetWord UdoneseWord + for _, wordlist := range UdoneseData.List { + if wordlist.Word == word { + targetWord = wordlist } } - } - var pendingMessage string = fmt.Sprintf("意思: [ %s ]\n", targetMeaning.Meaning) - - // 来源的用户或频道 - if targetMeaning.FromUsername != "" { - pendingMessage += fmt.Sprintf("From %s\n", targetMeaning.FromUsername, targetMeaning.FromName) - } else if targetMeaning.FromID != 0 { - if targetMeaning.FromID < 0 { - pendingMessage += fmt.Sprintf("From %s\n", utils.RemoveIDPrefix(targetMeaning.FromID), targetMeaning.FromName) - } else { - pendingMessage += fmt.Sprintf("From %s\n", targetMeaning.FromID, targetMeaning.FromName) + _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n", targetWord.Word, len(targetWord.MeaningList), targetWord.Used), + ParseMode: models.ParseModeHTML, + ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese word meaning list"). + Msg(logt.EditMessage) + handlerErr.Addf("failed to edit message to `udonese keyword manage keyboard`: %w", err) } - } - - // 由其他用户添加时的信息 - if targetMeaning.ViaUsername != "" { - pendingMessage += fmt.Sprintf("Via %s\n", targetMeaning.ViaUsername, targetMeaning.ViaName) - } else if targetMeaning.ViaID != 0 { - if targetMeaning.ViaID < 0 { - pendingMessage += fmt.Sprintf("Via %s\n", utils.RemoveIDPrefix(targetMeaning.ViaID), targetMeaning.ViaName) + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") { + wordAndIndex := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") + wordAndIndexList := strings.Split(wordAndIndex, "_") + meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) + if err != nil { + logger.Error(). + Err(err). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse meanning index") + handlerErr.Addf("failed to parse meanning index: %w", err) } else { - pendingMessage += fmt.Sprintf("Via %s\n", targetMeaning.ViaID, targetMeaning.ViaName) - } - } + var targetMeaning UdoneseMeaning - _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: pendingMessage, - ParseMode: models.ParseModeHTML, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "删除此意思", - CallbackData: fmt.Sprintf("udonese_delmeaning_%s_%d", wordAndIndexList[0], meanningIndex), - }, - { - Text: "返回", - CallbackData: "udonese_word_" + wordAndIndexList[0], - }, - }}}, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). - Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). - Str("content", "udonese meaning manage keyboard"). - Msg(logt.EditMessage) - return fmt.Errorf("failed to edit message to `udonese meaning manage keyboard`: %w", err) - } + for _, udonese := range UdoneseData.List { + if udonese.Word == wordAndIndexList[0] { + for i, meaning := range udonese.MeaningList { + if i == meanningIndex { + targetMeaning = meaning + } + } + } + } - return nil - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_delmeaning_") { - wordAndIndex := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_delmeaning_") - wordAndIndexList := strings.Split(wordAndIndex, "_") - meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) - if err != nil { - logger.Error(). - Err(err). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("Failed to parse meanning index") - return fmt.Errorf("failed to parse meanning index: %w", err) - } - var newMeaningList []UdoneseMeaning - var targetWord UdoneseWord - var deletedMeaning string + var pendingMessage string = fmt.Sprintf("意思: [ %s ]\n", targetMeaning.Meaning) - for index, udonese := range UdoneseData.List { - if udonese.Word == wordAndIndexList[0] { - for i, meaning := range udonese.MeaningList { - if i == meanningIndex { - deletedMeaning = meaning.Meaning + // 来源的用户或频道 + if targetMeaning.FromUsername != "" { + pendingMessage += fmt.Sprintf("From %s\n", targetMeaning.FromUsername, targetMeaning.FromName) + } else if targetMeaning.FromID != 0 { + if targetMeaning.FromID < 0 { + pendingMessage += fmt.Sprintf("From %s\n", utils.RemoveIDPrefix(targetMeaning.FromID), targetMeaning.FromName) } else { - newMeaningList = append(newMeaningList, meaning) + pendingMessage += fmt.Sprintf("From %s\n", targetMeaning.FromID, targetMeaning.FromName) } } - UdoneseData.List[index].MeaningList = newMeaningList - targetWord = UdoneseData.List[index] - } - } - err = SaveUdonese(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to save udonese data after deleting meaning") - _, msgerr := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: opts.Update.CallbackQuery.ID, - Text: "删除意思时保存数据库失败,请重试\n" + err.Error(), - ShowAlert: true, - }) - if msgerr != nil { + // 由其他用户添加时的信息 + if targetMeaning.ViaUsername != "" { + pendingMessage += fmt.Sprintf("Via %s\n", targetMeaning.ViaUsername, targetMeaning.ViaName) + } else if targetMeaning.ViaID != 0 { + if targetMeaning.ViaID < 0 { + pendingMessage += fmt.Sprintf("Via %s\n", utils.RemoveIDPrefix(targetMeaning.ViaID), targetMeaning.ViaName) + } else { + pendingMessage += fmt.Sprintf("Via %s\n", targetMeaning.ViaID, targetMeaning.ViaName) + } + } + + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: pendingMessage, + ParseMode: models.ParseModeHTML, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "删除此意思", + CallbackData: fmt.Sprintf("udonese_delmeaning_%s_%d", wordAndIndexList[0], meanningIndex), + }, + { + Text: "返回", + CallbackData: "udonese_word_" + wordAndIndexList[0], + }, + }}}, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese meaning manage keyboard"). + Msg(logt.EditMessage) + handlerErr.Addf("failed to edit message to `udonese meaning manage keyboard`: %w", err) + } + } + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_delmeaning_") { + wordAndIndex := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_delmeaning_") + wordAndIndexList := strings.Split(wordAndIndex, "_") + meanningIndex, err := strconv.Atoi(wordAndIndexList[1]) + if err != nil { logger.Error(). - Err(msgerr). - Msg(logt.AnswerCallback) + Err(err). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to parse meanning index") + handlerErr.Addf("failed to parse meanning index: %w", err) + } else { + var newMeaningList []UdoneseMeaning + var targetWord UdoneseWord + var deletedMeaning string + + for index, udonese := range UdoneseData.List { + if udonese.Word == wordAndIndexList[0] { + for i, meaning := range udonese.MeaningList { + if i == meanningIndex { + deletedMeaning = meaning.Meaning + } else { + newMeaningList = append(newMeaningList, meaning) + } + } + UdoneseData.List[index].MeaningList = newMeaningList + targetWord = UdoneseData.List[index] + } + } + + err = SaveUdonese(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to save udonese data after deleting meaning") + handlerErr.Addf("failed to save udonese data after deleting meaning: %w", err) + + _, err = opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "删除意思时保存数据库失败,请重试\n" + err.Error(), + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Str("content", "failed to save udonese data after delete meaning"). + Msg(logt.AnswerCallback) + handlerErr.Addf("failed to send `failed to save udonese data after delete meaning` callback answer: %w", err) + } + } else { + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n
已删除 [ %s ] 词中的 [ %s ] 意思", targetWord.Word, len(targetWord.MeaningList), targetWord.Used, targetWord.Word, deletedMeaning), + ParseMode: models.ParseModeHTML, + ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese meaning manage keyboard after delete meaning"). + Msg(logt.EditMessage) + handlerErr.Addf("failed to edit message to `udonese meaning manage keyboard after delete meaning`: %w", err) + } + } } - - return fmt.Errorf("failed to save udonese data after delete meaning: %w", err) - } - - var pendingMessage string = fmt.Sprintf("词: [ %s ]\n有 %d 个意思,已使用 %d 次\n
已删除 [ %s ] 词中的 [ %s ] 意思", targetWord.Word, len(targetWord.MeaningList), targetWord.Used, wordAndIndexList[0], deletedMeaning) - - _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: pendingMessage, - ParseMode: models.ParseModeHTML, - ReplyMarkup: targetWord.buildUdoneseWordKeyboard(), - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). - Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). - Str("content", "udonese meaning manage keyboard after delete meaning"). - Msg(logt.EditMessage) - return fmt.Errorf("failed to edit message to `udonese meaning manage keyboard after delete meaning`: %w", err) - } - - return nil - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_delword_") { - word := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_delword_") - var newWordList []UdoneseWord - for _, udonese := range UdoneseData.List { - if udonese.Word != word { - newWordList = append(newWordList, udonese) + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_delword_") { + word := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "udonese_delword_") + var newWordList []UdoneseWord + for _, udonese := range UdoneseData.List { + if udonese.Word != word { + newWordList = append(newWordList, udonese) + } } - } - UdoneseData.List = newWordList + UdoneseData.List = newWordList - err := SaveUdonese(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to save udonese data after delete word") - _, msgerr := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: opts.Update.CallbackQuery.ID, - Text: "删除词时保存数据库失败,请重试\n" + err.Error(), - ShowAlert: true, - }) - if msgerr != nil { + err := SaveUdonese(opts.Ctx) + if err != nil { logger.Error(). - Err(msgerr). - Msg(logt.AnswerCallback) + Err(err). + Msg("Failed to save udonese data after delete word") + handlerErr.Addf("failed to save udonese data after delete word: %w", err) + + _, err = opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "删除词时保存数据库失败,请重试\n" + err.Error(), + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Msg(logt.AnswerCallback) + handlerErr.Addf("failed to send `failed to save udonese data after delete word` callback answer: %w", err) + } + } else { + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: fmt.Sprintf("
已删除 [ %s ] 词", word), + ParseMode: models.ParseModeHTML, + ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "关闭菜单", + CallbackData: "udonese_done", + }}}}, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). + Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). + Str("content", "udonese word deleted notice"). + Msg(logt.EditMessage) + handlerErr.Addf("failed to edit message to `udonese word deleted notice`: %w", err) + } } - return fmt.Errorf("failed to save udonese data after delete meaning: %w", err) - } - - var pendingMessage string = fmt.Sprintf("
已删除 [ %s ] 词", word) - - _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: pendingMessage, - ParseMode: models.ParseModeHTML, - ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "关闭菜单", - CallbackData: "udonese_done", - }}}}, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). - Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). - Str("content", "udonese word deleted notice"). - Msg(logt.EditMessage) - return fmt.Errorf("failed to edit message to `udonese word deleted notice`: %w", err) } } - - return nil + return handlerErr.Flat() } diff --git a/plugins/plugin_voicelist.go b/plugins/plugin_voicelist.go index 26dc688..310d1ce 100644 --- a/plugins/plugin_voicelist.go +++ b/plugins/plugin_voicelist.go @@ -6,15 +6,12 @@ import ( "os" "path/filepath" "strings" - "time" "trbot/utils" - "trbot/utils/configs" "trbot/utils/consts" "trbot/utils/handler_structs" "trbot/utils/plugin_utils" "trbot/utils/yaml" - "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" ) @@ -22,7 +19,7 @@ import ( var VoiceLists []VoicePack var VoiceListErr error -var VoiceListDir string = filepath.Join(consts.YAMLDataBasePath, "voices/") +var VoiceListDir string = filepath.Join(consts.YAMLDataBasePath, "voices/") func init() { plugin_utils.AddInitializer(plugin_utils.Initializer{ @@ -84,7 +81,7 @@ func ReadVoicePackFromPath(ctx context.Context) error { return err } } - + err = filepath.Walk(VoiceListDir, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -95,7 +92,7 @@ func ReadVoicePackFromPath(ctx context.Context) error { } if strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml") { var singlePack VoicePack - + err = yaml.LoadYAML(path, &singlePack) if err != nil { logger.Error(). @@ -132,10 +129,6 @@ func VoiceListHandler(opts *handler_structs.SubHandlerParams) []models.InlineQue Str("VoiceListDir", VoiceListDir). Msg("No voices file in VoiceListDir") - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: configs.BotConfig.LogChatID, - Text: fmt.Sprintf("%s\nInline Mode: some user can't load voices", time.Now().Format(time.RFC3339)), - }) return []models.InlineQueryResult{&models.InlineQueryResultVoice{ ID: "none", Title: "无法读取到语音文件,请联系机器人管理员", diff --git a/utils/logt/log_template.go b/utils/logt/log_template.go index bdca0bc..6a6ad72 100644 --- a/utils/logt/log_template.go +++ b/utils/logt/log_template.go @@ -3,6 +3,7 @@ package logt const ( // LogTemplate is the template for log messages. SendMessage string = "Failed to send message" + SendDocument string = "Failed to send document" EditMessage string = "Failed to edit message" DeleteMessage string = "Failed to delete message" DeleteMessages string = "Failed to delete messages" diff --git a/utils/mterr/mult_errors.go b/utils/mterr/mult_errors.go new file mode 100644 index 0000000..34f9c28 --- /dev/null +++ b/utils/mterr/mult_errors.go @@ -0,0 +1,37 @@ +package mterr + +import ( + "errors" + "fmt" +) + +type MultiError struct { + Errors []error +} + +func (e *MultiError) Add(errs ...error) *MultiError { + for _, err := range errs { + if err != nil { + e.Errors = append(e.Errors, err) + } + } + return e +} + +// add formatted error by use fmt.Errorf() +func (e *MultiError) Addf(format string, a ...any) *MultiError { + e.Errors = append(e.Errors, fmt.Errorf(format, a...)) + return e +} + +// func (e *MultiError) AddWith(key string, dict *zerolog.Event) string { +// dict. +// } + +// a string error by use errors.Join() +func (e *MultiError) Flat() error { + if len(e.Errors) == 0 { + return nil + } + return errors.Join(e.Errors...) +} diff --git a/utils/type/message_utils/message_attribute.go b/utils/type/message_utils/message_attribute.go index 1338472..f7839c7 100644 --- a/utils/type/message_utils/message_attribute.go +++ b/utils/type/message_utils/message_attribute.go @@ -8,6 +8,8 @@ type MessageAttribute struct { IsFromLinkedChannel bool `yaml:"IsFromLinkedChannel,omitempty"` // is automatic forward post from linked channel IsUserAsChannel bool `yaml:"IsUserAsChannel,omitempty"` // user selected to send message as a channel IsHasSenderChat bool `yaml:"IsHasSenderChat,omitempty"` // sender of the message when sent on behalf of a chat, eg current group/supergroup or linked channel + IsFromBot bool `yaml:"IsFromBot,omitempty"` // message send by bot + IsFromPremium bool `yaml:"IsFromPremium,omitempty"` // message from a premium account IsFromBusinessBot bool `yaml:"IsFromBusinessBot,omitempty"` // the bot that actually sent the message on behalf of the business account IsChatEnableForum bool `yaml:"IsChatEnableForum,omitempty"` // group or supergroup is enable topic IsForwardMessage bool `yaml:"IsForwardMessage,omitempty"` // not a origin message, forward from somewhere @@ -19,7 +21,7 @@ type MessageAttribute struct { IsQuoteHasEntities bool `yaml:"IsQuoteHasEntities,omitempty"` // is quote message has entities IsManualQuote bool `yaml:"IsManualQuote,omitempty"` // user manually select text to quote a message. if false, just use 'reply to other chat' IsReplyToStory bool `yaml:"IsReplyToStory,omitempty"` // TODO - IsViaBot bool `yaml:"IsViaBot,omitempty"` // message by inline mode + IsViaBot bool `yaml:"IsViaBot,omitempty"` // message by using bot inline mode IsEdited bool `yaml:"IsEdited,omitempty"` // message aready edited IsFromOffline bool `yaml:"IsFromOffline,omitempty"` // eg scheduled message IsGroupedMedia bool `yaml:"IsGroupedMedia,omitempty"` // media group, like select more than one file or photo to send @@ -50,6 +52,14 @@ func GetMessageAttribute(msg *models.Message) MessageAttribute { } } } + if msg.From != nil { + if msg.From.IsBot { + attribute.IsFromBot = true + } + if msg.From.IsPremium { + attribute.IsFromPremium = true + } + } if msg.SenderBusinessBot != nil { attribute.IsFromBusinessBot = true } diff --git a/utils/utils.go b/utils/utils.go index 4df10dd..4273945 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -432,7 +432,7 @@ func getCurrentGoroutineStack() string { func PanicCatcher(ctx context.Context, pluginName string) { logger := zerolog.Ctx(ctx) - + panic := recover() if panic != nil { logger.Error(). -- 2.49.1 From 22123a2c18187fa5ab071685d97e55301c04aecf Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Thu, 26 Jun 2025 03:23:08 +0800 Subject: [PATCH 21/27] save changes handlers.go: fix wrong var utils/yaml/yaml.go remove fmt package plugins/teamspeak: allow check online client failed 5 times before reinit all and other: use err template plugin use yaml.Load() and yaml.Save() manage databases use mult error to recored all error --- handlers.go | 105 +- plugins/plugin_detect_keyword.go | 1470 ++++++++++++------------- plugins/plugin_sticker.go | 256 +++-- plugins/plugin_teamspeak3.go | 124 +-- plugins/plugin_udonese.go | 195 +--- utils/errt/log_template.go | 13 + utils/internal_plugin/register.go | 4 +- utils/logt/log_template.go | 11 - utils/{mterr => multe}/mult_errors.go | 2 +- utils/utils.go | 1 + utils/yaml/yaml.go | 29 +- 11 files changed, 1037 insertions(+), 1173 deletions(-) create mode 100644 utils/errt/log_template.go delete mode 100644 utils/logt/log_template.go rename utils/{mterr => multe}/mult_errors.go (97%) diff --git a/handlers.go b/handlers.go index b5dd8b8..79a3ad4 100644 --- a/handlers.go +++ b/handlers.go @@ -11,6 +11,7 @@ import ( "trbot/utils" "trbot/utils/configs" "trbot/utils/consts" + "trbot/utils/errt" "trbot/utils/handler_structs" "trbot/utils/plugin_utils" "trbot/utils/type/message_utils" @@ -189,7 +190,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Int("count", n.TotalCount), ) } - + } logger.Info(). @@ -299,7 +300,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("slashCommand", plugin.SlashCommand). Str("message", opts.Update.Message.Text). - Msg("Incremental message command count error") + Msg("Failed to incremental message command count") } err = plugin.Handler(opts) if err != nil { @@ -329,7 +330,8 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("message", opts.Update.Message.Text). - Msg("Failed to send `no this command` message") + Str("content", "no this command"). + Msg(errt.SendMessage) } err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { @@ -338,9 +340,9 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("message", opts.Update.Message.Text). - Msg("Incremental message command count error") + Msg("Failed to incremental message command count") } - + // if configs.BotConfig.LogChatID != 0 { mess.PrivateLogToChat(opts.Ctx, opts.Thebot, opts.Update) } } else if strings.HasSuffix(opts.Fields[0], "@" + consts.BotMe.Username) { // 当使用一个不存在的命令,但是命令末尾指定为此 bot 处理 @@ -357,7 +359,8 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("message", opts.Update.Message.Text). - Msg("Failed to send `no this command` message") + Str("content", "no this command"). + Msg(errt.SendMessage) } err = database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.Chat.ID, db_struct.MessageCommand) if err != nil { @@ -366,7 +369,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("message", opts.Update.Message.Text). - Msg("Incremental message command count error") + Msg("Failed to incremental message command count") } time.Sleep(time.Second * 10) _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ @@ -382,7 +385,8 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("message", opts.Update.Message.Text). - Msg("Failed to delete `no this command` message") + Str("content", "no this command"). + Msg(errt.DeleteMessages) } return } @@ -411,7 +415,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("fullCommand", plugin.FullCommand). Str("message", opts.Update.Message.Text). - Msg("Incremental message command count error") + Msg("Failed to incremental message command count") } err = plugin.Handler(opts) if err != nil { @@ -450,7 +454,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("suffixCommand", plugin.SuffixCommand). Str("message", opts.Update.Message.Text). - Msg("Incremental message command count error") + Msg("Failed to incremental message command count") } err = plugin.Handler(opts) if err != nil { @@ -472,9 +476,11 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { if plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type] != nil { msgTypeInString := message_utils.GetMessageType(opts.Update.Message).InString() + var needBuildSelectKeyboard bool + if plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString] != nil { - handlerInThisTypeCount := len(plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString]) - if handlerInThisTypeCount == 1 { + handlersInThisTypeCount := len(plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString]) + if handlersInThisTypeCount == 1 { // 虽然是遍历,但实际上只能遍历一次 for name, handler := range plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString] { if handler.AllowAutoTrigger { @@ -498,26 +504,11 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Msg("Error in handler by message type") } } else { - // 此 handler 不允许自动触发,回复一条带按钮的消息让用户手动操作 - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: fmt.Sprintf("请选择一个 [ %s ] 类型消息的功能", msgTypeInString), - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ReplyMarkup: plugin_utils.AllPlugins.HandlerByMessageType[opts.Update.Message.Chat.Type][msgTypeInString].BuildSelectKeyboard(), - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Str("messageType", string(msgTypeInString)). - Str("chatType", string(opts.Update.Message.Chat.Type)). - Int("handlerInThisTypeCount", handlerInThisTypeCount). - Msg("Failed to send `select a handler by message type keyboard` message") - } + needBuildSelectKeyboard = true } } - } else { + } + if needBuildSelectKeyboard { // 多个 handler 自动回复一条带按钮的消息让用户手动操作 _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.Chat.ID, @@ -532,8 +523,9 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("messageType", string(msgTypeInString)). Str("chatType", string(opts.Update.Message.Chat.Type)). - Int("handlerInThisTypeCount", handlerInThisTypeCount). - Msg("Failed to send `select a handler by message type keyboard` message") + Int("handlerInThisTypeCount", handlersInThisTypeCount). + Str("content", "select a handler by message type keyboard"). + Msg(errt.SendMessage) } } } else if opts.Update.Message.Chat.Type == models.ChatTypePrivate { @@ -551,7 +543,8 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("messageType", string(msgTypeInString)). Str("chatType", string(opts.Update.Message.Chat.Type)). - Msg("Failed to send `no handler by message type plugin for this message type` message") + Str("content", "no handler by message type plugin for this message type"). + Msg(errt.SendMessage) } } } @@ -633,7 +626,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `bot inline handler list` inline result") + Str("content", "bot inline handler list"). + Msg(errt.AnswerInlineQuery) } } else if strings.HasPrefix(opts.Update.InlineQuery.Query, configs.BotConfig.InlineSubCommandSymbol) { // 用户输入了分页符号和一些字符,判断接着的命令是否正确,正确则交给对应的插件处理,否则提示错误 @@ -670,7 +664,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Str("handlerCommand", plugin.Command). Str("handlerType", "returnResult"). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `inline handler` inline result") + Str("content", "sub inline handler"). + Msg(errt.AnswerInlineQuery) // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } return @@ -752,7 +747,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Title: fmt.Sprintf("不存在的命令 [%s]", opts.Fields[0]), Description: "请检查命令是否正确", InputMessageContent: &models.InputTextMessageContent{ - MessageText: "您在使用 inline 模式时没有选择正确的命令...", + MessageText: "您在使用 inline 模式时没有输入正确的命令...", ParseMode: models.ParseModeMarkdownV1, }, }}, @@ -762,11 +757,12 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `no this inline command` inline result") + Str("content", "no this inline command"). + Msg(errt.AnswerInlineQuery) } return } else { - // inline query 不以命令符号开头 + // inline query 不以命令符号开头,检查是否有默认 handler if opts.ChatInfo.DefaultInlinePlugin != "" { // 来自用户设定的默认命令 @@ -802,7 +798,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Str("userDefaultHandlerCommand", plugin.Command). Str("handlerType", "returnResult"). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `user default inline handler result` inline result") + Str("content", "user default inline handler result"). + Msg(errt.AnswerInlineQuery) // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } return @@ -852,20 +849,23 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { if plugin.Handler == nil { logger.Warn(). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("userDefaultHandlerCommand", plugin.PrefixCommand). + Str("userDefaultHandlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). Str("query", opts.Update.InlineQuery.Query). Msg("Hit user inline prefix manual answer handler, but this handler function is nil, skip") continue } err := plugin.Handler(opts) - logger.Error(). + if err != nil { + logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("userDefaultHandlerCommand", plugin.PrefixCommand). + Str("userDefaultHandlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). Str("query", opts.Update.InlineQuery.Query). Msg("Error in user inline prefix manual answer handler") + } + return } } @@ -893,8 +893,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). Str("userDefaultInlineCommand", opts.ChatInfo.DefaultInlinePlugin). - // Str("result", "invalid user default inline handler"). - Msg("Failed to send `invalid user default inline handler` inline result") + Str("content", "invalid user default inline handler"). + Msg(errt.AnswerInlineQuery) } return } else if configs.BotConfig.InlineDefaultHandler != "" { @@ -936,7 +936,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Str("defaultHandlerCommand", plugin.Command). Str("handlerType", "returnResult"). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `bot default inline handler result` inline result") + Str("content", "bot default inline handler result"). + Msg(errt.AnswerInlineQuery) // 本来想写一个发生错误后再给用户回答一个错误信息,让用户可以点击发送,结果同一个 ID 的 inlineQuery 只能回答一次 } return @@ -976,7 +977,7 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { // 符合命令前缀,完全由插件自行控制输出 for _, plugin := range plugin_utils.AllPlugins.InlinePrefixHandler { if plugin.Attr.IsOnlyAllowAdmin && !IsAdmin { continue } - if opts.ChatInfo.DefaultInlinePlugin == plugin.PrefixCommand { + if configs.BotConfig.InlineDefaultHandler == plugin.PrefixCommand { logger.Info(). Str("defaultHandlerPrefixCommand", plugin.PrefixCommand). Str("handlerType", "manuallyAnswerResult_PrefixCommand"). @@ -1031,7 +1032,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `invalid bot default inline handler` inline result") + Str("content", "invalid bot default inline handler"). + Msg(errt.AnswerInlineQuery) return } return @@ -1070,7 +1072,8 @@ func inlineHandler(opts *handler_structs.SubHandlerParams) { Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). - Msg("Failed to send `bot no default inline handler` inline result") + Str("content", "bot no default inline handler"). + Msg(errt.AnswerInlineQuery) return } } @@ -1095,7 +1098,8 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) { logger.Error(). Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Failed to send `this callback request is processing` callback answer") + Str("content", "this callback request is processing"). + Msg(errt.AnswerCallbackQuery) } return } else if params.ChatInfo.HasPendingCallbackQuery { @@ -1114,7 +1118,8 @@ func callbackQueryHandler(params *handler_structs.SubHandlerParams) { logger.Error(). Err(err). Dict(utils.GetUserDict(¶ms.Update.CallbackQuery.From)). - Msg("Failed to send `a callback request is processing, send new request later` callback answer") + Str("content", "a callback request is processing, send new request later"). + Msg(errt.AnswerCallbackQuery) } return } else { diff --git a/plugins/plugin_detect_keyword.go b/plugins/plugin_detect_keyword.go index d04ba9a..8d602d0 100644 --- a/plugins/plugin_detect_keyword.go +++ b/plugins/plugin_detect_keyword.go @@ -3,7 +3,6 @@ package plugins import ( "context" "fmt" - "io" "os" "path/filepath" "strconv" @@ -11,14 +10,15 @@ import ( "time" "trbot/utils" "trbot/utils/consts" + "trbot/utils/errt" "trbot/utils/handler_structs" - "trbot/utils/logt" + "trbot/utils/multe" "trbot/utils/plugin_utils" + "trbot/utils/yaml" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" - "gopkg.in/yaml.v3" ) var KeywordDataList KeywordData = KeywordData{ @@ -155,74 +155,41 @@ type ChatForUser struct { } func ReadKeywordList(ctx context.Context) error { - var lists KeywordData logger := zerolog.Ctx(ctx). With(). Str("pluginName", "DetectKeyword"). Str("funcName", "ReadKeywordList"). Logger() - // 打开数据库文件 - file, err := os.Open(KeywordDataPath) + err := yaml.LoadYAML(KeywordDataPath, &KeywordDataList) if err != nil { if os.IsNotExist(err) { - // 如果找不到文件,则新建一个 logger.Warn(). + Err(err). Str("path", KeywordDataPath). - Msg("Not found keyword data file. Create a new one") - err = SaveKeywordList(ctx) + Msg("Not found keyword list file. Created new one") + // 如果是找不到文件,新建一个 + err = yaml.SaveYAML(KeywordDataPath, &KeywordDataList) if err != nil { logger.Error(). Err(err). Str("path", KeywordDataPath). - Msg("Failed to create empty keyword data file") - KeywordDataErr = fmt.Errorf("failed to create empty keyword data file: %w", err) - return KeywordDataErr + Msg("Failed to create empty keyword list file") + KeywordDataErr = fmt.Errorf("failed to create empty keyword list file: %w", err) } } else { - // 其他错误 logger.Error(). Err(err). Str("path", KeywordDataPath). - Msg("Failed to open keyword data file") - KeywordDataErr = fmt.Errorf("failed to open keyword data file: %w", err) - return KeywordDataErr - } - } - defer file.Close() - - // 解码 yaml 文件 - err = yaml.NewDecoder(file).Decode(&lists) - if err != nil { - if err == io.EOF { - // 文件存在,但为空,则用空的数据库覆盖保存 - logger.Warn(). - Str("path", KeywordDataPath). - Msg("Keyword data file looks empty. now format it") - err = SaveKeywordList(ctx) - if err != nil { - // 保存空的数据库失败 - logger.Error(). - Err(err). - Str("path", KeywordDataPath). - Msg("Failed to create empty keyword data file") - KeywordDataErr = fmt.Errorf("failed to create empty keyword data file: %w", err) - return KeywordDataErr - } - } else { - // 其他错误 - logger.Error(). - Err(err). - Msg("Failed to decode keyword data list") - KeywordDataErr = fmt.Errorf("failed to decode keyword data list: %w", err) - return KeywordDataErr + Msg("Failed to load keyword list file") + KeywordDataErr = fmt.Errorf("failed to load keyword list file: %w", err) } + } else { + KeywordDataErr = nil } - // 一切正常 - KeywordDataList = lists buildListenList() - return nil + return KeywordDataErr } func SaveKeywordList(ctx context.Context) error { @@ -232,86 +199,18 @@ func SaveKeywordList(ctx context.Context) error { Str("funcName", "SaveKeywordList"). Logger() - data, err := yaml.Marshal(KeywordDataList) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to marshal keyword data list") - KeywordDataErr = fmt.Errorf("failed to marshal keyword data list: %w", err) - return KeywordDataErr - } - - // 检查数据库目录是否存在,不然则创建 - _, err = os.Stat(KeywordDataDir) - if err != nil { - if os.IsNotExist(err) { - logger.Warn(). - Str("directory", KeywordDataDir). - Msg("Not found keyword data directory, now create it") - err = os.MkdirAll(KeywordDataDir, 0755) - if err != nil { - logger.Error(). - Err(err). - Str("directory", KeywordDataDir). - Msg("Failed to create keyword data directory") - KeywordDataErr = fmt.Errorf("failed to create keyword data directory: %s", err) - return KeywordDataErr - } - logger.Trace(). - Str("directory", KeywordDataDir). - Msg("Create keyword data directory success") - } else { - logger.Error(). - Err(err). - Str("directory", KeywordDataDir). - Msg("Failed to open keyword data directory") - KeywordDataErr = fmt.Errorf("failed to open keyword data directory: %s", err) - return KeywordDataErr - } - } - - // 检查数据库文件是否存在,不然则创建 - _, err = os.Stat(KeywordDataPath) - if err != nil { - if os.IsNotExist(err) { - logger.Warn(). - Str("path", KeywordDataPath). - Msg("Not found keyword data file. Create a new one") - _, err := os.Create(KeywordDataPath) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to create keyword data file") - KeywordDataErr = fmt.Errorf("failed to create keyword data file: %w", err) - return KeywordDataErr - } - logger.Trace(). - Str("path", KeywordDataPath). - Msg("Created keyword data file success") - } else { - logger.Error(). - Err(err). - Str("path", KeywordDataPath). - Msg("Failed to open keyword data file") - KeywordDataErr = fmt.Errorf("failed to open keyword data file: %w", err) - return KeywordDataErr - } - } - - - err = os.WriteFile(KeywordDataPath, data, 0644) + err := yaml.SaveYAML(KeywordDataPath, &KeywordDataList) if err != nil { logger.Error(). Err(err). Str("path", KeywordDataPath). - Msg("Failed to write keyword data list into file") - KeywordDataErr = fmt.Errorf("failed to write keyword data list into file: %w", err) - return KeywordDataErr + Msg("Failed to save udonese list") + KeywordDataErr = fmt.Errorf("failed to save udonese list: %w", err) + } else { + KeywordDataErr = nil } - logger.Trace(). - Str("path", KeywordDataPath). - Msg("Save keyword data success") - return nil + + return KeywordDataErr } func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { @@ -321,6 +220,8 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Str("funcName", "addKeywordHandler"). Logger() + var handlerErr multe.MultiError + if opts.Update.Message.Chat.Type == models.ChatTypePrivate { // 与机器人的私聊对话 user := KeywordDataList.Users[opts.Update.Message.From.ID] @@ -340,7 +241,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Msg("Failed to init user and save keyword list") - return fmt.Errorf("failed to init user and save keyword list: %w", err) + return handlerErr.Addf("failed to init user and save keyword list: %w", err).Flat() } } @@ -358,14 +259,27 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Dict(utils.GetUserDict(opts.Update.Message.From)). Str("content", "no group for user notice"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `no group for user notice` message: %w", err) } - return nil - } - - if len(opts.Fields) > 1 { - if user.AddingChatID != 0 { - + } else { + if len(opts.Fields) == 1 { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: user.userStatus(), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: buildUserChatList(user), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "user group list keyboard"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `user group list keyboard` message: %w", err) + } + } else { // 限制关键词长度 if len(opts.Fields[1]) > 30 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ @@ -381,151 +295,141 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(opts.Update.Message.From)). Int("length", len(opts.Fields[1])). Str("content", "keyword is too long"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `keyword is too long` message: %w", err) } - return nil - } + } else { + if user.AddingChatID != 0 { + keyword := strings.ToLower(opts.Fields[1]) + var pendingMessage string + var button models.ReplyMarkup + var isKeywordExist bool - keyword := strings.ToLower(opts.Fields[1]) - var pendingMessage string - var button models.ReplyMarkup - var isKeywordExist bool + // 判断是全局关键词还是群组关键词 + if user.AddingChatID == user.UserID { + // 全局关键词 + for _, k := range user.GlobalKeyword { + if k == keyword { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("globalKeyword", keyword). + Msg("User add a global keyword") + user.GlobalKeyword = append(user.GlobalKeyword, keyword) + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("globalKeyword", keyword). + Msg("Failed to add global keyword and save keyword list") + return handlerErr.Addf("failed to add global keyword and save keyword list: %w", err).Flat() + } + pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ]", opts.Fields[1]) + } else { + pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", opts.Fields[1]) + } - // 判断是全局关键词还是群组关键词 - if user.AddingChatID == user.UserID { - // 全局关键词 - for _, k := range user.GlobalKeyword { - if k == keyword { - isKeywordExist = true - break + } else { + var chatForUser ChatForUser + var chatForUserIndex int + for i, c := range user.ChatsForUser { + if c.ChatID == user.AddingChatID { + chatForUser = c + chatForUserIndex = i + } + } + targetChat := KeywordDataList.Chats[chatForUser.ChatID] + + // 群组关键词 + for _, k := range chatForUser.Keyword { + if k == keyword { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("User add a keyword to chat") + chatForUser.Keyword = append(chatForUser.Keyword, keyword) + user.ChatsForUser[chatForUserIndex] = chatForUser + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("Failed to add keyword and save keyword list") + return handlerErr.Addf("failed to add keyword and save keyword list: %w", err).Flat() + } + + pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(opts.Fields[1])) + } else { + pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", opts.Fields[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) + } } - } - if !isKeywordExist { - logger.Debug(). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Str("globalKeyword", keyword). - Msg("User add a global keyword") - user.GlobalKeyword = append(user.GlobalKeyword, keyword) - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) + if isKeywordExist { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "✅ 完成", + CallbackData: "detectkw_u_finish", + }}}} + } else { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "↩️ 撤销操作", + CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", user.AddingChatID, opts.Fields[1]), + }, + { + Text: "✅ 完成", + CallbackData: "detectkw_u_finish", + }, + }}} + } + + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: pendingMessage, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: button, + }) if err != nil { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Str("globalKeyword", keyword). - Msg("Failed to add global keyword and save keyword list") - return fmt.Errorf("failed to add global keyword and save keyword list: %w", err) + Str("content", "keyword added notice"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `keyword added notice` message: %w", err) } - pendingMessage = fmt.Sprintf("已添加全局关键词: [ %s ]", opts.Fields[1]) } else { - pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", opts.Fields[1]) - } - - } else { - var chatForUser ChatForUser - var chatForUserIndex int - for i, c := range user.ChatsForUser { - if c.ChatID == user.AddingChatID { - chatForUser = c - chatForUserIndex = i - } - } - targetChat := KeywordDataList.Chats[chatForUser.ChatID] - - // 群组关键词 - for _, k := range chatForUser.Keyword { - if k == keyword { - isKeywordExist = true - break - } - } - if !isKeywordExist { - logger.Debug(). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Int64("chatID", chatForUser.ChatID). - Str("keyword", keyword). - Msg("User add a keyword to chat") - chatForUser.Keyword = append(chatForUser.Keyword, keyword) - user.ChatsForUser[chatForUserIndex] = chatForUser - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "您还没有选定要将关键词添加到哪个群组,请在下方挑选一个您已经添加的群组", + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: user.selectChat(opts.Fields[1]), + }) if err != nil { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Int64("chatID", chatForUser.ChatID). - Str("keyword", keyword). - Msg("Failed to add keyword and save keyword list") - return fmt.Errorf("failed to add keyword and save keyword list: %w", err) + Str("content", "keyword adding select keyboard"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `keyword adding select keyboard` message: %w", err) } - - pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ],您可以继续向此群组添加更多关键词\n", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(opts.Fields[1])) - } else { - pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", opts.Fields[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) } } - if isKeywordExist { - button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "✅ 完成", - CallbackData: "detectkw_u_finish", - }}}} - } else { - button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "↩️ 撤销操作", - CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", user.AddingChatID, opts.Fields[1]), - }, - { - Text: "✅ 完成", - CallbackData: "detectkw_u_finish", - }, - }}} - } - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: pendingMessage, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: button, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Str("content", "keyword added notice"). - Msg(logt.SendMessage) - } - } else { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "您还没有选定要将关键词添加到哪个群组,请在下方挑选一个您已经添加的群组", - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: user.selectChat(opts.Fields[1]), - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Str("content", "not selected chat yet"). - Msg(logt.SendMessage) - } - } - } else { - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: user.userStatus(), - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: buildUserChatList(user), - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Str("content", "user group list keyboard"). - Msg(logt.SendMessage) } } } else { @@ -549,9 +453,9 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Dict(utils.GetUserDict(opts.Update.Message.From)). Str("content", "function is disabled by admins"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `function is disabled by admins` message: %w", err) } - return nil } else { if chat.AddTime == "" { // 初始化群组 @@ -571,7 +475,7 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Msg("Failed to init chat and save keyword list") - return fmt.Errorf("failed to init chat and save keyword list: %w", err) + return handlerErr.Addf("failed to init chat and save keyword list: %w", err).Flat() } } if len(opts.Fields) == 1 { @@ -597,63 +501,10 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("content", "group record link button"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `group record link button` message: %w", err) } } else { - user := KeywordDataList.Users[opts.Update.Message.From.ID] - - if user.AddTime == "" { - // 初始化用户 - user = KeywordUserList{ - UserID: opts.Update.Message.From.ID, - AddTime: time.Now().Format(time.RFC3339), - Limit: 50, - IsNotInit: true, - } - KeywordDataList.Users[opts.Update.Message.From.ID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Failed to add a not init user and save keyword list") - return fmt.Errorf("failed to add a not init user and save keyword list: %w", err) - } - } - - var isChatAdded bool = false - var chatForUser ChatForUser - var chatForUserIndex int - - for index, keyword := range user.ChatsForUser { - if keyword.ChatID == chat.ChatID { - chatForUser = keyword - chatForUserIndex = index - isChatAdded = true - break - } - } - if !isChatAdded { - logger.Debug(). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Msg("User add a chat to listen list by set keyword in group") - chatForUser = ChatForUser{ - ChatID: chat.ChatID, - } - user.ChatsForUser = append(user.ChatsForUser, chatForUser) - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Failed to add chat to user listen list and save keyword list") - return fmt.Errorf("failed to add chat to user listen list and save keyword list: %w", err) - } - } - // 限制关键词长度 if len(opts.Fields[1]) > 30 { _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ @@ -669,75 +520,130 @@ func addKeywordHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Int("keywordLength", len(opts.Fields[1])). Str("content", "keyword is too long"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `keyword is too long` message: %w", err) } - return nil - } + } else { + user := KeywordDataList.Users[opts.Update.Message.From.ID] - keyword := strings.ToLower(opts.Fields[1]) - var pendingMessage string - var isKeywordExist bool - - for _, k := range chatForUser.Keyword { - if k == keyword { - isKeywordExist = true - break + if user.AddTime == "" { + // 初始化用户 + user = KeywordUserList{ + UserID: opts.Update.Message.From.ID, + AddTime: time.Now().Format(time.RFC3339), + Limit: 50, + IsNotInit: true, + } + KeywordDataList.Users[opts.Update.Message.From.ID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to add a not init user and save keyword list") + return handlerErr.Addf("failed to add a not init user and save keyword list: %w", err).Flat() + } } - } - if !isKeywordExist { - logger.Debug(). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Int64("chatID", chatForUser.ChatID). - Str("keyword", keyword). - Msg("User add a keyword to chat") - chatForUser.Keyword = append(chatForUser.Keyword, keyword) - user.ChatsForUser[chatForUserIndex] = chatForUser - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) + + var isChatAdded bool = false + var chatForUser ChatForUser + var chatForUserIndex int + + for index, keyword := range user.ChatsForUser { + if keyword.ChatID == chat.ChatID { + chatForUser = keyword + chatForUserIndex = index + isChatAdded = true + break + } + } + if !isChatAdded { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Msg("User add a chat to listen list by set keyword in group") + chatForUser = ChatForUser{ + ChatID: chat.ChatID, + } + user.ChatsForUser = append(user.ChatsForUser, chatForUser) + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to add chat to user listen list and save keyword list") + return handlerErr.Addf("failed to add chat to user listen list and save keyword list: %w", err).Flat() + } + } + + keyword := strings.ToLower(opts.Fields[1]) + var pendingMessage string + var isKeywordExist bool + + for _, k := range chatForUser.Keyword { + if k == keyword { + isKeywordExist = true + break + } + } + if !isKeywordExist { + logger.Debug(). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Int64("chatID", chatForUser.ChatID). + Str("keyword", keyword). + Msg("User add a keyword to chat") + chatForUser.Keyword = append(chatForUser.Keyword, keyword) + user.ChatsForUser[chatForUserIndex] = chatForUser + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("keyword", keyword). + Msg("Failed to add keyword and save keyword list") + return handlerErr.Addf("failed to add keyword and save keyword list: %w", err).Flat() + } + if user.IsNotInit { + pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表\n
若要在检测到关键词时收到提醒,请点击下方的按钮来初始化您的账号", strings.ToLower(opts.Fields[1])) + } else { + pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表,您可以继续向此群组添加更多关键词", strings.ToLower(opts.Fields[1])) + } + } else { + if user.IsNotInit { + pendingMessage = "您已经添加过这个关键词了。请点击下方的按钮来初始化您的账号以使用此功能" + } else { + pendingMessage = "您已经添加过这个关键词了,您可以继续向此群组添加其他关键词" + } + } + + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: pendingMessage, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "管理关键词", + URL: fmt.Sprintf("https://t.me/%s?start=detectkw_addgroup_%d", consts.BotMe.Username, chat.ChatID), + }}}}, + }) if err != nil { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Str("keyword", keyword). - Msg("Failed to add keyword and save keyword list") - return fmt.Errorf("failed to add keyword and save keyword list: %w", err) + Str("content", "keyword added notice"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `keyword added notice` message: %w", err) } - if user.IsNotInit { - pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表\n
若要在检测到关键词时收到提醒,请点击下方的按钮来初始化您的账号", strings.ToLower(opts.Fields[1])) - } else { - pendingMessage = fmt.Sprintf("已将 [ %s ] 添加到您的关键词列表,您可以继续向此群组添加更多关键词", strings.ToLower(opts.Fields[1])) - } - } else { - if user.IsNotInit { - pendingMessage = "您已经添加过这个关键词了。请点击下方的按钮来初始化您的账号以使用此功能" - } else { - pendingMessage = "您已经添加过这个关键词了,您可以继续向此群组添加其他关键词" - } - } - - _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: pendingMessage, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - ParseMode: models.ParseModeHTML, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "管理关键词", - URL: fmt.Sprintf("https://t.me/%s?start=detectkw_addgroup_%d", consts.BotMe.Username, chat.ChatID), - }}}}, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Dict(utils.GetChatDict(&opts.Update.Message.Chat)). - Str("content", "keyword added notice"). - Msg(logt.SendMessage) } } } } - return nil + return handlerErr.Flat() } func buildListenList() { @@ -769,6 +675,7 @@ func buildListenList() { } func KeywordDetector(opts *handler_structs.SubHandlerParams) error { + var handlerErr multe.MultiError var text string if opts.Update.Message.Caption != "" { text = strings.ToLower(opts.Update.Message.Caption) @@ -793,7 +700,7 @@ func KeywordDetector(opts *handler_structs.SubHandlerParams) error { if userKeywordList.ChatID == opts.Update.Message.Chat.ID { for _, keyword := range userKeywordList.Keyword { if strings.Contains(text, keyword) { - notifyUser(opts, user, opts.Update.Message.Chat.Title, keyword, text, false) + handlerErr.Add(notifyUser(opts, user, opts.Update.Message.Chat.Title, keyword, text, false)) break } } @@ -802,27 +709,29 @@ func KeywordDetector(opts *handler_structs.SubHandlerParams) error { // 用户全局设定的关键词 for _, userGlobalKeyword := range user.GlobalKeyword { if strings.Contains(text, userGlobalKeyword) { - notifyUser(opts, user, opts.Update.Message.Chat.Title, userGlobalKeyword, text, true) + handlerErr.Add(notifyUser(opts, user, opts.Update.Message.Chat.Title, userGlobalKeyword, text, true)) break } } } } - return nil + return handlerErr.Flat() } -func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, chatname, keyword, text string, isGlobalKeyword bool) { +func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, chatname, keyword, text string, isGlobalKeyword bool) error { logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "DetectKeyword"). Str("funcName", "notifyUser"). Logger() + var handlerErr multe.MultiError + var messageLink string = fmt.Sprintf("https://t.me/c/%s/%d", utils.RemoveIDPrefix(opts.Update.Message.Chat.ID), opts.Update.Message.ID) _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: user.UserID, - Text: fmt.Sprintf("在 %s 群组中\n来自 %s 的消息\n触发了设定的%s关键词 [ %s ]\n
%s", + Text: fmt.Sprintf("在 %s 群组中\n来自 %s 的消息\n触发了%s关键词 [ %s ]\n
%s", utils.RemoveIDPrefix(opts.Update.Message.Chat.ID), chatname, utils.GetMessageFromHyperLink(opts.Update.Message, models.ParseModeHTML), utils.TextForTrueOrFalse(isGlobalKeyword, "全局", "群组"), keyword, text, ), @@ -840,10 +749,13 @@ func notifyUser(opts *handler_structs.SubHandlerParams, user KeywordUserList, ch Int64("userID", user.UserID). Str("keyword", keyword). Str("content", "keyword detected notice to user"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `keyword detected notice to user` message to %d: %w", user.UserID, err) } user.MentionCount++ KeywordDataList.Users[user.UserID] = user + + return handlerErr.Flat() } func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { @@ -853,6 +765,8 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Str("funcName", "groupManageCallbackHandler"). Logger() + var handlerErr multe.MultiError + if !utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.CallbackQuery.Message.Message.Chat.ID, opts.Update.CallbackQuery.From.ID) { _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, @@ -865,44 +779,46 @@ func groupManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("content", "no permission to change group functions"). - Msg(logt.AnswerCallback) + Msg(errt.AnswerCallbackQuery) + handlerErr.Addf("failed to send `no permission to change group functions` callback answer: %w", err) } - return nil - } + } else { + chat := KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] - chat := KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] + if opts.Update.CallbackQuery.Data == "detectkw_g_switch" { + // 群组里的全局开关,是否允许群组内用户使用这个功能,优先级最高 + chat.IsDisable = !chat.IsDisable + KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] = chat + buildListenList() + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to change group switch and save keyword list") + handlerErr.Addf("failed to change group switch and save keyword list: %w", err) + } + } - if opts.Update.CallbackQuery.Data == "detectkw_g_switch" { - // 群组里的全局开关,是否允许群组内用户使用这个功能,优先级最高 - chat.IsDisable = !chat.IsDisable - KeywordDataList.Chats[opts.Update.CallbackQuery.Message.Message.Chat.ID] = chat - buildListenList() - err := SaveKeywordList(opts.Ctx) + _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: fmt.Sprintf("消息关键词检测\n此功能允许用户设定一些关键词,当机器人检测到群组内的消息包含用户设定的关键词时,向用户发送提醒\n\n当前群组中有 %d 个用户启用了此功能\n\n%s", len(chat.UsersID), utils.TextForTrueOrFalse(chat.IsDisable, "已为当前群组关闭关键词检测功能,已设定了关键词的用户将无法再收到此群组的提醒", "")), + ReplyMarkup: buildGroupManageKB(chat), + }) if err != nil { logger.Error(). Err(err). Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to change group switch and save keyword list") - return fmt.Errorf("failed to change group switch and save keyword list: %w", err) + Str("content", "group function manager keyboard"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `group function manager keyboard`: %w", err) } } - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: fmt.Sprintf("消息关键词检测\n此功能允许用户设定一些关键词,当机器人检测到群组内的消息包含用户设定的关键词时,向用户发送提醒\n\n当前群组中有 %d 个用户启用了此功能\n\n%s", len(chat.UsersID), utils.TextForTrueOrFalse(chat.IsDisable, "已为当前群组关闭关键词检测功能,已设定了关键词的用户将无法再收到此群组的提醒", "")), - ReplyMarkup: buildGroupManageKB(chat), - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("content", "group function manager keyboard"). - Msg(logt.EditMessage) - } - return nil + return handlerErr.Flat() } func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { @@ -911,6 +827,8 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Str("pluginName", "DetectKeyword"). Str("funcName", "userManageCallbackHandler"). Logger() + + var handlerErr multe.MultiError user := KeywordDataList.Users[opts.Update.CallbackQuery.From.ID] switch opts.Update.CallbackQuery.Data { @@ -938,12 +856,13 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("content", "this group is disable by admins"). - Msg(logt.AnswerCallback) + Msg(errt.AnswerCallbackQuery) + handlerErr.Addf("failed to send `this group is disable by admins` callback answer: %w", err) } - return nil + return handlerErr.Flat() default: if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") || strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_delkw_") { - // 撤销添加或删除关键词 + // 撤销添加或删除关键词 var chatIDAndKeyword string if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") { chatIDAndKeyword = strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") @@ -958,69 +877,232 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Msg("Failed to parse chat ID when user undo add or delete a keyword") - return fmt.Errorf("failed to parse chat ID when user undo add or delete a keyword: %w", err) - } - - // 删除关键词过程 - if chatID == user.UserID { - // 全局关键词 - var tempKeyword []string - for _, keyword := range user.GlobalKeyword { - if keyword != chatIDAndKeywordList[1] { - tempKeyword = append(tempKeyword, keyword) - } - } - user.GlobalKeyword = tempKeyword - KeywordDataList.Users[user.UserID] = user + handlerErr.Addf("failed to parse chat ID when user undo add or delete a keyword: %w", err) } else { - // 群组关键词 - for index, chatForUser := range KeywordDataList.Users[user.UserID].ChatsForUser { - if chatForUser.ChatID == chatID { - var tempKeyword []string - for _, keyword := range chatForUser.Keyword { - if keyword != chatIDAndKeywordList[1] { - tempKeyword = append(tempKeyword, keyword) - } + // 删除关键词过程 + if chatID == user.UserID { + // 全局关键词 + var tempKeyword []string + for _, keyword := range user.GlobalKeyword { + if keyword != chatIDAndKeywordList[1] { + tempKeyword = append(tempKeyword, keyword) } - chatForUser.Keyword = tempKeyword } - KeywordDataList.Users[user.UserID].ChatsForUser[index] = chatForUser + user.GlobalKeyword = tempKeyword + KeywordDataList.Users[user.UserID] = user + } else { + // 群组关键词 + for index, chatForUser := range KeywordDataList.Users[user.UserID].ChatsForUser { + if chatForUser.ChatID == chatID { + var tempKeyword []string + for _, keyword := range chatForUser.Keyword { + if keyword != chatIDAndKeywordList[1] { + tempKeyword = append(tempKeyword, keyword) + } + } + chatForUser.Keyword = tempKeyword + } + KeywordDataList.Users[user.UserID].ChatsForUser[index] = chatForUser + } } - } - err = SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Int64("chatID", chatID). - Str("keyword", chatIDAndKeywordList[1]). - Msg("Failed to undo add or remove keyword and save keyword list") - return fmt.Errorf("failed to undo add or remove keyword and save keyword list: %w", err) - } - - if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") { - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: "已撤销操作,您可以继续使用
/setkeyword 关键词 来添加其他关键词",
- ParseMode: models.ParseModeHTML,
- })
+ err = SaveKeywordList(opts.Ctx)
if err != nil {
logger.Error().
Err(err).
Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
Str("callbackQueryData", opts.Update.CallbackQuery.Data).
- Str("content", "add keyword has been canceled notice").
- Msg(logt.EditMessage)
+ Int64("chatID", chatID).
+ Str("keyword", chatIDAndKeywordList[1]).
+ Msg("Failed to undo add or remove keyword and save keyword list")
+ handlerErr.Addf("failed to undo add or remove keyword and save keyword list: %w", err)
+ } else {
+ if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_undo_") {
+ _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{
+ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID,
+ MessageID: opts.Update.CallbackQuery.Message.Message.ID,
+ Text: "已撤销操作,您可以继续使用 /setkeyword 关键词 来添加其他关键词",
+ ParseMode: models.ParseModeHTML,
+ })
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Str("content", "add keyword has been canceled notice").
+ Msg(errt.EditMessageText)
+ handlerErr.Addf("failed to edit message to `add keyword has been canceled notice`: %w", err)
+ }
+ } else {
+ var buttons [][]models.InlineKeyboardButton
+ var tempbutton []models.InlineKeyboardButton
+ var keywordCount int
+ var pendingMessage string
+
+ if chatID == user.UserID {
+ for index, keyword := range user.GlobalKeyword {
+ if index % 2 == 0 && index != 0 {
+ buttons = append(buttons, tempbutton)
+ tempbutton = []models.InlineKeyboardButton{}
+ }
+ tempbutton = append(tempbutton, models.InlineKeyboardButton{
+ Text: keyword,
+ CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", user.UserID, keyword),
+ })
+ keywordCount++
+ // buttons = append(buttons, tempbutton)
+ }
+ if len(tempbutton) != 0 {
+ buttons = append(buttons, tempbutton)
+ }
+ pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前设定了 %d 个全局关键词", chatIDAndKeywordList[1], keywordCount)
+
+ } else {
+ for _, chat := range user.ChatsForUser {
+ if chat.ChatID == chatID {
+ for index, keyword := range chat.Keyword {
+ if index % 2 == 0 && index != 0 {
+ buttons = append(buttons, tempbutton)
+ tempbutton = []models.InlineKeyboardButton{}
+ }
+ tempbutton = append(tempbutton, models.InlineKeyboardButton{
+ Text: keyword,
+ CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", chat.ChatID, keyword),
+ })
+ keywordCount++
+ // buttons = append(buttons, tempbutton)
+ }
+ if len(tempbutton) != 0 {
+ buttons = append(buttons, tempbutton)
+ }
+ }
+ }
+ pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前为 %s 群组设定了 %d 个关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName, keywordCount)
+ }
+
+ buttons = append(buttons, []models.InlineKeyboardButton{
+ {
+ Text: "⬅️ 返回主菜单",
+ CallbackData: "detectkw_u",
+ },
+ {
+ Text: "➕ 添加关键词",
+ CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID),
+ },
+ })
+
+ _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{
+ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID,
+ MessageID: opts.Update.CallbackQuery.Message.Message.ID,
+ Text: pendingMessage,
+ ParseMode: models.ParseModeHTML,
+ ReplyMarkup: &models.InlineKeyboardMarkup{
+ InlineKeyboard: buttons,
+ },
+ })
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Str("content", "keyword list keyboard with deleted keyword notice").
+ Msg(errt.EditMessageText)
+ }
+ }
}
+ }
+
+ return handlerErr.Flat()
+ } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_adding_") {
+ // 设定要往哪个群组里添加关键词
+ chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_adding_")
+ chatID_int64, err := strconv.ParseInt(chatID, 10, 64)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Msg("Failed to parse chat ID when user selecting chat to add keyword")
+ handlerErr.Addf("failed to parse chat ID when user selecting chat to add keyword: %w", err)
+ } else {
+ user := KeywordDataList.Users[user.UserID]
+ user.AddingChatID = chatID_int64
+ KeywordDataList.Users[user.UserID] = user
+ buildListenList()
+ err = SaveKeywordList(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Int64("chatID", chatID_int64).
+ Msg("Failed to set a chat ID for user add keyword and save keyword list")
+ handlerErr.Addf("failed to set a chat ID for user add keyword and save keyword list: %w", err)
+ } else {
+ var pendingMessage string
+ if chatID_int64 == user.UserID {
+ pendingMessage = "已将全局关键词设为添加关键词的目标,请继续使用 /setkeyword 关键词 来添加全局关键词"
+ } else {
+ pendingMessage = fmt.Sprintf("已将 %s 群组设为添加关键词的目标群组,请继续使用 /setkeyword 关键词 来为该群组添加关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName)
+ }
+
+ _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{
+ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID,
+ MessageID: opts.Update.CallbackQuery.Message.Message.ID,
+ Text: pendingMessage,
+ ParseMode: models.ParseModeHTML,
+ })
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Str("content", "already to add keyword notice").
+ Msg(errt.EditMessageText)
+ handlerErr.Addf("failed to edit message to `already to add keyword notice`: %w", err)
+ }
+ }
+ }
+
+ return handlerErr.Flat()
+ } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_switch_chat_") {
+ // 启用或禁用某个群组的关键词检测开关
+ id := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_switch_chat_")
+ id_int64, err := strconv.ParseInt(id, 10, 64)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Msg("Failed to parse chat ID when user change the group switch")
+ return handlerErr.Addf("failed to parse chat ID when user change the group switch: %w", err).Flat()
+ } else {
+ for index, chat := range KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser {
+ if chat.ChatID == id_int64 {
+ chat.IsDisable = !chat.IsDisable
+ }
+ KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser[index] = chat
+ }
+ }
+ // edit by the end
+ } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_chat_") {
+ // 显示某个群组的关键词列表
+ chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_chat_")
+ chatID_int64, err := strconv.ParseInt(chatID, 10, 64)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
+ Str("callbackQueryData", opts.Update.CallbackQuery.Data).
+ Msg("Failed to parse chat ID when user wanna manage keyword for group")
+ handlerErr.Addf("failed to parse chat ID when user wanna manage keyword for group: %w", err)
} else {
var buttons [][]models.InlineKeyboardButton
var tempbutton []models.InlineKeyboardButton
- var keywordCount int
var pendingMessage string
+ var keywordCount int
- if chatID == user.UserID {
+ if chatID_int64 == user.UserID {
+ // 全局关键词
for index, keyword := range user.GlobalKeyword {
if index % 2 == 0 && index != 0 {
buttons = append(buttons, tempbutton)
@@ -1031,16 +1113,19 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error {
CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", user.UserID, keyword),
})
keywordCount++
- // buttons = append(buttons, tempbutton)
}
if len(tempbutton) != 0 {
buttons = append(buttons, tempbutton)
}
- pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前设定了 %d 个全局关键词", chatIDAndKeywordList[1], keywordCount)
-
+ if len(buttons) == 0 {
+ pendingMessage = "您没有设定任何全局关键词\n点击下方按钮来添加全局关键词"
+ } else {
+ pendingMessage = fmt.Sprintf("您当前设定了 %d 个全局关键词\n全局关键词将对您添加的全部群组生效\n但在部分情况下,全局关键词不会生效:\n- 您手动将群组设定为禁用状态\n- 对应群组的管理员为该群组关闭了此功能", keywordCount) + } } else { - for _, chat := range user.ChatsForUser { - if chat.ChatID == chatID { + // 为群组设定的关键词 + for _, chat := range KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser { + if chat.ChatID == chatID_int64 { for index, keyword := range chat.Keyword { if index % 2 == 0 && index != 0 { buttons = append(buttons, tempbutton) @@ -1056,9 +1141,14 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { if len(tempbutton) != 0 { buttons = append(buttons, tempbutton) } + break } } - pendingMessage = fmt.Sprintf("已删除 [ %s ] 关键词\n\n您当前为 %s 群组设定了 %d 个关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName, keywordCount) + if len(buttons) == 0 { + pendingMessage = fmt.Sprintf("当前群组 %s 没有关键词\n点击下方按钮来为此群组添加关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName) + } else { + pendingMessage = fmt.Sprintf("您当前为 %s 群组设定了 %d 个关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName, keywordCount) + } } buttons = append(buttons, []models.InlineKeyboardButton{ @@ -1068,11 +1158,11 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { }, { Text: "➕ 添加关键词", - CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID), + CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID_int64), }, }) - _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, MessageID: opts.Update.CallbackQuery.Message.Message.ID, Text: pendingMessage, @@ -1086,175 +1176,13 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Str("content", "keyword list keyboard with deleted keyword notice"). - Msg(logt.EditMessage) + Str("content", "group keyword list keyboard"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `group keyword list keyboard`: %w", err) } } - return nil - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_adding_") { - // 设定要往哪个群组里添加关键词 - chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_adding_") - chatID_int64, err := strconv.ParseInt(chatID, 10, 64) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("Failed to parse chat ID when user selecting chat to add keyword") - return fmt.Errorf("failed to parse chat ID when user selecting chat to add keyword: %w", err) - } - user := KeywordDataList.Users[user.UserID] - user.AddingChatID = chatID_int64 - KeywordDataList.Users[user.UserID] = user - buildListenList() - err = SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Int64("chatID", chatID_int64). - Msg("Failed to set a chat ID for user add keyword and save keyword list") - return fmt.Errorf("failed to set a chat ID for user add keyword and save keyword list: %w", err) - } - - var pendingMessage string - if chatID_int64 == user.UserID { - pendingMessage = "已将全局关键词设为添加关键词的目标,请继续使用
/setkeyword 关键词 来添加全局关键词"
- } else {
- pendingMessage = fmt.Sprintf("已将 %s 群组设为添加关键词的目标群组,请继续使用 /setkeyword 关键词 来为该群组添加关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName)
- }
-
- _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{
- ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID,
- MessageID: opts.Update.CallbackQuery.Message.Message.ID,
- Text: pendingMessage,
- ParseMode: models.ParseModeHTML,
- })
- if err != nil {
- logger.Error().
- Err(err).
- Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
- Str("callbackQueryData", opts.Update.CallbackQuery.Data).
- Str("content", "already to add keyword notice").
- Msg(logt.EditMessage)
- }
- return nil
- } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_switch_chat_") {
- // 启用或禁用某个群组的关键词检测开关
- id := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_switch_chat_")
- id_int64, err := strconv.ParseInt(id, 10, 64)
- if err != nil {
- logger.Error().
- Err(err).
- Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
- Str("callbackQueryData", opts.Update.CallbackQuery.Data).
- Msg("Failed to parse chat ID when user change the group switch")
- return fmt.Errorf("failed to parse chat ID when user change the group switch: %w", err)
- }
- for index, chat := range KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser {
- if chat.ChatID == id_int64 {
- chat.IsDisable = !chat.IsDisable
- }
- KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser[index] = chat
- }
- } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_chat_") {
- // 显示某个群组的关键词列表
- chatID := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_chat_")
- chatID_int64, err := strconv.ParseInt(chatID, 10, 64)
- if err != nil {
- logger.Error().
- Err(err).
- Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)).
- Str("callbackQueryData", opts.Update.CallbackQuery.Data).
- Msg("Failed to parse chat ID when user wanna manage keyword for group")
- return fmt.Errorf("failed to parse chat ID when user wanna manage keyword for group: %w", err)
- }
- var buttons [][]models.InlineKeyboardButton
- var tempbutton []models.InlineKeyboardButton
- var pendingMessage string
- var keywordCount int
-
- if chatID_int64 == user.UserID {
- // 全局关键词
- for index, keyword := range user.GlobalKeyword {
- if index % 2 == 0 && index != 0 {
- buttons = append(buttons, tempbutton)
- tempbutton = []models.InlineKeyboardButton{}
- }
- tempbutton = append(tempbutton, models.InlineKeyboardButton{
- Text: keyword,
- CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", user.UserID, keyword),
- })
- keywordCount++
- }
- if len(tempbutton) != 0 {
- buttons = append(buttons, tempbutton)
- }
- if len(buttons) == 0 {
- pendingMessage = "您没有设定任何全局关键词\n点击下方按钮来添加全局关键词"
- } else {
- pendingMessage = fmt.Sprintf("您当前设定了 %d 个全局关键词\n全局关键词将对您添加的全部群组生效\n但在部分情况下,全局关键词不会生效:\n- 您手动将群组设定为禁用状态\n- 对应群组的管理员为该群组关闭了此功能", keywordCount) - } - } else { - // 为群组设定的关键词 - for _, chat := range KeywordDataList.Users[opts.Update.CallbackQuery.From.ID].ChatsForUser { - if chat.ChatID == chatID_int64 { - for index, keyword := range chat.Keyword { - if index % 2 == 0 && index != 0 { - buttons = append(buttons, tempbutton) - tempbutton = []models.InlineKeyboardButton{} - } - tempbutton = append(tempbutton, models.InlineKeyboardButton{ - Text: keyword, - CallbackData: fmt.Sprintf("detectkw_u_kw_%d_%s", chat.ChatID, keyword), - }) - keywordCount++ - // buttons = append(buttons, tempbutton) - } - if len(tempbutton) != 0 { - buttons = append(buttons, tempbutton) - } - break - } - } - if len(buttons) == 0 { - pendingMessage = fmt.Sprintf("当前群组 %s 没有关键词\n点击下方按钮来为此群组添加关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName) - } else { - pendingMessage = fmt.Sprintf("您当前为 %s 群组设定了 %d 个关键词", utils.RemoveIDPrefix(chatID_int64), KeywordDataList.Chats[chatID_int64].ChatName, keywordCount) - } - } - - buttons = append(buttons, []models.InlineKeyboardButton{ - { - Text: "⬅️ 返回主菜单", - CallbackData: "detectkw_u", - }, - { - Text: "➕ 添加关键词", - CallbackData: fmt.Sprintf("detectkw_u_adding_%d", chatID_int64), - }, - }) - - _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: pendingMessage, - ParseMode: models.ParseModeHTML, - ReplyMarkup: &models.InlineKeyboardMarkup{ - InlineKeyboard: buttons, - }, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Str("content", "group keyword list keyboard"). - Msg(logt.EditMessage) - } - return nil + return handlerErr.Flat() } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_kw_") { // 管理一个关键词 chatIDAndKeyword := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_kw_") @@ -1266,42 +1194,44 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Msg("Failed to parse chat ID when user wanna manage a keyword") - return fmt.Errorf("failed to parse chat ID when user wanna manage a keyword: %w", err) - } - - var pendingMessage string - - if chatID == user.UserID { - pendingMessage = fmt.Sprintf("[ %s ] 是您设定的全局关键词", chatIDAndKeywordList[1]) + handlerErr.Addf("failed to parse chat ID when user wanna manage a keyword: %w", err) } else { - pendingMessage = fmt.Sprintf("[ %s ] 是为 %s 群组设定的关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName) + var pendingMessage string + + if chatID == user.UserID { + pendingMessage = fmt.Sprintf("[ %s ] 是您设定的全局关键词", chatIDAndKeywordList[1]) + } else { + pendingMessage = fmt.Sprintf("[ %s ] 是为 %s 群组设定的关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(chatID), KeywordDataList.Chats[chatID].ChatName) + } + + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: pendingMessage, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "⬅️ 返回", + CallbackData: "detectkw_u_chat_" + chatIDAndKeywordList[0], + }, + { + Text: "❌ 删除此关键词", + CallbackData: "detectkw_u_delkw_" + chatIDAndKeyword, + }, + }}}, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "keyword manager keyboard"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `keyword manager keyboard`: %w", err) + } } - _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: pendingMessage, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "⬅️ 返回", - CallbackData: "detectkw_u_chat_" + chatIDAndKeywordList[0], - }, - { - Text: "❌ 删除此关键词", - CallbackData: "detectkw_u_delkw_" + chatIDAndKeyword, - }, - }}}, - ParseMode: models.ParseModeHTML, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Str("content", "keyword manager keyboard"). - Msg(logt.EditMessage) - } - return nil + return handlerErr.Flat() } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_add_") { chatIDAndKeyword := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "detectkw_u_add_") chatIDAndKeywordList := strings.Split(chatIDAndKeyword, "_") @@ -1312,116 +1242,118 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Msg("Failed to parse chat ID when user wanna add a keyword") - return fmt.Errorf("failed to parse chat ID when user wanna add a keyword: %w", err) - } + return handlerErr.Addf("failed to parse chat ID when user wanna add a keyword: %w", err).Flat() + } else { + var pendingMessage string + var button models.ReplyMarkup + var isKeywordExist bool - var pendingMessage string - var button models.ReplyMarkup - var isKeywordExist bool - - if chatID == user.UserID { - // 全局关键词 - for _, k := range user.GlobalKeyword { - if k == chatIDAndKeywordList[1] { - isKeywordExist = true - break + if chatID == user.UserID { + // 全局关键词 + for _, k := range user.GlobalKeyword { + if k == chatIDAndKeywordList[1] { + isKeywordExist = true + break + } } - } - if !isKeywordExist { - logger.Debug(). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Str("globalKeyword", chatIDAndKeywordList[1]). - Msg("User add a global keyword") - user.GlobalKeyword = append(user.GlobalKeyword, chatIDAndKeywordList[1]) - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). + if !isKeywordExist { + logger.Debug(). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Str("globalKeyword", chatIDAndKeywordList[1]). - Msg("Failed to add global keyword and save keyword list") - return fmt.Errorf("failed to add global keyword and save keyword list: %w", err) + Msg("User add a global keyword") + user.GlobalKeyword = append(user.GlobalKeyword, chatIDAndKeywordList[1]) + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("globalKeyword", chatIDAndKeywordList[1]). + Msg("Failed to add global keyword and save keyword list") + return handlerErr.Addf("failed to add global keyword and save keyword list: %w", err).Flat() + } + pendingMessage = fmt.Sprintf("已添加全局关键词 [ %s ]", chatIDAndKeywordList[1]) + } else { + pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", chatIDAndKeywordList[1]) } - pendingMessage = fmt.Sprintf("已添加全局关键词 [ %s ]", chatIDAndKeywordList[1]) } else { - pendingMessage = fmt.Sprintf("此全局关键词 [ %s ] 已存在", chatIDAndKeywordList[1]) - } - } else { - // 群组关键词 - var chatForUser ChatForUser - var chatForUserIndex int - for i, c := range user.ChatsForUser { - if c.ChatID == chatID { - chatForUser = c - chatForUserIndex = i + // 群组关键词 + var chatForUser ChatForUser + var chatForUserIndex int + for i, c := range user.ChatsForUser { + if c.ChatID == chatID { + chatForUser = c + chatForUserIndex = i + } } - } - targetChat := KeywordDataList.Chats[chatID] - for _, k := range chatForUser.Keyword { - if k == chatIDAndKeywordList[1] { - isKeywordExist = true - break + targetChat := KeywordDataList.Chats[chatID] + for _, k := range chatForUser.Keyword { + if k == chatIDAndKeywordList[1] { + isKeywordExist = true + break + } } - } - if !isKeywordExist { - logger.Debug(). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("User add a keyword to chat") - chatForUser.Keyword = append(chatForUser.Keyword, chatIDAndKeywordList[1]) - user.ChatsForUser[chatForUserIndex] = chatForUser - KeywordDataList.Users[user.UserID] = user - err := SaveKeywordList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). + if !isKeywordExist { + logger.Debug(). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Msg("Failed to add keyword and save keyword list") - return fmt.Errorf("failed to add keyword and save keyword list: %w", err) + Msg("User add a keyword to chat") + chatForUser.Keyword = append(chatForUser.Keyword, chatIDAndKeywordList[1]) + user.ChatsForUser[chatForUserIndex] = chatForUser + KeywordDataList.Users[user.UserID] = user + err := SaveKeywordList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to add keyword and save keyword list") + return handlerErr.Addf("failed to add keyword and save keyword list: %w", err).Flat() + } + pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ]", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(chatIDAndKeywordList[1])) + } else { + pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) } - pendingMessage = fmt.Sprintf("已为 %s 群组添加关键词 [ %s ]", utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName, strings.ToLower(chatIDAndKeywordList[1])) + } + + if isKeywordExist { + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "✅ 完成", + CallbackData: "detectkw_u", + }}}} } else { - pendingMessage = fmt.Sprintf("此关键词 [ %s ] 已存在于 %s 群组中,您可以继续向此群组添加其他关键词", chatIDAndKeywordList[1], utils.RemoveIDPrefix(targetChat.ChatID), targetChat.ChatName) + button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "↩️ 撤销操作", + CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", chatID, chatIDAndKeywordList[1]), + }, + { + Text: "✅ 完成", + CallbackData: "detectkw_u", + }, + }}} + } + _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: pendingMessage, + ReplyMarkup: button, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "keyword added notice"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `keyword added notice`: %w", err) } } - if isKeywordExist { - button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "✅ 完成", - CallbackData: "detectkw_u", - }}}} - } else { - button = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "↩️ 撤销操作", - CallbackData: fmt.Sprintf("detectkw_u_undo_%d_%s", chatID, chatIDAndKeywordList[1]), - }, - { - Text: "✅ 完成", - CallbackData: "detectkw_u", - }, - }}} - } - _, err = opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: pendingMessage, - ReplyMarkup: button, - ParseMode: models.ParseModeHTML, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("callbackQueryData", opts.Update.CallbackQuery.Data). - Str("content", "keyword added notice"). - Msg(logt.EditMessage) - } - return nil + return handlerErr.Flat() } } @@ -1437,7 +1369,8 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Str("content", "main manager keyboard"). - Msg(logt.EditMessage) + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `this group is disable by admins`: %w", err) } KeywordDataList.Users[opts.Update.CallbackQuery.From.ID] = user @@ -1449,9 +1382,10 @@ func userManageCallbackHandler(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Msg("Failed to save keyword list") - return fmt.Errorf("failed to save keyword list: %w", err) + handlerErr.Addf("failed to save keyword list: %w", err) } - return nil + + return handlerErr.Flat() } func buildGroupManageKB(chat KeywordChatList) models.ReplyMarkup { @@ -1474,6 +1408,8 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { Str("funcName", "startPrefixAddGroup"). Logger() + var handlerErr multe.MultiError + user := KeywordDataList.Users[opts.Update.Message.From.ID] if user.AddTime == "" { // 初始化用户 @@ -1492,7 +1428,7 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(opts.Update.Message.From)). Str("messageText", opts.Update.Message.Text). Msg("Failed to add user and save keyword list") - return fmt.Errorf("failed to add user and save keyword list: %w", err) + return handlerErr.Addf("failed to add user and save keyword list: %w", err).Flat() } } if user.IsNotInit { @@ -1506,7 +1442,7 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(opts.Update.Message.From)). Str("messageText", opts.Update.Message.Text). Msg("Failed to init user and save keyword list") - return fmt.Errorf("failed to init user and save keyword list: %w", err) + return handlerErr.Addf("failed to init user and save keyword list: %w", err).Flat() } if strings.HasPrefix(opts.Fields[1], "detectkw_addgroup_") { groupID := strings.TrimPrefix(opts.Fields[1], "detectkw_addgroup_") @@ -1517,7 +1453,7 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(opts.Update.Message.From)). Str("messageText", opts.Update.Message.Text). Msg("Failed to parse chat ID when user add a group by /start command") - return fmt.Errorf("failed to parse chat ID when user add a group by /start command: %w", err) + return handlerErr.Addf("failed to parse chat ID when user add a group by /start command: %w", err).Flat() } chat := KeywordDataList.Chats[groupID_int64] @@ -1555,7 +1491,8 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). Str("content", "added group in user list"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `added group in user list` message: %w", err) } } err := SaveKeywordList(opts.Ctx) @@ -1565,9 +1502,10 @@ func startPrefixAddGroup(opts *handler_structs.SubHandlerParams) error { Dict(utils.GetUserDict(opts.Update.Message.From)). Str("messageText", opts.Update.Message.Text). Msg("Failed to add group for user and save keyword list") - return fmt.Errorf("failed to add group for user and save keyword list: %w", err) + return handlerErr.Addf("failed to add group for user and save keyword list: %w", err).Flat() } - return nil + + return handlerErr.Flat() } func buildUserChatList(user KeywordUserList) models.ReplyMarkup { diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index 6360c82..c0a032c 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -15,8 +15,9 @@ import ( "trbot/utils" "trbot/utils/configs" "trbot/utils/consts" + "trbot/utils/errt" "trbot/utils/handler_structs" - "trbot/utils/logt" + "trbot/utils/multe" "trbot/utils/plugin_utils" "trbot/utils/type/message_utils" @@ -79,10 +80,12 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { Str("funcName", "EchoStickerHandler"). Logger() + var handlerErr multe.MultiError + if opts.Update.Message == nil && opts.Update.CallbackQuery != nil && strings.HasPrefix(opts.Update.CallbackQuery.Data, "HBMT_") && opts.Update.CallbackQuery.Message.Message != nil && opts.Update.CallbackQuery.Message.Message.ReplyToMessage != nil { // if this handler tigger by `handler by message type`, copy `update.CallbackQuery.Message.Message.ReplyToMessage` to `update.Message` opts.Update.Message = opts.Update.CallbackQuery.Message.Message.ReplyToMessage - logger.Debug(). + logger.Info(). Str("callbackQueryData", opts.Update.CallbackQuery.Data). Msg("copy `update.CallbackQuery.Message.Message.ReplyToMessage` to `update.Message`") } @@ -97,7 +100,8 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Incremental sticker download count error") + Msg("Failed to incremental sticker download count") + handlerErr.Addf("failed to incremental sticker download count: %w", err) } stickerData, err := EchoSticker(opts) @@ -105,7 +109,8 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Failed to download sticker") + Msg("Error when downloading sticker") + handlerErr.Addf("error when downloading sticker: %w", err) _, msgerr := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.Message.From.ID, @@ -117,65 +122,65 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { Err(msgerr). Dict(utils.GetUserDict(opts.Update.Message.From)). Str("content", "sticker download error"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `sticker download error` message: %w", msgerr) + } + } else { + documentParams := &bot.SendDocumentParams{ + ChatID: opts.Update.Message.From.ID, + ParseMode: models.ParseModeHTML, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + DisableNotification: true, + DisableContentTypeDetection: true, // Prevent the server convert gif to mp4 } - return fmt.Errorf("failed to download sticker: %w", err) - } - documentParams := &bot.SendDocumentParams{ - ChatID: opts.Update.Message.From.ID, - ParseMode: models.ParseModeHTML, - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - DisableNotification: true, - DisableContentTypeDetection: true, // Prevent the server convert gif to mp4 - } + var stickerFilePrefix, stickerFileSuffix string - var stickerFilePrefix, stickerFileSuffix string - - if opts.Update.Message.Sticker.IsVideo { - if stickerData.IsConverted { - stickerFileSuffix = "gif" + if opts.Update.Message.Sticker.IsVideo { + if stickerData.IsConverted { + stickerFileSuffix = "gif" + } else { + documentParams.Caption = "
see wikipedia/WebM" + stickerFileSuffix = "webm" + } + } else if opts.Update.Message.Sticker.IsAnimated { + documentParams.Caption = "
see stickers/animated-stickers" + stickerFileSuffix = "tgs.file" } else { - documentParams.Caption = "
see wikipedia/WebM" - stickerFileSuffix = "webm" + stickerFileSuffix = "png" + } + + if stickerData.IsCustomSticker { + stickerFilePrefix = "sticker" + } else { + stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) + + // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 + documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) + documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ + { + { Text: "下载贴纸包中的静态贴纸", CallbackData: fmt.Sprintf("S_%s", opts.Update.Message.Sticker.SetName) }, + }, + { + { Text: "下载整个贴纸包(不转换格式)", CallbackData: fmt.Sprintf("s_%s", opts.Update.Message.Sticker.SetName) }, + }, + }} + } + + documentParams.Document = &models.InputFileUpload{ Filename: fmt.Sprintf("%s.%s", stickerFilePrefix, stickerFileSuffix), Data: stickerData.Data } + + _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "sticker file"). + Msg(errt.SendDocument) + handlerErr.Addf("failed to send sticker file: %w", err) } - } else if opts.Update.Message.Sticker.IsAnimated { - documentParams.Caption = "
see stickers/animated-stickers" - stickerFileSuffix = "tgs.file" - } else { - stickerFileSuffix = "png" } - if stickerData.IsCustomSticker { - stickerFilePrefix = "sticker" - } else { - stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) - - // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 - documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) - documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ - { - { Text: "下载贴纸包中的静态贴纸", CallbackData: fmt.Sprintf("S_%s", opts.Update.Message.Sticker.SetName) }, - }, - { - { Text: "下载整个贴纸包(不转换格式)", CallbackData: fmt.Sprintf("s_%s", opts.Update.Message.Sticker.SetName) }, - }, - }} - } - - documentParams.Document = &models.InputFileUpload{ Filename: fmt.Sprintf("%s.%s", stickerFilePrefix, stickerFileSuffix), Data: stickerData.Data } - - _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(opts.Update.Message.From)). - Str("content", "sticker file"). - Msg(logt.SendDocument) - return fmt.Errorf("failed to send sticker file: %w", err) - } - - return nil + return handlerErr.Flat() } func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) { @@ -439,8 +444,8 @@ func EchoSticker(opts *handler_structs.SubHandlerParams) (*stickerDatas, error) logger.Error(). Err(err). Str("finalFullPath", finalFullPath). - Msg("Failed to open sticker file") - return nil, fmt.Errorf("failed to open sticker file [%s]: %w", finalFullPath, err) + Msg("Failed to open downloaded sticker file") + return nil, fmt.Errorf("failed to open downloaded sticker file [%s]: %w", finalFullPath, err) } return &data, nil @@ -453,6 +458,8 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Str("funcName", "DownloadStickerPackCallBackHandler"). Logger() + var handlerErr multe.MultiError + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, Text: "已请求下载,请稍候", @@ -464,7 +471,8 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("content", "start download stickerset"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `start download stickerset` message: %w", err) } err = database.IncrementalUsageCount(opts.Ctx, opts.Update.CallbackQuery.Message.Message.Chat.ID, db_struct.StickerSetDownloaded) @@ -472,7 +480,8 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.Message.From)). - Msg("Incremental sticker set download count error") + Msg("Failed to incremental sticker set download count") + handlerErr.Addf("failed to incremental sticker set download count: %w", err) } var packName string @@ -492,80 +501,83 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Msg("Failed to get sticker set info") + handlerErr.Addf("Failed to get sticker set info: %w", err) - _, msgerr := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ ChatID: opts.Update.CallbackQuery.From.ID, Text: fmt.Sprintf("获取贴纸包时发生了一些错误\n
Failed to get sticker set info: %s", err), ParseMode: models.ParseModeHTML, }) - if msgerr != nil { + if err != nil { logger.Error(). - Err(msgerr). + Err(err). Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("content", "get sticker set info error"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `get sticker set info error` message: %w", err) } - return fmt.Errorf("failed to get sticker set info: %w", err) - } - - stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Msg("Failed to download sticker set") - - _, msgerr := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.CallbackQuery.From.ID, - Text: fmt.Sprintf("下载贴纸包时发生了一些错误\n
Failed to download sticker set: %s", err), - ParseMode: models.ParseModeHTML, - }) - if msgerr != nil { - logger.Error(). - Err(msgerr). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("content", "download sticker set error"). - Msg(logt.SendMessage) - } - return fmt.Errorf("failed to download sticker set: %w", err) - } - - documentParams := &bot.SendDocumentParams{ - ChatID: opts.Update.CallbackQuery.From.ID, - ParseMode: models.ParseModeMarkdownV1, - } - - if isOnlyPNG { - documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸(仅转换后的 PNG 格式)", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) - documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d)_png.zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} } else { - documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) - documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d).zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} + stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to download sticker set") + handlerErr.Addf("failed to download sticker set: %w", err) + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + Text: fmt.Sprintf("下载贴纸包时发生了一些错误\n
Failed to download sticker set: %s", err), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "download sticker set error"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `download sticker set error` message: %w", err) + } + } else { + documentParams := &bot.SendDocumentParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + ParseMode: models.ParseModeMarkdownV1, + } + + if isOnlyPNG { + documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸(仅转换后的 PNG 格式)", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) + documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d)_png.zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} + } else { + documentParams.Caption = fmt.Sprintf("[%s](https://t.me/addstickers/%s) 已下载\n包含 %d 个贴纸", stickerData.StickerSetTitle, stickerData.StickerSetName, stickerData.StickerCount) + documentParams.Document = &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d).zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data} + } + + _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "sticker set zip file"). + Msg(errt.SendDocument) + handlerErr.Addf("failed to send sticker set zip file: %w", err) + } + + _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: botMessage.ID, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "start download stickerset notice"). + Msg(errt.DeleteMessage) + handlerErr.Addf("failed to delete `start download sticker set notice` message: %w", err) + } + } } - _, err = opts.Thebot.SendDocument(opts.Ctx, documentParams) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("content", "sticker set zip file"). - Msg(logt.SendDocument) - return fmt.Errorf("failed to send sticker set zip file: %w", err) - } - - _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: botMessage.ID, - }) - if err != nil { - logger.Error(). - Err(err). - Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). - Str("content", "start download stickerset notice"). - Msg(logt.DeleteMessage) - } - - return nil + return handlerErr.Flat() } func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.StickerSet, isOnlyPNG bool) (*stickerDatas, error) { diff --git a/plugins/plugin_teamspeak3.go b/plugins/plugin_teamspeak3.go index cb34815..4b523b8 100644 --- a/plugins/plugin_teamspeak3.go +++ b/plugins/plugin_teamspeak3.go @@ -7,8 +7,9 @@ import ( "path/filepath" "time" "trbot/utils/consts" + "trbot/utils/errt" "trbot/utils/handler_structs" - "trbot/utils/logt" + "trbot/utils/multe" "trbot/utils/plugin_utils" "trbot/utils/yaml" @@ -65,10 +66,8 @@ func init() { Handler: getOptsHandler, }) hasHandlerByChatID = true - return nil - } else { - return tsErr } + return tsErr }, }) @@ -91,50 +90,34 @@ func initTeamSpeak(ctx context.Context) bool { Str("funcName", "initTeamSpeak"). Logger() - // 判断配置文件是否存在 - _, err := os.Stat(tsDataDir) + var handlerErr multe.MultiError + + err := yaml.LoadYAML(tsDataPath, &tsData) if err != nil { if os.IsNotExist(err) { - // 不存在,创建一份空文件 logger.Warn(). Err(err). Str("path", tsDataPath). - Msg("Not found config file. Created new one") + Msg("Not found teamspeak config file. Created new one") err = yaml.SaveYAML(tsDataPath, &TSServerQuery{}) if err != nil { logger.Error(). Err(err). Str("path", tsDataPath). Msg("Failed to create empty config") - tsErr = fmt.Errorf("failed to create empty config: %w", err) + handlerErr.Addf("failed to create empty config: %w", err) } - logger.Warn(). - Str("path", tsDataPath). - Msg("Empty config file created, please fill in the config") } else { - // 文件存在,但是遇到了其他错误 logger.Error(). Err(err). Str("path", tsDataPath). Msg("Failed to read config file") - tsErr = fmt.Errorf("failed to read config file: %w", err) + + // 读取配置文件内容失败也不允许重新启动 + tsErr = handlerErr.Addf("failed to read config file: %w", err).Flat() + isCanReInit = false + return false } - - // 无法获取到服务器地址和账号,无法初始化并设定不可重新启动 - isCanReInit = false - return false - } - - err = yaml.LoadYAML(tsDataPath, &tsData) - if err != nil { - logger.Error(). - Err(err). - Str("path", tsDataPath). - Msg("Failed to read config file") - // 读取配置文件内容失败也不允许重新启动 - tsErr = fmt.Errorf("failed to read config file: %w", err) - isCanReInit = false - return false } // 如果服务器地址为空不允许重新启动 @@ -142,18 +125,18 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Str("path", tsDataPath). Msg("No URL in config") - tsErr = fmt.Errorf("no URL in config") + tsErr = handlerErr.Addf("no URL in config").Flat() isCanReInit = false return false } else { if tsClient != nil { tsClient.Close() } - tsClient, tsErr = ts3.NewClient(tsData.URL) - if tsErr != nil { + tsClient, err = ts3.NewClient(tsData.URL) + if err != nil { logger.Error(). - Err(tsErr). + Err(err). Str("path", tsDataPath). Msg("Failed to connect to server") - tsErr = fmt.Errorf("failed to connnect to server: %w", tsErr) + tsErr = handlerErr.Addf("failed to connnect to server: %w", err).Flat() return false } } @@ -163,7 +146,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Str("path", tsDataPath). Msg("No Name/Password in config") - tsErr = fmt.Errorf("no Name/Password in config") + tsErr = handlerErr.Addf("no Name/Password in config").Flat() isCanReInit = false return false } else { @@ -173,7 +156,7 @@ func initTeamSpeak(ctx context.Context) bool { Err(err). Str("path", tsDataPath). Msg("Failed to login to server") - tsErr = fmt.Errorf("failed to login to server: %w", err) + tsErr = handlerErr.Addf("failed to login to server: %w", err).Flat() isLoginFailed = true return false } else { @@ -186,7 +169,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Str("path", tsDataPath). Msg("No GroupID in config") - tsErr = fmt.Errorf("no GroupID in config") + tsErr = handlerErr.Addf("no GroupID in config").Flat() isCanReInit = false return false } @@ -198,7 +181,7 @@ func initTeamSpeak(ctx context.Context) bool { Err(err). Str("path", tsDataPath). Msg("Failed to get server version") - tsErr = fmt.Errorf("failed to get server version: %w", err) + tsErr = handlerErr.Addf("failed to get server version: %w", err).Flat() return false } else { logger.Info(). @@ -214,7 +197,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Err(err). Msg("Failed to switch server") - tsErr = fmt.Errorf("failed to switch server: %w", err) + tsErr = handlerErr.Addf("failed to switch server: %w", err).Flat() return false } @@ -224,6 +207,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Err(err). Msg("Failed to get bot info") + tsErr = handlerErr.Addf("failed to get bot info: %w", err).Flat() } else if m != nil && m.ClientName != botNickName { // 当 bot 自己的 nickname 不等于配置文件中的 nickname 时,才进行修改 err = tsClient.SetNick(botNickName) @@ -231,7 +215,7 @@ func initTeamSpeak(ctx context.Context) bool { logger.Error(). Err(err). Msg("Failed to set bot nickname") - tsErr = fmt.Errorf("failed to set nickname: %w", err) + tsErr = handlerErr.Addf("failed to set nickname: %w", err).Flat() } } @@ -268,6 +252,8 @@ func showStatus(opts *handler_structs.SubHandlerParams) error { Str("funcName", "showStatus"). Logger() + var handlerErr multe.MultiError + var pendingMessage string // 如果首次初始化没成功,没有添加根据群组 ID 来触发的 handler,用户发送 /ts3 后可以通过这个来自动获取 opts 并启动监听 @@ -291,6 +277,7 @@ func showStatus(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Msg("Failed to get online client") + handlerErr.Addf("failed to get online client: %w", err) pendingMessage = fmt.Sprintf("连接到 teamspeak 服务器发生错误:\n
%s", err) } else { pendingMessage += fmt.Sprintln("在线客户端:") @@ -323,10 +310,13 @@ func showStatus(opts *handler_structs.SubHandlerParams) error { } resetListenTicker <- true pendingMessage = "尝试重新初始化成功,现可正常运行" - } else if isListening { - pendingMessage += "尝试重新初始化失败,您可以使用 /ts3 命令来尝试手动初始化,或等待自动重连" } else { - pendingMessage += "尝试重新初始化失败,您需要在服务器在线时手动使用 /ts3 命令来尝试初始化" + handlerErr.Addf("failed to reinit teamspeak plugin: %w", tsErr) + if isListening { + pendingMessage += "尝试重新初始化失败,您可以使用 /ts3 命令来尝试手动初始化,或等待自动重连" + } else { + pendingMessage += "尝试重新初始化失败,您需要在服务器在线时手动使用 /ts3 命令来尝试初始化" + } } } else { pendingMessage += "这是一个无法恢复的错误,您可能需要联系机器人管理员" @@ -343,10 +333,12 @@ func showStatus(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Int64("chatID", opts.Update.Message.Chat.ID). - Str("content", "teamspeak status"). - Msg(logt.SendMessage) + Str("content", "teamspeak online client status"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `teamspeak online client status: %w`", err) } - return nil + + return handlerErr.Flat() } func listenUserStatus(ctx context.Context) { @@ -367,6 +359,7 @@ func listenUserStatus(ctx context.Context) { } var retryCount int = 1 + var checkFailedCount int = 0 var beforeOnlineClient []string for { @@ -377,7 +370,7 @@ func listenUserStatus(ctx context.Context) { retryCount = 1 case <-listenTicker.C: if isSuccessInit && isCanListening { - beforeOnlineClient = checkOnlineClientChange(ctx, beforeOnlineClient) + beforeOnlineClient = checkOnlineClientChange(ctx, &checkFailedCount, beforeOnlineClient) } else { logger.Info(). Msg("try reconnect...") @@ -402,7 +395,7 @@ func listenUserStatus(ctx context.Context) { Err(err). Int64("chatID", tsData.GroupID). Str("content", "success reconnect to server"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) } } else { // 无法成功则等待下一个周期继续尝试 @@ -417,7 +410,7 @@ func listenUserStatus(ctx context.Context) { } } -func checkOnlineClientChange(ctx context.Context, before []string) []string { +func checkOnlineClientChange(ctx context.Context, count *int, before []string) []string { var nowOnlineClient []string logger := zerolog.Ctx(ctx). With(). @@ -427,21 +420,26 @@ func checkOnlineClientChange(ctx context.Context, before []string) []string { olClient, err := tsClient.Server.ClientList() if err != nil { + *count++ logger.Error(). Err(err). + Int("failedCount", *count). Msg("Failed to get online client") - isCanListening = false - _, err := privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ - ChatID: tsData.GroupID, - Text: "已断开与服务器的连接,开始尝试自动重连", - ParseMode: models.ParseModeHTML, - }) - if err != nil { - logger.Error(). - Err(err). - Int64("chatID", tsData.GroupID). - Str("content", "disconnect to server"). - Msg(logt.SendMessage) + if *count == 5 { + *count = 0 + isCanListening = false + _, err := privateOpts.Thebot.SendMessage(privateOpts.Ctx, &bot.SendMessageParams{ + ChatID: tsData.GroupID, + Text: "已连续五次检查在线客户端失败,开始尝试自动重连", + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("chatID", tsData.GroupID). + Str("content", "failed to check online client 5 times, start auto reconnect"). + Msg(errt.SendMessage) + } } } else { for _, n := range olClient { @@ -512,6 +510,6 @@ func notifyClientChange(opts *handler_structs.SubHandlerParams, add, remove []st Err(err). Int64("chatID", tsData.GroupID). Str("content", "teamspeak user change notify"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) } } diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index db5dcfe..cc89b2a 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -3,7 +3,6 @@ package plugins import ( "context" "fmt" - "io" "os" "path/filepath" "strconv" @@ -13,16 +12,16 @@ import ( "trbot/utils" "trbot/utils/configs" "trbot/utils/consts" + "trbot/utils/errt" "trbot/utils/handler_structs" - "trbot/utils/logt" - "trbot/utils/mterr" + "trbot/utils/multe" "trbot/utils/plugin_utils" "trbot/utils/type/message_utils" + "trbot/utils/yaml" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" - "gopkg.in/yaml.v3" ) var UdoneseData Udonese @@ -106,6 +105,7 @@ func (list UdoneseWord) OutputMeanings() string { return pendingMessage } +// 管理一个词和其所有意思的键盘 func (list UdoneseWord) buildUdoneseWordKeyboard() models.ReplyMarkup { var buttons [][]models.InlineKeyboardButton for index, singleMeaning := range list.MeaningList { @@ -149,64 +149,35 @@ func ReadUdonese(ctx context.Context) error { Str("pluginName", "Udonese"). Str("funcName", "ReadUdonese"). Logger() - var udonese Udonese - file, err := os.Open(UdonesePath) + err := yaml.LoadYAML(UdonesePath, &UdoneseData) if err != nil { if os.IsNotExist(err) { - // 如果是找不到目录,新建一个 logger.Warn(). Err(err). Str("path", UdonesePath). Msg("Not found udonese list file. Created new one") - err = SaveUdonese(ctx) + // 如果是找不到文件,新建一个 + err = yaml.SaveYAML(UdonesePath, &UdoneseData) if err != nil { logger.Error(). Err(err). Str("path", UdonesePath). Msg("Failed to create empty udonese list file") UdoneseErr = fmt.Errorf("failed to create empty udonese list file: %w", err) - return UdoneseErr - } - } else { - // 其他错误 - logger.Error(). - Err(err). - Str("path", UdonesePath). - Msg("Failed to open udonese list file") - UdoneseErr = fmt.Errorf("failed to open udonese list file: %w", err) - return UdoneseErr - } - } - defer file.Close() - - err = yaml.NewDecoder(file).Decode(&udonese) - if err != nil { - if err == io.EOF { - logger.Warn(). - Str("path", UdonesePath). - Msg("udonese list file looks empty. now format it") - err = SaveUdonese(ctx) - if err != nil { - // 保存空的数据库失败 - logger.Error(). - Err(err). - Str("path", UdonesePath). - Msg("Failed to create empty udonese list file") - UdoneseErr = fmt.Errorf("failed to create empty udonese list file: %w", err) - return UdoneseErr } } else { logger.Error(). Err(err). - Msg("Failed to decode udonese list") - UdoneseErr = fmt.Errorf("failed to decode udonese list: %w", err) - return UdoneseErr + Str("path", UdonesePath). + Msg("Failed to load udonese list file") + UdoneseErr = fmt.Errorf("failed to load udonese list file: %w", err) } + } else { + UdoneseErr = nil } - UdoneseData = udonese - return nil + return UdoneseErr } func SaveUdonese(ctx context.Context) error { @@ -216,83 +187,17 @@ func SaveUdonese(ctx context.Context) error { Str("funcName", "SaveUdonese"). Logger() - data, err := yaml.Marshal(UdoneseData) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to marshal udonese list") - UdoneseErr = fmt.Errorf("failed to marshal udonese list: %w", err) - return UdoneseErr - } - - _, err = os.Stat(UdoneseDir) - if err != nil { - if os.IsNotExist(err) { - logger.Warn(). - Str("directory", UdoneseDir). - Msg("Not found udonese list directory, now create it") - err = os.MkdirAll(UdoneseDir, 0755) - if err != nil { - logger.Error(). - Err(err). - Str("directory", UdoneseDir). - Msg("Failed to create udonese list directory") - UdoneseErr = fmt.Errorf("failed to create udonese list directory: %s", err) - return UdoneseErr - } - logger.Trace(). - Str("directory", UdoneseDir). - Msg("Create udonese list directory success") - } else { - logger.Error(). - Err(err). - Str("directory", UdoneseDir). - Msg("Failed to open udonese list directory") - UdoneseErr = fmt.Errorf("failed to open udonese list directory: %s", err) - return UdoneseErr - } - } - - _, err = os.Stat(UdonesePath) - if err != nil { - if os.IsNotExist(err) { - logger.Warn(). - Str("path", UdonesePath). - Msg("Not found udonese list file. Create a new one") - _, err := os.Create(UdonesePath) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to create udonese list file") - UdoneseErr = fmt.Errorf("failed to create udonese list file: %w", err) - return UdoneseErr - } - logger.Trace(). - Str("path", UdonesePath). - Msg("Created udonese list file success") - } else { - logger.Error(). - Err(err). - Str("path", UdonesePath). - Msg("Failed to open udonese list file") - UdoneseErr = fmt.Errorf("failed to open udonese list file: %w", err) - return UdoneseErr - } - } - - err = os.WriteFile(UdonesePath, data, 0644) + err := yaml.SaveYAML(UdonesePath, &UdoneseData) if err != nil { logger.Error(). Err(err). Str("path", UdonesePath). - Msg("Failed to write udonese list into file") - UdoneseErr = fmt.Errorf("failed to write udonese list into file: %w", err) - return UdoneseErr + Msg("Failed to save udonese list") + UdoneseErr = fmt.Errorf("failed to save udonese list: %w", err) + } else { + UdoneseErr = nil } - logger.Trace(). - Str("path", UdonesePath). - Msg("Save udonese list success") - return nil + return UdoneseErr } // 如果要添加的意思重复,返回对应意思的单个词结构体指针,否则返回空指针 @@ -346,13 +251,14 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { // 不响应来自转发的命令 if opts.Update.Message.ForwardOrigin != nil { return nil } - var handlerErr mterr.MultiError logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "Udonese"). Str("funcName", "addUdoneseHandler"). Logger() + var handlerErr multe.MultiError + isManager := utils.AnyContains(opts.Update.Message.From.ID, UdoneseManagerIDs) if opts.Update.Message.Chat.ID != UdonGroupID { @@ -368,7 +274,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese not allowed group"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese not allowed group` message: %w", err) } } else { @@ -388,7 +294,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese admin command help"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese admin command help` message: %w", err) } } else /* 词信息 */ { @@ -414,7 +320,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese admin command no this word"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese admin command no this word` message: %w", err) } } else { @@ -431,7 +337,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese manage keyboard"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese manage keyboard` message: %w", err) } } @@ -449,7 +355,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese command help"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese command help` message: %w", err) } } @@ -608,7 +514,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "/udonese keyword added"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese keyword added` message: %w", err) } else { time.Sleep(time.Second * 10) @@ -625,7 +531,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.Message.Chat.ID). Ints("messageIDs", []int{ opts.Update.Message.ID, botMessage.ID }). Str("content", "/udonese keyword added"). - Msg(logt.DeleteMessages) + Msg(errt.DeleteMessages) handlerErr.Addf("failed to delete `/udonese keyword added` messages: %w", err) } } @@ -712,6 +618,17 @@ func udoneseInlineHandler(opts *handler_structs.SubHandlerParams) []models.Inlin }) } } + if len(udoneseResultList) == 0 { + udoneseResultList = append(udoneseResultList, &models.InlineQueryResultArticle{ + ID: "none", + Title: "没有记录任何内容", + Description: "什么都没有,使用 `/udonese <词> <意思>` 来添加吧", + InputMessageContent: models.InputTextMessageContent{ + MessageText: "使用 `/udonese <词> <单个意思>` 来添加记录", + ParseMode: models.ParseModeMarkdownV1, + }, + }) + } return udoneseResultList } @@ -719,23 +636,25 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { // 不响应来自转发的命令和空文本 if opts.Update.Message.ForwardOrigin != nil || len(opts.Fields) < 1 { return nil } - var handlerErr mterr.MultiError logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "Udonese"). Str("funcName", "udoneseGroupHandler"). Logger() + var handlerErr multe.MultiError + if UdoneseErr != nil { logger.Warn(). Err(UdoneseErr). Msg("Some error in while read udonese list, try to read again") + err := ReadUdonese(opts.Ctx) if err != nil { logger.Error(). Err(err). Msg("Failed to read udonese list") - handlerErr.Addf("failed to read udonese list: %w", err) + return handlerErr.Addf("failed to read udonese list: %w", err).Flat() } } @@ -774,7 +693,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "sms command usage"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `sms command usage` message: %w", err) } } else { @@ -793,7 +712,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "sms keyword meaning"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `sms keyword meaning` message: %w", err) } } @@ -816,7 +735,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Int64("chatID", opts.Update.Message.Chat.ID). Str("content", "sms keyword meaning"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `sms keyword meaning` message: %w", err) } } @@ -839,7 +758,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.Message.Chat.ID). Int("messageID", botMessage.ID). Str("content", "sms keyword no meaning"). - Msg(logt.SendMessage) + Msg(errt.SendMessage) handlerErr.Addf("failed to send `sms keyword no meaning` message: %w", err) } else { time.Sleep(time.Second * 10) @@ -853,7 +772,7 @@ func udoneseGroupHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.Message.Chat.ID). Int("messageID", botMessage.ID). Str("content", "sms keyword no meaning"). - Msg(logt.DeleteMessage) + Msg(errt.DeleteMessage) handlerErr.Addf("failed to delete `sms keyword no meaning` message: %w", err) } } @@ -893,13 +812,13 @@ func init() { } func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { - var handlerErr mterr.MultiError logger := zerolog.Ctx(opts.Ctx). With(). Str("pluginName", "Udonese"). Str("funcName", "udoneseCallbackHandler"). Logger() + var handlerErr multe.MultiError if !utils.AnyContains(opts.Update.CallbackQuery.From.ID, UdoneseManagerIDs) { _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ @@ -912,7 +831,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Err(err). Str("callbackQueryID", opts.Update.CallbackQuery.ID). Str("content", "udonese no edit permissions"). - Msg(logt.AnswerCallback) + Msg(errt.AnswerCallbackQuery) handlerErr.Addf("failed to send `udonese no edit permissions` inline result: %w", err) } } else { @@ -927,7 +846,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). Str("content", "udonese keyword manage keyboard"). - Msg(logt.DeleteMessage) + Msg(errt.DeleteMessage) handlerErr.Addf("failed to delete `udonese keyword manage keyboard` inline result: %w", err) } } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_word_") { @@ -952,7 +871,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). Str("content", "udonese word meaning list"). - Msg(logt.EditMessage) + Msg(errt.EditMessageText) handlerErr.Addf("failed to edit message to `udonese keyword manage keyboard`: %w", err) } } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "udonese_meaning_") { @@ -1024,7 +943,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). Str("content", "udonese meaning manage keyboard"). - Msg(logt.EditMessage) + Msg(errt.EditMessageText) handlerErr.Addf("failed to edit message to `udonese meaning manage keyboard`: %w", err) } } @@ -1073,7 +992,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { logger.Error(). Err(err). Str("content", "failed to save udonese data after delete meaning"). - Msg(logt.AnswerCallback) + Msg(errt.AnswerCallbackQuery) handlerErr.Addf("failed to send `failed to save udonese data after delete meaning` callback answer: %w", err) } } else { @@ -1090,7 +1009,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). Str("content", "udonese meaning manage keyboard after delete meaning"). - Msg(logt.EditMessage) + Msg(errt.EditMessageText) handlerErr.Addf("failed to edit message to `udonese meaning manage keyboard after delete meaning`: %w", err) } } @@ -1120,7 +1039,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { if err != nil { logger.Error(). Err(err). - Msg(logt.AnswerCallback) + Msg(errt.AnswerCallbackQuery) handlerErr.Addf("failed to send `failed to save udonese data after delete word` callback answer: %w", err) } } else { @@ -1140,7 +1059,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { Int64("chatID", opts.Update.CallbackQuery.Message.Message.Chat.ID). Int("messageID", opts.Update.CallbackQuery.Message.Message.ID). Str("content", "udonese word deleted notice"). - Msg(logt.EditMessage) + Msg(errt.EditMessageText) handlerErr.Addf("failed to edit message to `udonese word deleted notice`: %w", err) } } diff --git a/utils/errt/log_template.go b/utils/errt/log_template.go new file mode 100644 index 0000000..654a782 --- /dev/null +++ b/utils/errt/log_template.go @@ -0,0 +1,13 @@ +package errt + +const ( + // LogTemplate is the template for log messages. + SendMessage string = "Failed to send message" + SendDocument string = "Failed to send document" + EditMessageText string = "Failed to edit message text to" + EditMessageCaption string = "Failed to edit message caption to" + DeleteMessage string = "Failed to delete message" + DeleteMessages string = "Failed to delete messages" + AnswerCallbackQuery string = "Failed to answer callback query" + AnswerInlineQuery string = "Failed to answer inline query" +) diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go index 048623a..e5a6b45 100644 --- a/utils/internal_plugin/register.go +++ b/utils/internal_plugin/register.go @@ -286,7 +286,7 @@ func Register(ctx context.Context) { } } } - + signals.SIGNALS.Database_save <- true return nil }, @@ -455,7 +455,7 @@ func Register(ctx context.Context) { Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("command", "log"). Msg("Failed to send `log info` inline result") - + return err } } diff --git a/utils/logt/log_template.go b/utils/logt/log_template.go deleted file mode 100644 index 6a6ad72..0000000 --- a/utils/logt/log_template.go +++ /dev/null @@ -1,11 +0,0 @@ -package logt - -const ( - // LogTemplate is the template for log messages. - SendMessage string = "Failed to send message" - SendDocument string = "Failed to send document" - EditMessage string = "Failed to edit message" - DeleteMessage string = "Failed to delete message" - DeleteMessages string = "Failed to delete messages" - AnswerCallback string = "Failed to answer callback query" -) diff --git a/utils/mterr/mult_errors.go b/utils/multe/mult_errors.go similarity index 97% rename from utils/mterr/mult_errors.go rename to utils/multe/mult_errors.go index 34f9c28..4c05fdb 100644 --- a/utils/mterr/mult_errors.go +++ b/utils/multe/mult_errors.go @@ -1,4 +1,4 @@ -package mterr +package multe import ( "errors" diff --git a/utils/utils.go b/utils/utils.go index 4273945..e56181f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -352,6 +352,7 @@ func InlineQueryMatchMultKeyword(fields []string, keywords []string) bool { // 允许响应带有机器人用户名后缀的命令,例如 /help@examplebot func CommandMaybeWithSuffixUsername(commandFields []string, command string) bool { + if len(commandFields) == 0 { return false } atBotUsername := "@" + consts.BotMe.Username if commandFields[0] == command || commandFields[0] == command + atBotUsername { return true diff --git a/utils/yaml/yaml.go b/utils/yaml/yaml.go index 2af998d..f900e8c 100644 --- a/utils/yaml/yaml.go +++ b/utils/yaml/yaml.go @@ -1,7 +1,6 @@ package yaml import ( - "fmt" "os" "path/filepath" @@ -11,32 +10,22 @@ import ( // 一个通用的 yaml 结构体读取函数 func LoadYAML(pathToFile string, out interface{}) error { file, err := os.ReadFile(pathToFile) - if err != nil { - return fmt.Errorf("read file failed: %w", err) + if err == nil { + err = yaml.Unmarshal(file, out) } - if err := yaml.Unmarshal(file, out); err != nil { - return fmt.Errorf("decode yaml failed: %w", err) - } - - return nil + return err } // 一个通用的 yaml 结构体保存函数,目录和文件不存在则创建,并以结构体类型保存 func SaveYAML(pathToFile string, data interface{}) error { out, err := yaml.Marshal(data) - if err != nil { - return fmt.Errorf("failed to marshal YAML: %w", err) + if err == nil { + err = os.MkdirAll(filepath.Dir(pathToFile), 0755) + if err == nil { + err = os.WriteFile(pathToFile, out, 0644) + } } - dir := filepath.Dir(pathToFile) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("create directory failed: %w", err) - } - - if err := os.WriteFile(pathToFile, out, 0644); err != nil { - return fmt.Errorf("write data failed: %w", err) - } - - return nil + return err } -- 2.49.1 From 750360733c4b6721f05a90d4c317703c2518393a Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Fri, 27 Jun 2025 04:43:23 +0800 Subject: [PATCH 22/27] refactor limit message show message delete notice on test mode (whatever delete or not) add some icon in main menu display 3 message type button in one line --- bad_plugins/plugin_limit_message.go | 644 --------------------- logstruct.txt | 11 +- plugins/plugin_limit_message.go | 833 ++++++++++++++++++++++++++++ plugins/plugin_udonese.go | 8 +- utils/errt/log_template.go | 17 +- 5 files changed, 852 insertions(+), 661 deletions(-) delete mode 100644 bad_plugins/plugin_limit_message.go create mode 100644 plugins/plugin_limit_message.go diff --git a/bad_plugins/plugin_limit_message.go b/bad_plugins/plugin_limit_message.go deleted file mode 100644 index 9f7f6f0..0000000 --- a/bad_plugins/plugin_limit_message.go +++ /dev/null @@ -1,644 +0,0 @@ -package plugins - -import ( - "fmt" - "io" - "log" - "os" - "path/filepath" - "reflect" - "strings" - "time" - - "trbot/utils" - "trbot/utils/consts" - "trbot/utils/handler_structs" - "trbot/utils/plugin_utils" - "trbot/utils/type_utils" - - "github.com/go-telegram/bot" - "github.com/go-telegram/bot/models" - "gopkg.in/yaml.v3" -) - -var LimitMessageList map[int64]AllowMessages -var LimitMessageErr error - -var LimitMessage_path string = filepath.Join(consts.YAMLDataBasePath, "limitmessage/") - -type AllowMessages struct { - IsEnable bool `yaml:"IsEnable"` - IsUnderTest bool `yaml:"IsUnderTest"` - AddTime string `yaml:"AddTime"` - IsLogicAnd bool `yaml:"IsLogicAnd"` // true: `&&``, false: `||` - MessageType type_utils.MessageType `yaml:"MessageType"` - IsWhiteForType bool `yaml:"IsWhiteForType"` - MessageAttribute type_utils.MessageAttribute `yaml:"MessageAttribute"` - IsWhiteForAttribute bool `yaml:"IsWhiteForAttribute"` -} - -func init() { - ReadLimitMessageList() - plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ - Name: "Limit Message", - Saver: SaveLimitMessageList, - Loader: ReadLimitMessageList, - }) - plugin_utils.AddSlashSymbolCommandPlugins(plugin_utils.SlashSymbolCommand{ - SlashCommand: "limitmessage", - Handler: SomeMessageOnlyHandler, - }) - plugin_utils.AddCallbackQueryCommandPlugins(plugin_utils.CallbackQuery{ - CommandChar: "limitmsg_", - Handler: LimitMessageCallback, - }) - - plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ - Name: "限制群组消息", - Description: "此功能需要 bot 为群组管理员并拥有删除消息的权限\n可以按照消息类型和消息属性来自动删除不允许的消息,支持自定逻辑和黑白名单,作为管理员在群组中使用 /limitmessage 命令来查看菜单", - ParseMode: models.ParseModeHTML, - }) - buildLimitGroupList() -} - -func SaveLimitMessageList() error { - data, err := yaml.Marshal(LimitMessageList) - if err != nil { return err } - - if _, err := os.Stat(LimitMessage_path); os.IsNotExist(err) { - if err := os.MkdirAll(LimitMessage_path, 0755); err != nil { - return err - } - } - - if _, err := os.Stat(filepath.Join(LimitMessage_path, consts.YAMLFileName)); os.IsNotExist(err) { - _, err := os.Create(filepath.Join(LimitMessage_path, consts.YAMLFileName)) - if err != nil { - return err - } - } - - return os.WriteFile(filepath.Join(LimitMessage_path, consts.YAMLFileName), data, 0644) -} - -func ReadLimitMessageList() { - var limitMessageList map[int64]AllowMessages - - file, err := os.Open(filepath.Join(LimitMessage_path, consts.YAMLFileName)) - if err != nil { - // 如果是找不到目录,新建一个 - log.Println("[LimitMessage]: Not found database file. Created new one") - SaveLimitMessageList() - LimitMessageList, LimitMessageErr = map[int64]AllowMessages{}, err - return - } - defer file.Close() - - decoder := yaml.NewDecoder(file) - err = decoder.Decode(&limitMessageList) - if err != nil { - if err == io.EOF { - log.Println("[LimitMessage]: database looks empty. now format it") - SaveLimitMessageList() - LimitMessageList, LimitMessageErr = map[int64]AllowMessages{}, nil - return - } - log.Println("(func)ReadLimitMessageList:", err) - LimitMessageList, LimitMessageErr = map[int64]AllowMessages{}, err - return - } - LimitMessageList, LimitMessageErr = limitMessageList, nil -} - -func SomeMessageOnlyHandler(opts *handler_structs.SubHandlerParams) { - if opts.Update.Message.Chat.Type == "private" { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "此功能被设计为仅在群组中可用", - ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, - }) - } else if utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.Message.Chat.ID, opts.Update.Message.From.ID) { - thisChat := LimitMessageList[opts.Update.Message.Chat.ID] - - if thisChat.AddTime == "" { - thisChat.AddTime = time.Now().Format(time.RFC3339) - } - - if utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.Message.Chat.ID, consts.BotMe.ID) && utils.UserHavePermissionDeleteMessage(opts.Ctx, opts.Thebot, opts.Update.Message.Chat.ID, consts.BotMe.ID) { - - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "Limit Message 菜单", - ReplyMarkup: buildMessageAllKB(thisChat), - }) - opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageID: opts.Update.Message.ID, - }) - LimitMessageList[opts.Update.Message.Chat.ID] = thisChat - SaveLimitMessageList() - } else { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "启用此功能前,请先将机器人设为管理员\n如果还是提示本消息,请检查机器人是否有删除消息的权限", - }) - } - } else { - botMessage, _ := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "抱歉,您不是群组的管理员,无法为群组更改此功能", - }) - time.Sleep(time.Second * 5) - opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageIDs: []int{ - opts.Update.Message.ID, - botMessage.ID, - }, - }) - } -} - -func DeleteNotAllowMessage(opts *handler_structs.SubHandlerParams) { - - var deleteAction bool - if utils.AnyContains(opts.Update.Message.Chat.Type, models.ChatTypeGroup, models.ChatTypeSupergroup) { - // 处理消息删除逻辑,只有当群组启用该功能时才处理 - thisChat := LimitMessageList[opts.Update.Message.Chat.ID] - if thisChat.IsEnable { - this := type_utils.GetMessageType(opts.Update.Message) - thisattribute := type_utils.GetMessageAttribute(opts.Update.Message) - - // 根据规则的黑白名单选择判断逻辑 - if thisChat.IsLogicAnd { - deleteAction = CheckMessageType(this, thisChat.MessageType, thisChat.IsWhiteForType) && CheckMessageAttribute(thisattribute, thisChat.MessageAttribute, thisChat.IsWhiteForAttribute) - } else { - deleteAction = CheckMessageType(this, thisChat.MessageType, thisChat.IsWhiteForType) || CheckMessageAttribute(thisattribute, thisChat.MessageAttribute, thisChat.IsWhiteForAttribute) - } - - if deleteAction { - if thisChat.IsUnderTest { - opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "测试模式:此消息将被设定的规则删除", - DisableNotification: true, - ReplyParameters: &models.ReplyParameters{ - MessageID: opts.Update.Message.ID, - }, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ - { - Text: "删除此提醒", - CallbackData: "limitmsg_done", - }, - { - Text: "关闭测试模式", - CallbackData: "limitmsg_offtest", - }, - }}}, - }) - } else { - _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - MessageID: opts.Update.Message.ID, - }) - if err != nil { - log.Printf("Failed to delete message: %v", err) - } else { - log.Printf("Deleted message from %d in %d: %s\n", opts.Update.Message.From.ID, opts.Update.Message.Chat.ID, opts.Update.Message.Text) - } - } - } - } - } -} - -func CheckMessageType(this, target type_utils.MessageType, IsWhiteList bool) bool { - var delete bool = IsWhiteList - - v1 := reflect.ValueOf(this) - v2 := reflect.ValueOf(target) - t := reflect.TypeOf(this) - - for i := 0; i < v1.NumField(); i++ { - field := t.Field(i) - val1 := v1.Field(i).Interface() - val2 := v2.Field(i).Interface() - - if val1 == true && val1 == val2 { - if IsWhiteList { - fmt.Printf("白名单 消息类型 %s 不删除\n", field.Name) - delete = false - } else { - fmt.Printf("黑名单 消息类型 %s 删除\n", field.Name) - delete = true - } - } else if val1 == true && val1 != val2 { - if IsWhiteList { - fmt.Printf("白名单 ") - } else { - fmt.Printf("黑名单 ") - } - fmt.Printf("未命中 消息类型 %s 遵循默认规则 ", field.Name) - if delete { - fmt.Println("删除") - } else { - fmt.Println("不删除") - } - } - } - return delete -} - -func CheckMessageAttribute(this, target type_utils.MessageAttribute, IsWhiteList bool) bool { - var delete bool = IsWhiteList - var noAttribute bool = true // 如果没有命中任何消息属性,提示内容,根据黑白名单判断是否删除 - - v1 := reflect.ValueOf(this) - v2 := reflect.ValueOf(target) - t := reflect.TypeOf(this) - - for i := 0; i < v1.NumField(); i++ { - field := t.Field(i) - val1 := v1.Field(i).Interface() - val2 := v2.Field(i).Interface() - - - if val1 == true && val1 == val2 { - noAttribute = false - if IsWhiteList { - fmt.Printf("白名单 消息属性 %s 不删除\n", field.Name) - delete = false - } else { - fmt.Printf("黑名单 消息属性 %s 删除\n", field.Name) - delete = true - } - } else if val1 == true && val1 != val2 { - noAttribute = false - if IsWhiteList { - fmt.Printf("白名单 ") - } else { - fmt.Printf("黑名单 ") - } - fmt.Printf("未命中 消息属性 %s 遵循默认规则 ", field.Name) - if delete { - fmt.Println("删除") - } else { - fmt.Println("不删除") - } - } - } - if noAttribute { - if IsWhiteList { - fmt.Printf("白名单 ") - } else { - fmt.Printf("黑名单 ") - } - fmt.Printf("未命中 消息属性 无 遵循默认规则 ") - if delete { - fmt.Println("删除") - } else { - fmt.Println("不删除") - } - } - return delete -} - -func buttonText(text string, opt, IsWhiteList bool) string { - if opt { - if IsWhiteList { - return "✅ " + text - } else { - return "❌ " + text - } - } - - return text -} - -func buttonWhiteBlackRule(opt bool) string { - if opt { - return "白名单模式" - } - - return "黑名单模式" -} - -func buttonWhiteBlackDescription(opt bool) string { - if opt { - return "仅允许发送选中的项目,其他消息将被删除" - } - - return "将删除选中的项目" -} - -func buttonIsEnable(opt bool) string { - if opt { - return "当前已启用" - } - - return "当前已关闭" -} - -func buttonIsLogicAnd(opt bool) string { - if opt { - return "满足上方所有条件才删除消息" - } - - return "满足其中一个条件就删除消息" -} - -func buttonIsUnderTest(opt bool) string { - if opt { - return "点击关闭测试模式" - } - - return "点此开启测试模式" -} - -func buildMessageTypeKB(chat AllowMessages) models.ReplyMarkup { - - var msgTypeItems [][]models.InlineKeyboardButton - var msgTypeItemsTemp []models.InlineKeyboardButton - - v := reflect.ValueOf(chat.MessageType) // 解除指针获取值 - t := reflect.TypeOf(chat.MessageType) - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - value := v.Field(i) - if i % 2 == 0 && i != 0 { - msgTypeItems = append(msgTypeItems, msgTypeItemsTemp) - msgTypeItemsTemp = []models.InlineKeyboardButton{} - } - msgTypeItemsTemp = append(msgTypeItemsTemp, models.InlineKeyboardButton{ - Text: buttonText(field.Name, value.Bool(), chat.IsWhiteForType), - CallbackData: "limitmsg_type_" + field.Name, - }) - } - if len(msgTypeItemsTemp) != 0 { - msgTypeItems = append(msgTypeItems, msgTypeItemsTemp) - } - - - msgTypeItems = append(msgTypeItems, []models.InlineKeyboardButton{{ - Text: "返回上一级", - CallbackData: "limitmsg_back", - }}) - - kb := &models.InlineKeyboardMarkup{ - InlineKeyboard: msgTypeItems, - } - - return kb -} - -func buildMessageAttributeKB(chat AllowMessages) models.ReplyMarkup { - - var msgAttributeItems [][]models.InlineKeyboardButton - var msgAttributeItemsTemp []models.InlineKeyboardButton - - v := reflect.ValueOf(chat.MessageAttribute) // 解除指针获取值 - t := reflect.TypeOf(chat.MessageAttribute) - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - value := v.Field(i) - if i % 2 == 0 && i != 0 { - msgAttributeItems = append(msgAttributeItems, msgAttributeItemsTemp) - msgAttributeItemsTemp = []models.InlineKeyboardButton{} - } - msgAttributeItemsTemp = append(msgAttributeItemsTemp, models.InlineKeyboardButton{ - Text: buttonText(field.Name, value.Bool(), chat.IsWhiteForAttribute), - CallbackData: "limitmsg_attr_" + field.Name, - }) - } - if len(msgAttributeItemsTemp) != 0 { - msgAttributeItems = append(msgAttributeItems, msgAttributeItemsTemp) - } - - - msgAttributeItems = append(msgAttributeItems, []models.InlineKeyboardButton{{ - Text: "返回上一级", - CallbackData: "limitmsg_back", - }}) - - kb := &models.InlineKeyboardMarkup{ - InlineKeyboard: msgAttributeItems, - } - - return kb -} - -func buildMessageAllKB(chat AllowMessages) models.ReplyMarkup { - var chatAllow [][]models.InlineKeyboardButton - - chatAllow = append(chatAllow, []models.InlineKeyboardButton{ - { - Text: "选择消息类型", - CallbackData: "limitmsg_typekb", - }, - { - Text: "<-- " + buttonWhiteBlackRule(chat.IsWhiteForType), - CallbackData: "limitmsg_typekb_switchrule", - }, - }) - - chatAllow = append(chatAllow, []models.InlineKeyboardButton{ - { - Text: "选择消息属性", - CallbackData: "limitmsg_attrkb", - }, - { - Text: "<-- " + buttonWhiteBlackRule(chat.IsWhiteForAttribute), - CallbackData: "limitmsg_attrkb_switchrule", - }, - }) - - chatAllow = append(chatAllow, []models.InlineKeyboardButton{ - { - Text: buttonIsLogicAnd(chat.IsLogicAnd), - CallbackData: "limitmsg_switchlogic", - }, - }) - - chatAllow = append(chatAllow, []models.InlineKeyboardButton{ - { - Text: buttonIsUnderTest(chat.IsUnderTest), - CallbackData: "limitmsg_switchtest", - }, - }) - - chatAllow = append(chatAllow, []models.InlineKeyboardButton{ - { - Text: "关闭菜单", - CallbackData: "limitmsg_done", - }, - { - Text: buttonIsEnable(chat.IsEnable), - CallbackData: "limitmsg_switchenable", - }, - }) - - kb := &models.InlineKeyboardMarkup{ - InlineKeyboard: chatAllow, - } - - return kb -} - -func LimitMessageCallback(opts *handler_structs.SubHandlerParams) { - if !utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.CallbackQuery.Message.Message.Chat.ID, opts.Update.CallbackQuery.From.ID) { - opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ - CallbackQueryID: opts.Update.CallbackQuery.ID, - Text: "您没有权限修改此配置", - ShowAlert: true, - }) - return - } - thisChat := LimitMessageList[opts.Update.CallbackQuery.Message.Message.Chat.ID] - - var needRebuildGroupList bool - - switch opts.Update.CallbackQuery.Data { - case "limitmsg_typekb": - // opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ - // CallbackQueryID: opts.Update.CallbackQuery.ID, - // Text: "已选择消息类型", - // }) - opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: buttonWhiteBlackRule(thisChat.IsWhiteForType) + ": " + buttonWhiteBlackDescription(thisChat.IsWhiteForType), - ReplyMarkup: buildMessageTypeKB(thisChat), - }) - case "limitmsg_typekb_switchrule": - thisChat.IsWhiteForType = !thisChat.IsWhiteForType - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageAllKB(thisChat), - }) - case "limitmsg_attrkb": - opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: buttonWhiteBlackRule(thisChat.IsWhiteForAttribute) + ": " + buttonWhiteBlackDescription(thisChat.IsWhiteForAttribute) + "\n有一些项目可能无法使用", - ReplyMarkup: buildMessageAttributeKB(thisChat), - }) - case "limitmsg_attrkb_switchrule": - thisChat.IsWhiteForAttribute = !thisChat.IsWhiteForAttribute - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageAllKB(thisChat), - }) - case "limitmsg_back": - opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - Text: "Limit Message 菜单", - ReplyMarkup: buildMessageAllKB(thisChat), - }) - case "limitmsg_done": - opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - }) - case "limitmsg_switchenable": - thisChat.IsEnable = !thisChat.IsEnable - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageAllKB(thisChat), - }) - needRebuildGroupList = true - case "limitmsg_switchlogic": - thisChat.IsLogicAnd = !thisChat.IsLogicAnd - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageAllKB(thisChat), - }) - case "limitmsg_switchtest": - thisChat.IsUnderTest = !thisChat.IsUnderTest - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageAllKB(thisChat), - }) - needRebuildGroupList = true - case "limitmsg_offtest": - thisChat.IsUnderTest = false - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "删除此提醒", - CallbackData: "limitmsg_done", - }}}}, - }) - needRebuildGroupList = true - default: - if strings.HasPrefix(opts.Update.CallbackQuery.Data, "limitmsg_type_") { - callbackField := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "limitmsg_type_") - - data := thisChat.MessageType - v := reflect.ValueOf(data) // 解除指针获取值 - t := reflect.TypeOf(data) - newStruct := reflect.New(v.Type()).Elem() - newStruct.Set(v) // 复制原始值 - for i := 0; i < newStruct.NumField(); i++ { - if t.Field(i).Name == callbackField { - newStruct.Field(i).SetBool(!newStruct.Field(i).Bool()) - } - } - thisChat.MessageType = newStruct.Interface().(type_utils.MessageType) - - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageTypeKB(thisChat), - }) - } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "limitmsg_attr_") { - callbackField := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "limitmsg_attr_") - data := thisChat.MessageAttribute - v := reflect.ValueOf(data) // 解除指针获取值 - t := reflect.TypeOf(data) - newStruct := reflect.New(v.Type()).Elem() - newStruct.Set(v) // 复制原始值 - for i := 0; i < newStruct.NumField(); i++ { - if t.Field(i).Name == callbackField { - newStruct.Field(i).SetBool(!newStruct.Field(i).Bool()) - } - } - - thisChat.MessageAttribute = newStruct.Interface().(type_utils.MessageAttribute) - - opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ - ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, - MessageID: opts.Update.CallbackQuery.Message.Message.ID, - ReplyMarkup: buildMessageAttributeKB(thisChat), - }) - } - } - - LimitMessageList[opts.Update.CallbackQuery.Message.Message.Chat.ID] = thisChat - if needRebuildGroupList { - buildLimitGroupList() - } - SaveLimitMessageList() -} - -func buildLimitGroupList() { - for id, n := range LimitMessageList { - if n.IsEnable || n.IsUnderTest { - plugin_utils.AddHandlerByChatIDPlugins(plugin_utils.HandlerByChatID{ - ChatID: id, - PluginName: "limit_message", - Handler: DeleteNotAllowMessage, - }) - } else { - plugin_utils.RemoveHandlerByChatIDPlugin(id, "limit_message") - } - } -} diff --git a/logstruct.txt b/logstruct.txt index 6f7c592..ca4d18d 100644 --- a/logstruct.txt +++ b/logstruct.txt @@ -1,5 +1,6 @@ -bot.SendMessage: Failed to send [%s] message -bot.EditMessage: Failed to edit message to [%s] -bot.DeleteMessages: Failed to delete [%s] message -bot.AnswerInlineQuery: Failed to send [%s] inline answer (sub handler can add a `Str("command", "log")` ) -bot.AnswerCallbackQuery: Failed to send [%s] callback answer +bot.SendMessage: Failed to send [%s] message +bot.EditMessage: Failed to edit message to [%s] +bot.EditMessageReplyMarkup: Failed to edit message reply markup to [%s] +bot.DeleteMessages: Failed to delete [%s] message +bot.AnswerInlineQuery: Failed to send [%s] inline answer (sub handler can add a `Str("command", "log")` ) +bot.AnswerCallbackQuery: Failed to send [%s] callback answer diff --git a/plugins/plugin_limit_message.go b/plugins/plugin_limit_message.go new file mode 100644 index 0000000..442ca55 --- /dev/null +++ b/plugins/plugin_limit_message.go @@ -0,0 +1,833 @@ +package plugins + +import ( + "context" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "time" + + "trbot/utils" + "trbot/utils/consts" + "trbot/utils/errt" + "trbot/utils/handler_structs" + "trbot/utils/multe" + "trbot/utils/plugin_utils" + "trbot/utils/type/message_utils" + "trbot/utils/yaml" + + "github.com/go-telegram/bot" + "github.com/go-telegram/bot/models" + "github.com/rs/zerolog" +) + +var LimitMessageList map[int64]AllowMessages +var LimitMessageErr error + +var LimitMessageDir string = filepath.Join(consts.YAMLDataBasePath, "limitmessage/") +var LimitMessagePath string = filepath.Join(LimitMessageDir, consts.YAMLFileName) + +type AllowMessages struct { + IsEnable bool `yaml:"IsEnable"` + IsUnderTest bool `yaml:"IsUnderTest"` + AddTime string `yaml:"AddTime"` + IsLogicAnd bool `yaml:"IsLogicAnd"` // true: `&&``, false: `||` + IsWhiteForType bool `yaml:"IsWhiteForType"` + MessageType message_utils.MessageType `yaml:"MessageType"` + IsWhiteForAttribute bool `yaml:"IsWhiteForAttribute"` + MessageAttribute message_utils.MessageAttribute `yaml:"MessageAttribute"` +} + +func init() { + plugin_utils.AddInitializer(plugin_utils.Initializer{ + Name: "Limit Message", + Func: ReadLimitMessageList, + }) + plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ + Name: "Limit Message", + Saver: SaveLimitMessageList, + Loader: ReadLimitMessageList, + }) + plugin_utils.AddSlashSymbolCommandPlugins(plugin_utils.SlashSymbolCommand{ + SlashCommand: "limitmessage", + Handler: SomeMessageOnlyHandler, + }) + plugin_utils.AddCallbackQueryCommandPlugins(plugin_utils.CallbackQuery{ + CommandChar: "limitmsg_", + Handler: LimitMessageCallback, + }) + + plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ + Name: "限制群组消息", + Description: "此功能需要 bot 为群组管理员并拥有删除消息的权限\n可以按照消息类型和消息属性来自动删除不允许的消息,支持自定逻辑和黑白名单,作为管理员在群组中使用 /limitmessage 命令来查看菜单", + ParseMode: models.ParseModeHTML, + }) +} + +func ReadLimitMessageList(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "LimitMessage"). + Str("funcName", "ReadLimitMessageList"). + Logger() + + err := yaml.LoadYAML(LimitMessagePath, &LimitMessageList) + if err != nil { + if os.IsNotExist(err) { + logger.Warn(). + Err(err). + Str("path", LimitMessagePath). + Msg("Not found limit message list file. Created new one") + // 如果是找不到文件,新建一个 + err = yaml.SaveYAML(LimitMessagePath, &LimitMessageList) + if err != nil { + logger.Error(). + Err(err). + Str("path", LimitMessagePath). + Msg("Failed to create empty limit message list file") + LimitMessageErr = fmt.Errorf("failed to create empty limit message list file: %w", err) + } + } else { + logger.Error(). + Err(err). + Str("path", LimitMessagePath). + Msg("Failed to load limit message list file") + LimitMessageErr = fmt.Errorf("failed to load limit message list file: %w", err) + } + } else { + LimitMessageErr = nil + } + + buildLimitGroupList() + + return LimitMessageErr +} + +func SaveLimitMessageList(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("pluginName", "LimitMessage"). + Str("funcName", "SaveLimitMessageList"). + Logger() + err := yaml.SaveYAML(LimitMessagePath, &LimitMessageList) + if err != nil { + logger.Error(). + Err(err). + Str("path", LimitMessagePath). + Msg("Failed to save limit message list") + LimitMessageErr = fmt.Errorf("failed to save limit message list: %w", err) + } else { + LimitMessageErr = nil + } + return LimitMessageErr +} + +func SomeMessageOnlyHandler(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "LimitMessage"). + Str("funcName", "SomeMessageOnlyHandler"). + Logger() + + var handlerErr multe.MultiError + + if opts.Update.Message.Chat.Type == models.ChatTypePrivate { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "此功能被设计为仅在群组中可用", + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "limit message only allows in group"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `limit message only allows in group` message: %w", err) + } + } else { + if utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.Message.Chat.ID, opts.Update.Message.From.ID) { + thisChat := LimitMessageList[opts.Update.Message.Chat.ID] + + var isNeedInit bool = false + + if thisChat.AddTime == "" { + isNeedInit = true + thisChat.AddTime = time.Now().Format(time.RFC3339) + } + + if utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.Message.Chat.ID, consts.BotMe.ID) && utils.UserHavePermissionDeleteMessage(opts.Ctx, opts.Thebot, opts.Update.Message.Chat.ID, consts.BotMe.ID) { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "Limit Message 菜单", + ReplyMarkup: buildMessageAllKB(thisChat), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "limit message main menu"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `limit message main menu` message: %w", err) + } + _, err = opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + MessageID: opts.Update.Message.ID, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "limit message command"). + Msg(errt.DeleteMessage) + handlerErr.Addf("failed to delete `limit message command` message: %w", err) + } + if isNeedInit { + LimitMessageList[opts.Update.Message.Chat.ID] = thisChat + err = SaveLimitMessageList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to save limit message list after adding new chat") + handlerErr.Addf("failed to save limit message list after adding new chat: %w", err) + } + } + } else { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "启用此功能前,请先将机器人设为管理员\n如果还是提示本消息,请检查机器人是否有删除消息的权限", + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "bot need be admin and delete message permission"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `bot need be admin and delete message permission` message: %w", err) + } + } + } else { + botMessage, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "抱歉,您不是群组的管理员,无法为群组更改此功能", + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "non-admin can not change limit message config"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `non-admin can not change limit message config` message: %w", err) + } + time.Sleep(time.Second * 5) + _, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{ + ChatID: opts.Update.Message.Chat.ID, + MessageIDs: []int{ + opts.Update.Message.ID, + botMessage.ID, + }, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "non-admin can not change limit message config"). + Msg(errt.DeleteMessages) + handlerErr.Addf("failed to delete `non-admin can not change limit message config` messages: %w", err) + } + } + } + + return handlerErr.Flat() +} + +func DeleteNotAllowMessage(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "LimitMessage"). + Str("funcName", "SomeMessageOnlyHandler"). + Logger() + + var handlerErr multe.MultiError + + var deleteAction bool + var deleteHelp string = "当前模式:" + if utils.AnyContains(opts.Update.Message.Chat.Type, models.ChatTypeGroup, models.ChatTypeSupergroup) { + // 处理消息删除逻辑,只有当群组启用该功能时才处理 + thisChat := LimitMessageList[opts.Update.Message.Chat.ID] + if thisChat.IsEnable || thisChat.IsUnderTest { + thisMsgType := message_utils.GetMessageType(opts.Update.Message) + thisMsgAttr := message_utils.GetMessageAttribute(opts.Update.Message) + + // 根据规则的黑白名单选择判断逻辑 + if thisChat.IsLogicAnd { + deleteHelp += "同时触发两个规则才删除消息\n" + msgType, typeHelp := CheckMessageType(thisMsgType, thisChat.MessageType, thisChat.IsWhiteForType) + deleteHelp += "消息类型:" + typeHelp + if msgType { + msgAttr, attrHelp := CheckMessageAttribute(thisMsgAttr, thisChat.MessageAttribute, thisChat.IsWhiteForAttribute) + deleteHelp += "消息属性:" + attrHelp + if msgType && msgAttr { + deleteAction = true + } + } + + // deleteAction = CheckMessageType(thisMsgType, thisChat.MessageType, thisChat.IsWhiteForType) && CheckMessageAttribute(thisMsgAttr, thisChat.MessageAttribute, thisChat.IsWhiteForAttribute) + } else { + deleteHelp += "触发任一规则就删除消息\n" + msgType, typeHelp := CheckMessageType(thisMsgType, thisChat.MessageType, thisChat.IsWhiteForType) + deleteHelp += "消息类型:" + typeHelp + if msgType { + deleteAction = true + } else { + msgAttr, attrHelp := CheckMessageAttribute(thisMsgAttr, thisChat.MessageAttribute, thisChat.IsWhiteForAttribute) + deleteHelp += "消息属性:" + attrHelp + if msgAttr { + deleteAction = true + } + } + + // deleteAction = CheckMessageType(thisMsgType, thisChat.MessageType, thisChat.IsWhiteForType) || CheckMessageAttribute(thisMsgAttr, thisChat.MessageAttribute, thisChat.IsWhiteForAttribute) + } + + if thisChat.IsUnderTest { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: utils.TextForTrueOrFalse(deleteAction, "此消息会被设定的规则删除\n\n", "") + + deleteHelp + + utils.TextForTrueOrFalse(thisChat.IsEnable, "
当前已启用,关闭测试模式将开始删除触发了规则的消息", "
您可以继续进行测试,以便达到您想要的效果,之后请手动启用此功能\n"), + DisableNotification: true, + ParseMode: models.ParseModeHTML, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{ + { + Text: "打开配置菜单", + CallbackData: "limitmsg_back", + }, + { + Text: "关闭测试模式", + CallbackData: "limitmsg_offtest", + }, + }}}, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Str("content", "test mode delete message notification"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `test mode delete message notification` message: %w", err) + } + } else if deleteAction { + _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + MessageID: opts.Update.Message.ID, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("messageType", string(thisMsgType.InString())). + Int("messageID", opts.Update.Message.ID). + Str("content", "message trigger limit message rules"). + Bool("IsLogicAnd", thisChat.IsLogicAnd). + Msg(errt.DeleteMessage) + handlerErr.Addf("failed to delete `message trigger limit message rules` message: %w", err) + } else { + logger.Info(). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("messageType", string(thisMsgType.InString())). + Int("messageID", opts.Update.Message.ID). + Bool("IsLogicAnd", thisChat.IsLogicAnd). + Msg("Deleted message trigger limit message rules") + } + } + } + } + return handlerErr.Flat() +} + +func CheckMessageType(this, target message_utils.MessageType, IsWhiteList bool) (bool, string) { + var delete bool = IsWhiteList + var deleteHelp string + + v1 := reflect.ValueOf(this) + v2 := reflect.ValueOf(target) + t := reflect.TypeOf(this) + + for i := 0; i < v1.NumField(); i++ { + field := t.Field(i) + val1 := v1.Field(i).Interface() + val2 := v2.Field(i).Interface() + + if val1 == true && val1 == val2 { + deleteHelp += fmt.Sprintf("%s 消息类型 %s %s\n", + utils.TextForTrueOrFalse(IsWhiteList, "白名单", "黑名单"), + field.Name, + utils.TextForTrueOrFalse(IsWhiteList, "不删除", "删除"), + ) + if IsWhiteList { + delete = false + } else { + delete = true + } + } else if val1 == true && val1 != val2 { + deleteHelp += fmt.Sprintf("%s 未命中 消息类型 %s 遵循默认规则 %s\n", + utils.TextForTrueOrFalse(IsWhiteList, "白名单", "黑名单"), + field.Name, + utils.TextForTrueOrFalse(delete, "删除", "不删除"), + ) + } + } + return delete, deleteHelp +} + +func CheckMessageAttribute(this, target message_utils.MessageAttribute, IsWhiteList bool) (bool, string) { + var delete bool = IsWhiteList + var noAttribute bool = true // 如果没有命中任何消息属性,提示内容,根据黑白名单判断是否删除 + var deleteHelp string + + v1 := reflect.ValueOf(this) + v2 := reflect.ValueOf(target) + t := reflect.TypeOf(this) + + for i := 0; i < v1.NumField(); i++ { + field := t.Field(i) + val1 := v1.Field(i).Interface() + val2 := v2.Field(i).Interface() + + + if val1 == true && val1 == val2 { + noAttribute = false + deleteHelp += fmt.Sprintf("%s 消息属性 %s %s\n", + utils.TextForTrueOrFalse(IsWhiteList, "白名单", "黑名单"), + field.Name, + utils.TextForTrueOrFalse(IsWhiteList, "不删除", "删除"), + ) + if IsWhiteList { + delete = false + } else { + delete = true + } + } else if val1 == true && val1 != val2 { + noAttribute = false + deleteHelp += fmt.Sprintf("%s 未命中 消息属性 %s 遵循默认规则 %s\n", + utils.TextForTrueOrFalse(IsWhiteList, "白名单", "黑名单"), + field.Name, + utils.TextForTrueOrFalse(delete, "删除", "不删除"), + ) + } + } + if noAttribute { + deleteHelp += fmt.Sprintf("%s 未命中 消息属性 无 遵循默认规则 %s\n", + utils.TextForTrueOrFalse(IsWhiteList, "白名单", "黑名单"), + utils.TextForTrueOrFalse(delete, "删除", "不删除"), + ) + } + + return delete, deleteHelp +} + +func buttonText(text string, opt, IsWhiteList bool) string { + if opt { + return utils.TextForTrueOrFalse(IsWhiteList, "✅ ", "❌ ") + text + } + + return text +} + +func buildMessageTypeKB(chat AllowMessages) models.ReplyMarkup { + + var msgTypeItems [][]models.InlineKeyboardButton + var msgTypeItemsTemp []models.InlineKeyboardButton + + v := reflect.ValueOf(chat.MessageType) // 解除指针获取值 + t := reflect.TypeOf(chat.MessageType) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + if i % 3 == 0 && i != 0 { + msgTypeItems = append(msgTypeItems, msgTypeItemsTemp) + msgTypeItemsTemp = []models.InlineKeyboardButton{} + } + msgTypeItemsTemp = append(msgTypeItemsTemp, models.InlineKeyboardButton{ + Text: buttonText(field.Name, value.Bool(), chat.IsWhiteForType), + CallbackData: "limitmsg_type_" + field.Name, + }) + } + if len(msgTypeItemsTemp) != 0 { + msgTypeItems = append(msgTypeItems, msgTypeItemsTemp) + } + + + msgTypeItems = append(msgTypeItems, []models.InlineKeyboardButton{{ + Text: "⬅️ 返回上一级", + CallbackData: "limitmsg_back", + }}) + + kb := &models.InlineKeyboardMarkup{ + InlineKeyboard: msgTypeItems, + } + + return kb +} + +func buildMessageAttributeKB(chat AllowMessages) models.ReplyMarkup { + + var msgAttributeItems [][]models.InlineKeyboardButton + var msgAttributeItemsTemp []models.InlineKeyboardButton + + v := reflect.ValueOf(chat.MessageAttribute) // 解除指针获取值 + t := reflect.TypeOf(chat.MessageAttribute) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + if i % 2 == 0 && i != 0 { + msgAttributeItems = append(msgAttributeItems, msgAttributeItemsTemp) + msgAttributeItemsTemp = []models.InlineKeyboardButton{} + } + msgAttributeItemsTemp = append(msgAttributeItemsTemp, models.InlineKeyboardButton{ + Text: buttonText(field.Name, value.Bool(), chat.IsWhiteForAttribute), + CallbackData: "limitmsg_attr_" + field.Name, + }) + } + if len(msgAttributeItemsTemp) != 0 { + msgAttributeItems = append(msgAttributeItems, msgAttributeItemsTemp) + } + + + msgAttributeItems = append(msgAttributeItems, []models.InlineKeyboardButton{{ + Text: "⬅️ 返回上一级", + CallbackData: "limitmsg_back", + }}) + + kb := &models.InlineKeyboardMarkup{ + InlineKeyboard: msgAttributeItems, + } + + return kb +} + +func buildMessageAllKB(chat AllowMessages) models.ReplyMarkup { + var chatAllow [][]models.InlineKeyboardButton + + chatAllow = append(chatAllow, []models.InlineKeyboardButton{ + { + Text: "选择消息类型", + CallbackData: "limitmsg_typekb", + }, + { + Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsWhiteForType, "白名单模式", "黑名单模式"), + CallbackData: "limitmsg_typekb_switchrule", + }, + }) + + chatAllow = append(chatAllow, []models.InlineKeyboardButton{ + { + Text: "选择消息属性", + CallbackData: "limitmsg_attrkb", + }, + { + Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsWhiteForAttribute, "白名单模式", "黑名单模式"), + CallbackData: "limitmsg_attrkb_switchrule", + }, + }) + + chatAllow = append(chatAllow, []models.InlineKeyboardButton{ + { + Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsLogicAnd, "满足上方所有条件才删除消息", "满足其中一个条件就删除消息"), + CallbackData: "limitmsg_switchlogic", + }, + }) + + chatAllow = append(chatAllow, []models.InlineKeyboardButton{ + { + Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsUnderTest, "测试模式已开启 ✅", "测试模式已关闭 ❌"), + CallbackData: "limitmsg_switchtest", + }, + }) + + chatAllow = append(chatAllow, []models.InlineKeyboardButton{ + { + Text: "🚫 关闭菜单", + CallbackData: "limitmsg_done", + }, + { + Text: "🔄 " + utils.TextForTrueOrFalse(chat.IsEnable, "当前已启用 ✅", "当前已关闭 ❌"), + CallbackData: "limitmsg_switchenable", + }, + }) + + kb := &models.InlineKeyboardMarkup{ + InlineKeyboard: chatAllow, + } + + return kb +} + +func LimitMessageCallback(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "LimitMessage"). + Str("funcName", "LimitMessageCallback"). + Logger() + + var handlerErr multe.MultiError + + if !utils.UserIsAdmin(opts.Ctx, opts.Thebot, opts.Update.CallbackQuery.Message.Message.Chat.ID, opts.Update.CallbackQuery.From.ID) { + _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "您没有权限修改此配置", + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "no permission to change limit message config"). + Msg(errt.AnswerCallbackQuery) + handlerErr.Addf("failed to send `no permission to change limit message config` callback answer: %w", err) + } + } else { + thisChat := LimitMessageList[opts.Update.CallbackQuery.Message.Message.Chat.ID] + + var needRebuildGroupList bool + var needSavelimitMessageList bool + var needEditMainMenuMessage bool + + switch opts.Update.CallbackQuery.Data { + case "limitmsg_typekb": + // opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + // CallbackQueryID: opts.Update.CallbackQuery.ID, + // Text: "已选择消息类型", + // }) + _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: utils.TextForTrueOrFalse(thisChat.IsWhiteForType, "白名单模式", "黑名单模式") + ": " + utils.TextForTrueOrFalse(thisChat.IsWhiteForType, "仅允许发送选中的项目,其他消息将被删除", "将删除选中的项目"), + ReplyMarkup: buildMessageTypeKB(thisChat), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "limit message type keyboard"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `limit message type keyboard`: %w", err) + } + case "limitmsg_typekb_switchrule": + thisChat.IsWhiteForType = !thisChat.IsWhiteForType + needSavelimitMessageList = true + needEditMainMenuMessage = true + case "limitmsg_attrkb": + _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: utils.TextForTrueOrFalse(thisChat.IsWhiteForAttribute, "白名单模式", "黑名单模式") + ": " + utils.TextForTrueOrFalse(thisChat.IsWhiteForAttribute, "仅允许发送选中的项目,其他消息将被删除", "将删除选中的项目") + "\n有一些项目可能无法使用", + ReplyMarkup: buildMessageAttributeKB(thisChat), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "limit message attribute keyboard"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `limit message attribute keyboard`: %w", err) + } + case "limitmsg_attrkb_switchrule": + thisChat.IsWhiteForAttribute = !thisChat.IsWhiteForAttribute + needSavelimitMessageList = true + needEditMainMenuMessage = true + case "limitmsg_back": + needEditMainMenuMessage = true + case "limitmsg_done": + _, err := opts.Thebot.DeleteMessage(opts.Ctx, &bot.DeleteMessageParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "limit message main menu or test mode delete message notification"). + Msg(errt.DeleteMessage) + handlerErr.Addf("failed to delete `limit message main menu or test mode delete message notification` message: %w", err) + } + case "limitmsg_switchenable": + thisChat.IsEnable = !thisChat.IsEnable + if thisChat.IsEnable { thisChat.IsUnderTest = false } + needRebuildGroupList = true + needSavelimitMessageList = true + needEditMainMenuMessage = true + case "limitmsg_switchlogic": + thisChat.IsLogicAnd = !thisChat.IsLogicAnd + needSavelimitMessageList = true + needEditMainMenuMessage = true + case "limitmsg_switchtest": + thisChat.IsUnderTest = !thisChat.IsUnderTest + needEditMainMenuMessage = true + needRebuildGroupList = true + needSavelimitMessageList = true + case "limitmsg_offtest": + thisChat.IsUnderTest = false + needSavelimitMessageList = true + needRebuildGroupList = true + _, err := opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "删除此提醒", + CallbackData: "limitmsg_done", + }}}}, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "test mode turned off notice"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `test mode turned off notice`: %w", err) + } + default: + if strings.HasPrefix(opts.Update.CallbackQuery.Data, "limitmsg_type_") { + needSavelimitMessageList = true + callbackField := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "limitmsg_type_") + + data := thisChat.MessageType + v := reflect.ValueOf(data) // 解除指针获取值 + t := reflect.TypeOf(data) + newStruct := reflect.New(v.Type()).Elem() + newStruct.Set(v) // 复制原始值 + for i := 0; i < newStruct.NumField(); i++ { + if t.Field(i).Name == callbackField { + newStruct.Field(i).SetBool(!newStruct.Field(i).Bool()) + } + } + thisChat.MessageType = newStruct.Interface().(message_utils.MessageType) + + _, err := opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + ReplyMarkup: buildMessageTypeKB(thisChat), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "limit message type keyboard"). + Msg(errt.EditMessageReplyMarkup) + handlerErr.Addf("failed to edit message reply markup to `limit message type keyboard`: %w", err) + } + } else if strings.HasPrefix(opts.Update.CallbackQuery.Data, "limitmsg_attr_") { + needSavelimitMessageList = true + callbackField := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "limitmsg_attr_") + data := thisChat.MessageAttribute + v := reflect.ValueOf(data) // 解除指针获取值 + t := reflect.TypeOf(data) + newStruct := reflect.New(v.Type()).Elem() + newStruct.Set(v) // 复制原始值 + for i := 0; i < newStruct.NumField(); i++ { + if t.Field(i).Name == callbackField { + newStruct.Field(i).SetBool(!newStruct.Field(i).Bool()) + } + } + + thisChat.MessageAttribute = newStruct.Interface().(message_utils.MessageAttribute) + + _, err := opts.Thebot.EditMessageReplyMarkup(opts.Ctx, &bot.EditMessageReplyMarkupParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + ReplyMarkup: buildMessageAttributeKB(thisChat), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "limit message attribute keyboard"). + Msg(errt.EditMessageReplyMarkup) + handlerErr.Addf("failed to edit message reply markup to `limit message attribute keyboard`: %w", err) + } + } + } + + if needSavelimitMessageList { + LimitMessageList[opts.Update.CallbackQuery.Message.Message.Chat.ID] = thisChat + err := SaveLimitMessageList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Msg("Failed to save limit message list") + handlerErr.Addf("failed to save limit message list: %w", err) + _, err = opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "保存修改失败,请重试或联系机器人管理员\n" + err.Error(), + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Str("content", "failed to save limit message list"). + Msg(errt.AnswerCallbackQuery) + handlerErr.Addf("failed to send `failed to save limit message list` callback answer: %w", err) + } + } + } + + if needRebuildGroupList { + buildLimitGroupList() + } + + if needEditMainMenuMessage { + _, err := opts.Thebot.EditMessageText(opts.Ctx, &bot.EditMessageTextParams{ + ChatID: opts.Update.CallbackQuery.Message.Message.Chat.ID, + MessageID: opts.Update.CallbackQuery.Message.Message.ID, + Text: "Limit Message 菜单", + ReplyMarkup: buildMessageAllKB(thisChat), + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetChatDict(&opts.Update.CallbackQuery.Message.Message.Chat)). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQueryData", opts.Update.CallbackQuery.Data). + Str("content", "limit message main menu"). + Msg(errt.EditMessageText) + handlerErr.Addf("failed to edit message to `limit message main menu`: %w", err) + } + } + } + + return handlerErr.Flat() +} + +func buildLimitGroupList() { + for id, n := range LimitMessageList { + if n.IsEnable || n.IsUnderTest { + plugin_utils.AddHandlerByChatIDPlugins(plugin_utils.HandlerByChatID{ + ChatID: id, + PluginName: "limit_message", + Handler: DeleteNotAllowMessage, + }) + } else { + plugin_utils.RemoveHandlerByChatIDPlugin(id, "limit_message") + } + } +} diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index cc89b2a..9413ced 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -29,8 +29,8 @@ var UdoneseErr error var UdoneseDir string = filepath.Join(consts.YAMLDataBasePath, "udonese/") var UdonesePath string = filepath.Join(UdoneseDir, consts.YAMLFileName) -// var UdonGroupID int64 = -1002205667779 -var UdonGroupID int64 = -1002499888124 // trbot +var UdonGroupID int64 = -1002205667779 +// var UdonGroupID int64 = -1002499888124 // trbot var UdoneseManagerIDs []int64 = []int64{ 872082796, // akaudon 1086395364, // trle5 @@ -272,7 +272,7 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error { if err != nil { logger.Error(). Err(err). - Int64("chatID", opts.Update.Message.Chat.ID). + Dict(utils.GetChatDict(&opts.Update.Message.Chat)). Str("content", "/udonese not allowed group"). Msg(errt.SendMessage) handlerErr.Addf("failed to send `/udonese not allowed group` message: %w", err) @@ -985,7 +985,7 @@ func udoneseCallbackHandler(opts *handler_structs.SubHandlerParams) error { _, err = opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ CallbackQueryID: opts.Update.CallbackQuery.ID, - Text: "删除意思时保存数据库失败,请重试\n" + err.Error(), + Text: "删除意思时保存数据库失败,请重试或联系机器人管理员\n" + err.Error(), ShowAlert: true, }) if err != nil { diff --git a/utils/errt/log_template.go b/utils/errt/log_template.go index 654a782..f77454c 100644 --- a/utils/errt/log_template.go +++ b/utils/errt/log_template.go @@ -2,12 +2,13 @@ package errt const ( // LogTemplate is the template for log messages. - SendMessage string = "Failed to send message" - SendDocument string = "Failed to send document" - EditMessageText string = "Failed to edit message text to" - EditMessageCaption string = "Failed to edit message caption to" - DeleteMessage string = "Failed to delete message" - DeleteMessages string = "Failed to delete messages" - AnswerCallbackQuery string = "Failed to answer callback query" - AnswerInlineQuery string = "Failed to answer inline query" + SendMessage string = "Failed to send message" + SendDocument string = "Failed to send document" + EditMessageText string = "Failed to edit message text" + EditMessageCaption string = "Failed to edit message caption" + EditMessageReplyMarkup string = "Failed to edit message reply markup" + DeleteMessage string = "Failed to delete message" + DeleteMessages string = "Failed to delete messages" + AnswerCallbackQuery string = "Failed to answer callback query" + AnswerInlineQuery string = "Failed to answer inline query" ) -- 2.49.1 From 45a431134f4251f0f293d5ff6794952d82dc979f Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Fri, 27 Jun 2025 05:06:23 +0800 Subject: [PATCH 23/27] show a info log when download sticker/set --- main.go | 14 +++++--------- plugins/plugin_sticker.go | 12 +++++++++++- utils/configs/init.go | 4 +--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 5fe0ea3..7a63f6e 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,6 @@ import ( "github.com/go-telegram/bot" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/rs/zerolog/pkgerrors" ) @@ -24,16 +23,13 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() - // set stack trace func - zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack - var logger zerolog.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) - // attach logger into ctx - ctx = logger.WithContext(ctx) + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack // set stack trace func + logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger() + ctx = logger.WithContext(ctx) // attach logger into ctx // read bot configs - if err := configs.InitBot(ctx); err != nil { - logger.Fatal().Err(err).Msg("Failed to read bot configs") - } + err := configs.InitBot(ctx) + if err != nil { logger.Fatal().Err(err).Msg("Failed to read bot configs") } // writer log to a file or only display on console if configs.IsUseMultiLogWriter(&logger) { ctx = logger.WithContext(ctx) } // re-attach logger into ctx diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index c0a032c..82dd1f6 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -90,9 +90,10 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { Msg("copy `update.CallbackQuery.Message.Message.ReplyToMessage` to `update.Message`") } - logger.Debug(). + logger.Info(). Str("emoji", opts.Update.Message.Sticker.Emoji). Str("setName", opts.Update.Message.Sticker.SetName). + Dict(utils.GetUserDict(opts.Update.Message.From)). Msg("Start download sticker") err := database.IncrementalUsageCount(opts.Ctx, opts.Update.Message.From.ID, db_struct.StickerDownloaded) @@ -517,6 +518,15 @@ func DownloadStickerPackCallBackHandler(opts *handler_structs.SubHandlerParams) handlerErr.Addf("Failed to send `get sticker set info error` message: %w", err) } } else { + logger.Info(). + Dict("stickerSet", zerolog.Dict(). + Str("title", stickerSet.Title). + Str("name", stickerSet.Name). + Int("allCount", len(stickerSet.Stickers)), + ). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Start download sticker set") + stickerData, err := getStickerPack(opts, stickerSet, isOnlyPNG) if err != nil { logger.Error(). diff --git a/utils/configs/init.go b/utils/configs/init.go index e2fe547..c0dec61 100644 --- a/utils/configs/init.go +++ b/utils/configs/init.go @@ -266,7 +266,7 @@ func IsUseMultiLogWriter(logger *zerolog.Logger) bool { } } -func CheckConfig(ctx context.Context) error { +func CheckConfig(ctx context.Context) { logger := zerolog.Ctx(ctx) // 部分必要但可以留空的配置 @@ -343,8 +343,6 @@ func CheckConfig(ctx context.Context) error { Str("PaginationSymbol", BotConfig.InlinePaginationSymbol). Int("ResultsPerPage", BotConfig.InlineResultsPerPage). Msg("Inline mode config has been read") - - return nil } func ShowConst(ctx context.Context) { -- 2.49.1 From c99369a54093afd789c460272bdac0ef4d2e343b Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sat, 28 Jun 2025 15:12:00 +0800 Subject: [PATCH 24/27] refactor saved message logger --- bad_plugins/saved_message/functions.go | 834 ---------------- plugins/plugin_udonese.go | 29 +- plugins/saved_message/functions.go | 902 ++++++++++++++++++ .../saved_message/item_structs.go | 48 +- .../saved_message/utils.go | 203 ++-- plugins/sub_package_plugin.go | 6 +- utils/configs/config.go | 6 +- utils/internal_plugin/register.go | 82 +- utils/plugin_utils/handler_by_message_type.go | 16 +- 9 files changed, 1104 insertions(+), 1022 deletions(-) delete mode 100644 bad_plugins/saved_message/functions.go create mode 100644 plugins/saved_message/functions.go rename {bad_plugins => plugins}/saved_message/item_structs.go (91%) rename {bad_plugins => plugins}/saved_message/utils.go (81%) diff --git a/bad_plugins/saved_message/functions.go b/bad_plugins/saved_message/functions.go deleted file mode 100644 index 724e84f..0000000 --- a/bad_plugins/saved_message/functions.go +++ /dev/null @@ -1,834 +0,0 @@ -package saved_message - -import ( - "fmt" - "log" - "reflect" - "trbot/utils" - "trbot/utils/configs" - "trbot/utils/consts" - "trbot/utils/handler_structs" - "trbot/utils/plugin_utils" - "unicode/utf8" - - "github.com/go-telegram/bot" - "github.com/go-telegram/bot/models" - "github.com/rs/zerolog" -) - -func saveMessageHandler(opts *handler_structs.SubHandlerParams) { - logger := zerolog.Ctx(opts.Ctx). - With(). - Str("pluginName", "Saved Message"). - Str("funcName", "ReadSavedMessageList"). - Logger() - UserSavedMessage := SavedMessageSet[opts.Update.Message.From.ID] - - messageParams := &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, - ParseMode: models.ParseModeHTML, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "点击浏览您的收藏", - SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", - }}}}, - } - - if !UserSavedMessage.AgreePrivacyPolicy { - messageParams.Text = "此功能需要保存一些信息才能正常工作,在使用这个功能前,请先阅读一下我们会保存哪些信息" - messageParams.ReplyMarkup = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "点击查看", - URL: fmt.Sprintf("https://t.me/%s?start=savedmessage_privacy_policy", consts.BotMe.Username), - }}}} - _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams) - if err != nil { - log.Printf("Error response /save command initial info: %v", err) - } - return - } - - if UserSavedMessage.Limit == 0 && UserSavedMessage.Count == 0 { - UserSavedMessage.Limit = 100 - } - - // 若不是初次添加,为 0 就是不限制 - if UserSavedMessage.Limit != 0 && UserSavedMessage.Count >= UserSavedMessage.Limit { - messageParams.Text = "已达到限制,无法保存更多内容" - _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams) - if err != nil { - log.Printf("Error response /save command reach limit: %v", err) - } - return - } - - // var pendingMessage string - if opts.Update.Message.ReplyToMessage == nil { - messageParams.Text = "在回复一条消息的同时发送
/save 来添加"
- } else {
- var DescriptionText string
- // 获取使用命令保存时设定的描述
- if len(opts.Update.Message.Text) > len(opts.Fields[0])+1 {
- DescriptionText = opts.Update.Message.Text[len(opts.Fields[0])+1:]
- }
-
- var originInfo *OriginInfo
- if opts.Update.Message.ReplyToMessage.ForwardOrigin != nil && opts.Update.Message.ReplyToMessage.ForwardOrigin.MessageOriginHiddenUser == nil {
- originInfo = getMessageOriginData(opts.Update.Message.ReplyToMessage.ForwardOrigin)
- } else if opts.Update.Message.Chat.Type != models.ChatTypePrivate {
- originInfo = getMessageLink(opts.Update.Message)
- }
-
- var isSaved bool
- var messageLength int
- var pendingEntitites []models.MessageEntity
- var needChangeEntitites bool = true
-
- if opts.Update.Message.ReplyToMessage.Caption != "" {
- messageLength = utf8.RuneCountInString(opts.Update.Message.ReplyToMessage.Caption)
- pendingEntitites = opts.Update.Message.ReplyToMessage.CaptionEntities
- } else if opts.Update.Message.ReplyToMessage.Text != "" {
- messageLength = utf8.RuneCountInString(opts.Update.Message.ReplyToMessage.Text)
- pendingEntitites = opts.Update.Message.ReplyToMessage.Entities
- } else {
- needChangeEntitites = false
- }
-
- if needChangeEntitites {
- // 若字符长度大于设定的阈值,添加折叠样式引用再保存
- if messageLength > textExpandableLength {
- if len(pendingEntitites) == 1 && pendingEntitites[0].Type == models.MessageEntityTypeBlockquote && pendingEntitites[0].Offset == 0 && pendingEntitites[0].Length == messageLength {
- // 如果消息仅为一个消息格式实体,且是不折叠形式的引用,则将格式实体改为可折叠格式引用后再保存
- pendingEntitites = []models.MessageEntity{{
- Type: models.MessageEntityTypeExpandableBlockquote,
- Offset: 0,
- Length: messageLength,
- }}
- } else {
- // 其他则仅在末尾加一个可折叠形式的引用
- pendingEntitites = append(pendingEntitites, models.MessageEntity{
- Type: models.MessageEntityTypeExpandableBlockquote,
- Offset: 0,
- Length: messageLength,
- })
- }
- }
- }
-
- if opts.Update.Message.ReplyToMessage.Text != "" {
- for i, n := range UserSavedMessage.Item.OnlyText {
- if n.TitleAndMessageText == opts.Update.Message.ReplyToMessage.Text && reflect.DeepEqual(n.Entities, pendingEntitites) {
- isSaved = true
- messageParams.Text = "已保存过该文本\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此文本添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此文本的搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此文本的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.OnlyText[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- if err != nil {
- logger.Error().
- Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
- Msg("Update user saved `OnlyText` item keyword and save savedmessage list failed")
- SavedMessageErr = err
- return
- }
- }
- break
- }
- }
-
- if !isSaved {
- UserSavedMessage.Item.OnlyText = append(UserSavedMessage.Item.OnlyText, SavedMessageTypeCachedOnlyText{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- TitleAndMessageText: opts.Update.Message.ReplyToMessage.Text,
- Description: DescriptionText,
- Entities: pendingEntitites,
- LinkPreviewOptions: opts.Update.Message.ReplyToMessage.LinkPreviewOptions,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- if err != nil {
- logger.Error().
- Err(err).
- Dict("user", zerolog.Dict().
- Str("name", utils.ShowUserName(opts.Update.Message.From)).
- Str("username", opts.Update.Message.From.Username).
- Int64("ID", opts.Update.Message.From.ID),
- ).
- Msg("Add `OnlyText` item to user saved list and save savedmessage list failed")
- SavedMessageErr = err
- return
- }
- messageParams.Text = "已保存文本"
- }
- } else if opts.Update.Message.ReplyToMessage.Audio != nil {
- for i, n := range UserSavedMessage.Item.Audio {
- if n.FileID == opts.Update.Message.ReplyToMessage.Audio.FileID {
- isSaved = true
- messageParams.Text = "已保存过该音乐\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此音乐添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此音乐的搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此音乐的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Audio[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Audio = append(UserSavedMessage.Item.Audio, SavedMessageTypeCachedAudio{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Audio.FileID,
- Title: opts.Update.Message.ReplyToMessage.Audio.Title,
- FileName: opts.Update.Message.ReplyToMessage.Audio.FileName,
- Description: DescriptionText,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存音乐"
- }
- } else if opts.Update.Message.ReplyToMessage.Animation != nil {
- for i, n := range UserSavedMessage.Item.Mpeg4gif {
- if n.FileID == opts.Update.Message.ReplyToMessage.Animation.FileID {
- isSaved = true
- messageParams.Text = "已保存过该 GIF\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此 GIF 添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此 GIF 搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此 GIF 的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Mpeg4gif[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Mpeg4gif = append(UserSavedMessage.Item.Mpeg4gif, SavedMessageTypeCachedMpeg4Gif{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Animation.FileID,
- Title: opts.Update.Message.ReplyToMessage.Caption,
- Description: DescriptionText,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存 GIF"
- }
- } else if opts.Update.Message.ReplyToMessage.Document != nil {
- if opts.Update.Message.ReplyToMessage.Document.MimeType == "image/gif" {
- for i, n := range UserSavedMessage.Item.Gif {
- if n.FileID == opts.Update.Message.ReplyToMessage.Document.FileID {
- isSaved = true
- messageParams.Text = "已保存过该 GIF (文件)\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此 GIF (文件)添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此 GIF (文件)搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此 GIF (文件)的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Gif[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Gif = append(UserSavedMessage.Item.Gif, SavedMessageTypeCachedGif{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Document.FileID,
- Description: DescriptionText,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存 GIF (文件)"
- }
- } else {
- for i, n := range UserSavedMessage.Item.Document {
- if n.FileID == opts.Update.Message.ReplyToMessage.Document.FileID {
- isSaved = true
- messageParams.Text = "已保存过该文件\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此文件添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此文件搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此文件的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Document[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Document = append(UserSavedMessage.Item.Document, SavedMessageTypeCachedDocument{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Document.FileID,
- Title: opts.Update.Message.ReplyToMessage.Document.FileName,
- Description: DescriptionText,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存文件"
- }
- }
- } else if opts.Update.Message.ReplyToMessage.Photo != nil {
- for i, n := range UserSavedMessage.Item.Photo {
- if n.FileID == opts.Update.Message.ReplyToMessage.Photo[len(opts.Update.Message.ReplyToMessage.Photo)-1].FileID {
- isSaved = true
- messageParams.Text = "已保存过该图片\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此图片添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此图片搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此图片的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Photo[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Photo = append(UserSavedMessage.Item.Photo, SavedMessageTypeCachedPhoto{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Photo[len(opts.Update.Message.ReplyToMessage.Photo)-1].FileID,
- // Title: opts.Update.Message.ReplyToMessage.Caption,
- Description: DescriptionText,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- CaptionAboveMedia: opts.Update.Message.ReplyToMessage.ShowCaptionAboveMedia,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存图片"
- }
- } else if opts.Update.Message.ReplyToMessage.Sticker != nil {
- for i, n := range UserSavedMessage.Item.Sticker {
- if n.FileID == opts.Update.Message.ReplyToMessage.Sticker.FileID {
- isSaved = true
- messageParams.Text = "已保存过该贴纸\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此贴纸添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此贴纸的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Sticker[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
-
- if !isSaved {
- stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{Name: opts.Update.Message.ReplyToMessage.Sticker.SetName})
- if err != nil {
- log.Printf("Error response /save command sticker no pack info: %v", err)
- }
- if stickerSet != nil {
- // 属于一个贴纸包中的贴纸
- UserSavedMessage.Item.Sticker = append(UserSavedMessage.Item.Sticker, SavedMessageTypeCachedSticker{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Sticker.FileID,
- SetName: stickerSet.Name,
- SetTitle: stickerSet.Title,
- Description: DescriptionText,
- OriginInfo: originInfo,
- })
- } else {
- UserSavedMessage.Item.Sticker = append(UserSavedMessage.Item.Sticker, SavedMessageTypeCachedSticker{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Sticker.FileID,
- Description: DescriptionText,
- OriginInfo: originInfo,
- })
- }
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存贴纸"
- }
-
- } else if opts.Update.Message.ReplyToMessage.Video != nil {
- for i, n := range UserSavedMessage.Item.Video {
- if n.FileID == opts.Update.Message.ReplyToMessage.Video.FileID {
- isSaved = true
- messageParams.Text = "已保存过该视频\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此视频添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此视频搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此视频的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Video[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Video = append(UserSavedMessage.Item.Video, SavedMessageTypeCachedVideo{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Video.FileID,
- Title: opts.Update.Message.ReplyToMessage.Video.FileName,
- Description: DescriptionText,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存视频"
- }
-
- } else if opts.Update.Message.ReplyToMessage.VideoNote != nil {
- for i, n := range UserSavedMessage.Item.VideoNote {
- if n.FileID == opts.Update.Message.ReplyToMessage.VideoNote.FileID {
- isSaved = true
- messageParams.Text = "已保存过该圆形视频\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此圆形视频添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此圆形视频搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此圆形视频的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.VideoNote[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.VideoNote = append(UserSavedMessage.Item.VideoNote, SavedMessageTypeCachedVideoNote{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.VideoNote.FileID,
- Title: opts.Update.Message.ReplyToMessage.VideoNote.FileUniqueID,
- Description: DescriptionText,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存圆形视频"
- }
-
- } else if opts.Update.Message.ReplyToMessage.Voice != nil {
- for i, n := range UserSavedMessage.Item.Voice {
- if n.FileID == opts.Update.Message.ReplyToMessage.Voice.FileID {
- isSaved = true
- messageParams.Text = "已保存过该语音\n"
- if DescriptionText != "" {
- if n.Description == "" {
- messageParams.Text += fmt.Sprintf("已为此语音添加搜索关键词 [ %s ]", DescriptionText)
- } else if DescriptionText == n.Description {
- messageParams.Text += fmt.Sprintf("此语音搜索关键词未修改 [ %s ]", DescriptionText)
- break
- } else {
- messageParams.Text += fmt.Sprintf("已将此语音的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
- }
- n.Description = DescriptionText
- UserSavedMessage.Item.Voice[i] = n
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- }
- break
- }
- }
- if !isSaved {
- UserSavedMessage.Item.Voice = append(UserSavedMessage.Item.Voice, SavedMessageTypeCachedVoice{
- ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
- FileID: opts.Update.Message.ReplyToMessage.Voice.FileID,
- Title: DescriptionText,
- Description: opts.Update.Message.ReplyToMessage.Caption,
- Caption: opts.Update.Message.ReplyToMessage.Caption,
- CaptionEntities: pendingEntitites,
- OriginInfo: originInfo,
- })
- UserSavedMessage.Count++
- UserSavedMessage.SavedTimes++
- SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
- err := SaveSavedMessageList(opts.Ctx)
- messageParams.Text = "已保存语音"
- }
- } else {
- messageParams.Text = "暂不支持的消息类型"
- }
- }
-
- // fmt.Println(opts.ChatInfo)
-
- _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams)
- if err != nil {
- log.Printf("Error response /save command: %v", err)
- }
-}
-
-func channelSaveMessageHandler(opts *handler_structs.SubHandlerParams) {
- ChannelSavedMessage := SavedMessageSet[opts.Update.ChannelPost.From.ID]
-
- messageParams := &bot.SendMessageParams{
- ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID},
- ParseMode: models.ParseModeHTML,
- }
-
- if !ChannelSavedMessage.AgreePrivacyPolicy {
- messageParams.ChatID = opts.Update.ChannelPost.From.ID
- messageParams.Text = "此功能需要保存一些信息才能正常工作,在使用这个功能前,请先阅读一下我们会保存哪些信息"
- messageParams.ReplyMarkup = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
- Text: "点击查看",
- URL: fmt.Sprintf("https://t.me/%s?start=savedmessage_channel_privacy_policy", consts.BotMe.Username),
- }}}}
- _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams)
- if err != nil {
- log.Printf("Error response /save command initial info: %v", err)
- }
- return
- }
-
- if ChannelSavedMessage.DiscussionID == 0 {
- messageParams.Text = "您需要为此频道绑定一个讨论群组,用于接收收藏成功的确认信息与关键词更改"
- _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams)
- if err != nil {
- log.Printf("Error response /save command initial info: %v", err)
- }
- }
-
-}
-
-func InlineShowSavedMessageHandler(opts *handler_structs.SubHandlerParams) []models.InlineQueryResult {
- var InlineSavedMessageResultList []models.InlineQueryResult
-
- SavedMessage := SavedMessageSet[opts.ChatInfo.ID]
-
- keywordFields := utils.InlineExtractKeywords(opts.Fields)
-
- if len(keywordFields) == 0 {
- var all []models.InlineQueryResult
- for _, n := range SavedMessage.Item.All() {
- if n.onlyText != nil {
- all = append(all, n.onlyText)
- } else if n.audio != nil {
- all = append(all, n.audio)
- } else if n.document != nil {
- all = append(all, n.document)
- } else if n.gif != nil {
- all = append(all, n.gif)
- } else if n.photo != nil {
- all = append(all, n.photo)
- } else if n.sticker != nil {
- all = append(all, n.sticker)
- } else if n.video != nil {
- all = append(all, n.video)
- } else if n.videoNote != nil {
- all = append(all, n.videoNote)
- } else if n.voice != nil {
- all = append(all, n.voice)
- } else if n.mpeg4gif != nil {
- all = append(all, n.mpeg4gif)
- }
- }
- InlineSavedMessageResultList = all
- } else {
- var all []models.InlineQueryResult
- for _, n := range SavedMessage.Item.All() {
- if n.onlyText != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.onlyText.Description, n.onlyText.Title}) {
- all = append(all, n.onlyText)
- } else if n.audio != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.audio.Caption, n.sharedData.Description}) {
- all = append(all, n.audio)
- } else if n.mpeg4gif != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.mpeg4gif.Title, n.mpeg4gif.Caption, n.sharedData.Description}) {
- all = append(all, n.mpeg4gif)
- } else if n.document != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.document.Title, n.document.Caption, n.document.Description}) {
- all = append(all, n.document)
- } else if n.gif != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.gif.Title, n.gif.Caption, n.sharedData.Description}) {
- all = append(all, n.gif)
- } else if n.photo != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.photo.Title, n.photo.Caption, n.photo.Description}) {
- all = append(all, n.photo)
- } else if n.sticker != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.sharedData.Title, n.sharedData.Name, n.sharedData.Description}) {
- all = append(all, n.sticker)
- } else if n.video != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.video.Title, n.video.Caption, n.video.Description}) {
- all = append(all, n.video)
- } else if n.videoNote != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.videoNote.Title, n.videoNote.Caption, n.videoNote.Description}) {
- all = append(all, n.videoNote)
- } else if n.voice != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.voice.Title, n.voice.Caption, n.sharedData.Description}) {
- all = append(all, n.voice)
- }
- }
- InlineSavedMessageResultList = all
-
- if len(InlineSavedMessageResultList) == 0 {
- InlineSavedMessageResultList = append(InlineSavedMessageResultList, &models.InlineQueryResultArticle{
- ID: "none",
- Title: "没有符合关键词的内容",
- Description: fmt.Sprintf("没有找到包含 %s 的内容", keywordFields),
- InputMessageContent: models.InputTextMessageContent{
- MessageText: "用户在找不到想看的东西时无奈点击了提示信息...",
- ParseMode: models.ParseModeMarkdownV1,
- },
- })
- }
- }
-
- if len(InlineSavedMessageResultList) == 0 {
- _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
- InlineQueryID: opts.Update.InlineQuery.ID,
- Results: []models.InlineQueryResult{&models.InlineQueryResultArticle{
- ID: "empty",
- Title: "没有保存内容(点击查看详细教程)",
- Description: "对一条信息回复 /save 来保存它",
- InputMessageContent: models.InputTextMessageContent{
- MessageText: fmt.Sprintf("您可以在任何聊天的输入栏中输入 @%s +saved 来查看您的收藏\n若要添加,您需要确保机器人可以读取到您的指令,例如在群组中需要添加机器人,或点击 @%s 进入与机器人的聊天窗口,找到想要收藏的信息,然后对着那条信息回复 /save 即可\n若收藏成功,机器人会回复您并提示收藏成功,您也可以手动发送一条想要收藏的息,再使用 /save 命令回复它", consts.BotMe.Username, consts.BotMe.Username),
- ParseMode: models.ParseModeHTML,
- },
- }},
- Button: &models.InlineQueryResultsButton{
- Text: "点击此处快速跳转到机器人",
- StartParameter: "via-inline_noreply",
- },
- })
- if err != nil {
- log.Println("Error when answering inline [saved] command", err)
- }
- }
-
- _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{
- InlineQueryID: opts.Update.InlineQuery.ID,
- Results: utils.InlineResultPagination(opts.Fields, InlineSavedMessageResultList),
- IsPersonal: true,
- })
- if err != nil {
- log.Println("Error when answering inline [saved] command", err)
- }
-
- return InlineSavedMessageResultList
-}
-
-func SendPrivacyPolicy(opts *handler_structs.SubHandlerParams) {
- _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
- ChatID: opts.Update.Message.Chat.ID,
- Text: "目前此机器人仍在开发阶段中,此信息可能会有更改\n" +
- "本机器人提供收藏信息功能,您可以在回复一条信息时输入 /save 来收藏它,之后在 inline 模式下随时浏览您的收藏内容并发送\n\n" + - - "我们会记录哪些数据?\n" + - "1. 您的用户信息,例如 用户昵称、用户 ID、聊天类型(当您将此机器人添加到群组或频道中时)\n" + - "2. 您的使用情况,例如 消息计数、inline 调用计数、inline 条目计数、最后向机器人发送的消息、callback_query、inline_query 以及选择的 inline 结果\n" + - "3. 收藏信息内容,您需要注意这个,因为您是为了这个而阅读此内容,例如 存储的收藏信息数量、其图片上传到 Telegram 时的文件 ID、图片下方的文本,还有您在使用添加命令时所自定义的搜索关键词" + - "\n\n" + - - "我的数据安全吗?\n" + - "这是一个早期的项目,还有很多未发现的 bug 与漏洞,因此您不能也不应该将敏感的数据存储在此机器人中,若您觉得我们收集的信息不妥,您可以不点击底部的同意按钮,我们仅会收集一些基本的信息,防止对机器人造成滥用,基本信息为前一段的 1 至 2 条目" + - "\n\n" + - - "我收藏的消息,有谁可以看到?\n" + - "此功能被设计为每个人有单独的存储空间,如果您不手动从 inline 模式下选择信息并发送,其他用户是没法查看您的收藏列表的。不过,与上一个条目一样,为了防止滥用,我们是可以也有权利查看您收藏的内容的,请不要在其中保存隐私数据" + - "" + - - "\n\n" + - "内容待补充...", - ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "点击同意以上内容", - URL: fmt.Sprintf("https://t.me/%s?start=savedmessage_privacy_policy_agree", consts.BotMe.Username), - }}}}, - ParseMode: models.ParseModeHTML, - }) - if err != nil { - log.Println("error when send savedmessage_privacy_policy:", err) - return - } -} - -func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) { - logger := zerolog.Ctx(opts.Ctx). - With(). - Str("pluginName", "Saved Message"). - Str("funcName", "AgreePrivacyPolicy"). - Logger() - - var UserSavedMessage SavedMessage - // , ok := consts.Database.Data.SavedMessage[opts.ChatInfo.ID] - if len(SavedMessageSet) == 0 { - SavedMessageSet = map[int64]SavedMessage{} - // consts.Database.Data.SavedMessage[opts.ChatInfo.ID] = SavedMessages - } - UserSavedMessage.AgreePrivacyPolicy = true - SavedMessageSet[opts.ChatInfo.ID] = UserSavedMessage - err := SaveSavedMessageList(opts.Ctx) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Change user `AgreePrivacyPolicy` flag to true and save savemessage list failed") - SavedMessageErr = err - return - } - - _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ - ChatID: opts.Update.Message.Chat.ID, - Text: "您已成功开启收藏信息功能,回复一条信息的时候发送 /save 来使用收藏功能吧!\n由于服务器性能原因,每个人的收藏数量上限默认为 100 个,您也可以从机器人的个人信息中寻找管理员来申请更高的上限\n点击下方按钮来浏览您的收藏内容", - ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, - ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ - Text: "点击浏览您的收藏", - SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", - }}}}, - }) - if err != nil { - logger.Error(). - Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(opts.Update.Message.From)). - Str("username", opts.Update.Message.From.Username). - Int64("ID", opts.Update.Message.From.ID), - ). - Msg("Failed to send `saved message function enabled` message") - } -} - -func Init() { - plugin_utils.AddInitializer(plugin_utils.Initializer{ - Name: "Saved Message", - Func: ReadSavedMessageList, - }) - // ReadSavedMessageList() - plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ - Name: "Saved Message", - Saver: SaveSavedMessageList, - Loader: ReadSavedMessageList, - }) - plugin_utils.AddSlashSymbolCommandPlugins(plugin_utils.SlashSymbolCommand{ - SlashCommand: "save", - Handler: saveMessageHandler, - }) - plugin_utils.AddInlineHandlerPlugins(plugin_utils.InlineHandler{ - Command: "saved", - Handler: InlineShowSavedMessageHandler, - Description: "显示自己保存的消息", - }) - plugin_utils.AddSlashStartCommandPlugins([]plugin_utils.SlashStartHandler{ - { - Argument: "savedmessage_privacy_policy", - Handler: SendPrivacyPolicy, - }, - { - Argument: "savedmessage_privacy_policy_agree", - Handler: AgreePrivacyPolicy, - }, - // { - // Argument: "savedmessage_channel_privacy_policy", - // Handler: SendPrivacyPolicy, - // }, - // { - // Argument: "savedmessage_channel_privacy_policy_agree", - // Handler: AgreePrivacyPolicy, - // }, - }...) - plugin_utils.AddSlashStartWithPrefixCommandPlugins(plugin_utils.SlashStartWithPrefixHandler{ - Prefix: "via-inline", - Argument: "savedmessage-help", - Handler: saveMessageHandler, - }) - plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ - Name: "收藏消息", - Description: "此功能可以收藏用户指定的消息,之后使用 inline 模式查看并发送保存的内容\n\n保存消息:\n向机器人发送要保存的消息,然后使用
/save 关键词 命令回复要保存的消息,关键词可以忽略。若机器人在群组中,也可以直接使用 /save 关键词 命令回复要保存的消息。\n\n发送保存的消息:点击下方的按钮来使用 inline 模式,当您多次在 inline 模式下使用此 bot 时,在输入框中输入 @ 即可看到 bot 会出现在列表中",
- ParseMode: models.ParseModeHTML,
- ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{
- {{
- Text: "点击浏览您的收藏",
- SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ",
- }},
- {{
- Text: "将此功能设定为您的默认 inline 命令",
- CallbackData: "inline_default_noedit_saved",
- }},
- }},
- })
-}
diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go
index 9413ced..7bddeda 100644
--- a/plugins/plugin_udonese.go
+++ b/plugins/plugin_udonese.go
@@ -472,7 +472,22 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error {
Msg("Failed to save udonese list after add word")
handlerErr.Addf("failed to save udonese list after add word: %w", err)
- pendingMessage += fmt.Sprintln("保存语句时似乎发生了一些错误:\n", err)
+ pendingMessage += fmt.Sprintf("保存语句时似乎发生了一些错误: %s", err.Error()) + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: pendingMessage, + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Str("messageText", opts.Update.Message.Text). + Str("content", "failed save udonese list notice"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `failed to save udonese list notice` message: %w", err) + } + return handlerErr.Flat() } else { pendingMessage += fmt.Sprintf("已添加 [
%s]\n", opts.Fields[1])
pendingMessage += fmt.Sprintf("[%s] ", meaning)
@@ -512,10 +527,10 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Int64("chatID", opts.Update.Message.Chat.ID).
- Str("content", "/udonese keyword added").
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
+ Str("content", "udonese keyword added").
Msg(errt.SendMessage)
- handlerErr.Addf("failed to send `/udonese keyword added` message: %w", err)
+ handlerErr.Addf("failed to send `udonese keyword added` message: %w", err)
} else {
time.Sleep(time.Second * 10)
_, err = opts.Thebot.DeleteMessages(opts.Ctx, &bot.DeleteMessagesParams{
@@ -528,11 +543,11 @@ func addUdoneseHandler(opts *handler_structs.SubHandlerParams) error {
if err != nil {
logger.Error().
Err(err).
- Int64("chatID", opts.Update.Message.Chat.ID).
+ Dict(utils.GetChatDict(&opts.Update.Message.Chat)).
Ints("messageIDs", []int{ opts.Update.Message.ID, botMessage.ID }).
- Str("content", "/udonese keyword added").
+ Str("content", "udonese keyword added").
Msg(errt.DeleteMessages)
- handlerErr.Addf("failed to delete `/udonese keyword added` messages: %w", err)
+ handlerErr.Addf("failed to delete `udonese keyword added` messages: %w", err)
}
}
}
diff --git a/plugins/saved_message/functions.go b/plugins/saved_message/functions.go
new file mode 100644
index 0000000..dd3f3dd
--- /dev/null
+++ b/plugins/saved_message/functions.go
@@ -0,0 +1,902 @@
+package saved_message
+
+import (
+ "fmt"
+ "reflect"
+ "trbot/utils"
+ "trbot/utils/configs"
+ "trbot/utils/consts"
+ "trbot/utils/errt"
+ "trbot/utils/handler_structs"
+ "trbot/utils/multe"
+ "trbot/utils/plugin_utils"
+ "trbot/utils/type/message_utils"
+ "unicode/utf8"
+
+ "github.com/go-telegram/bot"
+ "github.com/go-telegram/bot/models"
+ "github.com/rs/zerolog"
+)
+
+func saveMessageHandler(opts *handler_structs.SubHandlerParams) error {
+ logger := zerolog.Ctx(opts.Ctx).
+ With().
+ Str("pluginName", "Saved Message").
+ Str("funcName", "saveMessageHandler").
+ Logger()
+
+ var handlerErr multe.MultiError
+ var needSave bool = true
+ UserSavedMessage := SavedMessageSet[opts.Update.Message.From.ID]
+
+ messageParams := &bot.SendMessageParams{
+ ChatID: opts.Update.Message.Chat.ID,
+ ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID},
+ ParseMode: models.ParseModeHTML,
+ ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
+ Text: "点击浏览您的收藏",
+ SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ",
+ }}}},
+ }
+
+ if !UserSavedMessage.AgreePrivacyPolicy {
+ messageParams.Text = "此功能需要保存一些信息才能正常工作,在使用这个功能前,请先阅读一下我们会保存哪些信息"
+ messageParams.ReplyMarkup = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{
+ Text: "点击查看",
+ URL: fmt.Sprintf("https://t.me/%s?start=savedmessage_privacy_policy", consts.BotMe.Username),
+ }}}}
+ _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Str("content", "need agree privacy policy").
+ Msg(errt.SendMessage)
+ handlerErr.Addf("failed to send `need agree privacy policy` message: %w", err)
+ }
+ } else {
+ if UserSavedMessage.Limit == 0 && UserSavedMessage.Count == 0 {
+ // 每个用户初次添加时,默认限制 100 条
+ UserSavedMessage.Limit = 100
+ }
+
+ // 若不是初次添加,为 0 就是不限制
+ if UserSavedMessage.Limit != 0 && UserSavedMessage.Count >= UserSavedMessage.Limit {
+ messageParams.Text = "已达到限制,无法保存更多内容"
+ _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Str("content", "reach saved limit").
+ Msg(errt.SendMessage)
+ handlerErr.Addf("failed to send `reach saved limit` message: %w", err)
+ }
+ } else {
+ // var pendingMessage string
+ if opts.Update.Message.ReplyToMessage == nil {
+ needSave = false
+ messageParams.Text = "在回复一条消息的同时发送 /save 来添加"
+ } else {
+ var DescriptionText string
+ // 获取使用命令保存时设定的描述
+ if len(opts.Update.Message.Text) > len(opts.Fields[0])+1 {
+ DescriptionText = opts.Update.Message.Text[len(opts.Fields[0])+1:]
+ }
+
+ var originInfo *OriginInfo
+ if opts.Update.Message.ReplyToMessage.ForwardOrigin != nil && opts.Update.Message.ReplyToMessage.ForwardOrigin.MessageOriginHiddenUser == nil {
+ originInfo = getMessageOriginData(opts.Update.Message.ReplyToMessage.ForwardOrigin)
+ } else if opts.Update.Message.Chat.Type != models.ChatTypePrivate {
+ originInfo = getMessageLink(opts.Update.Message)
+ }
+
+ var isSaved bool
+ var messageLength int
+ var pendingEntitites []models.MessageEntity
+ var needChangeEntitites bool = true
+
+ if opts.Update.Message.ReplyToMessage.Caption != "" {
+ messageLength = utf8.RuneCountInString(opts.Update.Message.ReplyToMessage.Caption)
+ pendingEntitites = opts.Update.Message.ReplyToMessage.CaptionEntities
+ } else if opts.Update.Message.ReplyToMessage.Text != "" {
+ messageLength = utf8.RuneCountInString(opts.Update.Message.ReplyToMessage.Text)
+ pendingEntitites = opts.Update.Message.ReplyToMessage.Entities
+ } else {
+ needChangeEntitites = false
+ }
+
+ if needChangeEntitites {
+ // 若字符长度大于设定的阈值,添加折叠样式引用再保存
+ if messageLength > textExpandableLength {
+ if len(pendingEntitites) == 1 && pendingEntitites[0].Type == models.MessageEntityTypeBlockquote && pendingEntitites[0].Offset == 0 && pendingEntitites[0].Length == messageLength {
+ // 如果消息仅为一个消息格式实体,且是不折叠形式的引用,则将格式实体改为可折叠格式引用后再保存
+ pendingEntitites = []models.MessageEntity{{
+ Type: models.MessageEntityTypeExpandableBlockquote,
+ Offset: 0,
+ Length: messageLength,
+ }}
+ } else {
+ // 其他则仅在末尾加一个可折叠形式的引用
+ pendingEntitites = append(pendingEntitites, models.MessageEntity{
+ Type: models.MessageEntityTypeExpandableBlockquote,
+ Offset: 0,
+ Length: messageLength,
+ })
+ }
+ }
+ }
+
+ replyMsgType := message_utils.GetMessageType(opts.Update.Message.ReplyToMessage)
+ switch {
+ case replyMsgType.OnlyText:
+ for i, n := range UserSavedMessage.Item.OnlyText {
+ if n.TitleAndMessageText == opts.Update.Message.ReplyToMessage.Text && reflect.DeepEqual(n.Entities, pendingEntitites) {
+ isSaved = true
+ messageParams.Text = "已保存过该文本\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此文本添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此文本的搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此文本的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.OnlyText[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+
+ if !isSaved {
+ UserSavedMessage.Item.OnlyText = append(UserSavedMessage.Item.OnlyText, SavedMessageTypeCachedOnlyText{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ TitleAndMessageText: opts.Update.Message.ReplyToMessage.Text,
+ Description: DescriptionText,
+ Entities: pendingEntitites,
+ LinkPreviewOptions: opts.Update.Message.ReplyToMessage.LinkPreviewOptions,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存文本"
+ }
+ case replyMsgType.Audio:
+ for i, n := range UserSavedMessage.Item.Audio {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Audio.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该音乐\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此音乐添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此音乐的搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此音乐的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Audio[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ UserSavedMessage.Item.Audio = append(UserSavedMessage.Item.Audio, SavedMessageTypeCachedAudio{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Audio.FileID,
+ Title: opts.Update.Message.ReplyToMessage.Audio.Title,
+ FileName: opts.Update.Message.ReplyToMessage.Audio.FileName,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存音乐"
+ }
+ case replyMsgType.Animation:
+ for i, n := range UserSavedMessage.Item.Mpeg4gif {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Animation.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该 GIF\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此 GIF 添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此 GIF 搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此 GIF 的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Mpeg4gif[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ UserSavedMessage.Item.Mpeg4gif = append(UserSavedMessage.Item.Mpeg4gif, SavedMessageTypeCachedMpeg4Gif{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Animation.FileID,
+ Title: opts.Update.Message.ReplyToMessage.Caption,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存 GIF"
+ }
+ case replyMsgType.Document:
+ if opts.Update.Message.ReplyToMessage.Document.MimeType == "image/gif" {
+ for i, n := range UserSavedMessage.Item.Gif {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Document.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该 GIF (文件)\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此 GIF (文件) 添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此 GIF (文件) 搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此 GIF (文件) 的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Gif[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ UserSavedMessage.Item.Gif = append(UserSavedMessage.Item.Gif, SavedMessageTypeCachedGif{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Document.FileID,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存 GIF (文件)"
+ }
+ } else {
+ for i, n := range UserSavedMessage.Item.Document {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Document.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该文件\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此文件添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此文件搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此文件的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Document[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ UserSavedMessage.Item.Document = append(UserSavedMessage.Item.Document, SavedMessageTypeCachedDocument{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Document.FileID,
+ Title: opts.Update.Message.ReplyToMessage.Document.FileName,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存文件"
+ }
+ }
+ case replyMsgType.Photo:
+ for i, n := range UserSavedMessage.Item.Photo {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Photo[len(opts.Update.Message.ReplyToMessage.Photo)-1].FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该图片\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此图片添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此图片搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此图片的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Photo[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ UserSavedMessage.Item.Photo = append(UserSavedMessage.Item.Photo, SavedMessageTypeCachedPhoto{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Photo[len(opts.Update.Message.ReplyToMessage.Photo)-1].FileID,
+ // Title: opts.Update.Message.ReplyToMessage.Caption,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ CaptionAboveMedia: opts.Update.Message.ReplyToMessage.ShowCaptionAboveMedia,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存图片"
+ }
+ case replyMsgType.Sticker:
+ for i, n := range UserSavedMessage.Item.Sticker {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Sticker.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该贴纸\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此贴纸添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此贴纸搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此贴纸的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Sticker[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+
+ if !isSaved {
+ if opts.Update.Message.ReplyToMessage.Sticker.SetName != "" {
+ stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{Name: opts.Update.Message.ReplyToMessage.Sticker.SetName})
+ if err != nil {
+ logger.Warn().
+ Err(err).
+ Str("setName", opts.Update.Message.ReplyToMessage.Sticker.SetName).
+ Msg("Failed to get sticker set info, save it as a custom sticker")
+ }
+ if stickerSet != nil {
+ // 属于一个贴纸包中的贴纸
+ UserSavedMessage.Item.Sticker = append(UserSavedMessage.Item.Sticker, SavedMessageTypeCachedSticker{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Sticker.FileID,
+ SetName: stickerSet.Name,
+ SetTitle: stickerSet.Title,
+ Description: DescriptionText,
+ Emoji: opts.Update.Message.ReplyToMessage.Sticker.Emoji,
+ OriginInfo: originInfo,
+ })
+ } else {
+ // 有贴纸信息,但是对应的贴纸包已经删掉了
+ UserSavedMessage.Item.Sticker = append(UserSavedMessage.Item.Sticker, SavedMessageTypeCachedSticker{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Sticker.FileID,
+ Description: DescriptionText,
+ Emoji: opts.Update.Message.ReplyToMessage.Sticker.Emoji,
+ OriginInfo: originInfo,
+ })
+ }
+ } else {
+ UserSavedMessage.Item.Sticker = append(UserSavedMessage.Item.Sticker, SavedMessageTypeCachedSticker{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Sticker.FileID,
+ Description: DescriptionText,
+ Emoji: opts.Update.Message.ReplyToMessage.Sticker.Emoji,
+ OriginInfo: originInfo,
+ })
+ }
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存贴纸"
+ }
+ case replyMsgType.Video:
+ for i, n := range UserSavedMessage.Item.Video {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Video.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该视频\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此视频添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此视频搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此视频的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Video[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ videoTitle := opts.Update.Message.ReplyToMessage.Video.FileName
+ if videoTitle == "" {
+ videoTitle = "video.mp4"
+ }
+ UserSavedMessage.Item.Video = append(UserSavedMessage.Item.Video, SavedMessageTypeCachedVideo{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Video.FileID,
+ Title: videoTitle,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存视频"
+ }
+ case replyMsgType.VideoNote:
+ for i, n := range UserSavedMessage.Item.VideoNote {
+ if n.FileID == opts.Update.Message.ReplyToMessage.VideoNote.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该圆形视频\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此圆形视频添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此圆形视频搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此圆形视频的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.VideoNote[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ UserSavedMessage.Item.VideoNote = append(UserSavedMessage.Item.VideoNote, SavedMessageTypeCachedVideoNote{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.VideoNote.FileID,
+ Title: opts.Update.Message.ReplyToMessage.VideoNote.FileUniqueID,
+ Description: DescriptionText,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存圆形视频"
+ }
+ case replyMsgType.Voice:
+ for i, n := range UserSavedMessage.Item.Voice {
+ if n.FileID == opts.Update.Message.ReplyToMessage.Voice.FileID {
+ isSaved = true
+ messageParams.Text = "已保存过该语音\n"
+ if DescriptionText != "" {
+ if n.Description == "" {
+ messageParams.Text += fmt.Sprintf("已为此语音添加搜索关键词 [ %s ]", DescriptionText)
+ } else if DescriptionText == n.Description {
+ messageParams.Text += fmt.Sprintf("此语音搜索关键词未修改 [ %s ]", DescriptionText)
+ needSave = false
+ break
+ } else {
+ messageParams.Text += fmt.Sprintf("已将此语音的搜索关键词从 [ %s ] 改为 [ %s ]", n.Description, DescriptionText)
+ }
+ n.Description = DescriptionText
+ UserSavedMessage.Item.Voice[i] = n
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ }
+ break
+ }
+ }
+ if !isSaved {
+ voiceTitle := DescriptionText
+ if voiceTitle == "" {
+ voiceTitle = opts.Update.Message.ReplyToMessage.Voice.MimeType
+ }
+ UserSavedMessage.Item.Voice = append(UserSavedMessage.Item.Voice, SavedMessageTypeCachedVoice{
+ ID: fmt.Sprintf("%d", UserSavedMessage.SavedTimes),
+ FileID: opts.Update.Message.ReplyToMessage.Voice.FileID,
+ Title: voiceTitle,
+ Description: DescriptionText,
+ Caption: opts.Update.Message.ReplyToMessage.Caption,
+ CaptionEntities: pendingEntitites,
+ OriginInfo: originInfo,
+ })
+ UserSavedMessage.Count++
+ UserSavedMessage.SavedTimes++
+ SavedMessageSet[opts.Update.Message.From.ID] = UserSavedMessage
+ messageParams.Text = "已保存语音"
+ }
+ default:
+ messageParams.Text = "暂不支持的消息类型"
+ }
+
+ if needSave {
+ err := SaveSavedMessageList(opts.Ctx)
+ if err != nil {
+ logger.Error().
+ Err(err).
+ Dict(utils.GetUserDict(opts.Update.Message.From)).
+ Str("saveMessageType", string(replyMsgType.InString())).
+ Msg("Failed to save savedmessage list after save a item")
+ handlerErr.Addf("failed to save savedmessage list after save a item: %w", err)
+
+ _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{
+ ChatID: opts.Update.Message.Chat.ID,
+ Text: fmt.Sprintf("保存内容时保存收藏列表数据库失败,请稍后再试或联系机器人管理员\n%s", err.Error()), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("saveMessageType", string(replyMsgType.InString())). + Str("content", "failed to save savedmessage list notice"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `failed to save savedmessage list notice` message: %w", err) + } + + return handlerErr.Flat() + } + } + } + + _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "saved message response"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `saved message response` message: %w", err) + } + } + } + + return handlerErr.Flat() +} + +// func channelSaveMessageHandler(opts *handler_structs.SubHandlerParams) { +// ChannelSavedMessage := SavedMessageSet[opts.Update.ChannelPost.From.ID] + +// messageParams := &bot.SendMessageParams{ +// ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, +// ParseMode: models.ParseModeHTML, +// } + +// if !ChannelSavedMessage.AgreePrivacyPolicy { +// messageParams.ChatID = opts.Update.ChannelPost.From.ID +// messageParams.Text = "此功能需要保存一些信息才能正常工作,在使用这个功能前,请先阅读一下我们会保存哪些信息" +// messageParams.ReplyMarkup = &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ +// Text: "点击查看", +// URL: fmt.Sprintf("https://t.me/%s?start=savedmessage_channel_privacy_policy", consts.BotMe.Username), +// }}}} +// _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams) +// if err != nil { +// log.Printf("Error response /save command initial info: %v", err) +// } +// return +// } + +// if ChannelSavedMessage.DiscussionID == 0 { +// messageParams.Text = "您需要为此频道绑定一个讨论群组,用于接收收藏成功的确认信息与关键词更改" +// _, err := opts.Thebot.SendMessage(opts.Ctx, messageParams) +// if err != nil { +// log.Printf("Error response /save command initial info: %v", err) +// } +// } + +// } + +func InlineShowSavedMessageHandler(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "InlineShowSavedMessageHandler"). + Logger() + + var handlerErr multe.MultiError + var InlineSavedMessageResultList []models.InlineQueryResult + var button *models.InlineQueryResultsButton + + SavedMessage := SavedMessageSet[opts.ChatInfo.ID] + + keywordFields := utils.InlineExtractKeywords(opts.Fields) + + if len(keywordFields) == 0 { + var all []models.InlineQueryResult + for _, n := range SavedMessage.Item.All() { + if n.onlyText != nil { + all = append(all, n.onlyText) + } else if n.audio != nil { + all = append(all, n.audio) + } else if n.document != nil { + all = append(all, n.document) + } else if n.gif != nil { + all = append(all, n.gif) + } else if n.photo != nil { + all = append(all, n.photo) + } else if n.sticker != nil { + all = append(all, n.sticker) + } else if n.video != nil { + all = append(all, n.video) + } else if n.videoNote != nil { + all = append(all, n.videoNote) + } else if n.voice != nil { + all = append(all, n.voice) + } else if n.mpeg4gif != nil { + all = append(all, n.mpeg4gif) + } + } + InlineSavedMessageResultList = all + } else { + var all []models.InlineQueryResult + for _, n := range SavedMessage.Item.All() { + if n.onlyText != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.onlyText.Description, n.onlyText.Title}) { + all = append(all, n.onlyText) + } else if n.audio != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.audio.Caption, n.sharedData.Description, n.sharedData.Title, n.sharedData.FileName}) { + all = append(all, n.audio) + } else if n.document != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.document.Title, n.document.Caption, n.document.Description}) { + all = append(all, n.document) + } else if n.gif != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.gif.Title, n.gif.Caption, n.sharedData.Description}) { + all = append(all, n.gif) + } else if n.mpeg4gif != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.mpeg4gif.Title, n.mpeg4gif.Caption, n.sharedData.Description}) { + all = append(all, n.mpeg4gif) + } else if n.photo != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.photo.Title, n.photo.Caption, n.photo.Description}) { + all = append(all, n.photo) + } else if n.sticker != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.sharedData.Title, n.sharedData.Name, n.sharedData.Description, n.sharedData.FileName}) { + all = append(all, n.sticker) + } else if n.video != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.video.Title, n.video.Caption, n.video.Description}) { + all = append(all, n.video) + } else if n.videoNote != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.videoNote.Title, n.videoNote.Caption, n.videoNote.Description}) { + all = append(all, n.videoNote) + } else if n.voice != nil && utils.InlineQueryMatchMultKeyword(keywordFields, []string{n.voice.Title, n.voice.Caption, n.sharedData.Description}) { + all = append(all, n.voice) + } + } + InlineSavedMessageResultList = all + + if len(InlineSavedMessageResultList) == 0 { + InlineSavedMessageResultList = append(InlineSavedMessageResultList, &models.InlineQueryResultArticle{ + ID: "none", + Title: "没有符合关键词的内容", + Description: fmt.Sprintf("没有找到包含 %s 的内容", keywordFields), + InputMessageContent: models.InputTextMessageContent{ + MessageText: "用户在找不到想看的东西时无奈点击了提示信息...", + ParseMode: models.ParseModeMarkdownV1, + }, + }) + } + } + + if len(InlineSavedMessageResultList) == 0 { + InlineSavedMessageResultList = append(InlineSavedMessageResultList, &models.InlineQueryResultArticle{ + ID: "empty", + Title: "没有保存内容(点击查看详细教程)", + Description: "对一条信息回复 /save 来保存它", + InputMessageContent: models.InputTextMessageContent{ + MessageText: fmt.Sprintf("您可以在任何聊天的输入栏中输入 @%s +saved来查看您的收藏\n若要添加,您需要确保机器人可以读取到您的指令,例如在群组中需要添加机器人,或点击 @%s 进入与机器人的聊天窗口,找到想要收藏的信息,然后对着那条信息回复 /save 即可\n若收藏成功,机器人会回复您并提示收藏成功,您也可以手动发送一条想要收藏的息,再使用 /save 命令回复它", consts.BotMe.Username, consts.BotMe.Username), + ParseMode: models.ParseModeHTML, + }, + }) + button = &models.InlineQueryResultsButton{ + Text: "点击此处快速跳转到机器人", + StartParameter: "via-inline_noreply", + } + } + + _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ + InlineQueryID: opts.Update.InlineQuery.ID, + Results: utils.InlineResultPagination(opts.Fields, InlineSavedMessageResultList), + IsPersonal: true, + Button: button, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("query", opts.Update.InlineQuery.Query). + Str("content", "saved message result"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `saved message result` inline answer: %w", err) + } + + return handlerErr.Flat() +} + +func SendPrivacyPolicy(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "SendPrivacyPolicy"). + Logger() + + var handlerErr multe.MultiError + + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "目前此机器人仍在开发阶段中,此信息可能会有更改\n" + + "本机器人提供收藏信息功能,您可以在回复一条信息时输入 /save 来收藏它,之后在 inline 模式下随时浏览您的收藏内容并发送\n\n" + + + "我们会记录哪些数据?\n" + + "1. 您的用户信息,例如 用户昵称、用户 ID、聊天类型(当您将此机器人添加到群组或频道中时)\n" + + "2. 您的使用情况,例如 消息计数、inline 调用计数、inline 条目计数、最后向机器人发送的消息、callback_query、inline_query 以及选择的 inline 结果\n" + + "3. 收藏信息内容,您需要注意这个,因为您是为了这个而阅读此内容,例如 存储的收藏信息数量、其图片上传到 Telegram 时的文件 ID、图片下方的文本,还有您在使用添加命令时所自定义的搜索关键词" + + "\n\n" + + + "我的数据安全吗?\n" + + "这是一个早期的项目,还有很多未发现的 bug 与漏洞,因此您不能也不应该将敏感的数据存储在此机器人中,若您觉得我们收集的信息不妥,您可以不点击底部的同意按钮,我们仅会收集一些基本的信息,防止对机器人造成滥用,基本信息为前一段的 1 至 2 条目" + + "\n\n" + + + "我收藏的消息,有谁可以看到?\n" + + "此功能被设计为每个人有单独的存储空间,如果您不手动从 inline 模式下选择信息并发送,其他用户是没法查看您的收藏列表的。不过,与上一个条目一样,为了防止滥用,我们是可以也有权利查看您收藏的内容的,请不要在其中保存隐私数据" + + "" + + + "\n\n" + + "内容待补充...", + ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "点击同意以上内容", + URL: fmt.Sprintf("https://t.me/%s?start=savedmessage_privacy_policy_agree", consts.BotMe.Username), + }}}}, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "saved message privacy policy"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `saved message privacy policy` message: %w", err) + } + + return handlerErr.Flat() +} + +func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "Saved Message"). + Str("funcName", "AgreePrivacyPolicy"). + Logger() + + var handlerErr multe.MultiError + + var UserSavedMessage SavedMessage + if len(SavedMessageSet) == 0 { SavedMessageSet = map[int64]SavedMessage{} } + UserSavedMessage.AgreePrivacyPolicy = true + SavedMessageSet[opts.ChatInfo.ID] = UserSavedMessage + + err := SaveSavedMessageList(opts.Ctx) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("failed to save savemessage list after user agree privacy policy") + handlerErr.Addf("failed to save savemessage list after user agree privacy policy: %w", err) + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: fmt.Sprintf("保存收藏列表数据库失败,请稍后再试或联系机器人管理员\n%s", err.Error()), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "failed to save savedmessage list notice"). + Msg(errt.SendMessage) + handlerErr.Addf("failed to send `failed to save savedmessage list notice` message: %w", err) + } + } else { + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.Chat.ID, + Text: "您已成功开启收藏信息功能,回复一条信息的时候发送 /save 来使用收藏功能吧!\n由于服务器性能原因,每个人的收藏数量上限默认为 100 个,您也可以从机器人的个人信息中寻找管理员来申请更高的上限\n点击下方按钮来浏览您的收藏内容", + ReplyParameters: &models.ReplyParameters{MessageID: opts.Update.Message.ID}, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "点击浏览您的收藏", + SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", + }}}}, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to send `saved message function enabled` message") + handlerErr.Addf("failed to send `saved message function enabled` message: %w", err) + } + } + + return handlerErr.Flat() +} + +func Init() { + plugin_utils.AddInitializer(plugin_utils.Initializer{ + Name: "Saved Message", + Func: ReadSavedMessageList, + }) + // ReadSavedMessageList() + plugin_utils.AddDataBaseHandler(plugin_utils.DatabaseHandler{ + Name: "Saved Message", + Saver: SaveSavedMessageList, + Loader: ReadSavedMessageList, + }) + plugin_utils.AddSlashSymbolCommandPlugins(plugin_utils.SlashSymbolCommand{ + SlashCommand: "save", + Handler: saveMessageHandler, + }) + plugin_utils.AddInlineManualHandlerPlugins(plugin_utils.InlineManualHandler{ + Command: "saved", + Handler: InlineShowSavedMessageHandler, + Description: "显示自己保存的消息", + }) + plugin_utils.AddSlashStartCommandPlugins([]plugin_utils.SlashStartHandler{ + { + Argument: "savedmessage_privacy_policy", + Handler: SendPrivacyPolicy, + }, + { + Argument: "savedmessage_privacy_policy_agree", + Handler: AgreePrivacyPolicy, + }, + // { + // Argument: "savedmessage_channel_privacy_policy", + // Handler: SendPrivacyPolicy, + // }, + // { + // Argument: "savedmessage_channel_privacy_policy_agree", + // Handler: AgreePrivacyPolicy, + // }, + }...) + plugin_utils.AddSlashStartWithPrefixCommandPlugins(plugin_utils.SlashStartWithPrefixHandler{ + Prefix: "via-inline", + Argument: "savedmessage-help", + Handler: saveMessageHandler, + }) + plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ + Name: "收藏消息", + Description: "此功能可以收藏用户指定的消息,之后使用 inline 模式查看并发送保存的内容\n\n保存消息:\n向机器人发送要保存的消息,然后使用 /save 关键词命令回复要保存的消息,关键词可以忽略。若机器人在群组中,也可以直接使用/save 关键词命令回复要保存的消息。\n\n发送保存的消息:点击下方的按钮来使用 inline 模式,当您多次在 inline 模式下使用此 bot 时,在输入框中输入@即可看到 bot 会出现在列表中", + ParseMode: models.ParseModeHTML, + ReplyMarkup: &models.InlineKeyboardMarkup{InlineKeyboard: [][]models.InlineKeyboardButton{ + {{ + Text: "点击浏览您的收藏", + SwitchInlineQueryCurrentChat: configs.BotConfig.InlineSubCommandSymbol + "saved ", + }}, + {{ + Text: "将此功能设定为您的默认 inline 命令", + CallbackData: "inline_default_noedit_saved", + }}, + }}, + }) +} diff --git a/bad_plugins/saved_message/item_structs.go b/plugins/saved_message/item_structs.go similarity index 91% rename from bad_plugins/saved_message/item_structs.go rename to plugins/saved_message/item_structs.go index 35d08d7..7c96361 100644 --- a/bad_plugins/saved_message/item_structs.go +++ b/plugins/saved_message/item_structs.go @@ -10,6 +10,7 @@ type SavedMessageSharedData struct { Description string } +// models.InlineQueryResultArticle type SavedMessageTypeCachedOnlyText struct { ID string `yaml:"ID"` TitleAndMessageText string `yaml:"TitleAndMessageText"` @@ -21,12 +22,14 @@ type SavedMessageTypeCachedOnlyText struct { OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedAudio type SavedMessageTypeCachedAudio struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` Caption string `yaml:"Caption,omitempty"` CaptionEntities []models.MessageEntity `yaml:"CaptionEntities,omitempty"` + // SharedData Title string `yaml:"Title,omitempty"` FileName string `yaml:"FileName,omitempty"` Description string `yaml:"Description,omitempty"` @@ -35,6 +38,7 @@ type SavedMessageTypeCachedAudio struct { OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedDocument type SavedMessageTypeCachedDocument struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` @@ -47,6 +51,7 @@ type SavedMessageTypeCachedDocument struct { OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedGif type SavedMessageTypeCachedGif struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` @@ -54,12 +59,29 @@ type SavedMessageTypeCachedGif struct { Caption string `yaml:"Caption,omitempty"` CaptionEntities []models.MessageEntity `yaml:"CaptionEntities,omitempty"` + // SharedData Description string `yaml:"Description,omitempty"` IsDeleted bool `yaml:"IsDeleted,omitempty"` OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedMpeg4Gif +type SavedMessageTypeCachedMpeg4Gif struct { + ID string `yaml:"ID"` + FileID string `yaml:"FileID"` + Title string `yaml:"Title,omitempty"` + Caption string `yaml:"Caption,omitempty"` + CaptionEntities []models.MessageEntity `yaml:"CaptionEntities,omitempty"` + + // SharedData + Description string `yaml:"Description,omitempty"` + + IsDeleted bool `yaml:"IsDeleted,omitempty"` + OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` +} + +// models.InlineQueryResultCachedPhoto type SavedMessageTypeCachedPhoto struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` @@ -73,18 +95,22 @@ type SavedMessageTypeCachedPhoto struct { OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedSticker type SavedMessageTypeCachedSticker struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` + // SharedData SetName string `yaml:"SetName,omitempty"` SetTitle string `yaml:"SetTitle,omitempty"` Description string `yaml:"Description,omitempty"` + Emoji string `yaml:"Emoji,omitempty"` // store in sharedata.FileName IsDeleted bool `yaml:"IsDeleted,omitempty"` OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedVideo type SavedMessageTypeCachedVideo struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` @@ -97,18 +123,20 @@ type SavedMessageTypeCachedVideo struct { OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedDocument type SavedMessageTypeCachedVideoNote struct { - IsDeleted bool `yaml:"IsDeleted,omitempty"` - OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` - ID string `yaml:"ID"` FileID string `yaml:"FileID"` Title string `yaml:"Title"` Description string `yaml:"Description,omitempty"` Caption string `yaml:"Caption,omitempty"` // 利用 bot 修改信息可以发出带文字的圆形视频,但是发送后不带文字 CaptionEntities []models.MessageEntity `yaml:"CaptionEntities,omitempty"` + + IsDeleted bool `yaml:"IsDeleted,omitempty"` + OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` } +// models.InlineQueryResultCachedVoice type SavedMessageTypeCachedVoice struct { ID string `yaml:"ID"` FileID string `yaml:"FileID"` @@ -116,19 +144,7 @@ type SavedMessageTypeCachedVoice struct { Caption string `yaml:"Caption,omitempty"` CaptionEntities []models.MessageEntity `yaml:"CaptionEntities,omitempty"` - Description string `yaml:"Description,omitempty"` - - IsDeleted bool `yaml:"IsDeleted,omitempty"` - OriginInfo *OriginInfo `yaml:"OriginInfo,omitempty"` -} - -type SavedMessageTypeCachedMpeg4Gif struct { - ID string `yaml:"ID"` - FileID string `yaml:"FileID"` - Title string `yaml:"Title,omitempty"` - Caption string `yaml:"Caption,omitempty"` - CaptionEntities []models.MessageEntity `yaml:"CaptionEntities,omitempty"` - + // SharedData Description string `yaml:"Description,omitempty"` IsDeleted bool `yaml:"IsDeleted,omitempty"` diff --git a/bad_plugins/saved_message/utils.go b/plugins/saved_message/utils.go similarity index 81% rename from bad_plugins/saved_message/utils.go rename to plugins/saved_message/utils.go index 6d2b800..7980056 100644 --- a/bad_plugins/saved_message/utils.go +++ b/plugins/saved_message/utils.go @@ -3,24 +3,22 @@ package saved_message import ( "context" "fmt" - "io" - "log" "os" "path/filepath" "strconv" "trbot/utils" "trbot/utils/consts" - "trbot/utils/type_utils" + "trbot/utils/type/message_utils" + "trbot/utils/yaml" "github.com/go-telegram/bot/models" "github.com/rs/zerolog" - "gopkg.in/yaml.v3" ) var SavedMessageSet map[int64]SavedMessage var SavedMessageErr error -var SavedMessage_path string = filepath.Join(consts.YAMLDataBasePath, "savedmessage/") +var SavedMessagePath string = filepath.Join(consts.YAMLDataBasePath, "savedmessage/", consts.YAMLFileName) var textExpandableLength int = 150 @@ -43,106 +41,55 @@ func SaveSavedMessageList(ctx context.Context) error { Str("funcName", "SaveSavedMessageList"). Logger() - data, err := yaml.Marshal(SavedMessageSet) + err := yaml.SaveYAML(SavedMessagePath, &SavedMessageSet) if err != nil { logger.Error(). Err(err). - Msg("Failed to marshal keyword list") - SavedMessageErr = err - return err + Str("path", SavedMessagePath). + Msg("Failed to save savedmessage list") + SavedMessageErr = fmt.Errorf("failed to save savedmessage list: %w", err) + } else { + SavedMessageErr = nil } - _, err = os.Stat(SavedMessage_path) - if err != nil { - if os.IsNotExist(err) { - logger.Warn(). - Msg("Savedmessage data directory not exist, now create it") - err = os.MkdirAll(SavedMessage_path, 0755) - if err != nil { - logger.Error(). - Err(err). - Msg("Failed to create savedmessage data directory") - SavedMessageErr = err - return err - } - logger.Trace(). - Msg("Savedmessage data directory created successfully") - } else { - logger.Error(). - Err(err). - Msg("Open savedmessage data directory failed") - SavedMessageErr = err - return err - } - } - - - if _, err := os.Stat(filepath.Join(SavedMessage_path, consts.YAMLFileName)); os.IsNotExist(err) { - _, err := os.Create(filepath.Join(SavedMessage_path, consts.YAMLFileName)) - if err != nil { - return err - } - } - - return os.WriteFile(filepath.Join(SavedMessage_path, consts.YAMLFileName), data, 0644) + return SavedMessageErr } func ReadSavedMessageList(ctx context.Context) error { - var savedList map[int64]SavedMessage logger := zerolog.Ctx(ctx). With(). Str("pluginName", "Saved Message"). Str("funcName", "ReadSavedMessageList"). Logger() - file, err := os.Open(filepath.Join(SavedMessage_path, consts.YAMLFileName)) + err := yaml.LoadYAML(SavedMessagePath, &SavedMessageSet) if err != nil { if os.IsNotExist(err) { logger.Warn(). - Msg("Not found database file. Create a new one") - // 如果是找不到目录,新建一个 - err = SaveSavedMessageList(ctx) + Err(err). + Str("path", SavedMessagePath). + Msg("Not found savedmessage list file. Created new one") + // 如果是找不到文件,新建一个 + err = yaml.SaveYAML(SavedMessagePath, &SavedMessageSet) if err != nil { logger.Error(). Err(err). - Msg("Create empty database file failed") - SavedMessageErr = err - return err + Str("path", SavedMessagePath). + Msg("Failed to create empty savedmessage list file") + SavedMessageErr = fmt.Errorf("failed to create empty savedmessage list file: %w", err) } } else { logger.Error(). Err(err). - Msg("Open database file failed") - SavedMessageErr = err - return err + Str("path", SavedMessagePath). + Msg("Failed to load savedmessage list file") + SavedMessageErr = fmt.Errorf("failed to load savedmessage list file: %w", err) } + } else { + SavedMessageErr = nil } - defer file.Close() - decoder := yaml.NewDecoder(file) - err = decoder.Decode(&savedList) - if err != nil { - if err == io.EOF { - logger.Warn(). - Msg("Saved Message list looks empty. now format it") - err = SaveSavedMessageList(ctx) - if err != nil { - logger.Error(). - Err(err). - Msg("Create empty database file failed") - SavedMessageErr = err - return err - } - } else { - logger.Error(). - Err(err). - Msg("Failed to decode savedmessage list") - SavedMessageErr = err - return err - } - } - SavedMessageSet = savedList - return nil + return SavedMessageErr } type sortstruct struct { @@ -165,12 +112,12 @@ type SavedMessageItems struct { Audio []SavedMessageTypeCachedAudio `yaml:"Audio,omitempty"` Document []SavedMessageTypeCachedDocument `yaml:"Document,omitempty"` Gif []SavedMessageTypeCachedGif `yaml:"Gif,omitempty"` + Mpeg4gif []SavedMessageTypeCachedMpeg4Gif `yaml:"Mpeg4Gif,omitempty"` Photo []SavedMessageTypeCachedPhoto `yaml:"Photo,omitempty"` Sticker []SavedMessageTypeCachedSticker `yaml:"Sticker,omitempty"` Video []SavedMessageTypeCachedVideo `yaml:"Video,omitempty"` VideoNote []SavedMessageTypeCachedVideoNote `yaml:"VideoNote,omitempty"` Voice []SavedMessageTypeCachedVoice `yaml:"Voice,omitempty"` - Mpeg4gif []SavedMessageTypeCachedMpeg4Gif `yaml:"Mpeg4Gif,omitempty"` } func (s *SavedMessageItems) All() []sortstruct { @@ -180,14 +127,14 @@ func (s *SavedMessageItems) All() []sortstruct { for _, v := range s.OnlyText { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].onlyText != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } // var pendingTitle string @@ -209,14 +156,14 @@ func (s *SavedMessageItems) All() []sortstruct { for _, v := range s.Audio { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].audio != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } list[index].audio = &models.InlineQueryResultCachedAudio{ @@ -228,20 +175,22 @@ func (s *SavedMessageItems) All() []sortstruct { } list[index].sharedData = &SavedMessageSharedData{ + Title: v.Title, + FileName: v.FileName, Description: v.Description, } } for _, v := range s.Document { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].document != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } list[index].document = &models.InlineQueryResultCachedDocument{ @@ -257,14 +206,14 @@ func (s *SavedMessageItems) All() []sortstruct { for _, v := range s.Gif { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].gif != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } list[index].gif = &models.InlineQueryResultCachedGif{ @@ -280,17 +229,42 @@ func (s *SavedMessageItems) All() []sortstruct { Description: v.Description, } } + for _, v := range s.Mpeg4gif { + index, err := strconv.Atoi(v.ID) + if err != nil { + fmt.Println("no an valid id", err) + continue + } + if len(list) <= index { + list = append(list, make([]sortstruct, index-len(list)+1)...) + } + if list[index].mpeg4gif != nil { + fmt.Println("duplicate id", v.ID) + continue + } + list[index].mpeg4gif = &models.InlineQueryResultCachedMpeg4Gif{ + ID: v.ID, + Mpeg4FileID: v.FileID, + Title: v.Title, + Caption: v.Caption, + CaptionEntities: v.CaptionEntities, + ReplyMarkup: buildFromInfoButton(v.OriginInfo), + } + list[index].sharedData = &SavedMessageSharedData{ + Description: v.Description, + } + } for _, v := range s.Photo { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].photo != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } list[index].photo = &models.InlineQueryResultCachedPhoto{ @@ -307,14 +281,14 @@ func (s *SavedMessageItems) All() []sortstruct { for _, v := range s.Sticker { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].sticker != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } list[index].sticker = &models.InlineQueryResultCachedSticker{ @@ -327,21 +301,25 @@ func (s *SavedMessageItems) All() []sortstruct { Name: v.SetName, Title: v.SetTitle, Description: v.Description, + FileName: v.Emoji, } } for _, v := range s.Video { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].video != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } + if v.Title == "" { + v.Title = "video.mp4" + } list[index].video = &models.InlineQueryResultCachedVideo{ ID: v.ID, VideoFileID: v.FileID, @@ -355,14 +333,14 @@ func (s *SavedMessageItems) All() []sortstruct { for _, v := range s.VideoNote { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].document != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } list[index].document = &models.InlineQueryResultCachedDocument{ @@ -378,16 +356,19 @@ func (s *SavedMessageItems) All() []sortstruct { for _, v := range s.Voice { index, err := strconv.Atoi(v.ID) if err != nil { - log.Println("no an valid id", err) + fmt.Println("no an valid id", err) continue } if len(list) <= index { list = append(list, make([]sortstruct, index-len(list)+1)...) } if list[index].voice != nil { - log.Println("duplicate id", v.ID) + fmt.Println("duplicate id", v.ID) continue } + if v.Title == "" { + v.Title = "audio" + } list[index].voice = &models.InlineQueryResultCachedVoice{ ID: v.ID, VoiceFileID: v.FileID, @@ -400,31 +381,7 @@ func (s *SavedMessageItems) All() []sortstruct { Description: v.Description, } } - for _, v := range s.Mpeg4gif { - index, err := strconv.Atoi(v.ID) - if err != nil { - log.Println("no an valid id", err) - continue - } - if len(list) <= index { - list = append(list, make([]sortstruct, index-len(list)+1)...) - } - if list[index].mpeg4gif != nil { - log.Println("duplicate id", v.ID) - continue - } - list[index].mpeg4gif = &models.InlineQueryResultCachedMpeg4Gif{ - ID: v.ID, - Mpeg4FileID: v.FileID, - Title: v.Title, - Caption: v.Caption, - CaptionEntities: v.CaptionEntities, - ReplyMarkup: buildFromInfoButton(v.OriginInfo), - } - list[index].sharedData = &SavedMessageSharedData{ - Description: v.Description, - } - } + // for _, n := range list { // if n.audio != nil { @@ -482,7 +439,7 @@ func getMessageOriginData(msgOrigin *models.MessageOrigin) *OriginInfo { func getMessageLink(msg *models.Message) *OriginInfo { // if msg.From.ID == msg.Chat.ID { // } - attr := type_utils.GetMessageAttribute(msg) + attr := message_utils.GetMessageAttribute(msg) if attr.IsFromLinkedChannel || attr.IsFromAnonymous || attr.IsUserAsChannel { return &OriginInfo{ FromName: utils.ShowChatName(msg.SenderChat), diff --git a/plugins/sub_package_plugin.go b/plugins/sub_package_plugin.go index 5a9f08c..cc78f83 100644 --- a/plugins/sub_package_plugin.go +++ b/plugins/sub_package_plugin.go @@ -1,5 +1,9 @@ package plugins +import ( + "trbot/plugins/saved_message" +) + /* This `sub_package_plugin.go` file allow you to import other packages. @@ -28,5 +32,5 @@ package plugins ``` */ func InitPlugins() { - // saved_message.Init() + saved_message.Init() } diff --git a/utils/configs/config.go b/utils/configs/config.go index 4dafbd4..bd32ff2 100644 --- a/utils/configs/config.go +++ b/utils/configs/config.go @@ -1,7 +1,7 @@ package configs import ( - "log" + "fmt" "strings" "github.com/go-telegram/bot" @@ -67,10 +67,10 @@ func (c config)LevelForZeroLog(forLogFile bool) zerolog.Level { return zerolog.PanicLevel default: if forLogFile { - log.Printf("Unknown log level [ %s ], using error level for log file", c.LogLevel) + fmt.Printf("Unknown log level [ %s ], using error level for log file", c.LogLevel) return zerolog.ErrorLevel } else { - log.Printf("Unknown log level [ %s ], using info level for console", c.LogLevel) + fmt.Printf("Unknown log level [ %s ], using info level for console", c.LogLevel) return zerolog.InfoLevel } } diff --git a/utils/internal_plugin/register.go b/utils/internal_plugin/register.go index e5a6b45..240ad6d 100644 --- a/utils/internal_plugin/register.go +++ b/utils/internal_plugin/register.go @@ -11,8 +11,10 @@ import ( "trbot/utils" "trbot/utils/configs" "trbot/utils/consts" + "trbot/utils/errt" "trbot/utils/handler_structs" "trbot/utils/mess" + "trbot/utils/multe" "trbot/utils/plugin_utils" "trbot/utils/signals" @@ -310,6 +312,7 @@ func Register(ctx context.Context) { }, Handler: func(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx) + var handlerErr multe.MultiError keywords := utils.InlineExtractKeywords(opts.Fields) if len(keywords) == 0 { _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ @@ -329,9 +332,10 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). - Str("command", "uaav"). - Msg("Failed to send `usage tips` inline result") - return err + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). + Str("content", "uaav command usage tips"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `uaav command usage tips` inline answer: %w", err) } } else if len(keywords) == 1 { if strings.HasPrefix(keywords[0], "https://") { @@ -349,10 +353,11 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). - Str("command", "uaav"). - Msg("Failed to send `valid voice url` inline result") - return err + Str("content", "uaav valid voice url"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `uaav valid voice url` inline answer: %w", err) } } else { _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ @@ -372,10 +377,11 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). - Str("command", "uaav"). - Msg("Failed to send `URL invalid` inline result") - return err + Str("content", "uaav invalid URL"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `uaav invalid URL` inline answer: %w", err) } } } else { @@ -396,19 +402,33 @@ func Register(ctx context.Context) { if err != nil { logger.Error(). Err(err). + Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("query", opts.Update.InlineQuery.Query). Str("command", "uaav"). Msg("Failed to send `too much argumunt` inline result") return err } } - return nil + return handlerErr.Flat() }, Description: "将一个音频链接作为语音格式发送", }) // inline 模式中以前缀触发的命令,需要自行处理输出。 plugin_utils.AddInlinePrefixHandlerPlugins([]plugin_utils.InlinePrefixHandler{ + { + PrefixCommand: "panic", + Attr: plugin_utils.InlineHandlerAttr{ + IsHideInCommandList: true, + IsCantBeDefault: true, + IsOnlyAllowAdmin: true, + }, + Handler: func(opts *handler_structs.SubHandlerParams) error { + // zerolog.Ctx(ctx).Error().Stack().Err(errors.WithStack(errors.New("test panic"))).Msg("") + panic("test panic") + }, + Description: "测试 panic", + }, { PrefixCommand: "log", Attr: plugin_utils.InlineHandlerAttr{ @@ -418,6 +438,7 @@ func Register(ctx context.Context) { }, Handler: func(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx) + var handlerErr multe.MultiError logs, err := mess.ReadLog() if err != nil { logger.Error(). @@ -425,8 +446,8 @@ func Register(ctx context.Context) { Str("query", opts.Update.InlineQuery.Query). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). Str("command", "log"). - Msg("Read log by inline command failed") - return err + Msg("Failed to read log by inline command") + handlerErr.Addf("failed to read log: %w", err) } if logs != nil { log_count := len(logs) @@ -453,13 +474,13 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("command", "log"). - Msg("Failed to send `log info` inline result") - - return err + Str("query", opts.Update.InlineQuery.Query). + Str("content", "log infos"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `log infos` inline answer: %w", err) } } - return nil + return handlerErr.Flat() }, Description: "显示日志", }, @@ -472,6 +493,7 @@ func Register(ctx context.Context) { }, Handler: func(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx) + var handlerErr multe.MultiError signals.SIGNALS.PluginDB_reload <- true _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -493,10 +515,12 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("command", "reloadpdb"). - Msg("Failed to send `reload plugin database info` inline result") + Str("query", opts.Update.InlineQuery.Query). + Str("content", "plugin database reloaded"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `plugin database reloaded` inline answer: %w", err) } - return err + return handlerErr.Flat() }, Description: "重新读取插件数据库", }, @@ -509,6 +533,7 @@ func Register(ctx context.Context) { }, Handler: func(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx) + var handlerErr multe.MultiError signals.SIGNALS.PluginDB_save <- true _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -530,10 +555,12 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("command", "savepdb"). - Msg("Failed to send `save plugin database info` inline result") + Str("query", opts.Update.InlineQuery.Query). + Str("content", "plugin database saved"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `plugin database saved` inline answer: %w", err) } - return err + return handlerErr.Flat() }, Description: "保存插件数据库", }, @@ -546,6 +573,7 @@ func Register(ctx context.Context) { }, Handler: func(opts *handler_structs.SubHandlerParams) error { logger := zerolog.Ctx(opts.Ctx) + var handlerErr multe.MultiError signals.SIGNALS.Database_save <- true _, err := opts.Thebot.AnswerInlineQuery(opts.Ctx, &bot.AnswerInlineQueryParams{ InlineQueryID: opts.Update.InlineQuery.ID, @@ -567,10 +595,12 @@ func Register(ctx context.Context) { logger.Error(). Err(err). Dict(utils.GetUserDict(opts.Update.InlineQuery.From)). - Str("command", "savedb"). - Msg("Failed to send `save database info` inline result") + Str("query", opts.Update.InlineQuery.Query). + Str("content", "database saved"). + Msg(errt.AnswerInlineQuery) + handlerErr.Addf("failed to send `database saved` inline answer: %w", err) } - return err + return handlerErr.Flat() }, Description: "保存数据库", }, diff --git a/utils/plugin_utils/handler_by_message_type.go b/utils/plugin_utils/handler_by_message_type.go index 4b9f403..da096c0 100644 --- a/utils/plugin_utils/handler_by_message_type.go +++ b/utils/plugin_utils/handler_by_message_type.go @@ -40,7 +40,7 @@ type HandlerByMessageType struct { /* If more than one such plugin is registered or the `AllowAutoTrigger`` flag is not `true` - + The bot will reply to the message that triggered this plugin and send a keyboard to let the user choose which plugin they want to use. @@ -48,7 +48,7 @@ type HandlerByMessageType struct { In this case, the data that the plugin needs to process will change from `update.Message` to in `opts.Update.CallbackQuery.Message.Message.ReplyToMessage`. - + But I'm not sure whether this field will be empty, so need to manually judge it in the plugin. @@ -104,11 +104,7 @@ func SelectHandlerByMessageTypeHandlerCallback(opts *handler_structs.SubHandlerP err := fmt.Errorf("no enough fields") logger.Error(). Err(err). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("CallbackQuery", opts.Update.CallbackQuery.Data). Msg("User selected callback query doesn't have enough fields") return err @@ -117,11 +113,7 @@ func SelectHandlerByMessageTypeHandlerCallback(opts *handler_structs.SubHandlerP handler, isExist := AllPlugins.HandlerByMessageType[models.ChatType(chatType)][message_utils.MessageTypeList(messageType)][pluginName] if isExist { logger.Debug(). - Dict("user", zerolog.Dict(). - Str("name", utils.ShowUserName(&opts.Update.CallbackQuery.From)). - Str("username", opts.Update.CallbackQuery.From.Username). - Int64("ID", opts.Update.CallbackQuery.From.ID), - ). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). Str("messageType", messageType). Str("pluginName", pluginName). Str("chatType", chatType). -- 2.49.1 From cc3dba55c440037a3a3b65efd761d427d1f25057 Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Sat, 28 Jun 2025 16:09:32 +0800 Subject: [PATCH 25/27] use index as key meaning result ID --- plugins/plugin_udonese.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index 7bddeda..f69961d 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -606,10 +606,10 @@ func udoneseInlineHandler(opts *handler_structs.SubHandlerParams) []models.Inlin } // 通过意思查找词 if utils.InlineQueryMatchMultKeyword(keywordFields, data.OnlyMeaning()) { - for _, n := range data.MeaningList { + for i, n := range data.MeaningList { if utils.InlineQueryMatchMultKeyword(keywordFields, []string{strings.ToLower(n.Meaning)}) { udoneseResultList = append(udoneseResultList, &models.InlineQueryResultArticle{ - ID: n.Meaning + "-meaning", + ID: fmt.Sprintf("%s-meaning-%d", data.Word, i), Title: n.Meaning, Description: fmt.Sprintf("%s 对应的词是 %s", n.Meaning, data.Word), InputMessageContent: models.InputTextMessageContent{ -- 2.49.1 From 8a7ca0f0b4ad36cb7190464ac728261bdc1970ec Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Mon, 30 Jun 2025 21:52:49 +0800 Subject: [PATCH 26/27] add plugin database map nil check show photo file id when get photo message --- handlers.go | 1 + plugins/plugin_limit_message.go | 4 ++++ plugins/saved_message/functions.go | 1 - plugins/saved_message/utils.go | 4 ++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/handlers.go b/handlers.go index 79a3ad4..6b17a5f 100644 --- a/handlers.go +++ b/handlers.go @@ -42,6 +42,7 @@ func defaultHandler(ctx context.Context, thebot *bot.Bot, update *models.Update) Dict(utils.GetChatDict(&update.Message.Chat)). Int("messageID", update.Message.ID). Str("caption", update.Message.Caption). + Str("photoID", update.Message.Photo[len(update.Message.Photo)-1].FileID). Msg("photoMessage") } else if update.Message.Sticker != nil { logger.Info(). diff --git a/plugins/plugin_limit_message.go b/plugins/plugin_limit_message.go index 442ca55..ab00a56 100644 --- a/plugins/plugin_limit_message.go +++ b/plugins/plugin_limit_message.go @@ -100,6 +100,10 @@ func ReadLimitMessageList(ctx context.Context) error { LimitMessageErr = nil } + if LimitMessageList == nil { + LimitMessageList = map[int64]AllowMessages{} + } + buildLimitGroupList() return LimitMessageErr diff --git a/plugins/saved_message/functions.go b/plugins/saved_message/functions.go index dd3f3dd..47396ac 100644 --- a/plugins/saved_message/functions.go +++ b/plugins/saved_message/functions.go @@ -794,7 +794,6 @@ func AgreePrivacyPolicy(opts *handler_structs.SubHandlerParams) error { var handlerErr multe.MultiError var UserSavedMessage SavedMessage - if len(SavedMessageSet) == 0 { SavedMessageSet = map[int64]SavedMessage{} } UserSavedMessage.AgreePrivacyPolicy = true SavedMessageSet[opts.ChatInfo.ID] = UserSavedMessage diff --git a/plugins/saved_message/utils.go b/plugins/saved_message/utils.go index 7980056..8c95025 100644 --- a/plugins/saved_message/utils.go +++ b/plugins/saved_message/utils.go @@ -89,6 +89,10 @@ func ReadSavedMessageList(ctx context.Context) error { SavedMessageErr = nil } + if SavedMessageSet == nil { + SavedMessageSet = map[int64]SavedMessage{} + } + return SavedMessageErr } -- 2.49.1 From f4e904ef4d0389b41543f6d99e5e620be2f2734e Mon Sep 17 00:00:00 2001 From: Hubert Chen <01@trle5.xyz> Date: Fri, 4 Jul 2025 00:54:31 +0800 Subject: [PATCH 27/27] refactor yaml database logger plugin_sticker: add sticker collect to channel feature allow detected `addsticker` link and show download sticker set button database: remove `IsInitialized` and `InitializedErr` in `DatabaseBackend` struct add `context.Context` params in `DatabaseBackend.Initializer` yaml_db: using `yaml.LoadYAML()` and `yaml.SaveYAML()` to save database file using `zerolog` logger replace `log` fix database file protection logic remove `addToYamlDB()` func update `Database.UpdateTimestamp` when change some flag handlers: fix `CustomSymbolCommand` trigger: use `strings.HasPrefix()` to replace `utils.CommandMaybeWithSuffixUsername()`, custom symbol command will not include robot username suffix consts: rename `YAMLDataBasePath` to `YAMLDataBaseDir` mess: remove `PrintLogAndSave()` func --- database/initial.go | 18 +- database/redis_db/redis.go | 10 +- database/yaml_db/yaml.go | 378 ++++++++++++++++++++----------- handlers.go | 2 +- plugins/plugin_detect_keyword.go | 2 +- plugins/plugin_limit_message.go | 2 +- plugins/plugin_sticker.go | 286 +++++++++++++++++++++-- plugins/plugin_teamspeak3.go | 2 +- plugins/plugin_udonese.go | 2 +- plugins/plugin_voicelist.go | 2 +- plugins/saved_message/utils.go | 2 +- utils/consts/consts.go | 4 +- utils/mess/mess.go | 19 -- utils/signals/signals.go | 2 +- 14 files changed, 531 insertions(+), 200 deletions(-) diff --git a/database/initial.go b/database/initial.go index 273c166..c8b35ad 100644 --- a/database/initial.go +++ b/database/initial.go @@ -18,9 +18,7 @@ type DatabaseBackend struct { // 数据库等级,低优先级的数据库不会实时同步更改,程序仅会在高优先级数据库不可用才会尝试使用其中的数据 IsLowLevel bool - Initializer func() (bool, error) // 数据库初始化函数 - IsInitialized bool // 是否已被成功初始化 - InitializedErr error // 初始化错误 + Initializer func(ctx context.Context) error // 数据库初始化函数 // 数据库保存和读取函数 SaveDatabase func(ctx context.Context) error @@ -47,8 +45,13 @@ func AddDatabaseBackends(ctx context.Context, backends ...DatabaseBackend) int { var count int for _, db := range backends { - db.IsInitialized, db.InitializedErr = db.Initializer() - if db.IsInitialized { + err := db.Initializer(ctx) + if err != nil { + logger.Error(). + Err(err). + Str("database", db.Name). + Msg("Failed to initialize database") + } else { if db.IsLowLevel { DBBackends_LowLevel = append(DBBackends_LowLevel, db) } else { @@ -59,11 +62,6 @@ func AddDatabaseBackends(ctx context.Context, backends ...DatabaseBackend) int { Str("databaseLevel", utils.TextForTrueOrFalse(db.IsLowLevel, "low", "high")). Msg("Database initialized") count++ - } else { - logger.Error(). - Err(db.InitializedErr). - Str("database", db.Name). - Msg("Failed to initialize database") } } diff --git a/database/redis_db/redis.go b/database/redis_db/redis.go index 6d489da..3d7e3db 100644 --- a/database/redis_db/redis.go +++ b/database/redis_db/redis.go @@ -17,7 +17,7 @@ import ( var UserDB *redis.Client // 用户数据 -func InitializeDB() (bool, error) { +func InitializeDB(ctx context.Context) error { if configs.BotConfig.RedisURL != "" { if configs.BotConfig.RedisDatabaseID != -1 { UserDB = redis.NewClient(&redis.Options{ @@ -25,15 +25,15 @@ func InitializeDB() (bool, error) { Password: configs.BotConfig.RedisPassword, DB: configs.BotConfig.RedisDatabaseID, }) - err := UserDB.Ping(context.Background()).Err() + err := UserDB.Ping(ctx).Err() if err != nil { - return false, fmt.Errorf("failed to ping Redis [%d] database: %w", configs.BotConfig.RedisDatabaseID, err) + return fmt.Errorf("failed to ping Redis [%d] database: %w", configs.BotConfig.RedisDatabaseID, err) } } - return true, nil + return nil } else { - return false, fmt.Errorf("RedisURL is empty") + return fmt.Errorf("RedisURL is empty") } } diff --git a/database/yaml_db/yaml.go b/database/yaml_db/yaml.go index 779eedc..4d6641b 100644 --- a/database/yaml_db/yaml.go +++ b/database/yaml_db/yaml.go @@ -3,8 +3,6 @@ package yaml_db import ( "context" "fmt" - "io" - "log" "os" "path/filepath" "reflect" @@ -12,14 +10,16 @@ import ( "trbot/database/db_struct" "trbot/utils" "trbot/utils/consts" - "trbot/utils/mess" + "trbot/utils/yaml" "github.com/go-telegram/bot/models" - "gopkg.in/yaml.v3" + "github.com/rs/zerolog" ) var Database DataBaseYaml +var YAMLDatabasePath = filepath.Join(consts.YAMLDataBaseDir, consts.YAMLFileName) + // 需要重构,错误信息不足 type DataBaseYaml struct { @@ -32,167 +32,267 @@ type DataBaseYaml struct { } `yaml:"Data"` } -func InitializeDB() (bool, error) { - if consts.YAMLDataBasePath != "" { - var err error - Database, err = ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)) +func InitializeDB(ctx context.Context) error { + if consts.YAMLDataBaseDir != "" { + err := ReadDatabase(ctx) if err != nil { - return false, fmt.Errorf("failed to read yaml databse: %s", err) + return fmt.Errorf("failed to read yaml database: %s", err) } - return true, nil + return nil } else { - return false, fmt.Errorf("yaml database path is empty") + return fmt.Errorf("yaml database path is empty") } } func SaveDatabase(ctx context.Context) error { + logger := zerolog.Ctx(ctx). + With(). + Str("database", "yaml"). + Str("funcName", "SaveDatabase"). + Logger() + Database.UpdateTimestamp = time.Now().Unix() - return SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) + err := yaml.SaveYAML(YAMLDatabasePath, &Database) + if err != nil { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to save database") + return fmt.Errorf("failed to save database: %w", err) + } + + return nil } func ReadDatabase(ctx context.Context) error { - var err error - Database, err = ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)) - return err + logger := zerolog.Ctx(ctx). + With(). + Str("database", "yaml"). + Str("funcName", "ReadDatabase"). + Logger() + + err := yaml.LoadYAML(YAMLDatabasePath, &Database) + if err != nil { + if os.IsNotExist(err) { + logger.Warn(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Not found database file. Created new one") + // 如果是找不到文件,新建一个 + err = yaml.SaveYAML(YAMLDatabasePath, &Database) + if err != nil { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to create empty database file") + return fmt.Errorf("failed to create empty database file: %w", err) + } + } else { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to read database file") + return fmt.Errorf("failed to read database file: %w", err) + } + } + + return nil } -func ReadYamlDB(pathToFile string) (DataBaseYaml, error) { - file, err := os.Open(pathToFile) +func ReadYamlDB(ctx context.Context, pathToFile string) (*DataBaseYaml, error) { + logger := zerolog.Ctx(ctx). + With(). + Str("database", "yaml"). + Str("funcName", "ReadYamlDB"). + Logger() + + var tempDatabase *DataBaseYaml + err := yaml.LoadYAML(pathToFile, &tempDatabase) if err != nil { - log.Println("[Database_yaml]: Not found Database file. Created new one") - err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, DataBaseYaml{}) - if err != nil { - return DataBaseYaml{}, err + if os.IsNotExist(err) { + logger.Warn(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Not found database file. Created new one") + // 如果是找不到文件,新建一个 + err = yaml.SaveYAML(YAMLDatabasePath, &tempDatabase) + if err != nil { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to create empty database file") + return nil, fmt.Errorf("failed to create empty database file: %w", err) + } } else { - return DataBaseYaml{}, nil + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to read database file") + return nil, fmt.Errorf("failed to read database file: %w", err) } } - defer file.Close() - var Database DataBaseYaml - decoder := yaml.NewDecoder(file) - err = decoder.Decode(&Database) - if err != nil { - if err == io.EOF { - log.Println("[Database]: Database looks empty. now format it") - SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, DataBaseYaml{}) - return DataBaseYaml{}, nil - } - return DataBaseYaml{}, err - } - - return Database, nil + return tempDatabase, nil } // 路径 文件名 YAML 数据结构体 -func SaveYamlDB(path string, name string, Database interface{}) error { - data, err := yaml.Marshal(Database) - if err != nil { return err } +func SaveYamlDB(ctx context.Context, path, name string, tempDatabase interface{}) error { + logger := zerolog.Ctx(ctx). + With(). + Str("database", "yaml"). + Str("funcName", "SaveDatabase"). + Logger() - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.MkdirAll(path, 0755); err != nil { - return err - } - } - - if _, err := os.Stat(path + name); os.IsNotExist(err) { - _, err := os.Create(path + name) - if err != nil { - return err - } - } - - return os.WriteFile(path + name, data, 0644) -} - -// 添加数据 -func addToYamlDB(params *db_struct.ChatInfo) { - Database.Data.ChatInfo = append(Database.Data.ChatInfo, *params) -} - -func AutoSaveDatabaseHandler() { - // 先读取一下数据库文件 - savedDatabase, err := ReadYamlDB(filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName)) + err := yaml.SaveYAML(filepath.Join(path, name), &tempDatabase) if err != nil { - log.Println("some issues when read Database file", err) - // 如果读取数据库文件时发现数据库为空,使用当前的数据重建数据库文件 - if reflect.DeepEqual(savedDatabase, DataBaseYaml{}){ - mess.PrintLogAndSave("The Database file is empty, recovering Database file using current data") - err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) - if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when recovering empty Database:", err)) - } else { - mess.PrintLogAndSave(fmt.Sprintf("The Database is recovered to %s", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))) - } - return - } + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to save database") + return fmt.Errorf("failed to save database: %w", err) } - // 没有修改就跳过保存 - if reflect.DeepEqual(savedDatabase, Database) { - log.Println("looks Database no any change, skip autosave this time") - } else { - // 如果数据库文件中有设定专用的 `FORCEOVERWRITE: true` 覆写标记,无论任何修改,先保存程序中的数据,再读取新的数据替换掉当前的数据并保存 - if savedDatabase.ForceOverwrite { - mess.PrintLogAndSave(fmt.Sprintf("The `FORCEOVERWRITE: true` in %s is detected", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))) - oldFileName := fmt.Sprintf("beforeOverwritten_%d_%s", time.Now().Unix(), consts.YAMLFileName) - err := SaveYamlDB(consts.YAMLDataBasePath, oldFileName, savedDatabase) - if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when saving the Database before overwritten:", err)) - } else { - mess.PrintLogAndSave(fmt.Sprintf("The Database before overwritten is saved to %s", consts.YAMLDataBasePath + oldFileName)) - } - Database = savedDatabase - Database.ForceOverwrite = false // 移除强制覆盖标记 - err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) - if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when recreat Database using new Database:", err)) - } else { - mess.PrintLogAndSave(fmt.Sprintf("Success read data from the new file and saved to %s", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))) - } - } else if savedDatabase.UpdateTimestamp > Database.UpdateTimestamp { // 没有设定覆写标记,检测到本地的数据库更新时间比程序中的更新时间更晚 - log.Println("The saved Database is newer than current data in the program") - // 如果只是更新时间有差别,更新一下时间,再保存就行 - if reflect.DeepEqual(savedDatabase.Data, Database.Data) { - log.Println("But current data and Database is the same, updating UpdateTimestamp in the Database only") - Database.UpdateTimestamp = time.Now().Unix() - err := SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) - if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when update Timestamp in Database:", err)) - } else { - mess.PrintLogAndSave("Update Timestamp in Database at " + time.Now().Format(time.RFC3339)) - } - } else { - // 数据库文件与程序中的数据不同,将新的数据文件改名另存为 `edited_时间戳_文件名`,再使用程序中的数据还原数据文件 - log.Println("Saved Database is different from the current Database") - editedFileName := fmt.Sprintf("edited_%d_%s", time.Now().Unix(), consts.YAMLFileName) - // 提示不要在程序运行的时候乱动数据库文件 - log.Println("Do not modify the Database file while the program is running, saving modified file and recovering Database file using current data") - err := SaveYamlDB(consts.YAMLDataBasePath, editedFileName, savedDatabase) - if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when saving modified Database:", err)) - } else { - mess.PrintLogAndSave(fmt.Sprintf("The modified Database is saved to %s", consts.YAMLDataBasePath + editedFileName)) - } - err = SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) - if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when recovering Database:", err)) - } else { - mess.PrintLogAndSave(fmt.Sprintf("The Database is recovered to %s", filepath.Join(consts.YAMLDataBasePath, consts.YAMLFileName))) - } - } - } else { // 数据有更改,程序内的更新时间也比本地数据库晚,正常保存 - // 正常情况下更新时间就是会比程序内的时间晚,手动修改数据库途中如果有数据变动,而手动修改的时候没有修改时间戳,不会触发上面的保护机制,会直接覆盖手动修改的内容 - // 所以无论如何都尽量不要手动修改数据库文件,如果必要也请在开头添加专用的 `FORCEOVERWRITE: true` 覆写标记,或停止程序后再修改 - Database.UpdateTimestamp = time.Now().Unix() - err := SaveYamlDB(consts.YAMLDataBasePath, consts.YAMLFileName, Database) + return nil +} + +func AutoSaveDatabaseHandler(ctx context.Context) { + logger := zerolog.Ctx(ctx). + With(). + Str("database", "yaml"). + Str("funcName", "AutoSaveDatabaseHandler"). + Logger() + + // 先读取一下数据库文件 + savedDatabase, err := ReadYamlDB(ctx, YAMLDatabasePath) + if err != nil { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to read database file") + } else { + // 如果读取数据库文件时发现数据库为空,使用当前的数据重建数据库文件 + if savedDatabase == nil { + logger.Warn(). + Str("path", YAMLDatabasePath). + Msg("The database file is empty, recover database file using current data") + err = SaveYamlDB(ctx, consts.YAMLDataBaseDir, consts.YAMLFileName, Database) if err != nil { - mess.PrintLogAndSave(fmt.Sprintln("some issues happend when auto saving Database:", err)) + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to recover database file using current data") } else { - mess.PrintLogAndSave("auto save at " + time.Now().Format(time.RFC3339)) + logger.Warn(). + Str("path", YAMLDatabasePath). + Msg("The database file is recovered using current data") + } + } else if reflect.DeepEqual(*savedDatabase, Database) { + // 没有修改就跳过保存 + logger.Debug().Msg("looks Database no any change, skip autosave this time") + } else { + // 如果数据库文件中有设定专用的 `FORCEOVERWRITE: true` 覆写标记 + // 无论任何修改,先保存程序中的数据,再读取新的数据替换掉当前的数据并保存 + if savedDatabase.ForceOverwrite { + logger.Warn(). + Str("path", YAMLDatabasePath). + Msg("Detected `FORCEOVERWRITE: true` in database file, save current database to another file first") + + oldFileName := fmt.Sprintf("beforeOverwritten_%d_%s", time.Now().Unix(), consts.YAMLFileName) + oldFilePath := filepath.Join(consts.YAMLDataBaseDir, oldFileName) + + err := SaveYamlDB(ctx, consts.YAMLDataBaseDir, oldFileName, savedDatabase) + if err != nil { + logger.Warn(). + Err(err). + Str("oldPath", oldFilePath). + Msg("Failed to save the database before overwrite") + } else { + logger.Warn(). + Err(err). + Str("oldPath", oldFilePath). + Msg("The Database before overwrite is saved to another file") + } + Database = *savedDatabase + Database.ForceOverwrite = false // 移除强制覆盖标记 + err = SaveYamlDB(ctx, consts.YAMLDataBaseDir, consts.YAMLFileName, Database) + if err != nil { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to save the database after overwrite") + } else { + logger.Warn(). + Str("path", YAMLDatabasePath). + Msg("Read new database file and save to the old file") + } + } else { + // 没有设定覆写标记,检测到本地的数据库更新时间比程序中的更新时间更晚 + if savedDatabase.UpdateTimestamp >= Database.UpdateTimestamp { + logger.Warn(). + Msg("The database file is newer than current data in the program") + // 如果只是更新时间有差别,更新一下时间,再保存就行 + if reflect.DeepEqual(savedDatabase.Data, Database.Data) { + logger.Warn(). + Msg("But current data and Database is the same, updating UpdateTimestamp in the Database only") + Database.UpdateTimestamp = time.Now().Unix() + err := SaveYamlDB(ctx, consts.YAMLDataBaseDir, consts.YAMLFileName, Database) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to save database after updating UpdateTimestamp") + } + } else { + // 数据库文件与程序中的数据不同,提示不要在程序运行的时候乱动数据库文件 + logger.Warn(). + Str("notice", "Do not modify the database file while the program is running, If you want to overwrite the current database, please add the field `FORCEOVERWRITE: true` at the beginning of the file."). + Msg("The database file is different from the current database, saving modified file and recovering database file using current data in the program") + + // 将新的数据文件改名另存为 `edited_时间戳_文件名`,再使用程序中的数据还原数据文件 + editedFileName := fmt.Sprintf("edited_%d_%s", time.Now().Unix(), consts.YAMLFileName) + editedFilePath := filepath.Join(consts.YAMLDataBaseDir, editedFileName) + + err := SaveYamlDB(ctx, consts.YAMLDataBaseDir, editedFileName, savedDatabase) + if err != nil { + logger.Error(). + Err(err). + Str("editedPath", editedFilePath). + Msg("Failed to save modified database") + } else { + logger.Warn(). + Str("editedPath", editedFilePath). + Msg("The modified database is saved to another file") + } + err = SaveYamlDB(ctx, consts.YAMLDataBaseDir, consts.YAMLFileName, Database) + if err != nil { + logger.Error(). + Err(err). + Str("path", YAMLDatabasePath). + Msg("Failed to recover database file") + } else { + logger.Warn(). + Str("path", YAMLDatabasePath). + Msg("The database file is recovered using current data in the program") + } + } + } else { + // 数据有更改,程序内的更新时间也比本地数据库晚,正常保存 + // 无论如何都尽量不要手动修改数据库文件,如果必要也请在开头添加专用的 `FORCEOVERWRITE: true` 覆写标记,或停止程序后再修改 + Database.UpdateTimestamp = time.Now().Unix() + err := SaveYamlDB(ctx, consts.YAMLDataBaseDir, consts.YAMLFileName, Database) + if err != nil { + logger.Error(). + Err(err). + Msg("Failed to auto save database") + } else { + logger.Debug(). + Str("path", YAMLDatabasePath). + Msg("The database is auto saved") + } + } } } } + } // 初次添加群组时,获取必要信息 @@ -202,7 +302,7 @@ func InitChat(ctx context.Context, chat *models.Chat) error { return nil // 群组已存在,不重复添加 } } - addToYamlDB(&db_struct.ChatInfo{ + Database.Data.ChatInfo = append(Database.Data.ChatInfo, db_struct.ChatInfo{ ID: chat.ID, ChatType: chat.Type, ChatName: utils.ShowChatName(chat), @@ -217,7 +317,7 @@ func InitUser(ctx context.Context, user *models.User) error { return nil // 用户已存在,不重复添加 } } - addToYamlDB(&db_struct.ChatInfo{ + Database.Data.ChatInfo = append(Database.Data.ChatInfo, db_struct.ChatInfo{ ID: user.ID, ChatType: models.ChatTypePrivate, ChatName: utils.ShowUserName(user), @@ -239,6 +339,7 @@ func GetChatInfo(ctx context.Context, id int64) (*db_struct.ChatInfo, error) { func IncrementalUsageCount(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_UsageCount) error { for Index, Data := range Database.Data.ChatInfo { if Data.ID == chatID { + Database.UpdateTimestamp = time.Now().Unix() v := reflect.ValueOf(&Database.Data.ChatInfo[Index]).Elem() for i := 0; i < v.NumField(); i++ { if v.Type().Field(i).Name == string(fieldName) { @@ -254,6 +355,7 @@ func IncrementalUsageCount(ctx context.Context, chatID int64, fieldName db_struc func RecordLatestData(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_LatestData, value string) error { for Index, Data := range Database.Data.ChatInfo { if Data.ID == chatID { + Database.UpdateTimestamp = time.Now().Unix() v := reflect.ValueOf(&Database.Data.ChatInfo[Index]).Elem() for i := 0; i < v.NumField(); i++ { if v.Type().Field(i).Name == string(fieldName) { @@ -269,6 +371,7 @@ func RecordLatestData(ctx context.Context, chatID int64, fieldName db_struct.Cha func UpdateOperationStatus(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_Status, value bool) error { for Index, Data := range Database.Data.ChatInfo { if Data.ID == chatID { + Database.UpdateTimestamp = time.Now().Unix() v := reflect.ValueOf(&Database.Data.ChatInfo[Index]).Elem() for i := 0; i < v.NumField(); i++ { if v.Type().Field(i).Name == string(fieldName) { @@ -284,6 +387,7 @@ func UpdateOperationStatus(ctx context.Context, chatID int64, fieldName db_struc func SetCustomFlag(ctx context.Context, chatID int64, fieldName db_struct.ChatInfoField_CustomFlag, value string) error { for Index, Data := range Database.Data.ChatInfo { if Data.ID == chatID { + Database.UpdateTimestamp = time.Now().Unix() v := reflect.ValueOf(&Database.Data.ChatInfo[Index]).Elem() for i := 0; i < v.NumField(); i++ { if v.Type().Field(i).Name == string(fieldName) { diff --git a/handlers.go b/handlers.go index 6b17a5f..8038d82 100644 --- a/handlers.go +++ b/handlers.go @@ -394,7 +394,7 @@ func messageHandler(opts *handler_structs.SubHandlerParams) { } else if len(opts.Update.Message.Text) > 0 { // 没有 `/` 号作为前缀,检查是不是自定义命令 for _, plugin := range plugin_utils.AllPlugins.CustomSymbolCommand { - if utils.CommandMaybeWithSuffixUsername(opts.Fields, plugin.FullCommand) { + if strings.HasPrefix(opts.Update.Message.Text, plugin.FullCommand) { logger.Info(). Str("fullCommand", plugin.FullCommand). Str("message", opts.Update.Message.Text). diff --git a/plugins/plugin_detect_keyword.go b/plugins/plugin_detect_keyword.go index 8d602d0..5f673c4 100644 --- a/plugins/plugin_detect_keyword.go +++ b/plugins/plugin_detect_keyword.go @@ -26,7 +26,7 @@ var KeywordDataList KeywordData = KeywordData{ Users: map[int64]KeywordUserList{}, } var KeywordDataErr error -var KeywordDataDir string = filepath.Join(consts.YAMLDataBasePath, "detectkeyword/") +var KeywordDataDir string = filepath.Join(consts.YAMLDataBaseDir, "detectkeyword/") var KeywordDataPath string = filepath.Join(KeywordDataDir, consts.YAMLFileName) func init() { diff --git a/plugins/plugin_limit_message.go b/plugins/plugin_limit_message.go index ab00a56..791c6e3 100644 --- a/plugins/plugin_limit_message.go +++ b/plugins/plugin_limit_message.go @@ -26,7 +26,7 @@ import ( var LimitMessageList map[int64]AllowMessages var LimitMessageErr error -var LimitMessageDir string = filepath.Join(consts.YAMLDataBasePath, "limitmessage/") +var LimitMessageDir string = filepath.Join(consts.YAMLDataBaseDir, "limitmessage/") var LimitMessagePath string = filepath.Join(LimitMessageDir, consts.YAMLFileName) type AllowMessages struct { diff --git a/plugins/plugin_sticker.go b/plugins/plugin_sticker.go index 82dd1f6..08e8b9b 100644 --- a/plugins/plugin_sticker.go +++ b/plugins/plugin_sticker.go @@ -10,6 +10,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" "trbot/database" "trbot/database/db_struct" "trbot/utils" @@ -27,6 +28,8 @@ import ( "golang.org/x/image/webp" ) +var StickerCollectionChannelID int64 = -1002506914682 + var StickerCache_path string = filepath.Join(consts.CacheDirectory, "sticker/") var StickerCachePNG_path string = filepath.Join(consts.CacheDirectory, "sticker_png/") var StickerCacheGIF_path string = filepath.Join(consts.CacheDirectory, "sticker_gif/") @@ -44,6 +47,20 @@ func init() { CommandChar: "S", Handler: DownloadStickerPackCallBackHandler, }, + { + CommandChar: "c", + Handler: collectStickerSet, + }, + }...) + plugin_utils.AddCustomSymbolCommandPlugins([]plugin_utils.CustomSymbolCommand{ + { + FullCommand: "https://t.me/addstickers/", + Handler: getStickerPackInfo, + }, + { + FullCommand: "t.me/addstickers/", + Handler: getStickerPackInfo, + }, }...) plugin_utils.AddHandlerHelpInfo(plugin_utils.HandlerHelp{ Name: "贴纸下载", @@ -71,6 +88,10 @@ type stickerDatas struct { StickerIndex int StickerSetName string // 贴纸包的 urlname StickerSetTitle string // 贴纸包名称 + + WebP int + WebM int + tgs int } func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { @@ -154,18 +175,27 @@ func EchoStickerHandler(opts *handler_structs.SubHandlerParams) error { if stickerData.IsCustomSticker { stickerFilePrefix = "sticker" } else { - stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) - - // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 - documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) - documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ + var button [][]models.InlineKeyboardButton = [][]models.InlineKeyboardButton{ { { Text: "下载贴纸包中的静态贴纸", CallbackData: fmt.Sprintf("S_%s", opts.Update.Message.Sticker.SetName) }, }, { { Text: "下载整个贴纸包(不转换格式)", CallbackData: fmt.Sprintf("s_%s", opts.Update.Message.Sticker.SetName) }, }, - }} + } + + if StickerCollectionChannelID != 0 && utils.AnyContains(opts.Update.Message.From.ID, configs.BotConfig.AdminIDs) { + button = append(button, []models.InlineKeyboardButton{{ + Text: "⭐️ 收藏至频道", + CallbackData: fmt.Sprintf("c_%s", stickerData.StickerSetName), + }}) + } + + stickerFilePrefix = fmt.Sprintf("%s_%d", stickerData.StickerSetName, stickerData.StickerIndex) + + // 仅在不为自定义贴纸时显示下载整个贴纸包按钮 + documentParams.Caption += fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerData.StickerSetName, stickerData.StickerSetTitle, stickerData.StickerCount) + documentParams.ReplyMarkup = &models.InlineKeyboardMarkup{ InlineKeyboard: button } } documentParams.Document = &models.InputFileUpload{ Filename: fmt.Sprintf("%s.%s", stickerFilePrefix, stickerFileSuffix), Data: stickerData.Data } @@ -617,10 +647,6 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S var allCached bool = true var allConverted bool = true - var stickerCount_webm int - var stickerCount_tgs int - var stickerCount_webp int - for i, sticker := range stickerSet.Stickers { stickerfileName := fmt.Sprintf("%s %d %s.", sticker.SetName, i, sticker.FileID) var fileSuffix string @@ -628,13 +654,13 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S // 根据贴纸类型设置文件扩展名和统计贴纸数量 if sticker.IsVideo { fileSuffix = "webm" - stickerCount_webm++ + data.WebM++ } else if sticker.IsAnimated { fileSuffix = "tgs" - stickerCount_tgs++ + data.tgs++ } else { fileSuffix = "webp" - stickerCount_webp++ + data.WebP++ } var originFullPath string = filepath.Join(filePath, stickerfileName + fileSuffix) @@ -776,22 +802,22 @@ func getStickerPack(opts *handler_structs.SubHandlerParams, stickerSet *models.S // 根据要下载的类型设置压缩包的文件名和路径以及压缩包中的贴纸数量 if isOnlyPNG { - if stickerCount_webp == 0 { + if data.WebP == 0 { logger.Warn(). Dict("stickerSet", zerolog.Dict(). Str("stickerSetName", stickerSet.Name). - Int("WebP", stickerCount_webp). - Int("tgs", stickerCount_tgs). - Int("WebM", stickerCount_webm), + Int("WebP", data.WebP). + Int("tgs", data.tgs). + Int("WebM", data.WebM), ). Msg("There are no static stickers in the sticker set") return nil, fmt.Errorf("there are no static stickers in the sticker set [%s]", stickerSet.Name) } - data.StickerCount = stickerCount_webp + data.StickerCount = data.WebP zipFileName = fmt.Sprintf("%s(%d)_png.zip", stickerSet.Name, data.StickerCount) compressFolderPath = PNGFilePath } else { - data.StickerCount = stickerCount_webp + stickerCount_webm + stickerCount_tgs + data.StickerCount = data.WebP + data.WebM + data.tgs zipFileName = fmt.Sprintf("%s(%d).zip", stickerSet.Name, data.StickerCount) compressFolderPath = filePath } @@ -995,3 +1021,225 @@ func showCachedStickers(opts *handler_structs.SubHandlerParams) error { }) return err } + +func collectStickerSet(opts *handler_structs.SubHandlerParams) error { + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "StickerDownload"). + Str("funcName", "collectStickerSet"). + Logger() + + var handlerErr multe.MultiError + + if StickerCollectionChannelID == 0 { + _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "未设置贴纸包收集频道", + ShowAlert: true, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("callbackQuery", opts.Update.CallbackQuery.Data). + Str("content", "collect channel ID not set"). + Msg(errt.AnswerCallbackQuery) + } + } else { + stickerSetName := strings.TrimPrefix(opts.Update.CallbackQuery.Data, "c_") + + stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{ Name: stickerSetName }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to get sticker set info") + handlerErr.Addf("Failed to get sticker set info: %w", err) + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + Text: fmt.Sprintf("获取贴纸包时发生了一些错误\nFailed to get sticker set info: %s", err), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "get sticker set info error"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `get sticker set info error` message: %w", err) + } + } else { + _, err := opts.Thebot.AnswerCallbackQuery(opts.Ctx, &bot.AnswerCallbackQueryParams{ + CallbackQueryID: opts.Update.CallbackQuery.ID, + Text: "已开始下载贴纸包", + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "start downloading sticker pack notice"). + Msg(errt.AnswerCallbackQuery) + handlerErr.Addf("Failed to send `start downloading sticker pack notice` callback answer: %w", err) + } + stickerData, err := getStickerPack(opts, stickerSet, false) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Msg("Failed to download sticker set") + handlerErr.Addf("failed to download sticker set: %w", err) + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + Text: fmt.Sprintf("下载贴纸包时发生了一些错误\nFailed to download sticker set: %s", err), + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "download sticker set error"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `download sticker set error` message: %w", err) + } + } else { + var pendingMessage string = fmt.Sprintf("[%s](https://t.me/addstickers/%s)\n", stickerData.StickerSetTitle, stickerData.StickerSetName) + if stickerData.WebP > 0 { + pendingMessage += fmt.Sprintf("%d(静态) ", stickerData.WebP) + } + if stickerData.WebM > 0 { + pendingMessage += fmt.Sprintf("%d(动态) ", stickerData.WebM) + } + if stickerData.tgs > 0 { + pendingMessage += fmt.Sprintf("%d(矢量) ", stickerData.tgs) + } + _, err := opts.Thebot.SendDocument(opts.Ctx, &bot.SendDocumentParams{ + ChatID: StickerCollectionChannelID, + ParseMode: models.ParseModeMarkdownV1, + Caption: fmt.Sprintf("%s 共 %d 个贴纸\n存档时间 %s", pendingMessage, stickerData.StickerCount, time.Now().Format(time.RFC3339)), + Document: &models.InputFileUpload{Filename: fmt.Sprintf("%s(%d).zip", stickerData.StickerSetName, stickerData.StickerCount), Data: stickerData.Data}, + ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ + Text: "查看贴纸包", URL: "https://t.me/addstickers/" + stickerData.StickerSetName }, + }}}, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("channelID", StickerCollectionChannelID). + Str("stickerSetName", stickerSetName). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "collect sticker set file"). + Msg(errt.SendDocument) + handlerErr.Addf("Failed to send `collect sticker set` file: %w", err) + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.CallbackQuery.From.ID, + Text: fmt.Sprintf("将贴纸包发送到收藏频道失败:%s", err.Error()), + ParseMode: models.ParseModeHTML, + DisableNotification: true, + }) + if err != nil { + logger.Error(). + Err(err). + Int64("channelID", StickerCollectionChannelID). + Str("stickerSetName", stickerSetName). + Dict(utils.GetUserDict(&opts.Update.CallbackQuery.From)). + Str("content", "collect sticker set failed notice"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `collect sticker set failed notice` message: %w", err) + } + } + } + } + + } + + return handlerErr.Flat() +} + +func getStickerPackInfo(opts *handler_structs.SubHandlerParams) error { + if opts.Update.Message == nil || opts.Update.Message.Text == "" { + return nil + } + + logger := zerolog.Ctx(opts.Ctx). + With(). + Str("pluginName", "StickerDownload"). + Str("funcName", "getStickerPackInfo"). + Logger() + + var handlerErr multe.MultiError + var stickerSetName string + + if strings.HasPrefix(opts.Update.Message.Text, "https://t.me/addstickers/") { + stickerSetName = strings.TrimPrefix(opts.Update.Message.Text, "https://t.me/addstickers/") + } else if strings.HasPrefix(opts.Update.Message.Text, "t.me/addstickers/") { + stickerSetName = strings.TrimPrefix(opts.Update.Message.Text, "t.me/addstickers/") + } + + if stickerSetName != "" { + stickerSet, err := opts.Thebot.GetStickerSet(opts.Ctx, &bot.GetStickerSetParams{ Name: stickerSetName }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Msg("Failed to get sticker set info") + handlerErr.Addf("Failed to get sticker set info: %w", err) + + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.From.ID, + Text: fmt.Sprintf("获取贴纸包信息时发生了一些错误\nFailed to get sticker set info: %s", err), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + ParseMode: models.ParseModeHTML, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "get sticker set info error"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `get sticker set info error` message: %w", err) + } + } else { + _, err = opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.From.ID, + Text: fmt.Sprintf("%s 贴纸包中一共有 %d 个贴纸\n", stickerSet.Name, stickerSet.Title, len(stickerSet.Stickers)), + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + DisableNotification: true, + ParseMode: models.ParseModeHTML, + ReplyMarkup: &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ + { + { Text: "下载贴纸包中的静态贴纸", CallbackData: fmt.Sprintf("S_%s", stickerSet.Name) }, + }, + { + { Text: "下载整个贴纸包(不转换格式)", CallbackData: fmt.Sprintf("s_%s", stickerSet.Name) }, + }, + }}, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "sticker set info"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `sticker set info` message: %w", err) + } + } + } else { + _, err := opts.Thebot.SendMessage(opts.Ctx, &bot.SendMessageParams{ + ChatID: opts.Update.Message.From.ID, + Text: "请发送一个有效的贴纸链接", + ReplyParameters: &models.ReplyParameters{ MessageID: opts.Update.Message.ID }, + }) + if err != nil { + logger.Error(). + Err(err). + Dict(utils.GetUserDict(opts.Update.Message.From)). + Str("content", "empty sticker link notice"). + Msg(errt.SendMessage) + handlerErr.Addf("Failed to send `empty sticker link notice` message: %w", err) + } + } + + return handlerErr.Flat() +} diff --git a/plugins/plugin_teamspeak3.go b/plugins/plugin_teamspeak3.go index 4b523b8..0d8d1eb 100644 --- a/plugins/plugin_teamspeak3.go +++ b/plugins/plugin_teamspeak3.go @@ -27,7 +27,7 @@ import ( var tsClient *ts3.Client var tsErr error -var tsDataDir string = filepath.Join(consts.YAMLDataBasePath, "teamspeak/") +var tsDataDir string = filepath.Join(consts.YAMLDataBaseDir, "teamspeak/") var tsDataPath string = filepath.Join(tsDataDir, consts.YAMLFileName) var botNickName string = "trbot_teamspeak_plugin" diff --git a/plugins/plugin_udonese.go b/plugins/plugin_udonese.go index f69961d..3bc2b08 100644 --- a/plugins/plugin_udonese.go +++ b/plugins/plugin_udonese.go @@ -27,7 +27,7 @@ import ( var UdoneseData Udonese var UdoneseErr error -var UdoneseDir string = filepath.Join(consts.YAMLDataBasePath, "udonese/") +var UdoneseDir string = filepath.Join(consts.YAMLDataBaseDir, "udonese/") var UdonesePath string = filepath.Join(UdoneseDir, consts.YAMLFileName) var UdonGroupID int64 = -1002205667779 // var UdonGroupID int64 = -1002499888124 // trbot diff --git a/plugins/plugin_voicelist.go b/plugins/plugin_voicelist.go index 310d1ce..606d176 100644 --- a/plugins/plugin_voicelist.go +++ b/plugins/plugin_voicelist.go @@ -19,7 +19,7 @@ import ( var VoiceLists []VoicePack var VoiceListErr error -var VoiceListDir string = filepath.Join(consts.YAMLDataBasePath, "voices/") +var VoiceListDir string = filepath.Join(consts.YAMLDataBaseDir, "voices/") func init() { plugin_utils.AddInitializer(plugin_utils.Initializer{ diff --git a/plugins/saved_message/utils.go b/plugins/saved_message/utils.go index 8c95025..68bc604 100644 --- a/plugins/saved_message/utils.go +++ b/plugins/saved_message/utils.go @@ -18,7 +18,7 @@ import ( var SavedMessageSet map[int64]SavedMessage var SavedMessageErr error -var SavedMessagePath string = filepath.Join(consts.YAMLDataBasePath, "savedmessage/", consts.YAMLFileName) +var SavedMessagePath string = filepath.Join(consts.YAMLDataBaseDir, "savedmessage/", consts.YAMLFileName) var textExpandableLength int = 150 diff --git a/utils/consts/consts.go b/utils/consts/consts.go index bb52251..af791cf 100644 --- a/utils/consts/consts.go +++ b/utils/consts/consts.go @@ -6,11 +6,11 @@ import ( var WebhookListenPort string = "localhost:2847" -var YAMLDataBasePath string = "./db_yaml/" +var YAMLDataBaseDir string = "./db_yaml/" var YAMLFileName string = "metadata.yaml" var CacheDirectory string = "./cache/" -var LogFilePath string = YAMLDataBasePath + "log.txt" +var LogFilePath string = YAMLDataBaseDir + "log.txt" var BotMe *models.User // 用于存储 bot 信息 diff --git a/utils/mess/mess.go b/utils/mess/mess.go index e46429a..0af6ad5 100644 --- a/utils/mess/mess.go +++ b/utils/mess/mess.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "fmt" - "log" "os" "runtime" @@ -15,24 +14,6 @@ import ( "github.com/go-telegram/bot/models" ) -func PrintLogAndSave(message string) { - log.Println(message) - // 打开日志文件,如果不存在则创建 - file, err := os.OpenFile(consts.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - log.Println(err) - return - } - defer file.Close() - - // 将文本写入日志文件 - _, err = file.WriteString(message + "\n") - if err != nil { - log.Println(err) - return - } -} - // 从 log.txt 读取文件 func ReadLog() ([]string, error) { // 打开日志文件 diff --git a/utils/signals/signals.go b/utils/signals/signals.go index dc51fb9..c4aa5ab 100644 --- a/utils/signals/signals.go +++ b/utils/signals/signals.go @@ -33,7 +33,7 @@ func SignalsHandler(ctx context.Context) { for { select { case <-every10Min.C: // 每次 Ticker 触发时执行任务 - yaml_db.AutoSaveDatabaseHandler() + yaml_db.AutoSaveDatabaseHandler(ctx) case <-ctx.Done(): if saveDatabaseRetryCount == 0 { logger.Warn().Msg("Cancle signal received") } err := database.SaveDatabase(ctx) -- 2.49.1