简单demo

打卡
每天定时任务  针对单独一个群
This commit is contained in:
2022-07-20 13:43:22 +08:00
parent 39b4f61916
commit 849f198a54
9 changed files with 214 additions and 67 deletions

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="sginin@112.125.95.197" uuid="648f20fe-bb0e-408d-b41a-66d06d631bb9">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://112.125.95.197:3306/sginin</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -3,25 +3,62 @@ package controller
import (
"fmt"
"github.com/eatmoreapple/openwechat"
"go-bot/bot/tool"
"github.com/robfig/cron/v3"
"time"
)
var self = tool.GetCurrentUser()
func SignInBegin(msg *openwechat.Message) {
groups, err := tool.GetGroups()
// 新建一个定时任务对象
// 根据cron表达式进行时间调度cron可以精确到秒大部分表达式格式也是从秒开始。
// crontab := cron.New() 默认从分开始进行时间调度
var crontab = cron.New(cron.WithSeconds()) //精确到秒
func SignInBegin(msg *openwechat.Message, self *openwechat.Self) {
groups, err := self.Groups()
name := groups.GetByNickName("叫啥好呢")
fmt.Printf(name.NickName, err)
// 发送到微信
//定义定时器调用的任务函数
task := func() {
self.SendTextToGroup(name, "同志们学习了!")
}
//定时任务
spec := "0 0 9 * * ?" //cron表达式每天9点
// 添加定时任务,
crontab.AddFunc(spec, task)
// 启动定时器
crontab.Start()
self.SendTextToGroup(name, "打卡程序启动成功!")
// 定时任务是另起协程执行的,这里使用 select 简答阻塞.实际开发中需要
// 根据实际情况进行控制
//阻塞主线程停止
//select {
//查询语句保持程序运行在这里等同于for{}
//}
}
func SignInEnd(msg *openwechat.Message) {
groups, err := tool.GetGroups()
func SignInEnd(msg *openwechat.Message, self *openwechat.Self) {
groups, err := self.Groups()
name := groups.GetByNickName("叫啥好呢")
fmt.Printf(name.NickName, err)
crontab.Stop()
// 发送到微信
self.SendTextToGroup(name, "打卡程序结束运行成功!")
}
func SignIn(msg *openwechat.Message, self *openwechat.Self) {
// 获取消息发送者
sender, err := msg.SenderInGroup()
nickName := sender.NickName
// 获取群聊组
groups, err := self.Groups()
name := groups.GetByNickName("叫啥好呢")
fmt.Printf(name.NickName, err)
// 当前时间
timeStr := time.Now().Format("2006-01-02 15:04:05")
self.SendTextToGroup(name, nickName+":今日打卡成功!\n"+
"时间:"+timeStr)
}

27
controller/userData.go Normal file
View File

@@ -0,0 +1,27 @@
package controller
import (
"fmt"
"github.com/eatmoreapple/openwechat"
"go-bot/bot/database"
"time"
)
func GetUsers(msg *openwechat.Message, self *openwechat.Self) {
user := database.SelectData()
// 获取消息发送者
sender, err := msg.SenderInGroup()
nickName := sender.NickName
// 获取群聊组
groups, err := self.Groups()
name := groups.GetByNickName("叫啥好呢")
fmt.Printf(name.NickName, err)
// 当前时间
timeStr := time.Now().Format("2006-01-02 15:04:05")
self.SendTextToGroup(name,
nickName+": "+user+"\n"+
"当前时间: "+timeStr)
}

66
database/database.go Normal file
View File

@@ -0,0 +1,66 @@
package database
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var (
DB *sql.DB
)
func Database() {
DB, _ = sql.Open("mysql", "sginIn:sXeP48Xje2NLJ4Ek@(112.125.95.197:3306)/sginin")
//设置数据库最大连接数
DB.SetConnMaxLifetime(100)
//设置上数据库最大闲置连接数
DB.SetMaxIdleConns(10)
//验证连接
if err := DB.Ping(); err != nil {
fmt.Println("数据库连接失败!")
return
}
fmt.Println("数据库链接成功!")
}
// 表结构
type User struct {
Uid string
Account string
Username string
Role string
Integration int
}
func SelectData() string {
rows, err := DB.Query("SELECT * FROM User")
if err != nil {
fmt.Println(err.Error())
}
var integration_ sql.NullInt64
var uid_, account_wx_, username_, role_ sql.NullString
var user User
var stringLine string
for rows.Next() {
err := rows.Scan(&uid_, &account_wx_, &username_, &role_, &integration_)
if err != nil {
fmt.Println(err.Error())
}
user = User{
Uid: uid_.String,
Account: account_wx_.String,
Username: username_.String,
Role: role_.String,
Integration: int(integration_.Int64),
}
fmt.Println(user)
stringLine = fmt.Sprintf("\n uid: %s \n account: %s \n username:%s \n role: %s \n integration: %d \n",
user.Uid, user.Account, user.Username, user.Role, user.Integration)
}
return stringLine
}

10
go.mod
View File

@@ -2,4 +2,12 @@ module go-bot/bot
go 1.18
require github.com/eatmoreapple/openwechat v1.1.11
require (
github.com/eatmoreapple/openwechat v1.1.11
github.com/robfig/cron v1.2.0
)
require (
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/robfig/cron/v3 v3.0.0 // indirect
)

6
go.sum
View File

@@ -1,2 +1,8 @@
github.com/eatmoreapple/openwechat v1.1.11 h1:YJL8tUenK1NTflNPt5lWOl0KWcP0CTGjWNYAvN0dGFk=
github.com/eatmoreapple/openwechat v1.1.11/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=

79
main.go
View File

@@ -4,7 +4,8 @@ import (
"fmt"
"github.com/eatmoreapple/openwechat"
"go-bot/bot/controller"
"go-bot/bot/tool"
"go-bot/bot/database"
"strings"
)
func main() {
@@ -13,20 +14,14 @@ func main() {
bot := openwechat.DefaultBot(openwechat.Desktop)
// 创建热存储容器对象
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
// 注册消息处理函数
bot.MessageHandler = func(msg *openwechat.Message) {
if msg.IsText() && msg.Content == "英语" {
controller.SingleChoice(msg)
}
if msg.IsText() && msg.Content == "打卡开启" {
controller.SignInBegin(msg)
}
if msg.IsText() && msg.Content == "打卡关闭" {
controller.SignInEnd(msg)
//bot.HotLogin(reloadStorage)
// 执行热登录
err := bot.HotLogin(reloadStorage)
if err != nil {
//登陆
if err := bot.Login(); err != nil {
fmt.Println(err)
return
}
}
@@ -34,37 +29,51 @@ func main() {
// 注册登陆二维码回调
bot.UUIDCallback = openwechat.PrintlnQrcodeUrl
// 登陆
//if err := bot.Login(); err != nil {
// fmt.Println(err)
// return
//}
// 执行热登录
err := bot.HotLogin(reloadStorage)
if err != nil {
return
}
// 获取登陆的用户
self, err := bot.GetCurrentUser()
if err != nil {
fmt.Println(err)
tool.SetCurrentUser(*self)
fmt.Println(self, err)
return
}
// 获取所有的好友
friends, err := self.Friends()
fmt.Println(friends, err)
//friends, err := self.Friends()
//fmt.Println(friends, err)
// 获取所有的群组
groups, err := self.Groups()
fmt.Println(groups, err)
// 阻塞主goroutine, 直到发生异常或者用户主动退出
err = bot.Block()
if err != nil {
return
//链接数据库
database.Database()
// 注册消息处理函数
bot.MessageHandler = func(msg *openwechat.Message) {
if msg.IsText() {
split := strings.Split(msg.Content, " ")
fmt.Printf("command: %s \n", split)
if split[0] == "英语" {
controller.SingleChoice(msg)
}
if split[0] == "打卡开启" {
controller.SignInBegin(msg, self)
}
if split[0] == "打卡关闭" {
controller.SignInEnd(msg, self)
}
if split[0] == "今日打卡" {
controller.SignIn(msg, self)
}
if split[0] == "我的信息" {
controller.GetUsers(msg, self)
}
}
}
// 阻塞主goroutine, 直到发生异常或者用户主动退出
bot.Block()
}

View File

@@ -1 +1 @@
{"Cookies":{"https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login":[],"https://login.wx.qq.com/jslogin":[],"https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck":[],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit":[],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage":[{"Name":"wxuin","Value":"2194844303","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-19T15:58:03Z","RawExpires":"Tue, 19-Jul-2022 15:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxuin=2194844303; Domain=wx2.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:58:03 GMT; Secure","Unparsed":null},{"Name":"wxsid","Value":"3ja7eY9R5gZKROxB","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-19T15:58:03Z","RawExpires":"Tue, 19-Jul-2022 15:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxsid=3ja7eY9R5gZKROxB; Domain=wx2.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:58:03 GMT; Secure","Unparsed":null},{"Name":"wxloadtime","Value":"1658203083","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-19T15:58:03Z","RawExpires":"Tue, 19-Jul-2022 15:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxloadtime=1658203083; Domain=wx2.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:58:03 GMT; Secure","Unparsed":null},{"Name":"mm_lang","Value":"zh_CN","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-19T15:58:03Z","RawExpires":"Tue, 19-Jul-2022 15:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"mm_lang=zh_CN; Domain=wx2.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:58:03 GMT; Secure","Unparsed":null},{"Name":"wxuin","Value":"2194844303","Path":"/","Domain":".qq.com","Expires":"2022-07-19T15:58:03Z","RawExpires":"Tue, 19-Jul-2022 15:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxuin=2194844303; Domain=.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:58:03 GMT; Secure","Unparsed":null},{"Name":"webwx_data_ticket","Value":"gSd7va7i40Env/bo1zQ5tKeD","Path":"/","Domain":".qq.com","Expires":"2022-07-19T15:58:03Z","RawExpires":"Tue, 19-Jul-2022 15:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"webwx_data_ticket=gSd7va7i40Env/bo1zQ5tKeD; Domain=.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:58:03 GMT; Secure","Unparsed":null},{"Name":"webwx_auth_ticket","Value":"CIsBEMzU3/UNGoABiuzA9TE2LDNQHw+tveTc40qRTDPP9vKnwE7U4MTo//0WtCZi1vXisrxkntmtOLK/YhqnpS9UJDZP2THQ3JA5yKVr2gKJVB0adjF9ZdGcVibHNHIXZQEMs8mNJXAZJV4KaShujAq2jMTYnhl3OWzsTawVyVOMy1hr6DDuVt4bf1A=","Path":"/","Domain":"wx2.qq.com","Expires":"2032-07-16T03:58:03Z","RawExpires":"Fri, 16-Jul-2032 03:58:03 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"webwx_auth_ticket=CIsBEMzU3/UNGoABiuzA9TE2LDNQHw+tveTc40qRTDPP9vKnwE7U4MTo//0WtCZi1vXisrxkntmtOLK/YhqnpS9UJDZP2THQ3JA5yKVr2gKJVB0adjF9ZdGcVibHNHIXZQEMs8mNJXAZJV4KaShujAq2jMTYnhl3OWzsTawVyVOMy1hr6DDuVt4bf1A=; Domain=wx2.qq.com; Path=/; Expires=Fri, 16-Jul-2032 03:58:03 GMT; Secure","Unparsed":null}],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify":[],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync":[{"Name":"wxpluginkey","Value":"1658191861","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-19T15:57:29Z","RawExpires":"Tue, 19-Jul-2022 15:57:29 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxpluginkey=1658191861; Domain=wx2.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:57:29 GMT; Secure","Unparsed":null},{"Name":"wxuin","Value":"2194844303","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-22T03:57:29Z","RawExpires":"Fri, 22-Jul-2022 03:57:29 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxuin=2194844303; Domain=wx2.qq.com; Path=/; Expires=Fri, 22-Jul-2022 03:57:29 GMT; Secure","Unparsed":null},{"Name":"wxsid","Value":"PXBUQpeS/rNCHphb","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-19T15:57:29Z","RawExpires":"Tue, 19-Jul-2022 15:57:29 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxsid=PXBUQpeS/rNCHphb; Domain=wx2.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:57:29 GMT; Secure","Unparsed":null},{"Name":"webwx_data_ticket","Value":"gScVE0QIARq8/TLoQa8zSdk+","Path":"/","Domain":".qq.com","Expires":"2022-07-19T15:57:29Z","RawExpires":"Tue, 19-Jul-2022 15:57:29 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"webwx_data_ticket=gScVE0QIARq8/TLoQa8zSdk+; Domain=.qq.com; Path=/; Expires=Tue, 19-Jul-2022 15:57:29 GMT; Secure","Unparsed":null}]},"BaseRequest":{"Uin":2194844303,"Sid":"3ja7eY9R5gZKROxB","Skey":"@crypt_f512dfa9_3080a1d9cb4d4e22faf871ad4076cedf","DeviceID":"e434466226300474"},"LoginInfo":{"Ret":0,"WxUin":2194844303,"IsGrayScale":1,"Message":"","SKey":"@crypt_f512dfa9_3080a1d9cb4d4e22faf871ad4076cedf","WxSid":"3ja7eY9R5gZKROxB","PassTicket":"cRef7K39Z95crml48C6x2ISoyYVxFxCEVBR9AP9Dn5wdiMLZYyP9x3qdDjjTn5UI"},"WechatDomain":"wx2.qq.com","UUID":"4fCYctjW2A=="}
{"Cookies":{"https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login":[],"https://login.wx.qq.com/jslogin":[],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit":[],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage":[{"Name":"wxuin","Value":"2194844303","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-20T14:14:57Z","RawExpires":"Wed, 20-Jul-2022 14:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxuin=2194844303; Domain=wx2.qq.com; Path=/; Expires=Wed, 20-Jul-2022 14:14:57 GMT; Secure","Unparsed":null},{"Name":"wxsid","Value":"QAci3vj2TiuTDrGn","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-20T14:14:57Z","RawExpires":"Wed, 20-Jul-2022 14:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxsid=QAci3vj2TiuTDrGn; Domain=wx2.qq.com; Path=/; Expires=Wed, 20-Jul-2022 14:14:57 GMT; Secure","Unparsed":null},{"Name":"wxloadtime","Value":"1658283297","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-20T14:14:57Z","RawExpires":"Wed, 20-Jul-2022 14:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxloadtime=1658283297; Domain=wx2.qq.com; Path=/; Expires=Wed, 20-Jul-2022 14:14:57 GMT; Secure","Unparsed":null},{"Name":"mm_lang","Value":"zh_CN","Path":"/","Domain":"wx2.qq.com","Expires":"2022-07-20T14:14:57Z","RawExpires":"Wed, 20-Jul-2022 14:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"mm_lang=zh_CN; Domain=wx2.qq.com; Path=/; Expires=Wed, 20-Jul-2022 14:14:57 GMT; Secure","Unparsed":null},{"Name":"wxuin","Value":"2194844303","Path":"/","Domain":".qq.com","Expires":"2022-07-20T14:14:57Z","RawExpires":"Wed, 20-Jul-2022 14:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"wxuin=2194844303; Domain=.qq.com; Path=/; Expires=Wed, 20-Jul-2022 14:14:57 GMT; Secure","Unparsed":null},{"Name":"webwx_data_ticket","Value":"gSd/JXwMnsMus3oBSYZPDD2e","Path":"/","Domain":".qq.com","Expires":"2022-07-20T14:14:57Z","RawExpires":"Wed, 20-Jul-2022 14:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"webwx_data_ticket=gSd/JXwMnsMus3oBSYZPDD2e; Domain=.qq.com; Path=/; Expires=Wed, 20-Jul-2022 14:14:57 GMT; Secure","Unparsed":null},{"Name":"webwxuvid","Value":"ee5632a55c23b70f4f4a294edf958376d195c56cc895d36d28df5320d5b7812e310025f1fcad3c3672d121bb3e47b8d8","Path":"/","Domain":"wx2.qq.com","Expires":"2032-07-17T02:14:57Z","RawExpires":"Sat, 17-Jul-2032 02:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"webwxuvid=ee5632a55c23b70f4f4a294edf958376d195c56cc895d36d28df5320d5b7812e310025f1fcad3c3672d121bb3e47b8d8; Domain=wx2.qq.com; Path=/; Expires=Sat, 17-Jul-2032 02:14:57 GMT; Secure","Unparsed":null},{"Name":"webwx_auth_ticket","Value":"CIsBELDR96oFGoABR5BNHjfarNyaAe5Gk4yKSkqRTDPP9vKnwE7U4MTo//04LOnOK9yNJ6dALnjQ3BHFIeSBztcXu2hjQAVHxjL4dc7SK3BB0FEJFSRLkX1IAl390WgJgQFd83xFa3qdYNtuAzBI+ewvw7vwYmqIbwyCKKabSdMZSE89Qq4wBG/mHpE=","Path":"/","Domain":"wx2.qq.com","Expires":"2032-07-17T02:14:57Z","RawExpires":"Sat, 17-Jul-2032 02:14:57 GMT","MaxAge":0,"Secure":true,"HttpOnly":false,"SameSite":0,"Raw":"webwx_auth_ticket=CIsBELDR96oFGoABR5BNHjfarNyaAe5Gk4yKSkqRTDPP9vKnwE7U4MTo//04LOnOK9yNJ6dALnjQ3BHFIeSBztcXu2hjQAVHxjL4dc7SK3BB0FEJFSRLkX1IAl390WgJgQFd83xFa3qdYNtuAzBI+ewvw7vwYmqIbwyCKKabSdMZSE89Qq4wBG/mHpE=; Domain=wx2.qq.com; Path=/; Expires=Sat, 17-Jul-2032 02:14:57 GMT; Secure","Unparsed":null}],"https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify":[]},"BaseRequest":{"Uin":2194844303,"Sid":"QAci3vj2TiuTDrGn","Skey":"@crypt_f512dfa9_762cc7fe272eb8cc02bd291e1bf05bf2","DeviceID":"e266442848611621"},"LoginInfo":{"Ret":0,"WxUin":2194844303,"IsGrayScale":1,"Message":"","SKey":"@crypt_f512dfa9_762cc7fe272eb8cc02bd291e1bf05bf2","WxSid":"QAci3vj2TiuTDrGn","PassTicket":"HFz62xk4cjkl53UCxXBocZdLWTqHbWP%2FGq2DtLX%2B7ew2bNnq2kOgWLcJjvbfnwRd"},"WechatDomain":"wx2.qq.com","UUID":"IdusqXJuQg=="}

View File

@@ -1,25 +1,7 @@
package tool
import "github.com/eatmoreapple/openwechat"
import "hash/maphash"
var self openwechat.Self
// SetCurrentUser 设置 当前登录用户
func SetCurrentUser(user openwechat.Self) {
self = user
}
// GetCurrentUser 获取当前登录用户
func GetCurrentUser() openwechat.Self {
return self
}
// GetFriends 获取所有的好友
func GetFriends() (openwechat.Friends, error) {
return self.Friends()
}
// GetGroups 获取所有的群组
func GetGroups() (openwechat.Groups, error) {
return self.Groups()
type Self struct {
commandMap maphash.Hash
}