From 5a1ede06463cf0065086a970d986cf4cfd47dd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AF=BB=E6=AC=A2?= Date: Wed, 20 Dec 2023 15:42:50 +0800 Subject: [PATCH] =?UTF-8?q?:new:=20=E6=96=B0=E5=A2=9E=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E6=96=B0=E5=A2=9E=E9=9B=B7=E7=A5=9E?= =?UTF-8?q?=E5=8A=A0=E9=80=9F=E5=99=A8=E6=93=8D=E4=BD=9C=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entity/plugindata.go | 13 +++ initialization/plugin.go | 6 ++ model/leigod.go | 73 ++++++++++++++++ plugin/plugins/command.go | 84 ++++++++++++++++++ plugin/plugins/leigod.go | 173 ++++++++++++++++++++++++++++++++++++++ utils/leigod.go | 169 +++++++++++++++++++++++++++++++++++++ utils/string.go | 15 ++++ vo/leigod.go | 8 ++ 8 files changed, 541 insertions(+) create mode 100644 entity/plugindata.go create mode 100644 model/leigod.go create mode 100644 plugin/plugins/command.go create mode 100644 plugin/plugins/leigod.go create mode 100644 utils/leigod.go create mode 100644 utils/string.go create mode 100644 vo/leigod.go diff --git a/entity/plugindata.go b/entity/plugindata.go new file mode 100644 index 00000000..473e3797 --- /dev/null +++ b/entity/plugindata.go @@ -0,0 +1,13 @@ +package entity + +// PluginData +// @description: 插件数据 +type PluginData struct { + UserId string `json:"userId"` // 用户Id + PluginCode string `json:"pluginCode"` // 插件编码 + Data string `json:"data"` // 数据 +} + +func (PluginData) TableName() string { + return "t_plugin_data" +} diff --git a/initialization/plugin.go b/initialization/plugin.go index 2efd4011..5340a9a0 100644 --- a/initialization/plugin.go +++ b/initialization/plugin.go @@ -22,6 +22,12 @@ func Plugin() { return true }, plugins.SaveToDb) + // 私聊指令消息 + dispatcher.RegisterHandler(func(m *model.Message) bool { + // 私聊消息直接进去 + return m.IsPrivateText() + }, plugins.Command) + // AI消息插件 dispatcher.RegisterHandler(func(m *model.Message) bool { // 群内@或者私聊文字消息 diff --git a/model/leigod.go b/model/leigod.go new file mode 100644 index 00000000..274f15aa --- /dev/null +++ b/model/leigod.go @@ -0,0 +1,73 @@ +package model + +// LeiGodLoginResp +// @description: 雷神登录返回 +type LeiGodLoginResp struct { + LoginInfo struct { + AccountToken string `json:"account_token"` // Token + ExpiryTime string `json:"expiry_time"` // 有效期 + NnToken string `json:"nn_token"` + } `json:"login_info"` + UserInfo struct { + Nickname string `json:"nickname"` + Email string `json:"email"` + Mobile string `json:"mobile"` + Avatar string `json:"avatar"` + RegionCode int `json:"region_code"` + } `json:"user_info"` +} + +// LeiGodUserInfoResp +// @description: 雷神用户信息返回 +type LeiGodUserInfoResp struct { + UserPauseTime int `json:"user_pause_time"` + Nickname string `json:"nickname"` + Email string `json:"email"` + CountryCode string `json:"country_code"` + Mobile string `json:"mobile"` + UserName string `json:"user_name"` + MasterAccount string `json:"master_account"` + Birthday string `json:"birthday"` + PublicIp string `json:"public_ip"` + Sex string `json:"sex"` + LastLoginTime string `json:"last_login_time"` + LastLoginIp string `json:"last_login_ip"` + PauseStatus string `json:"pause_status"` // 暂停状态 + PauseStatusId int `json:"pause_status_id"` // 暂停状态 0未暂停1已暂停 + LastPauseTime string `json:"last_pause_time"` // 最后一次暂停时间 + VipLevel string `json:"vip_level"` + Avatar string `json:"avatar"` + AvatarNew string `json:"avatar_new"` + PackageId string `json:"package_id"` + IsSwitchPackage int `json:"is_switch_package"` + PackageTitle string `json:"package_title"` + PackageLevel string `json:"package_level"` + BillingType string `json:"billing_type"` + Lang string `json:"lang"` + StopedRemaining string `json:"stoped_remaining"` + ExpiryTime string `json:"expiry_time"` // 剩余时长 + ExpiryTimeSamp int `json:"expiry_time_samp"` // 剩余时长秒数 + Address string `json:"address"` + MobileContactType string `json:"mobile_contact_type"` + MobileContactNumber string `json:"mobile_contact_number"` + MobileContactTitle string `json:"mobile_contact_title"` + RegionCode int `json:"region_code"` + IsPayUser string `json:"is_pay_user"` + WallLogSwitch string `json:"wall_log_switch"` + IsSetAdminPass int `json:"is_set_admin_pass"` + ExpiredExperienceTime string `json:"expired_experience_time"` + ExperienceExpiryTime string `json:"experience_expiry_time"` + ExperienceTime int `json:"experience_time"` + FirstInvoiceDiscount int `json:"first_invoice_discount"` + NnNumber string `json:"nn_number"` + UserSignature string `json:"user_signature"` + MobileExpiryTime string `json:"mobile_expiry_time"` + MobileExpiryTimeSamp int `json:"mobile_expiry_time_samp"` + MobilePauseStatus int `json:"mobile_pause_status"` + BlackExpiredTime string `json:"black_expired_time"` + MobileExperienceTime string `json:"mobile_experience_time"` + SuperTime string `json:"super_time"` + NowDate string `json:"now_date"` + NowTimeSamp int `json:"now_time_samp"` + UserEarnMinutes string `json:"user_earn_minutes"` +} diff --git a/plugin/plugins/command.go b/plugin/plugins/command.go new file mode 100644 index 00000000..af7f32dd --- /dev/null +++ b/plugin/plugins/command.go @@ -0,0 +1,84 @@ +package plugins + +import ( + "go-wechat/plugin" + "go-wechat/utils" + "strings" +) + +// Command +// @description: 自定义指令 +// @param m +func Command(m *plugin.MessageContext) { + // 判断是不是指令 + if !strings.HasPrefix(m.Content, "/") { + return + } + + // 用空格分割消息,下标0表示指令 + msgArray := strings.Split(m.Content, " ") + cmd := msgArray[0] + + switch cmd { + case "/帮助", "/h", "/help", "/?", "/?": + helpCmd(m) + case "/ls", "/雷神": + leiGodCmd(m.FromUser, msgArray[1], msgArray[2:]...) + default: + utils.SendMessage(m.FromUser, m.GroupUser, "指令错误", 0) + } + + // 中止后续消息处理 + m.Abort() +} + +// helpCmd +// @description: 帮助指令 +func helpCmd(m *plugin.MessageContext) { + str := `帮助菜单: +指令消息必须以'/'开头,比如: '/帮助'。 +支持的指令: + +#1. 雷神加速器 +/ls option args +option: 指令选项,可选值: + 绑定账户:'绑定'、'b',参数: 账户名 密码 [-f],-f表示强制绑定,非必传项 + 详情: '详情'、'i' + 暂停: '暂停'、'p' +示例: 绑定: +/ls 绑定 123456 123456 或者 /ls b 123456 123456 +` + utils.SendMessage(m.FromUser, m.GroupUser, str, 0) + +} + +// leiGodCmd +// @description: 雷神加速器指令 +// @param userId +// @param cmd +// @param args +// @return string +func leiGodCmd(userId, cmd string, args ...string) { + lg := newLeiGod(userId) + + var replyMsg string + switch cmd { + case "绑定", "b": + var force bool + if len(args) == 3 && args[2] == "-f" { + force = true + } + replyMsg = lg.binding(args[0], args[1], force) + case "详情", "i": + replyMsg = lg.info() + case "暂停", "p": + replyMsg = lg.pause() + default: + replyMsg = "指令错误" + } + + // 返回消息 + if strings.TrimSpace(replyMsg) != "" { + utils.SendMessage(userId, "", replyMsg, 0) + } +} diff --git a/plugin/plugins/leigod.go b/plugin/plugins/leigod.go new file mode 100644 index 00000000..4ecd8eb1 --- /dev/null +++ b/plugin/plugins/leigod.go @@ -0,0 +1,173 @@ +package plugins + +import ( + "encoding/json" + "errors" + "fmt" + "go-wechat/client" + "go-wechat/entity" + "go-wechat/model" + "go-wechat/utils" + "go-wechat/vo" + "gorm.io/gorm" + "log" +) + +// leiGod +// @description: 雷神加速器相关接口 +type leiGodI interface { + binding(string, string, bool) string // 绑定雷神加速器账号 + info() string // 账户详情 + pause() string // 暂停加速 +} + +type leiGod struct { + userId string // 用户Id +} + +// newLeiGod +// @description: 创建一个雷神加速器实例 +// @param userId +// @return leiGodI +func newLeiGod(userId string) leiGodI { + return &leiGod{userId: userId} +} + +// binding +// @description: 绑定雷神加速器账号 +// @receiver l +// @param account +// @param password +// @param force +// @return flag +func (l leiGod) binding(account, password string, force bool) (replyMsg string) { + log.Printf("用户[%s]绑定雷神加速器账号[%s] -> %s", l.userId, account, password) + + // 取出已绑定的账号 + var data entity.PluginData + client.MySQL.Where("user_id = ?", l.userId).Where("plugin_code = 'leigod'").First(&data) + + var ac vo.LeiGodAccount + if data.UserId != "" { + if err := json.Unmarshal([]byte(data.Data), &ac); err != nil { + log.Printf("用户[%s]已绑定雷神账号解析失败: %v", l.userId, err) + return + } + log.Printf("用户[%s]已绑定账号[%s]", l.userId, ac.Account) + } + + // 如果已经绑定账号,且不是强制绑定,则返回 + if ac.Account != "" && !force { + replyMsg = "您已绑定账号[" + ac.Account + "],如需更换请使用 -f 参数: \n/雷神 绑定 账号 密码 -f" + return + } + + accountStr := fmt.Sprintf("{\"account\": \"%s\", \"password\":\"%s\"}", account, password) + + // 绑定账号 + var err error + if data.UserId != "" { + // 修改 + err = client.MySQL.Model(&data). + Where("user_id = ?", l.userId). + Where("plugin_code = 'leigod'"). + Update("data", accountStr).Error + } else { + // 新增 + data = entity.PluginData{ + UserId: l.userId, + PluginCode: "leigod", + Data: accountStr, + } + err = client.MySQL.Create(&data).Error + } + + if err != nil { + log.Printf("用户[%s]绑定雷神账号失败: %v", l.userId, err) + replyMsg = "绑定失败: " + err.Error() + } else { + replyMsg = "绑定成功" + } + + return +} + +// info +// @description: 账户详情 +// @receiver l +// @return replyMsg +func (l leiGod) info() (replyMsg string) { + log.Printf("用户[%s]获取雷神账户详情", l.userId) + + // 取出已绑定的账号 + var data entity.PluginData + err := client.MySQL.Where("user_id = ?", l.userId).Where("plugin_code = 'leigod'").First(&data).Error + if err != nil { + log.Printf("用户[%s]获取雷神账户详情失败: %v", l.userId, err) + if errors.Is(err, gorm.ErrRecordNotFound) { + replyMsg = "您还未绑定账号,请先绑定后再使用,绑定指定:\n/雷神 绑定 你的账号 你的密码" + } else { + replyMsg = "系统错误: " + err.Error() + } + return + } + + // 解析为结构体 + var ac vo.LeiGodAccount + if err = json.Unmarshal([]byte(data.Data), &ac); err != nil { + log.Printf("用户[%s]已绑定雷神账号解析失败: %v", l.userId, err) + replyMsg = "系统炸了,请耐心等待修复" + return + } + + lgu := utils.LeiGodUtil(ac.Account, ac.Password) + if err = lgu.Login(); err != nil { + return "登录失败: " + err.Error() + } + var ui model.LeiGodUserInfoResp + if ui, err = lgu.Info(); err != nil { + return "获取详情失败: " + err.Error() + } + replyMsg = fmt.Sprintf("#账户 %s\n#剩余时长 %s\n#暂停状态 %s\n#最后暂停时间 %s", + ui.Mobile, ui.ExpiryTime, ui.PauseStatus, ui.LastPauseTime) + return +} + +// pause +// @description: 暂停加速 +// @receiver l +// @return flag +func (l leiGod) pause() (replyMsg string) { + log.Printf("用户[%s]暂停加速", l.userId) + + // 取出已绑定的账号 + var data entity.PluginData + err := client.MySQL.Where("user_id = ?", l.userId).Where("plugin_code = 'leigod'").First(&data).Error + if err != nil { + log.Printf("用户[%s]获取雷神账户详情失败: %v", l.userId, err) + if errors.Is(err, gorm.ErrRecordNotFound) { + replyMsg = "您还未绑定账号,请先绑定后再使用,绑定指定:\n/雷神 绑定 你的账号 你的密码" + } else { + replyMsg = "系统错误: " + err.Error() + } + return + } + + // 解析为结构体 + var ac vo.LeiGodAccount + if err = json.Unmarshal([]byte(data.Data), &ac); err != nil { + log.Printf("用户[%s]已绑定雷神账号解析失败: %v", l.userId, err) + replyMsg = "系统炸了,请耐心等待修复" + return + } + + lgu := utils.LeiGodUtil(ac.Account, ac.Password) + if err = lgu.Login(); err != nil { + return "登录失败: " + err.Error() + } + if err = lgu.Pause(); err != nil { + return "暂停失败: " + err.Error() + } + + return "暂停成功" +} diff --git a/utils/leigod.go b/utils/leigod.go new file mode 100644 index 00000000..c20f6289 --- /dev/null +++ b/utils/leigod.go @@ -0,0 +1,169 @@ +package utils + +import ( + "crypto/md5" + "encoding/json" + "errors" + "fmt" + "github.com/go-resty/resty/v2" + "go-wechat/model" + "log" +) + +// LeiGod +// @description: 雷神加速器相关接口 +type LeiGod interface { + Login() error // 登录 + Info() (model.LeiGodUserInfoResp, error) // 获取用户信息 + Pause() error // 暂停加速 +} + +type leiGod struct { + account, password string // 账号、密码 + token string +} + +// LeiGodUtil +// @description: 创建一个雷神加速器工具类 +// @param userId +// @return leiGodI +func LeiGodUtil(account, password string) LeiGod { + // 把密码md5一下 + hash := md5.New() + hash.Write([]byte(password)) + password = fmt.Sprintf("%x", hash.Sum(nil)) + + return &leiGod{account: account, password: password} +} + +// Login +// @description: 登录 +// @receiver l +// @return string +func (l *leiGod) Login() (err error) { + // 组装参数 + param := map[string]any{ + "account_token": nil, + "country_code": 86, + "lang": "zh_CN", + "os_type": 4, + "mobile_num": l.account, + "username": l.account, + "password": l.password, + "region_code": 1, + "src_channel": "guanwang", + "sem_ad_img_url": map[string]any{ + "btn_yrl": "", + "url": "", + }, + } + pbs, _ := json.Marshal(param) + + var loginResp model.Response[any] + var resp *resty.Response + + res := resty.New() + resp, err = res.R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetBody(string(pbs)). + SetResult(&loginResp). + Post("https://webapi.leigod.com/api/auth/login") + if err != nil { + log.Panicf("雷神加速器登录失败: %s", err.Error()) + } + log.Printf("雷神加速器登录结果: %s", unicodeToText(resp.String())) + + // 返回状态码不是0表示有错 + if loginResp.Code != 0 { + return errors.New(loginResp.Msg) + } + + // 将Data字段转为结构体 + var bs []byte + if bs, err = json.Marshal(loginResp.Data); err != nil { + return + } + + var loginInfo model.LeiGodLoginResp + if err = json.Unmarshal(bs, &loginInfo); err != nil { + return + } + + if loginInfo.LoginInfo.AccountToken != "" { + l.token = loginInfo.LoginInfo.AccountToken + } + + return +} + +// Info +// @description: 获取用户信息 +// @receiver l +// @return string +func (l *leiGod) Info() (ui model.LeiGodUserInfoResp, err error) { + // 组装参数 + param := map[string]any{ + "account_token": l.token, + "lang": "zh_CN", + "os_type": 4, + } + pbs, _ := json.Marshal(param) + + var userInfoResp model.Response[model.LeiGodUserInfoResp] + var resp *resty.Response + + res := resty.New() + resp, err = res.R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetBody(string(pbs)). + SetResult(&userInfoResp). + Post("https://webapi.leigod.com/api/user/info") + if err != nil { + log.Panicf("雷神加速器用户信息获取失败: %s", err.Error()) + } + log.Printf("雷神加速器用户信息获取结果: %s", unicodeToText(resp.String())) + + // 返回状态码不是0表示有错 + if userInfoResp.Code != 0 { + err = errors.New(userInfoResp.Msg) + return + } + + return userInfoResp.Data, err +} + +// Pause +// @description: 暂停加速 +// @receiver l +// @return string +func (l *leiGod) Pause() (err error) { + // 组装参数 + param := map[string]any{ + "account_token": l.token, + "lang": "zh_CN", + "os_type": 4, + } + pbs, _ := json.Marshal(param) + + var pauseResp model.Response[any] + var resp *resty.Response + + res := resty.New() + resp, err = res.R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetBody(string(pbs)). + SetResult(&pauseResp). + Post("https://webapi.leigod.com/api/user/pause") + if err != nil { + log.Panicf("雷神加速器暂停失败: %s", err.Error()) + } + log.Printf("雷神加速器暂停结果: %s", unicodeToText(resp.String())) + + // 返回状态码不是0表示有错 + if pauseResp.Code != 0 { + err = errors.New(pauseResp.Msg) + return + } + + return +} diff --git a/utils/string.go b/utils/string.go new file mode 100644 index 00000000..57157d27 --- /dev/null +++ b/utils/string.go @@ -0,0 +1,15 @@ +package utils + +import ( + "strconv" + "strings" +) + +// unicodeToText +// @description: unicode转文本 +// @param str +// @return dst +func unicodeToText(str string) (dst string) { + dst, _ = strconv.Unquote(strings.Replace(strconv.Quote(str), `\\u`, `\u`, -1)) + return +} diff --git a/vo/leigod.go b/vo/leigod.go new file mode 100644 index 00000000..922378f8 --- /dev/null +++ b/vo/leigod.go @@ -0,0 +1,8 @@ +package vo + +// LeiGodAccount +// @description: 雷神账号 +type LeiGodAccount struct { + Account string `json:"account"` // 账号 + Password string `json:"password"` // 密码 +}