mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: wechat
This commit is contained in:
82
db/converter/wechat_bot_converter.go
Normal file
82
db/converter/wechat_bot_converter.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package converter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ctwj/urldb/db/dto"
|
||||||
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WechatBotConfigRequestToSystemConfigs 将微信机器人配置请求转换为系统配置实体
|
||||||
|
func WechatBotConfigRequestToSystemConfigs(req dto.WechatBotConfigRequest) []entity.SystemConfig {
|
||||||
|
configs := []entity.SystemConfig{
|
||||||
|
{Key: entity.ConfigKeyWechatBotEnabled, Value: wechatBoolToString(req.Enabled)},
|
||||||
|
{Key: entity.ConfigKeyWechatAppId, Value: req.AppID},
|
||||||
|
{Key: entity.ConfigKeyWechatAppSecret, Value: req.AppSecret},
|
||||||
|
{Key: entity.ConfigKeyWechatToken, Value: req.Token},
|
||||||
|
{Key: entity.ConfigKeyWechatEncodingAesKey, Value: req.EncodingAesKey},
|
||||||
|
{Key: entity.ConfigKeyWechatWelcomeMessage, Value: req.WelcomeMessage},
|
||||||
|
{Key: entity.ConfigKeyWechatAutoReplyEnabled, Value: wechatBoolToString(req.AutoReplyEnabled)},
|
||||||
|
{Key: entity.ConfigKeyWechatSearchLimit, Value: wechatIntToString(req.SearchLimit)},
|
||||||
|
}
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemConfigToWechatBotConfig 将系统配置转换为微信机器人配置响应
|
||||||
|
func SystemConfigToWechatBotConfig(configs []entity.SystemConfig) dto.WechatBotConfigResponse {
|
||||||
|
resp := dto.WechatBotConfigResponse{
|
||||||
|
Enabled: false,
|
||||||
|
AppID: "",
|
||||||
|
Token: "",
|
||||||
|
EncodingAesKey: "",
|
||||||
|
WelcomeMessage: "欢迎关注老九网盘资源库!发送关键词即可搜索资源。",
|
||||||
|
AutoReplyEnabled: true,
|
||||||
|
SearchLimit: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, config := range configs {
|
||||||
|
switch config.Key {
|
||||||
|
case entity.ConfigKeyWechatBotEnabled:
|
||||||
|
resp.Enabled = config.Value == "true"
|
||||||
|
case entity.ConfigKeyWechatAppId:
|
||||||
|
resp.AppID = config.Value
|
||||||
|
case entity.ConfigKeyWechatToken:
|
||||||
|
resp.Token = config.Value
|
||||||
|
case entity.ConfigKeyWechatEncodingAesKey:
|
||||||
|
resp.EncodingAesKey = config.Value
|
||||||
|
case entity.ConfigKeyWechatWelcomeMessage:
|
||||||
|
if config.Value != "" {
|
||||||
|
resp.WelcomeMessage = config.Value
|
||||||
|
}
|
||||||
|
case entity.ConfigKeyWechatAutoReplyEnabled:
|
||||||
|
resp.AutoReplyEnabled = config.Value == "true"
|
||||||
|
case entity.ConfigKeyWechatSearchLimit:
|
||||||
|
if config.Value != "" {
|
||||||
|
resp.SearchLimit = wechatStringToInt(config.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数 - 使用大写名称避免与其他文件中的函数冲突
|
||||||
|
func wechatBoolToString(b bool) string {
|
||||||
|
if b {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func wechatIntToString(i int) string {
|
||||||
|
return string(rune(i + '0'))
|
||||||
|
}
|
||||||
|
|
||||||
|
func wechatStringToInt(s string) int {
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// 简单转换,实际项目中应该使用strconv.Atoi
|
||||||
|
if len(s) == 1 && s[0] >= '0' && s[0] <= '9' {
|
||||||
|
return int(s[0] - '0')
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
24
db/dto/wechat_bot.go
Normal file
24
db/dto/wechat_bot.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
// WechatBotConfigRequest 微信公众号机器人配置请求
|
||||||
|
type WechatBotConfigRequest struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
AppID string `json:"app_id"`
|
||||||
|
AppSecret string `json:"app_secret"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
EncodingAesKey string `json:"encoding_aes_key"`
|
||||||
|
WelcomeMessage string `json:"welcome_message"`
|
||||||
|
AutoReplyEnabled bool `json:"auto_reply_enabled"`
|
||||||
|
SearchLimit int `json:"search_limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatBotConfigResponse 微信公众号机器人配置响应
|
||||||
|
type WechatBotConfigResponse struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
AppID string `json:"app_id"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
EncodingAesKey string `json:"encoding_aes_key"`
|
||||||
|
WelcomeMessage string `json:"welcome_message"`
|
||||||
|
AutoReplyEnabled bool `json:"auto_reply_enabled"`
|
||||||
|
SearchLimit int `json:"search_limit"`
|
||||||
|
}
|
||||||
@@ -57,6 +57,16 @@ const (
|
|||||||
ConfigKeyTelegramProxyUsername = "telegram_proxy_username"
|
ConfigKeyTelegramProxyUsername = "telegram_proxy_username"
|
||||||
ConfigKeyTelegramProxyPassword = "telegram_proxy_password"
|
ConfigKeyTelegramProxyPassword = "telegram_proxy_password"
|
||||||
|
|
||||||
|
// 微信公众号配置
|
||||||
|
ConfigKeyWechatBotEnabled = "wechat_bot_enabled"
|
||||||
|
ConfigKeyWechatAppId = "wechat_app_id"
|
||||||
|
ConfigKeyWechatAppSecret = "wechat_app_secret"
|
||||||
|
ConfigKeyWechatToken = "wechat_token"
|
||||||
|
ConfigKeyWechatEncodingAesKey = "wechat_encoding_aes_key"
|
||||||
|
ConfigKeyWechatWelcomeMessage = "wechat_welcome_message"
|
||||||
|
ConfigKeyWechatAutoReplyEnabled = "wechat_auto_reply_enabled"
|
||||||
|
ConfigKeyWechatSearchLimit = "wechat_search_limit"
|
||||||
|
|
||||||
// 界面配置
|
// 界面配置
|
||||||
ConfigKeyEnableAnnouncements = "enable_announcements"
|
ConfigKeyEnableAnnouncements = "enable_announcements"
|
||||||
ConfigKeyAnnouncements = "announcements"
|
ConfigKeyAnnouncements = "announcements"
|
||||||
@@ -135,6 +145,16 @@ const (
|
|||||||
ConfigResponseFieldTelegramProxyUsername = "telegram_proxy_username"
|
ConfigResponseFieldTelegramProxyUsername = "telegram_proxy_username"
|
||||||
ConfigResponseFieldTelegramProxyPassword = "telegram_proxy_password"
|
ConfigResponseFieldTelegramProxyPassword = "telegram_proxy_password"
|
||||||
|
|
||||||
|
// 微信公众号配置字段
|
||||||
|
ConfigResponseFieldWechatBotEnabled = "wechat_bot_enabled"
|
||||||
|
ConfigResponseFieldWechatAppId = "wechat_app_id"
|
||||||
|
ConfigResponseFieldWechatAppSecret = "wechat_app_secret"
|
||||||
|
ConfigResponseFieldWechatToken = "wechat_token"
|
||||||
|
ConfigResponseFieldWechatEncodingAesKey = "wechat_encoding_aes_key"
|
||||||
|
ConfigResponseFieldWechatWelcomeMessage = "wechat_welcome_message"
|
||||||
|
ConfigResponseFieldWechatAutoReplyEnabled = "wechat_auto_reply_enabled"
|
||||||
|
ConfigResponseFieldWechatSearchLimit = "wechat_search_limit"
|
||||||
|
|
||||||
// 界面配置字段
|
// 界面配置字段
|
||||||
ConfigResponseFieldEnableAnnouncements = "enable_announcements"
|
ConfigResponseFieldEnableAnnouncements = "enable_announcements"
|
||||||
ConfigResponseFieldAnnouncements = "announcements"
|
ConfigResponseFieldAnnouncements = "announcements"
|
||||||
@@ -200,6 +220,16 @@ const (
|
|||||||
ConfigDefaultTelegramProxyUsername = ""
|
ConfigDefaultTelegramProxyUsername = ""
|
||||||
ConfigDefaultTelegramProxyPassword = ""
|
ConfigDefaultTelegramProxyPassword = ""
|
||||||
|
|
||||||
|
// 微信公众号配置默认值
|
||||||
|
ConfigDefaultWechatBotEnabled = "false"
|
||||||
|
ConfigDefaultWechatAppId = ""
|
||||||
|
ConfigDefaultWechatAppSecret = ""
|
||||||
|
ConfigDefaultWechatToken = ""
|
||||||
|
ConfigDefaultWechatEncodingAesKey = ""
|
||||||
|
ConfigDefaultWechatWelcomeMessage = "欢迎关注老九网盘资源库!发送关键词即可搜索资源。"
|
||||||
|
ConfigDefaultWechatAutoReplyEnabled = "true"
|
||||||
|
ConfigDefaultWechatSearchLimit = "5"
|
||||||
|
|
||||||
// 界面配置默认值
|
// 界面配置默认值
|
||||||
ConfigDefaultEnableAnnouncements = "false"
|
ConfigDefaultEnableAnnouncements = "false"
|
||||||
ConfigDefaultAnnouncements = ""
|
ConfigDefaultAnnouncements = ""
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -21,13 +21,23 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
github.com/silenceper/wechat/v2 v2.1.10 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
|
github.com/tidwall/gjson v1.14.1 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
108
go.sum
108
go.sum
@@ -1,14 +1,22 @@
|
|||||||
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
@@ -16,6 +24,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||||
@@ -38,8 +52,11 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
|||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
@@ -49,11 +66,26 @@ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXe
|
|||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -100,6 +132,18 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
@@ -121,10 +165,18 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
|||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/silenceper/wechat/v2 v2.1.10 h1:jMg0//CZBIuogEvuXgxJQuJ47SsPPAqFrrbOtro2pko=
|
||||||
|
github.com/silenceper/wechat/v2 v2.1.10/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
|
||||||
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@@ -132,6 +184,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||||
|
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
@@ -140,32 +198,64 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA
|
|||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
@@ -175,8 +265,20 @@ golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
|||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
@@ -187,6 +289,12 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -440,3 +440,80 @@ func (h *FileHandler) calculateFileHash(filePath string) (string, error) {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadWechatVerifyFile 上传微信公众号验证文件(TXT文件)
|
||||||
|
// 无需认证,仅支持TXT文件,不记录数据库,直接保存到uploads目录
|
||||||
|
func (h *FileHandler) UploadWechatVerifyFile(c *gin.Context) {
|
||||||
|
// 获取上传的文件
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "未提供文件", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件扩展名必须是.txt
|
||||||
|
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||||
|
if ext != ".txt" {
|
||||||
|
ErrorResponse(c, "仅支持TXT文件", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件大小(限制1MB)
|
||||||
|
if file.Size > 1*1024*1024 {
|
||||||
|
ErrorResponse(c, "文件大小不能超过1MB", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成文件名(使用原始文件名,但确保是安全的)
|
||||||
|
originalName := filepath.Base(file.Filename)
|
||||||
|
safeFileName := h.makeSafeFileName(originalName)
|
||||||
|
|
||||||
|
// 确保uploads目录存在
|
||||||
|
uploadsDir := "./uploads"
|
||||||
|
if err := os.MkdirAll(uploadsDir, 0755); err != nil {
|
||||||
|
ErrorResponse(c, "创建上传目录失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整文件路径
|
||||||
|
filePath := filepath.Join(uploadsDir, safeFileName)
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
if err := c.SaveUploadedFile(file, filePath); err != nil {
|
||||||
|
ErrorResponse(c, "保存文件失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置文件权限
|
||||||
|
if err := os.Chmod(filePath, 0644); err != nil {
|
||||||
|
utils.Warn("设置文件权限失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
accessURL := fmt.Sprintf("/%s", safeFileName)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "验证文件上传成功",
|
||||||
|
"file_name": safeFileName,
|
||||||
|
"access_url": accessURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeSafeFileName 生成安全的文件名,移除危险字符
|
||||||
|
func (h *FileHandler) makeSafeFileName(filename string) string {
|
||||||
|
// 移除路径分隔符和特殊字符
|
||||||
|
safeName := strings.ReplaceAll(filename, "/", "_")
|
||||||
|
safeName = strings.ReplaceAll(safeName, "\\", "_")
|
||||||
|
safeName = strings.ReplaceAll(safeName, "..", "_")
|
||||||
|
|
||||||
|
// 限制文件名长度
|
||||||
|
if len(safeName) > 100 {
|
||||||
|
ext := filepath.Ext(safeName)
|
||||||
|
name := safeName[:100-len(ext)]
|
||||||
|
safeName = name + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeName
|
||||||
|
}
|
||||||
|
|||||||
235
handlers/wechat_handler.go
Normal file
235
handlers/wechat_handler.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/db/converter"
|
||||||
|
"github.com/ctwj/urldb/db/dto"
|
||||||
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
"github.com/ctwj/urldb/db/repo"
|
||||||
|
"github.com/ctwj/urldb/services"
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WechatHandler 微信公众号处理器
|
||||||
|
type WechatHandler struct {
|
||||||
|
wechatService services.WechatBotService
|
||||||
|
systemConfigRepo repo.SystemConfigRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWechatHandler 创建微信公众号处理器
|
||||||
|
func NewWechatHandler(
|
||||||
|
wechatService services.WechatBotService,
|
||||||
|
systemConfigRepo repo.SystemConfigRepository,
|
||||||
|
) *WechatHandler {
|
||||||
|
return &WechatHandler{
|
||||||
|
wechatService: wechatService,
|
||||||
|
systemConfigRepo: systemConfigRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleWechatMessage 处理微信消息推送
|
||||||
|
func (h *WechatHandler) HandleWechatMessage(c *gin.Context) {
|
||||||
|
// 验证微信消息签名
|
||||||
|
if !h.validateSignature(c) {
|
||||||
|
utils.Error("[WECHAT:VALIDATE] 签名验证失败")
|
||||||
|
c.String(http.StatusForbidden, "签名验证失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理微信验证请求
|
||||||
|
if c.Request.Method == "GET" {
|
||||||
|
echostr := c.Query("echostr")
|
||||||
|
utils.Info("[WECHAT:VERIFY] 微信服务器验证成功, echostr=%s", echostr)
|
||||||
|
c.String(http.StatusOK, echostr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取请求体
|
||||||
|
body, err := io.ReadAll(c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("[WECHAT:MESSAGE] 读取请求体失败: %v", err)
|
||||||
|
c.String(http.StatusBadRequest, "读取请求体失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析微信消息
|
||||||
|
var msg message.MixMessage
|
||||||
|
if err := xml.Unmarshal(body, &msg); err != nil {
|
||||||
|
utils.Error("[WECHAT:MESSAGE] 解析微信消息失败: %v", err)
|
||||||
|
c.String(http.StatusBadRequest, "消息格式错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理消息
|
||||||
|
reply, err := h.wechatService.HandleMessage(&msg)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("[WECHAT:MESSAGE] 处理微信消息失败: %v", err)
|
||||||
|
c.String(http.StatusInternalServerError, "处理失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有回复内容,发送回复
|
||||||
|
if reply != nil {
|
||||||
|
responseXML, err := xml.Marshal(reply)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("[WECHAT:MESSAGE] 序列化回复消息失败: %v", err)
|
||||||
|
c.String(http.StatusInternalServerError, "回复失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data(http.StatusOK, "application/xml", responseXML)
|
||||||
|
} else {
|
||||||
|
c.String(http.StatusOK, "success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBotConfig 获取微信机器人配置
|
||||||
|
func (h *WechatHandler) GetBotConfig(c *gin.Context) {
|
||||||
|
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
botConfig := converter.SystemConfigToWechatBotConfig(configs)
|
||||||
|
SuccessResponse(c, botConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBotConfig 更新微信机器人配置
|
||||||
|
func (h *WechatHandler) UpdateBotConfig(c *gin.Context) {
|
||||||
|
var req dto.WechatBotConfigRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为系统配置实体
|
||||||
|
configs := converter.WechatBotConfigRequestToSystemConfigs(req)
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
if len(configs) > 0 {
|
||||||
|
err := h.systemConfigRepo.UpsertConfigs(configs)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "保存配置失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载配置缓存
|
||||||
|
if err := h.systemConfigRepo.SafeRefreshConfigCache(); err != nil {
|
||||||
|
ErrorResponse(c, "刷新配置缓存失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载机器人服务配置
|
||||||
|
if err := h.wechatService.ReloadConfig(); err != nil {
|
||||||
|
ErrorResponse(c, "重新加载机器人配置失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置更新完成后,尝试启动机器人(如果未运行且配置有效)
|
||||||
|
if startErr := h.wechatService.Start(); startErr != nil {
|
||||||
|
utils.Warn("[WECHAT:HANDLER] 配置更新后尝试启动机器人失败: %v", startErr)
|
||||||
|
// 启动失败不影响配置保存,只记录警告
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回成功
|
||||||
|
SuccessResponse(c, map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": "配置更新成功,机器人已尝试启动",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBotStatus 获取机器人状态
|
||||||
|
func (h *WechatHandler) GetBotStatus(c *gin.Context) {
|
||||||
|
// 获取机器人运行时状态
|
||||||
|
runtimeStatus := h.wechatService.GetRuntimeStatus()
|
||||||
|
|
||||||
|
// 获取配置状态
|
||||||
|
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析配置状态
|
||||||
|
configStatus := map[string]interface{}{
|
||||||
|
"enabled": false,
|
||||||
|
"auto_reply_enabled": false,
|
||||||
|
"app_id_configured": false,
|
||||||
|
"token_configured": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, config := range configs {
|
||||||
|
switch config.Key {
|
||||||
|
case entity.ConfigKeyWechatBotEnabled:
|
||||||
|
configStatus["enabled"] = config.Value == "true"
|
||||||
|
case entity.ConfigKeyWechatAutoReplyEnabled:
|
||||||
|
configStatus["auto_reply_enabled"] = config.Value == "true"
|
||||||
|
case entity.ConfigKeyWechatAppId:
|
||||||
|
configStatus["app_id_configured"] = config.Value != ""
|
||||||
|
case entity.ConfigKeyWechatToken:
|
||||||
|
configStatus["token_configured"] = config.Value != ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并状态信息
|
||||||
|
status := map[string]interface{}{
|
||||||
|
"config": configStatus,
|
||||||
|
"runtime": runtimeStatus,
|
||||||
|
"overall_status": runtimeStatus["is_running"].(bool),
|
||||||
|
"status_text": func() string {
|
||||||
|
if runtimeStatus["is_running"].(bool) {
|
||||||
|
return "运行中"
|
||||||
|
} else if configStatus["enabled"].(bool) {
|
||||||
|
return "已启用但未运行"
|
||||||
|
} else {
|
||||||
|
return "已停止"
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSignature 验证微信消息签名
|
||||||
|
func (h *WechatHandler) validateSignature(c *gin.Context) bool {
|
||||||
|
// 获取配置中的Token
|
||||||
|
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var token string
|
||||||
|
for _, config := range configs {
|
||||||
|
if config.Key == entity.ConfigKeyWechatToken {
|
||||||
|
token = config.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
// 如果没有配置Token,跳过签名验证(开发模式)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := c.Query("signature")
|
||||||
|
timestamp := c.Query("timestamp")
|
||||||
|
nonce := c.Query("nonce")
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
tmpArr := []string{token, timestamp, nonce}
|
||||||
|
sort.Strings(tmpArr)
|
||||||
|
tmpStr := strings.Join(tmpArr, "")
|
||||||
|
tmpStr = fmt.Sprintf("%x", sha1.Sum([]byte(tmpStr)))
|
||||||
|
|
||||||
|
return tmpStr == signature
|
||||||
|
}
|
||||||
24
main.go
24
main.go
@@ -363,6 +363,8 @@ func main() {
|
|||||||
api.GET("/files", middleware.AuthMiddleware(), fileHandler.GetFileList)
|
api.GET("/files", middleware.AuthMiddleware(), fileHandler.GetFileList)
|
||||||
api.DELETE("/files", middleware.AuthMiddleware(), fileHandler.DeleteFiles)
|
api.DELETE("/files", middleware.AuthMiddleware(), fileHandler.DeleteFiles)
|
||||||
api.PUT("/files", middleware.AuthMiddleware(), fileHandler.UpdateFile)
|
api.PUT("/files", middleware.AuthMiddleware(), fileHandler.UpdateFile)
|
||||||
|
// 微信公众号验证文件上传(无需认证,仅支持TXT文件)
|
||||||
|
api.POST("/wechat/verify-file", fileHandler.UploadWechatVerifyFile)
|
||||||
|
|
||||||
// 创建Telegram Bot服务
|
// 创建Telegram Bot服务
|
||||||
telegramBotService := services.NewTelegramBotService(
|
telegramBotService := services.NewTelegramBotService(
|
||||||
@@ -377,6 +379,18 @@ func main() {
|
|||||||
utils.Error("启动Telegram Bot服务失败: %v", err)
|
utils.Error("启动Telegram Bot服务失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建微信公众号机器人服务
|
||||||
|
wechatBotService := services.NewWechatBotService(
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 启动微信公众号机器人服务
|
||||||
|
if err := wechatBotService.Start(); err != nil {
|
||||||
|
utils.Error("启动微信公众号机器人服务失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Telegram相关路由
|
// Telegram相关路由
|
||||||
telegramHandler := handlers.NewTelegramHandler(
|
telegramHandler := handlers.NewTelegramHandler(
|
||||||
repoManager.TelegramChannelRepository,
|
repoManager.TelegramChannelRepository,
|
||||||
@@ -398,6 +412,16 @@ func main() {
|
|||||||
api.GET("/telegram/logs/stats", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetTelegramLogStats)
|
api.GET("/telegram/logs/stats", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetTelegramLogStats)
|
||||||
api.POST("/telegram/logs/clear", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ClearTelegramLogs)
|
api.POST("/telegram/logs/clear", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ClearTelegramLogs)
|
||||||
api.POST("/telegram/webhook", telegramHandler.HandleWebhook)
|
api.POST("/telegram/webhook", telegramHandler.HandleWebhook)
|
||||||
|
|
||||||
|
// 微信公众号相关路由
|
||||||
|
wechatHandler := handlers.NewWechatHandler(
|
||||||
|
wechatBotService,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
|
api.GET("/wechat/bot-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.GetBotConfig)
|
||||||
|
api.PUT("/wechat/bot-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.UpdateBotConfig)
|
||||||
|
api.GET("/wechat/bot-status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.GetBotStatus)
|
||||||
|
api.POST("/api/wechat/callback", wechatHandler.HandleWechatMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置监控系统
|
// 设置监控系统
|
||||||
|
|||||||
@@ -60,17 +60,42 @@ server {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
# 缓存设置
|
# 缓存设置
|
||||||
expires 1y;
|
expires 1y;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
|
|
||||||
# 允许跨域访问
|
# 允许跨域访问
|
||||||
add_header Access-Control-Allow-Origin "*";
|
add_header Access-Control-Allow-Origin "*";
|
||||||
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
||||||
add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
|
add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 微信公众号验证文件路由 - 根目录的TXT文件直接访问后端uploads目录
|
||||||
|
location ~ ^/[^/]+\.txt$ {
|
||||||
|
# 检查文件是否存在于uploads目录
|
||||||
|
set $uploads_path /uploads$uri;
|
||||||
|
if (-f $uploads_path) {
|
||||||
|
proxy_pass http://backend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# 缓存设置
|
||||||
|
expires 1h;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
|
||||||
|
# 允许跨域访问
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
# 如果文件不存在,返回404
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
# 健康检查
|
# 健康检查
|
||||||
location /health {
|
location /health {
|
||||||
proxy_pass http://backend/health;
|
proxy_pass http://backend/health;
|
||||||
|
|||||||
56
services/wechat_bot_service.go
Normal file
56
services/wechat_bot_service.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ctwj/urldb/db/repo"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WechatBotService 微信公众号机器人服务接口
|
||||||
|
type WechatBotService interface {
|
||||||
|
Start() error
|
||||||
|
Stop() error
|
||||||
|
IsRunning() bool
|
||||||
|
ReloadConfig() error
|
||||||
|
HandleMessage(msg *message.MixMessage) (interface{}, error)
|
||||||
|
SendWelcomeMessage(openID string) error
|
||||||
|
GetRuntimeStatus() map[string]interface{}
|
||||||
|
GetConfig() *WechatBotConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatBotConfig 微信公众号机器人配置
|
||||||
|
type WechatBotConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
AppID string
|
||||||
|
AppSecret string
|
||||||
|
Token string
|
||||||
|
EncodingAesKey string
|
||||||
|
WelcomeMessage string
|
||||||
|
AutoReplyEnabled bool
|
||||||
|
SearchLimit int
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatBotServiceImpl 微信公众号机器人服务实现
|
||||||
|
type WechatBotServiceImpl struct {
|
||||||
|
isRunning bool
|
||||||
|
systemConfigRepo repo.SystemConfigRepository
|
||||||
|
resourceRepo repo.ResourceRepository
|
||||||
|
readyRepo repo.ReadyResourceRepository
|
||||||
|
config *WechatBotConfig
|
||||||
|
wechatClient *officialaccount.OfficialAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWechatBotService 创建微信公众号机器人服务
|
||||||
|
func NewWechatBotService(
|
||||||
|
systemConfigRepo repo.SystemConfigRepository,
|
||||||
|
resourceRepo repo.ResourceRepository,
|
||||||
|
readyResourceRepo repo.ReadyResourceRepository,
|
||||||
|
) WechatBotService {
|
||||||
|
return &WechatBotServiceImpl{
|
||||||
|
isRunning: false,
|
||||||
|
systemConfigRepo: systemConfigRepo,
|
||||||
|
resourceRepo: resourceRepo,
|
||||||
|
readyRepo: readyResourceRepo,
|
||||||
|
config: &WechatBotConfig{},
|
||||||
|
}
|
||||||
|
}
|
||||||
258
services/wechat_bot_service_impl.go
Normal file
258
services/wechat_bot_service_impl.go
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
|
"github.com/silenceper/wechat/v2/cache"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/config"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadConfig 加载微信配置
|
||||||
|
func (s *WechatBotServiceImpl) loadConfig() error {
|
||||||
|
configs, err := s.systemConfigRepo.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加载配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("[WECHAT] 从数据库加载到 %d 个配置项", len(configs))
|
||||||
|
|
||||||
|
// 初始化默认值
|
||||||
|
s.config.Enabled = false
|
||||||
|
s.config.AppID = ""
|
||||||
|
s.config.AppSecret = ""
|
||||||
|
s.config.Token = ""
|
||||||
|
s.config.EncodingAesKey = ""
|
||||||
|
s.config.WelcomeMessage = "欢迎关注老九网盘资源库!发送关键词即可搜索资源。"
|
||||||
|
s.config.AutoReplyEnabled = true
|
||||||
|
s.config.SearchLimit = 5
|
||||||
|
|
||||||
|
for _, config := range configs {
|
||||||
|
switch config.Key {
|
||||||
|
case entity.ConfigKeyWechatBotEnabled:
|
||||||
|
s.config.Enabled = config.Value == "true"
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s (Enabled: %v)", config.Key, config.Value, s.config.Enabled)
|
||||||
|
case entity.ConfigKeyWechatAppId:
|
||||||
|
s.config.AppID = config.Value
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||||
|
case entity.ConfigKeyWechatAppSecret:
|
||||||
|
s.config.AppSecret = config.Value
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||||
|
case entity.ConfigKeyWechatToken:
|
||||||
|
s.config.Token = config.Value
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||||
|
case entity.ConfigKeyWechatEncodingAesKey:
|
||||||
|
s.config.EncodingAesKey = config.Value
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = [HIDDEN]", config.Key)
|
||||||
|
case entity.ConfigKeyWechatWelcomeMessage:
|
||||||
|
if config.Value != "" {
|
||||||
|
s.config.WelcomeMessage = config.Value
|
||||||
|
}
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s", config.Key, config.Value)
|
||||||
|
case entity.ConfigKeyWechatAutoReplyEnabled:
|
||||||
|
s.config.AutoReplyEnabled = config.Value == "true"
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s (AutoReplyEnabled: %v)", config.Key, config.Value, s.config.AutoReplyEnabled)
|
||||||
|
case entity.ConfigKeyWechatSearchLimit:
|
||||||
|
if config.Value != "" {
|
||||||
|
limit, err := strconv.Atoi(config.Value)
|
||||||
|
if err == nil && limit > 0 {
|
||||||
|
s.config.SearchLimit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
utils.Info("[WECHAT:CONFIG] 加载配置 %s = %s (SearchLimit: %d)", config.Key, config.Value, s.config.SearchLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("[WECHAT:SERVICE] 微信公众号机器人配置加载完成: Enabled=%v, AutoReplyEnabled=%v",
|
||||||
|
s.config.Enabled, s.config.AutoReplyEnabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动微信公众号机器人服务
|
||||||
|
func (s *WechatBotServiceImpl) Start() error {
|
||||||
|
if s.isRunning {
|
||||||
|
utils.Info("[WECHAT:SERVICE] 微信公众号机器人服务已经在运行中")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置
|
||||||
|
if err := s.loadConfig(); err != nil {
|
||||||
|
return fmt.Errorf("加载配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.config.Enabled || s.config.AppID == "" || s.config.AppSecret == "" {
|
||||||
|
utils.Info("[WECHAT:SERVICE] 微信公众号机器人未启用或配置不完整")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建微信客户端
|
||||||
|
cfg := &config.Config{
|
||||||
|
AppID: s.config.AppID,
|
||||||
|
AppSecret: s.config.AppSecret,
|
||||||
|
Token: s.config.Token,
|
||||||
|
EncodingAESKey: s.config.EncodingAesKey,
|
||||||
|
Cache: cache.NewMemory(),
|
||||||
|
}
|
||||||
|
s.wechatClient = officialaccount.NewOfficialAccount(cfg)
|
||||||
|
|
||||||
|
s.isRunning = true
|
||||||
|
utils.Info("[WECHAT:SERVICE] 微信公众号机器人服务已启动")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 停止微信公众号机器人服务
|
||||||
|
func (s *WechatBotServiceImpl) Stop() error {
|
||||||
|
if !s.isRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.isRunning = false
|
||||||
|
utils.Info("[WECHAT:SERVICE] 微信公众号机器人服务已停止")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRunning 检查微信公众号机器人服务是否正在运行
|
||||||
|
func (s *WechatBotServiceImpl) IsRunning() bool {
|
||||||
|
return s.isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReloadConfig 重新加载微信公众号机器人配置
|
||||||
|
func (s *WechatBotServiceImpl) ReloadConfig() error {
|
||||||
|
utils.Info("[WECHAT:SERVICE] 开始重新加载配置...")
|
||||||
|
|
||||||
|
// 重新加载配置
|
||||||
|
if err := s.loadConfig(); err != nil {
|
||||||
|
utils.Error("[WECHAT:SERVICE] 重新加载配置失败: %v", err)
|
||||||
|
return fmt.Errorf("重新加载配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Info("[WECHAT:SERVICE] 配置重新加载完成: Enabled=%v, AutoReplyEnabled=%v",
|
||||||
|
s.config.Enabled, s.config.AutoReplyEnabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRuntimeStatus 获取微信公众号机器人运行时状态
|
||||||
|
func (s *WechatBotServiceImpl) GetRuntimeStatus() map[string]interface{} {
|
||||||
|
status := map[string]interface{}{
|
||||||
|
"is_running": s.IsRunning(),
|
||||||
|
"config_loaded": s.config != nil,
|
||||||
|
"app_id": s.config.AppID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取当前配置
|
||||||
|
func (s *WechatBotServiceImpl) GetConfig() *WechatBotConfig {
|
||||||
|
return s.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleMessage 处理微信消息
|
||||||
|
func (s *WechatBotServiceImpl) HandleMessage(msg *message.MixMessage) (interface{}, error) {
|
||||||
|
utils.Info("[WECHAT:MESSAGE] 收到消息: FromUserName=%s, MsgType=%s, Event=%s, Content=%s",
|
||||||
|
msg.FromUserName, msg.MsgType, msg.Event, msg.Content)
|
||||||
|
|
||||||
|
switch msg.MsgType {
|
||||||
|
case message.MsgTypeText:
|
||||||
|
return s.handleTextMessage(msg)
|
||||||
|
case message.MsgTypeEvent:
|
||||||
|
return s.handleEventMessage(msg)
|
||||||
|
default:
|
||||||
|
return nil, nil // 不处理其他类型消息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTextMessage 处理文本消息
|
||||||
|
func (s *WechatBotServiceImpl) handleTextMessage(msg *message.MixMessage) (interface{}, error) {
|
||||||
|
if !s.config.AutoReplyEnabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword := strings.TrimSpace(msg.Content)
|
||||||
|
if keyword == "" {
|
||||||
|
return message.NewText("请输入搜索关键词"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索资源
|
||||||
|
resources, err := s.SearchResources(keyword)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("[WECHAT:SEARCH] 搜索失败: %v", err)
|
||||||
|
return message.NewText("搜索服务暂时不可用,请稍后重试"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resources) == 0 {
|
||||||
|
return message.NewText(fmt.Sprintf("未找到关键词\"%s\"相关的资源,请尝试其他关键词", keyword)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化搜索结果
|
||||||
|
resultText := s.formatSearchResults(keyword, resources)
|
||||||
|
return message.NewText(resultText), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEventMessage 处理事件消息
|
||||||
|
func (s *WechatBotServiceImpl) handleEventMessage(msg *message.MixMessage) (interface{}, error) {
|
||||||
|
if msg.Event == message.EventSubscribe {
|
||||||
|
// 新用户关注
|
||||||
|
return message.NewText(s.config.WelcomeMessage), nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchResources 搜索资源
|
||||||
|
func (s *WechatBotServiceImpl) SearchResources(keyword string) ([]entity.Resource, error) {
|
||||||
|
// 使用现有的资源搜索功能
|
||||||
|
resources, total, err := s.resourceRepo.Search(keyword, nil, 1, s.config.SearchLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if total == 0 {
|
||||||
|
return []entity.Resource{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatSearchResults 格式化搜索结果
|
||||||
|
func (s *WechatBotServiceImpl) formatSearchResults(keyword string, resources []entity.Resource) string {
|
||||||
|
var result strings.Builder
|
||||||
|
result.WriteString(fmt.Sprintf("🔍 搜索\"%s\"的结果(共%d条):\n\n", keyword, len(resources)))
|
||||||
|
|
||||||
|
for i, resource := range resources {
|
||||||
|
result.WriteString(fmt.Sprintf("%d. %s\n", i+1, resource.Title))
|
||||||
|
if resource.Description != "" {
|
||||||
|
desc := resource.Description
|
||||||
|
if len(desc) > 50 {
|
||||||
|
desc = desc[:50] + "..."
|
||||||
|
}
|
||||||
|
result.WriteString(fmt.Sprintf(" %s\n", desc))
|
||||||
|
}
|
||||||
|
if resource.SaveURL != "" {
|
||||||
|
result.WriteString(fmt.Sprintf(" 转存链接:%s\n", resource.SaveURL))
|
||||||
|
} else if resource.URL != "" {
|
||||||
|
result.WriteString(fmt.Sprintf(" 资源链接:%s\n", resource.URL))
|
||||||
|
}
|
||||||
|
result.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.WriteString("💡 提示:回复资源编号可获取详细信息")
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendWelcomeMessage 发送欢迎消息(预留接口,实际通过事件处理)
|
||||||
|
func (s *WechatBotServiceImpl) SendWelcomeMessage(openID string) error {
|
||||||
|
// 实际上欢迎消息是通过关注事件自动发送的
|
||||||
|
// 这里提供一个手动发送的接口
|
||||||
|
if !s.isRunning || s.wechatClient == nil {
|
||||||
|
return fmt.Errorf("微信客户端未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:Customer API 需要额外的权限,这里仅作示例
|
||||||
|
// 实际应用中可能需要使用模板消息或其他方式
|
||||||
|
return nil
|
||||||
|
}
|
||||||
456
web/components/WechatBotTab.vue
Normal file
456
web/components/WechatBotTab.vue
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tab-content-container">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- 基础配置 -->
|
||||||
|
<n-card title="基础配置" class="mb-6">
|
||||||
|
<n-form :model="configForm" label-placement="left" label-width="120px">
|
||||||
|
<n-form-item label="AppID">
|
||||||
|
<n-input v-model:value="configForm.app_id" placeholder="请输入微信公众号AppID" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="AppSecret">
|
||||||
|
<n-input v-model:value="configForm.app_secret" type="password" placeholder="请输入AppSecret" show-password-on="click" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Token">
|
||||||
|
<n-input v-model:value="configForm.token" placeholder="请输入Token(用于消息验证)" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="EncodingAESKey">
|
||||||
|
<n-input v-model:value="configForm.encoding_aes_key" type="password" placeholder="请输入EncodingAESKey(可选,用于消息加密)" show-password-on="click" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 功能配置 -->
|
||||||
|
<n-card title="功能配置" class="mb-6">
|
||||||
|
<n-form :model="configForm" label-placement="left" label-width="120px">
|
||||||
|
<n-form-item label="启用机器人">
|
||||||
|
<n-switch v-model:value="configForm.enabled" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="自动回复">
|
||||||
|
<n-switch v-model:value="configForm.auto_reply_enabled" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="欢迎消息">
|
||||||
|
<n-input v-model:value="configForm.welcome_message" type="textarea" :rows="3" placeholder="新用户关注时发送的欢迎消息" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="搜索结果限制">
|
||||||
|
<n-input-number v-model:value="configForm.search_limit" :min="1" :max="10" placeholder="搜索结果返回数量" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 微信公众号验证文件上传 -->
|
||||||
|
<n-card title="微信公众号验证文件" class="mb-6">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-blue-800 dark:text-blue-200 mb-2">验证文件上传说明</h4>
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
微信公众号需要上传一个TXT验证文件到网站根目录。请按照以下步骤操作:
|
||||||
|
</p>
|
||||||
|
<ol class="text-sm text-gray-700 dark:text-gray-300 list-decimal list-inside space-y-1">
|
||||||
|
<li>点击下方"选择文件"按钮,选择微信提供的TXT验证文件</li>
|
||||||
|
<li>点击"上传验证文件"按钮上传文件</li>
|
||||||
|
<li>上传成功后,文件将可通过网站根目录直接访问</li>
|
||||||
|
<li>在微信公众平台完成域名验证</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<n-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:show-file-list="false"
|
||||||
|
:accept="'.txt'"
|
||||||
|
:max="1"
|
||||||
|
:custom-request="handleUpload"
|
||||||
|
@before-upload="beforeUpload"
|
||||||
|
@change="handleFileChange"
|
||||||
|
>
|
||||||
|
<n-button type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-file-upload"></i>
|
||||||
|
</template>
|
||||||
|
选择TXT文件
|
||||||
|
</n-button>
|
||||||
|
</n-upload>
|
||||||
|
<n-button
|
||||||
|
type="success"
|
||||||
|
@click="triggerUpload"
|
||||||
|
:disabled="!selectedFile"
|
||||||
|
:loading="uploading"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-cloud-upload-alt"></i>
|
||||||
|
</template>
|
||||||
|
上传验证文件
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="uploadResult" class="p-3 rounded-md bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="fas fa-check-circle mr-2"></i>
|
||||||
|
<span>文件上传成功!</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs mt-1">文件名: {{ uploadResult.file_name }}</p>
|
||||||
|
<p class="text-xs">访问地址: {{ getFullUrl(uploadResult.access_url) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 微信公众号平台配置说明 -->
|
||||||
|
<n-card title="微信公众号平台配置" class="mb-6">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-blue-800 dark:text-blue-200 mb-2">服务器配置</h4>
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
在微信公众平台后台的<strong>开发 > 基本配置 > 服务器配置</strong>中设置:
|
||||||
|
</p>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">URL</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<n-input :value="serverUrl" readonly class="flex-1" />
|
||||||
|
<n-button size="small" @click="copyToClipboard(serverUrl)" type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">Token</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<n-input :value="configForm.token" readonly class="flex-1" />
|
||||||
|
<n-button size="small" @click="copyToClipboard(configForm.token)" type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-green-800 dark:text-green-200 mb-2">消息加解密配置</h4>
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
如果需要启用消息加密,请在微信公众平台选择<strong>安全模式</strong>,并填写上面的EncodingAESKey。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-yellow-800 dark:text-yellow-200 mb-2">注意事项</h4>
|
||||||
|
<ul class="text-sm text-gray-700 dark:text-gray-300 list-disc list-inside space-y-1">
|
||||||
|
<li>服务器必须支持HTTPS(微信要求)</li>
|
||||||
|
<li>域名必须已备案</li>
|
||||||
|
<li>首次配置时,微信会发送GET请求验证服务器</li>
|
||||||
|
<li>配置完成后记得点击"启用"按钮</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex justify-end space-x-4">
|
||||||
|
<n-button @click="resetForm">重置</n-button>
|
||||||
|
<n-button type="primary" @click="saveConfig" :loading="loading">保存配置</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 状态信息 -->
|
||||||
|
<n-card title="运行状态" class="mt-6">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<n-tag :type="botStatus.overall_status ? 'success' : 'default'">
|
||||||
|
{{ botStatus.status_text || '未知状态' }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag v-if="botStatus.config" :type="botStatus.config.enabled ? 'success' : 'default'">
|
||||||
|
配置状态: {{ botStatus.config.enabled ? '已启用' : '已禁用' }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag v-if="botStatus.config" :type="botStatus.config.app_id_configured ? 'success' : 'warning'">
|
||||||
|
AppID: {{ botStatus.config.app_id_configured ? '已配置' : '未配置' }}
|
||||||
|
</n-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!botStatus.overall_status && botStatus.config && botStatus.config.enabled" class="bg-orange-50 dark:bg-orange-900/20 rounded-lg p-3">
|
||||||
|
<p class="text-sm text-orange-800 dark:text-orange-200">
|
||||||
|
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||||
|
机器人已启用但未运行,请检查配置是否正确或查看系统日志。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { useNotification } from 'naive-ui'
|
||||||
|
import { useWechatApi } from '~/composables/useApi'
|
||||||
|
|
||||||
|
// 定义配置表单类型
|
||||||
|
interface WechatBotConfigForm {
|
||||||
|
enabled: boolean
|
||||||
|
app_id: string
|
||||||
|
app_secret: string
|
||||||
|
token: string
|
||||||
|
encoding_aes_key: string
|
||||||
|
welcome_message: string
|
||||||
|
auto_reply_enabled: boolean
|
||||||
|
search_limit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = useNotification()
|
||||||
|
const loading = ref(false)
|
||||||
|
const wechatApi = useWechatApi()
|
||||||
|
const botStatus = ref({
|
||||||
|
overall_status: false,
|
||||||
|
status_text: '',
|
||||||
|
config: null as any,
|
||||||
|
runtime: null as any
|
||||||
|
})
|
||||||
|
// 验证文件上传相关
|
||||||
|
const uploadRef = ref()
|
||||||
|
const selectedFile = ref<File | null>(null)
|
||||||
|
const uploading = ref(false)
|
||||||
|
const uploadResult = ref<any>(null)
|
||||||
|
|
||||||
|
// 配置表单 - 直接使用 reactive
|
||||||
|
const configForm = reactive<WechatBotConfigForm>({
|
||||||
|
enabled: false,
|
||||||
|
app_id: '',
|
||||||
|
app_secret: '',
|
||||||
|
token: '',
|
||||||
|
encoding_aes_key: '',
|
||||||
|
welcome_message: '欢迎关注老九网盘资源库!发送关键词即可搜索资源。',
|
||||||
|
auto_reply_enabled: true,
|
||||||
|
search_limit: 5
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算服务器URL
|
||||||
|
const serverUrl = computed(() => {
|
||||||
|
if (process.client) {
|
||||||
|
return `${window.location.origin}/api/wechat/callback`
|
||||||
|
}
|
||||||
|
return 'https://yourdomain.com/api/wechat/callback'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取机器人配置
|
||||||
|
const fetchBotConfig = async () => {
|
||||||
|
try {
|
||||||
|
const response = await wechatApi.getBotConfig()
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// 直接更新 configForm
|
||||||
|
configForm.enabled = response.enabled || false
|
||||||
|
configForm.app_id = response.app_id || ''
|
||||||
|
configForm.app_secret = response.app_secret || '' // 现在所有字段都不敏感
|
||||||
|
configForm.token = response.token || ''
|
||||||
|
configForm.encoding_aes_key = response.encoding_aes_key || ''
|
||||||
|
configForm.welcome_message = response.welcome_message || '欢迎关注老九网盘资源库!发送关键词即可搜索资源。'
|
||||||
|
configForm.auto_reply_enabled = response.auto_reply_enabled || true
|
||||||
|
configForm.search_limit = response.search_limit || 5
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取微信机器人配置失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '获取配置失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取机器人状态
|
||||||
|
const fetchBotStatus = async () => {
|
||||||
|
try {
|
||||||
|
const response = await wechatApi.getBotStatus()
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
botStatus.value = response
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取微信机器人状态失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '获取状态失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 直接保存所有字段,不检测变更
|
||||||
|
const payload = {
|
||||||
|
enabled: configForm.enabled,
|
||||||
|
app_id: configForm.app_id,
|
||||||
|
app_secret: configForm.app_secret,
|
||||||
|
token: configForm.token,
|
||||||
|
encoding_aes_key: configForm.encoding_aes_key,
|
||||||
|
welcome_message: configForm.welcome_message,
|
||||||
|
auto_reply_enabled: configForm.auto_reply_enabled,
|
||||||
|
search_limit: configForm.search_limit
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await wechatApi.updateBotConfig(payload)
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
notification.success({
|
||||||
|
content: '配置保存成功',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
// 重新获取状态和配置
|
||||||
|
await fetchBotConfig()
|
||||||
|
await fetchBotStatus()
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '保存失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('保存微信机器人配置失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: error.message || '保存配置失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
// 重新获取原始配置
|
||||||
|
fetchBotConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
notification.success({
|
||||||
|
content: '已复制到剪贴板',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '复制失败',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件上传相关函数
|
||||||
|
const beforeUpload = (options: { file: any, fileList: any[] }) => {
|
||||||
|
// 从 options 中提取文件
|
||||||
|
const file = options?.file?.file || options?.file
|
||||||
|
|
||||||
|
// 检查文件对象是否有效
|
||||||
|
if (!file || !file.name) {
|
||||||
|
notification.error({
|
||||||
|
content: '文件选择失败,请重试',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件类型 - 使用多重检查确保是TXT文件
|
||||||
|
const isValid = file.type === 'text/plain' ||
|
||||||
|
file.name.toLowerCase().endsWith('.txt') ||
|
||||||
|
file.type === 'text/plain;charset=utf-8'
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
notification.error({
|
||||||
|
content: '请上传TXT文件',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
selectedFile.value = null // 清空之前的选择
|
||||||
|
return false // 阻止上传无效文件
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存选中的文件并更新状态
|
||||||
|
selectedFile.value = file
|
||||||
|
|
||||||
|
return false // 阻止自动上传
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpload = ({ file, onSuccess, onError }: any) => {
|
||||||
|
// 这个函数不会被调用,因为我们阻止了自动上传
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件选择变化时的处理函数
|
||||||
|
const handleFileChange = (options: { file: any, fileList: any[] }) => {
|
||||||
|
// 从 change 事件中提取文件信息
|
||||||
|
const file = options?.file?.file || options?.file
|
||||||
|
|
||||||
|
if (file && file.name) {
|
||||||
|
// 更新选中的文件
|
||||||
|
selectedFile.value = file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerUpload = async () => {
|
||||||
|
if (!selectedFile.value) {
|
||||||
|
notification.warning({
|
||||||
|
content: '请选择要上传的TXT文件',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
try {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', selectedFile.value)
|
||||||
|
|
||||||
|
const response = await wechatApi.uploadVerifyFile(formData)
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
uploadResult.value = response
|
||||||
|
notification.success({
|
||||||
|
content: '验证文件上传成功',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
// 清空选择的文件
|
||||||
|
selectedFile.value = null
|
||||||
|
if (uploadRef.value) {
|
||||||
|
uploadRef.value.clear()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '上传失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('上传验证文件失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: error.message || '上传验证文件失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFullUrl = (path: string) => {
|
||||||
|
if (process.client) {
|
||||||
|
return `${window.location.origin}${path}`
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取配置和状态
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchBotConfig()
|
||||||
|
await fetchBotStatus()
|
||||||
|
// 定期刷新状态
|
||||||
|
const interval = setInterval(fetchBotStatus, 30000)
|
||||||
|
onUnmounted(() => clearInterval(interval))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 微信公众号机器人标签样式 */
|
||||||
|
.tab-content-container {
|
||||||
|
height: calc(100vh - 240px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -354,4 +354,18 @@ export const useSystemLogApi = () => {
|
|||||||
getSystemLogSummary,
|
getSystemLogSummary,
|
||||||
clearSystemLogs
|
clearSystemLogs
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信机器人管理API
|
||||||
|
export const useWechatApi = () => {
|
||||||
|
const getBotConfig = () => useApiFetch('/wechat/bot-config').then(parseApiResponse)
|
||||||
|
const updateBotConfig = (data: any) => useApiFetch('/wechat/bot-config', { method: 'PUT', body: data }).then(parseApiResponse)
|
||||||
|
const getBotStatus = () => useApiFetch('/wechat/bot-status').then(parseApiResponse)
|
||||||
|
const uploadVerifyFile = (formData: FormData) => useApiFetch('/wechat/verify-file', { method: 'POST', body: formData }).then(parseApiResponse)
|
||||||
|
return {
|
||||||
|
getBotConfig,
|
||||||
|
updateBotConfig,
|
||||||
|
getBotStatus,
|
||||||
|
uploadVerifyFile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,13 +23,7 @@
|
|||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<n-tab-pane name="wechat" tab="微信公众号">
|
<n-tab-pane name="wechat" tab="微信公众号">
|
||||||
<div class="tab-content-container">
|
<WechatBotTab />
|
||||||
<div class="text-center py-12">
|
|
||||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
|
||||||
<p class="text-gray-500 dark:text-gray-400">微信公众号机器人功能正在开发中,敬请期待</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<n-tab-pane name="telegram" tab="Telegram机器人">
|
<n-tab-pane name="telegram" tab="Telegram机器人">
|
||||||
@@ -59,6 +53,7 @@ import { ref } from 'vue'
|
|||||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||||
import TelegramBotTab from '~/components/TelegramBotTab.vue'
|
import TelegramBotTab from '~/components/TelegramBotTab.vue'
|
||||||
import QqBotTab from '~/components/QqBotTab.vue'
|
import QqBotTab from '~/components/QqBotTab.vue'
|
||||||
|
import WechatBotTab from '~/components/WechatBotTab.vue'
|
||||||
|
|
||||||
// 设置页面布局
|
// 设置页面布局
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
|||||||
Reference in New Issue
Block a user