diff --git a/api/admin/robot/delete.go b/api/admin/robot/delete.go new file mode 100644 index 0000000..92f4c00 --- /dev/null +++ b/api/admin/robot/delete.go @@ -0,0 +1,25 @@ +package robot + +import ( + "github.com/gin-gonic/gin" + "unicode/utf8" + "wechat-robot/pkg/response" + robotService "wechat-robot/service/robot" +) + +// DeleteById +// @description: 删除机器人 +// @param ctx +func DeleteById(ctx *gin.Context) { + var id = ctx.Param("id") + if utf8.RuneCountInString(id) != 32 { + response.New(ctx).SetMsg("参数错误").Fail() + return + } + // 删除数据 + if err := robotService.DeleteById(id); err != nil { + response.New(ctx).SetMsg("删除失败").SetError(err).Fail() + } else { + response.New(ctx).SetMsg("删除成功").Success() + } +} diff --git a/api/admin/robot/save.go b/api/admin/robot/save.go new file mode 100644 index 0000000..d700c8b --- /dev/null +++ b/api/admin/robot/save.go @@ -0,0 +1,31 @@ +package robot + +import ( + "github.com/gin-gonic/gin" + robotParam "wechat-robot/model/param/robot" + "wechat-robot/pkg/response" + robotService "wechat-robot/service/robot" +) + +// Save +// @description: 保存机器人 +// @param ctx +func Save(ctx *gin.Context) { + var p robotParam.Save + if err := ctx.ShouldBind(&p); err != nil { + response.New(ctx).SetMsg("参数错误").SetError(err).Fail() + return + } + + // 参数校验 + if p.Id == "" && (p.HookApi == "" || p.Version == 0) { + response.New(ctx).SetMsg("参数错误").Fail() + return + } + + if err := robotService.Save(p); err != nil { + response.New(ctx).SetMsg("保存失败").SetError(err).Fail() + return + } + response.New(ctx).SetMsg("保存成功").Success() +} diff --git a/api/admin/robot/select.go b/api/admin/robot/select.go new file mode 100644 index 0000000..3ea8838 --- /dev/null +++ b/api/admin/robot/select.go @@ -0,0 +1,26 @@ +package robot + +import ( + "github.com/gin-gonic/gin" + robotParam "wechat-robot/model/param/robot" + "wechat-robot/pkg/response" + robotService "wechat-robot/service/robot" +) + +// GetAll +// @description: 获取所有机器人 +// @param ctx +func GetAll(ctx *gin.Context) { + var p robotParam.GetAll + if err := ctx.ShouldBind(&p); err != nil { + response.New(ctx).SetMsg("参数错误").SetError(err).Fail() + return + } + + records, err := robotService.GetAll(p) + if err != nil { + response.New(ctx).SetMsg("数据获取失败").SetError(err).Fail() + return + } + response.New(ctx).SetData(records).Success() +} diff --git a/go.mod b/go.mod index 8280710..0ea4e8c 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/validator/v10 v10.17.0 github.com/go-redis/redis/v8 v8.11.5 + github.com/go-resty/resty/v2 v2.11.0 github.com/google/uuid v1.6.0 github.com/mojocn/base64Captcha v1.3.6 github.com/spf13/viper v1.18.2 diff --git a/go.sum b/go.sum index bc7acc5..4295b0a 100644 --- a/go.sum +++ b/go.sum @@ -315,6 +315,8 @@ github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/go-redis/redis/v8 v8.0.0-beta.5/go.mod h1:Mm9EH/5UMRx680UIryN6rd5XFn/L7zORPqLV+1D5thQ= 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.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -985,6 +987,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1076,6 +1079,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1176,11 +1181,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1190,6 +1199,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -1198,6 +1208,9 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/model/entity/robot.go b/model/entity/robot.go index 5209e4c..b9c8e16 100644 --- a/model/entity/robot.go +++ b/model/entity/robot.go @@ -15,6 +15,10 @@ type Robot struct { DataSavePath string `json:"dataSavePath" gorm:"type:varchar(255);comment:'微信保存目录'"` // 微信保存目录 DbKey string `json:"dbKey" gorm:"type:varchar(255);comment:'数据库的SQLCipher的加密key'"` // 数据库的SQLCipher的加密key,可以使用该key配合decrypt.py解密数据库 HookApi string `json:"hookApi" gorm:"type:varchar(255);comment:'hook接口地址'"` // hook接口地址 + Remark string `json:"remark" gorm:"type:varchar(255);comment:'备注'"` // 备注 + Version int `json:"version" gorm:"type:int;comment:'版本号'"` // 版本号 + VncUrl string `json:"vncUrl" gorm:"type:varchar(255);comment:'vnc地址'"` // vnc地址 + Tag string `json:"tag" gorm:"type:varchar(255);comment:'标签'"` // 标签 } // TableName diff --git a/model/param/robot/robot.go b/model/param/robot/robot.go new file mode 100644 index 0000000..42b0f06 --- /dev/null +++ b/model/param/robot/robot.go @@ -0,0 +1,13 @@ +package robot + +import "wechat-robot/pkg/wxhelper" + +// Save +// @description: 保存机器人 +type Save struct { + Id string `json:"id" form:"id"` // id + HookApi string `json:"hookApi" form:"hookApi"` // hook接口地址 + Version wxhelper.WeChatVersion `json:"version" form:"version"` // 版本 + VncUrl string `json:"vncUrl" form:"vncUrl"` // vnc地址 + Remark string `json:"remark" form:"remark"` // 备注 +} diff --git a/model/param/robot/select.go b/model/param/robot/select.go new file mode 100644 index 0000000..a66fdbe --- /dev/null +++ b/model/param/robot/select.go @@ -0,0 +1,8 @@ +package robot + +// GetAll +// @description: 查询所有机器人入参 +type GetAll struct { + Keyword string `json:"keyword" form:"keyword"` // 关键字 + Tag string `json:"tag" form:"tag"` // 标签 +} diff --git a/model/robot/chatroom.go b/model/robot/chatroom.go new file mode 100644 index 0000000..91ba3bc --- /dev/null +++ b/model/robot/chatroom.go @@ -0,0 +1,20 @@ +package model + +// ChatRoomDetailInfo +// @description: 群聊详情 +type ChatRoomDetailInfo struct { + Admin string `json:"admin"` // 群主微信 + ChatRoomId string `json:"chatRoomId"` // 群Id + Notice string `json:"notice"` // 群公告 + Xml string `json:"xml"` // 不知道是啥玩意儿 +} + +// GroupUser +// @description: 群成员返回结果 +type GroupUser struct { + Admin string `json:"admin"` // 群主微信 + AdminNickname string `json:"adminNickname"` // 群主昵称 + ChatRoomId string `json:"chatRoomId"` // 群Id + MemberNickname string `json:"memberNickname"` // 成员昵称 `^G`切割 + Members string `json:"members"` // 成员Id `^G`切割 +} diff --git a/model/robot/friend.go b/model/robot/friend.go new file mode 100644 index 0000000..3104172 --- /dev/null +++ b/model/robot/friend.go @@ -0,0 +1,26 @@ +package model + +// FriendItem +// @description: 好友列表数据 +type FriendItem struct { + CustomAccount string `json:"customAccount"` // 微信号 + EncryptName string `json:"encryptName"` // 不知道 + Nickname string `json:"nickname"` // 昵称 + Pinyin string `json:"pinyin"` // 昵称拼音大写首字母 + PinyinAll string `json:"pinyinAll"` // 昵称全拼 + Reserved1 int `json:"reserved1"` // 未知 + Reserved2 int `json:"reserved2"` // 未知 + Type int `json:"type"` // 类型 + VerifyFlag int `json:"verifyFlag"` // 未知 + WxId string `json:"wxid"` // 微信原始Id +} + +// ContactProfile +// @description: 好友资料 +type ContactProfile struct { + Account string `json:"account"` // 账号 + HeadImage string `json:"headImage"` // 头像 + Nickname string `json:"nickname"` // 昵称 + V3 string `json:"v3"` // v3 + WxId string `json:"wxid"` // 微信Id +} diff --git a/model/robot/response.go b/model/robot/response.go new file mode 100644 index 0000000..4bf376e --- /dev/null +++ b/model/robot/response.go @@ -0,0 +1,9 @@ +package model + +// Response +// @description: 基础返回结构体 +type Response[T any] struct { + Code int `json:"code"` // 状态码 + Data T `json:"data"` // 数据 + Msg string `json:"msg"` // 消息 +} diff --git a/model/robot/userinfo.go b/model/robot/userinfo.go new file mode 100644 index 0000000..c6449df --- /dev/null +++ b/model/robot/userinfo.go @@ -0,0 +1,18 @@ +package model + +// UserInfo +// @description: 机器人用户信息 +type UserInfo struct { + WxId string `json:"wxid"` // 微信Id + Account string `json:"account"` // 微信号 + Name string `json:"name"` // 昵称 + HeadImage string `json:"headImage"` // 头像 + Mobile string `json:"mobile"` // 手机 + Signature string `json:"signature"` // 个人签名 + Country string `json:"country"` // 国家 + Province string `json:"province"` // 省 + City string `json:"city"` // 城市 + CurrentDataPath string `json:"currentDataPath"` // 当前数据目录,登录的账号目录 + DataSavePath string `json:"dataSavePath"` // 微信保存目录 + DbKey string `json:"dbKey"` // 数据库的SQLCipher的加密key,可以使用该key配合decrypt.py解密数据库 +} diff --git a/pkg/wxhelper/base.go b/pkg/wxhelper/base.go new file mode 100644 index 0000000..8f74345 --- /dev/null +++ b/pkg/wxhelper/base.go @@ -0,0 +1,44 @@ +package wxhelper + +import ( + "errors" + robotModel "wechat-robot/model/robot" + "wechat-robot/utils" +) + +// CheckLogin +// @description: 检查是否登录 +// @receiver wx +// @return flag +func (wx wxHelper) CheckLogin() (flag bool) { + var api string + if api, flag = wx.version.GetApi("CheckLogin"); !flag { + return + } + // 调用接口 + var resp robotModel.Response[any] + _ = utils.HttpClientUtils().Post(wx.host+api, nil, &resp, 0) + return resp.Code == 1 +} + +// UserInfo +// @description: 获取机器人信息 +// @receiver wx +// @return data +// @return err +func (wx wxHelper) UserInfo() (data robotModel.UserInfo, err error) { + var api string + var flag bool + if api, flag = wx.version.GetApi("UserInfo"); !flag { + err = errors.New("不支持的接口") + return + } + // 调用接口 + var resp robotModel.Response[robotModel.UserInfo] + if err = utils.HttpClientUtils().Post(wx.host+api, nil, &resp, 0); err != nil { + return + } + + data = resp.Data + return +} diff --git a/pkg/wxhelper/chatroom.go b/pkg/wxhelper/chatroom.go new file mode 100644 index 0000000..12c312e --- /dev/null +++ b/pkg/wxhelper/chatroom.go @@ -0,0 +1,61 @@ +package wxhelper + +import robotModel "wechat-robot/model/robot" + +// GetChatRoomDetailInfo +// @description: 获取群聊详情 +// @receiver wx +// @param chatRoomId +// @return data +func (wx wxHelper) GetChatRoomDetailInfo(chatRoomId string) (data robotModel.ChatRoomDetailInfo) { + return +} + +// AddMemberToChatRoom +// @description: 添加群成员 +// @receiver wx +// @param chatRoomId +// @param memberWxIds +// @return err +func (wx wxHelper) AddMemberToChatRoom(chatRoomId string, memberWxIds []string) (err error) { + return +} + +// DelMemberFromChatRoom +// @description: 删除群成员 +// @receiver wx +// @param chatRoomId +// @param memberWxIds +// @return err +func (wx wxHelper) DelMemberFromChatRoom(chatRoomId string, memberWxIds []string) (err error) { + return +} + +// GetMemberFromChatRoom +// @description: 获取群成员 +// @receiver wx +// @param chatRoomId +// @return data +func (wx wxHelper) GetMemberFromChatRoom(chatRoomId string) (data robotModel.GroupUser) { + return +} + +// InviteMemberToChatRoom +// @description: 邀请入群 +// @receiver wx +// @param chatRoomId +// @param memberWxIds +// @return err +func (wx wxHelper) InviteMemberToChatRoom(chatRoomId string, memberWxIds []string) (err error) { + return +} + +// ModifyChatRoomNickname +// @description: 修改群内昵称 +// @receiver wx +// @param wxId +// @param nickname +// @return err +func (wx wxHelper) ModifyChatRoomNickname(wxId, nickname string) (err error) { + return +} diff --git a/pkg/wxhelper/constant.go b/pkg/wxhelper/constant.go new file mode 100644 index 0000000..6b8e7f7 --- /dev/null +++ b/pkg/wxhelper/constant.go @@ -0,0 +1,79 @@ +package wxhelper + +// WeChatVersion 微信版本 +type WeChatVersion int + +// 微信版本 +const ( + WechatVersion39223 WeChatVersion = 39223 + WechatVersion39581 WeChatVersion = 39581 + WechatVersion39825 WeChatVersion = 39825 +) + +// 接口映射 +var apiMap = map[WeChatVersion]map[string]string{ + WechatVersion39223: v39223ApiMap, + WechatVersion39581: v39581ApiMap, + WechatVersion39825: v39581ApiMap, +} + +var v39223ApiMap = map[string]string{ + "CheckLogin": "/api/?type=0", + "UserInfo": "/api/?type=1", + "SendTextMsg": "/api/?type=2", + "SendAtTest": "/api/?type=3", + "SendFileMsg": "/api/?type=6", + "SendImagesMsg": "/api/?type=5", + "SendCustomEmotionMsg": "", + "GetContactList": "/api/?type=46", + "GetChatRoomDetailInfo": "/api/?type=47", + "AddMemberToChatRoom": "/api/?type=28", + "DelMemberFromChatRoom": "/api/?type=27", + "GetMemberFromChatRoom": "/api/?type=25", + "GetContactProfile": "/api/?type=25", + "InviteMemberToChatRoom": "", + "ModifyChatRoomNickname": "/api/?type=31", + "StartHook": "/api/?type=9", + "StopHook": "/api/?type=10", + "DownloadAttach": "/api/?type=56", + "DecodeImage": "/api/?type=48", +} + +var v39581ApiMap = map[string]string{ + "CheckLogin": "/api/checkLogin", + "UserInfo": "/api/userInfo", + "SendTextMsg": "/api/sendTextMsg", + "SendAtTest": "/api/sendAtText", + "SendFileMsg": "/api/sendFileMsg", + "SendImagesMsg": "/api/sendImagesMsg", + "SendCustomEmotionMsg": "/api/sendCustomEmotion", + "GetContactList": "/api/getContactList", + "GetChatRoomDetailInfo": "/api/getChatRoomDetailInfo", + "AddMemberToChatRoom": "/api/addMemberToChatRoom", + "DelMemberFromChatRoom": "/api/delMemberFromChatRoom", + "GetMemberFromChatRoom": "/api/getMemberFromChatRoom", + "GetContactProfile": "/api/getContactProfile", + "InviteMemberToChatRoom": "/api/InviteMemberToChatRoom", + "ModifyChatRoomNickname": "/api/modifyNickname", + "QuitChatRoom": "/api/quitChatRoom", + "StartHook": "/api/hookSyncMsg", + "StopHook": "/api/unhookSyncMsg", + "DownloadAttach": "/api/downloadAttach", + "DecodeImage": "/api/decodeImage", +} + +// GetApi +// @description: 获取接口地址 +// @receiver v +// @return string +func (v WeChatVersion) GetApi(code string) (api string, ok bool) { + var am map[string]string + // 判断版本是否支持 + if am, ok = apiMap[v]; !ok { + return + } + + // 获取接口 + api, ok = am[code] + return +} diff --git a/pkg/wxhelper/contact.go b/pkg/wxhelper/contact.go new file mode 100644 index 0000000..4a5d1ae --- /dev/null +++ b/pkg/wxhelper/contact.go @@ -0,0 +1,21 @@ +package wxhelper + +import robotModel "wechat-robot/model/robot" + +// GetContactList +// @description: 获取联系人列表 +// @receiver wx +// @return []robotModel.FriendItem +func (wx wxHelper) GetContactList() (friends []robotModel.FriendItem) { + + return +} + +// GetContactProfile +// @description: 获取好友资料 +// @receiver wx +// @param wxId +// @return robotModel.ContactProfile +func (wx wxHelper) GetContactProfile(wxId string) (profile robotModel.ContactProfile) { + return +} diff --git a/pkg/wxhelper/file.go b/pkg/wxhelper/file.go new file mode 100644 index 0000000..82a7f93 --- /dev/null +++ b/pkg/wxhelper/file.go @@ -0,0 +1,20 @@ +package wxhelper + +// DownloadAttach +// @description: 下载附件 +// @receiver wx +// @param msgId +// @return err +func (wx wxHelper) DownloadAttach(msgId int) (err error) { + return +} + +// DecodeImage +// @description: 解码图片 +// @receiver wx +// @param filePath +// @param storeDir +// @return err +func (wx wxHelper) DecodeImage(filePath, storeDir string) (err error) { + return +} diff --git a/pkg/wxhelper/hook.go b/pkg/wxhelper/hook.go new file mode 100644 index 0000000..20df6bd --- /dev/null +++ b/pkg/wxhelper/hook.go @@ -0,0 +1,19 @@ +package wxhelper + +// StartHook +// @description: 启动hook +// @receiver wx +// @param tcpIp +// @param tcpPort +// @return err +func (wx wxHelper) StartHook(tcpIp string, tcpPort int) (err error) { + return +} + +// StopHook +// @description: 停止hook +// @receiver wx +// @return err +func (wx wxHelper) StopHook() (err error) { + return +} diff --git a/pkg/wxhelper/message.go b/pkg/wxhelper/message.go new file mode 100644 index 0000000..0dbebb5 --- /dev/null +++ b/pkg/wxhelper/message.go @@ -0,0 +1,46 @@ +package wxhelper + +// SendTextMsg +// @description: 发送文字消息 +// @receiver wx +// @param toUserId +// @param atUserId +// @param msg +// @return err +func (wx wxHelper) SendTextMsg(toUserId, atUserId, msg string) (err error) { + + return +} + +// SendFileMsg +// @description: 发送文件消息 +// @receiver wx +// @param toUserId +// @param filePath +// @return err +func (wx wxHelper) SendFileMsg(toUserId, filePath string) (err error) { + + return +} + +// SendImagesMsg +// @description: 发送图片消息 +// @receiver wx +// @param toUserId +// @param imagePath +// @return err +func (wx wxHelper) SendImagesMsg(toUserId, imagePath string) (err error) { + + return +} + +// SendCustomEmotionMsg +// @description: 发送自定义表情消息 +// @receiver wx +// @param toUserId +// @param emotionPath +// @return err +func (wx wxHelper) SendCustomEmotionMsg(toUserId string, emotionPath string) (err error) { + + return +} diff --git a/pkg/wxhelper/wxhelper.go b/pkg/wxhelper/wxhelper.go new file mode 100644 index 0000000..53454f5 --- /dev/null +++ b/pkg/wxhelper/wxhelper.go @@ -0,0 +1,52 @@ +package wxhelper + +import ( + "errors" + "slices" + robotModel "wechat-robot/model/robot" +) + +type wxHelper struct { + host string // hook接口地址 + version WeChatVersion // 微信版本 +} + +// Api +// @description: 微信助手接口 +type Api interface { + CheckLogin() bool // 检查是否登录 + UserInfo() (robotModel.UserInfo, error) // 获取机器人信息 + SendTextMsg(toUserId, atUserId, msg string) error // 发送文字消息 + SendFileMsg(toUserId, filePath string) error // 发送文件消息 + SendImagesMsg(toUserId, imagePath string) error // 发送图片消息 + SendCustomEmotionMsg(toUserId string, emotionPath string) error // 发送自定义表情消息 + GetContactList() []robotModel.FriendItem // 获取联系人列表 + GetChatRoomDetailInfo(chatRoomId string) robotModel.ChatRoomDetailInfo // 获取群聊详情 + AddMemberToChatRoom(chatRoomId string, memberWxIds []string) error // 添加群成员 + DelMemberFromChatRoom(chatRoomId string, memberWxIds []string) error // 删除群成员 + GetMemberFromChatRoom(chatRoomId string) robotModel.GroupUser // 获取群成员 + GetContactProfile(wxId string) robotModel.ContactProfile // 获取好友资料 + InviteMemberToChatRoom(chatRoomId string, memberWxIds []string) error // 邀请入群 + ModifyChatRoomNickname(wxId, nickname string) error // 修改群内昵称 + StartHook(tcpIp string, tcpPort int) error // 开启消息监听 - 暂时只支持tcp,因为http有点儿不太好使 + StopHook() error // 关闭消息监听 + DownloadAttach(msgId int) error // 下载附件 + DecodeImage(filePath, storeDir string) error // 解码图片 +} + +// New +// @description: 创建微信助手 +// @param host +// @return Api +func New(host string, version WeChatVersion) (Api, error) { + // 判断微信版本是否支持 + keys := make([]WeChatVersion, 0, len(apiMap)) + for k := range apiMap { + keys = append(keys, k) + } + if !slices.Contains(keys, version) { + return nil, errors.New("不支持的微信版本") + } + + return &wxHelper{host: host, version: version}, nil +} diff --git a/router/admin/robot.go b/router/admin/robot.go new file mode 100644 index 0000000..3090325 --- /dev/null +++ b/router/admin/robot.go @@ -0,0 +1,15 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + robotApi "wechat-robot/api/admin/robot" +) + +// robot +// @description: 机器人相关接口 +// @param g +func robot(g *gin.RouterGroup) { + g.GET("", robotApi.GetAll) // 获取所有机器人 + g.POST("", robotApi.Save) // 保存机器人 + g.DELETE("/:id", robotApi.DeleteById) // 删除机器人 +} diff --git a/router/admin/route.go b/router/admin/route.go index 8c2f93f..c9b2818 100644 --- a/router/admin/route.go +++ b/router/admin/route.go @@ -13,5 +13,6 @@ func InitRoute(g *gin.RouterGroup) { g.Use(middleware.AuthorizeToken()) menu(g.Group("/menu")) // 菜单相关 + robot(g.Group("/robot")) // 机器人相关 aiAssistant(g.Group("/ai-assistant")) // AI助手相关 } diff --git a/service/robot/delete.go b/service/robot/delete.go new file mode 100644 index 0000000..d90c31f --- /dev/null +++ b/service/robot/delete.go @@ -0,0 +1,16 @@ +package robot + +import ( + "wechat-robot/internal/database" + "wechat-robot/model/entity" +) + +// DeleteById +// @description: 删除机器人 +// @param id +// @return err +func DeleteById(id string) (err error) { + // 删除数据 + err = database.Client.Where("id = ?", id).Delete(&entity.Robot{}).Error + return +} diff --git a/service/robot/save.go b/service/robot/save.go new file mode 100644 index 0000000..f7cafb6 --- /dev/null +++ b/service/robot/save.go @@ -0,0 +1,70 @@ +package robot + +import ( + "errors" + "log" + "wechat-robot/internal/database" + "wechat-robot/model/entity" + robotParam "wechat-robot/model/param/robot" + "wechat-robot/pkg/wxhelper" +) + +// Save +// @description: 保存机器人 +// @param p +// @return err +func Save(p robotParam.Save) (err error) { + if p.Id != "" { + // 是修改,暂时只支持修改备注 + err = database.Client.Model(&entity.Robot{}).Where("id = ?", p.Id).Update("remark", p.Remark).Error + return + } + // 新增 + + // 获取机器人信息,如果获取失败,就返回错误信息 + cli, err := wxhelper.New(p.HookApi, p.Version) + if err != nil { + return + } + isLogin := cli.CheckLogin() + log.Printf("机器人是否登录: %v", isLogin) + if !isLogin { + err = errors.New("机器人未登录") + return + } + + // 取出登录账号信息 + info, err := cli.UserInfo() + if err != nil { + return + } + log.Printf("机器人Id: %s", info.WxId) + log.Printf("机器人微信号: %s", info.Account) + log.Printf("机器人名称: %s", info.Name) + + // 先查询是否存在 + var ent entity.Robot + var count int64 + if err = database.Client.Model(&ent).Where("wxid = ?", info.WxId).Count(&count).Error; err != nil { + return + } + if count > 0 { + err = errors.New("机器人已存在") + return + } + + ent.WxId = info.WxId + ent.Account = info.Account + ent.Nickname = info.Name + ent.Avatar = info.HeadImage + ent.Mobile = info.Mobile + ent.CurrentDataPath = info.CurrentDataPath + ent.DataSavePath = info.DataSavePath + ent.DbKey = info.DbKey + ent.HookApi = p.HookApi + ent.Remark = p.Remark + ent.Version = int(p.Version) + ent.VncUrl = p.VncUrl + err = database.Client.Create(&ent).Error + return +} diff --git a/service/robot/select.go b/service/robot/select.go index 8e1a8fa..4989004 100644 --- a/service/robot/select.go +++ b/service/robot/select.go @@ -1 +1,25 @@ package robot + +import ( + "database/sql" + "wechat-robot/internal/database" + "wechat-robot/model/entity" + robotParam "wechat-robot/model/param/robot" +) + +// GetAll +// @description: 查询所有机器人 +// @param p robotParam.GetAll 查询所有机器人入参 +// @return records []entity.Robot 查询所有机器人返回值 +// @return err error 查询所有机器人返回值 +func GetAll(p robotParam.GetAll) (records []entity.Robot, err error) { + tx := database.Client.Order("created_at desc") + if p.Keyword != "" { + tx.Where("nickname LIKE @keyword OR remark LIKE @keyword", sql.Named("keyword", "%"+p.Keyword+"%")) + } + if p.Tag != "" { + tx.Where("tag LIKE ?", "%"+p.Tag+"%") + } + err = tx.Find(&records).Error + return +} diff --git a/utils/httpclient.go b/utils/httpclient.go new file mode 100644 index 0000000..d01174e --- /dev/null +++ b/utils/httpclient.go @@ -0,0 +1,53 @@ +package utils + +import ( + "encoding/json" + "github.com/go-resty/resty/v2" + "log" + "time" +) + +type HttpClient interface { + Post(api string, param map[string]any, response any, retryCount int) (err error) +} + +type httpClient struct{} + +// HttpClientUtils +// @description: Http客户端工具 +// @return HttpClient +func HttpClientUtils() HttpClient { + return &httpClient{} +} + +// Post +// @description: 发送Post请求 +// @receiver c +// @param api +// @param param +// @param response +// @param retryCount +// @return err +func (c httpClient) Post(api string, param map[string]any, response any, retryCount int) (err error) { + if retryCount > 5 { + log.Printf("重试五次失败,停止发送") + return + } + + pbs, _ := json.Marshal(param) + + var r *resty.Response + r, err = resty.New().R(). + SetHeader("Content-Type", "application/json;chartset=utf-8"). + SetBody(string(pbs)). + SetResult(response). + Post(api) + if err != nil { + log.Printf("请求失败: %s", err.Error()) + // 休眠五秒后重新发送 + time.Sleep(5 * time.Second) + return c.Post(api, param, response, retryCount+1) + } + log.Printf("请求结果: %s", r.String()) + return +}