🆕 新增指令插件,新增雷神加速器操作指令 #2

Merged
李寻欢 merged 1 commits from dev into main 2023-12-20 15:43:32 +08:00
8 changed files with 541 additions and 0 deletions

13
entity/plugindata.go Normal file
View File

@ -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"
}

View File

@ -22,6 +22,12 @@ func Plugin() {
return true return true
}, plugins.SaveToDb) }, plugins.SaveToDb)
// 私聊指令消息
dispatcher.RegisterHandler(func(m *model.Message) bool {
// 私聊消息直接进去
return m.IsPrivateText()
}, plugins.Command)
// AI消息插件 // AI消息插件
dispatcher.RegisterHandler(func(m *model.Message) bool { dispatcher.RegisterHandler(func(m *model.Message) bool {
// 群内@或者私聊文字消息 // 群内@或者私聊文字消息

73
model/leigod.go Normal file
View File

@ -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"`
}

84
plugin/plugins/command.go Normal file
View File

@ -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)
}
}

173
plugin/plugins/leigod.go Normal file
View File

@ -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 "暂停成功"
}

169
utils/leigod.go Normal file
View File

@ -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
}

15
utils/string.go Normal file
View File

@ -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
}

8
vo/leigod.go Normal file
View File

@ -0,0 +1,8 @@
package vo
// LeiGodAccount
// @description: 雷神账号
type LeiGodAccount struct {
Account string `json:"account"` // 账号
Password string `json:"password"` // 密码
}