From a458771a795102f25fe4e4519f5f8eabf9e684e6 Mon Sep 17 00:00:00 2001 From: shylinux Date: Wed, 20 Oct 2021 16:51:21 +0800 Subject: [PATCH] opt wx --- base/aaa/role.go | 13 +++++--- conf.go | 1 + misc/mp/login.go | 75 +++++++++++++++++++++---------------------- misc/mp/mp.go | 11 +------ misc/mp/mp.shy | 2 +- misc/wework/bot.go | 35 +++++++++++--------- misc/wework/wework.go | 4 ++- misc/wx/access.go | 10 +++--- misc/wx/login.go | 54 ++++++++----------------------- misc/wx/menu.go | 35 ++++++++++++++++++++ misc/wx/wx.go | 9 +----- misc/wx/wx.shy | 3 ++ render.go | 2 +- type.go | 6 ++++ 14 files changed, 134 insertions(+), 126 deletions(-) diff --git a/base/aaa/role.go b/base/aaa/role.go index 7f2ee93c..96a5d425 100644 --- a/base/aaa/role.go +++ b/base/aaa/role.go @@ -29,6 +29,9 @@ func _role_list(m *ice.Message, userrole string) { } }) } +func _role_chain(arg ...string) string { + return strings.ReplaceAll(kit.Keys(arg), "/", ice.PT) +} func _role_black(m *ice.Message, userrole, chain string, status bool) { m.Richs(ROLE, nil, userrole, func(key string, value map[string]interface{}) { m.Log_MODIFY(ROLE, userrole, BLACK, chain) @@ -57,7 +60,7 @@ func _role_right(m *ice.Message, userrole string, keys ...string) (ok bool) { } } - if m.Warn(!ok, ice.ErrNotRight, userrole, " of ", keys) { + if m.Warn(!ok, ice.ErrNotRight, userrole, ice.OF, keys) { return } if userrole == TECH { @@ -72,7 +75,7 @@ func _role_right(m *ice.Message, userrole string, keys ...string) (ok bool) { } } - if m.Warn(!ok, ice.ErrNotRight, userrole, " of ", keys) { + if m.Warn(!ok, ice.ErrNotRight, userrole, ice.OF, keys) { return } // 普通用户 @@ -117,13 +120,13 @@ func init() { }}, BLACK: {Name: "black role chain...", Help: "黑名单", Hand: func(m *ice.Message, arg ...string) { - _role_black(m, arg[0], strings.ReplaceAll(kit.Keys(arg[1:]), "/", ice.PT), true) + _role_black(m, arg[0], _role_chain(arg[1:]...), true) }}, WHITE: {Name: "white role chain...", Help: "白名单", Hand: func(m *ice.Message, arg ...string) { - _role_white(m, arg[0], strings.ReplaceAll(kit.Keys(arg[1:]), "/", ice.PT), true) + _role_white(m, arg[0], _role_chain(arg[1:]...), true) }}, RIGHT: {Name: "right role chain...", Help: "查看权限", Hand: func(m *ice.Message, arg ...string) { - if _role_right(m, arg[0], kit.Split(strings.ReplaceAll(kit.Keys(arg[1:]), "/", ice.PT), ice.PT)...) { + if _role_right(m, arg[0], kit.Split(_role_chain(arg[1:]...), ice.PT)...) { m.Echo(ice.OK) } }}, diff --git a/conf.go b/conf.go index 1d120630..97dde5d7 100644 --- a/conf.go +++ b/conf.go @@ -5,6 +5,7 @@ const ( SP = " " PT = "." NL = "\n" + OF = " of " OK = "ok" TRUE = "true" diff --git a/misc/mp/login.go b/misc/mp/login.go index 2f909115..307a9b51 100644 --- a/misc/mp/login.go +++ b/misc/mp/login.go @@ -4,6 +4,7 @@ import ( ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/aaa" "shylinux.com/x/icebergs/base/mdb" + "shylinux.com/x/icebergs/base/tcp" "shylinux.com/x/icebergs/base/web" "shylinux.com/x/icebergs/core/chat" kit "shylinux.com/x/toolkits" @@ -14,50 +15,48 @@ const ( APPMM = "appmm" ACCESS = "access" OPENID = "openid" - WEIXIN = "weixin" ) const LOGIN = "login" func init() { - Index.Merge(&ice.Context{ - Configs: map[string]*ice.Config{ - LOGIN: {Name: LOGIN, Help: "认证", Value: kit.Data( - WEIXIN, "https://api.weixin.qq.com", APPID, "", APPMM, "", - )}, - }, - Commands: map[string]*ice.Command{ - ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Cmd(web.SPIDE, mdb.CREATE, WEIXIN, m.Conf(LOGIN, kit.Keym(WEIXIN))) - }}, - "/login/": {Name: "/login/", Help: "认证", Action: map[string]*ice.Action{ - aaa.SESS: {Name: "sess code", Help: "会话", Hand: func(m *ice.Message, arg ...string) { - msg := m.Cmd(web.SPIDE, WEIXIN, web.SPIDE_GET, "/sns/jscode2session?grant_type=authorization_code", - "js_code", m.Option(kit.MDB_CODE), APPID, m.Conf(LOGIN, kit.Keym(APPID)), "secret", m.Conf(LOGIN, kit.Keym(APPMM))) + Index.Merge(&ice.Context{Configs: map[string]*ice.Config{ + LOGIN: {Name: LOGIN, Help: "认证", Value: kit.Data( + tcp.SERVER, "https://api.weixin.qq.com", + APPID, "", APPMM, "", "tokens", "", + )}, + }, Commands: map[string]*ice.Command{ + ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + m.Cmd(web.SPIDE, mdb.CREATE, MP, m.Conf(LOGIN, kit.Keym(tcp.SERVER))) + }}, + "/login/": {Name: "/login/", Help: "认证", Action: map[string]*ice.Action{ + aaa.SESS: {Name: "sess code", Help: "会话", Hand: func(m *ice.Message, arg ...string) { + msg := m.Cmd(web.SPIDE, MP, web.SPIDE_GET, "/sns/jscode2session?grant_type=authorization_code", + "js_code", m.Option(kit.MDB_CODE), APPID, m.Config(APPID), "secret", m.Config(APPMM)) - // 用户登录 - m.Option(ice.MSG_USERZONE, MP) - m.Echo(aaa.SessCreate(msg, msg.Append(OPENID))) - }}, - aaa.USER: {Name: "user", Help: "用户", Hand: func(m *ice.Message, arg ...string) { - m.Option(aaa.USERNAME, m.Option(ice.MSG_USERNAME)) - m.Cmd(aaa.USER, mdb.MODIFY, aaa.USERZONE, MP, aaa.USERNICK, m.Option("nickName"), - aaa.AVATAR, m.Option("avatarUrl"), aaa.GENDER, kit.Select("女", "男", m.Option(aaa.GENDER) == "1"), - aaa.COUNTRY, m.Option(aaa.COUNTRY), aaa.LANGUAGE, m.Option(aaa.LANGUAGE), - aaa.CITY, m.Option(aaa.CITY), aaa.PROVINCE, m.Option(aaa.PROVINCE), - ) - }}, - chat.SCAN: {Name: "scan", Help: "扫码", Hand: func(m *ice.Message, arg ...string) { - m.Cmdy(chat.SCAN, arg) - }}, + // 用户登录 + m.Option(ice.MSG_USERZONE, MP) + m.Echo(aaa.SessCreate(msg, msg.Append(OPENID))) }}, - LOGIN: {Name: "login appid auto login", Help: "认证", Action: map[string]*ice.Action{ - mdb.CREATE: {Name: "create appid appmm", Help: "登录", Hand: func(m *ice.Message, arg ...string) { - m.Conf(LOGIN, kit.Keym(APPID), m.Option(APPID)) - m.Conf(LOGIN, kit.Keym(APPMM), m.Option(APPMM)) - }}, - }, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Echo(m.Conf(LOGIN, kit.Keym(APPID))) + aaa.USER: {Name: "user", Help: "用户", Hand: func(m *ice.Message, arg ...string) { + m.Option(aaa.USERNAME, m.Option(ice.MSG_USERNAME)) + m.Cmd(aaa.USER, mdb.MODIFY, aaa.USERZONE, MP, aaa.USERNICK, m.Option("nickName"), + aaa.AVATAR, m.Option("avatarUrl"), aaa.GENDER, kit.Select("女", "男", m.Option(aaa.GENDER) == "1"), + aaa.COUNTRY, m.Option(aaa.COUNTRY), aaa.LANGUAGE, m.Option(aaa.LANGUAGE), + aaa.CITY, m.Option(aaa.CITY), aaa.PROVINCE, m.Option(aaa.PROVINCE), + ) }}, - }, + chat.SCAN: {Name: "scan", Help: "扫码", Hand: func(m *ice.Message, arg ...string) { + m.Cmdy(chat.SCAN, arg) + }}, + }}, + LOGIN: {Name: "login appid auto login", Help: "认证", Action: map[string]*ice.Action{ + mdb.CREATE: {Name: "create appid appmm", Help: "登录", Hand: func(m *ice.Message, arg ...string) { + m.Config(APPID, m.Option(APPID)) + m.Config(APPMM, m.Option(APPMM)) + }}, + }, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + m.Echo(m.Config(APPID)) + }}, + }, }) } diff --git a/misc/mp/mp.go b/misc/mp/mp.go index 931c6a94..d7a3a1fe 100644 --- a/misc/mp/mp.go +++ b/misc/mp/mp.go @@ -8,15 +8,6 @@ import ( const MP = "mp" -var Index = &ice.Context{Name: MP, Help: "小程序", - Commands: map[string]*ice.Command{ - ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Load() - }}, - ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Save() - }}, - }, -} +var Index = &ice.Context{Name: MP, Help: "小程序"} func init() { chat.Index.Register(Index, &web.Frame{}) } diff --git a/misc/mp/mp.shy b/misc/mp/mp.shy index 4745af60..d4297507 100644 --- a/misc/mp/mp.shy +++ b/misc/mp/mp.shy @@ -1,5 +1,5 @@ title "微信小程序" -refer "" ` +refer ` 官网 https://weixin.qq.com/ 后台 https://mp.weixin.qq.com/ 文档 https://developers.weixin.qq.com/miniprogram/dev/api/ diff --git a/misc/wework/bot.go b/misc/wework/bot.go index 52e9f8b3..2a231516 100644 --- a/misc/wework/bot.go +++ b/misc/wework/bot.go @@ -5,8 +5,6 @@ import ( "crypto/cipher" "crypto/sha1" "encoding/base64" - "encoding/hex" - "sort" "strings" ice "shylinux.com/x/icebergs" @@ -20,35 +18,40 @@ const BOT = "bot" func init() { Index.Merge(&ice.Context{Configs: map[string]*ice.Config{ BOT: {Name: "bot", Help: "机器人", Value: kit.Data( - kit.MDB_SHORT, "name", kit.MDB_FIELD, "time,name,token,ekey,hook", + kit.MDB_SHORT, kit.MDB_NAME, kit.MDB_FIELD, "time,name,token,ekey,hook", )}, }, Commands: map[string]*ice.Command{ - ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Load() }}, - ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Save() }}, web.WEB_LOGIN: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {}}, - "/bot/": {Name: "/bot", Help: "机器人", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + "/bot/": {Name: "/bot/", Help: "机器人", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { msg := m.Cmd(BOT, arg[0]) - list := []string{msg.Append("token"), m.Option("nonce"), m.Option("timestamp"), m.Option("echostr")} - sort.Strings(list) - res := sha1.Sum([]byte(strings.Join(list, ""))) - m.Debug(hex.EncodeToString(res[:])) + check := kit.Sort([]string{msg.Append("token"), m.Option("nonce"), m.Option("timestamp"), m.Option("echostr")}) + sig := kit.Format(sha1.Sum([]byte(strings.Join(check, "")))) + m.Debug("what %v", sig) + m.Debug("what %v", check) + if m.Warn(sig != m.Option("msg_signature"), ice.ErrNotRight) { + // return + } - aeskey, err := base64.StdEncoding.DecodeString(msg.Append("ekey")) + m.Debug("what %v", msg.Append("ekey")) + aeskey, err := base64.RawURLEncoding.DecodeString(msg.Append("ekey")) m.Assert(err) - en_msg, err := base64.StdEncoding.DecodeString(m.Option("echostr")) + + en_msg, err := base64.RawURLEncoding.DecodeString(m.Option("echostr")) m.Assert(err) + block, err := aes.NewCipher(aeskey) m.Assert(err) + mode := cipher.NewCBCDecrypter(block, aeskey[:aes.BlockSize]) mode.CryptBlocks(en_msg, en_msg) - }}, BOT: {Name: "bot name chat text:textarea auto create", Help: "机器人", Action: ice.MergeAction(map[string]*ice.Action{ - mdb.CREATE: {Name: "create name token ekey hook", Help: "创建"}, + mdb.CREATE: {Name: "create name token ekey hook", Help: "创建", Hand: func(m *ice.Message, arg ...string) { + m.Cmd(web.SPIDE, mdb.CREATE, m.Option("name"), m.Option("hook")) + m.Cmdy(mdb.INSERT, m.PrefixKey(), "", mdb.HASH, arg) + }}, }, mdb.HashAction()), Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { if mdb.HashSelect(m, arg...); len(arg) > 2 { - m.SetAppend() - m.Cmd(web.SPIDE, mdb.CREATE, arg[0], m.Append("hook")) m.Cmdy(web.SPIDE, arg[0], "", kit.Format(kit.Dict( "chatid", arg[1], "msgtype", "text", "text.content", arg[2], ))) diff --git a/misc/wework/wework.go b/misc/wework/wework.go index 67a978a8..f9ef8304 100644 --- a/misc/wework/wework.go +++ b/misc/wework/wework.go @@ -6,6 +6,8 @@ import ( "shylinux.com/x/icebergs/core/chat" ) -var Index = &ice.Context{Name: "wework", Help: "企业微信"} +const WEWORK = "wework" + +var Index = &ice.Context{Name: WEWORK, Help: "企业微信"} func init() { chat.Index.Register(Index, &web.Frame{}) } diff --git a/misc/wx/access.go b/misc/wx/access.go index 4244e72e..73cf11be 100644 --- a/misc/wx/access.go +++ b/misc/wx/access.go @@ -21,6 +21,7 @@ func _wx_sign(m *ice.Message, nonce, stamp string) string { kit.Format("noncestr=%s", nonce), }), "&")))) } + func _wx_config(m *ice.Message, nonce string) { m.Option(APPID, m.Config(APPID)) m.Option(ssh.SCRIPT, m.Config(ssh.SCRIPT)) @@ -36,7 +37,6 @@ const ( EXPIRES = "expires" ) const ( - WEIXIN = "weixin" ERRCODE = "errcode" ERRMSG = "errmsg" ) @@ -51,7 +51,7 @@ func init() { )}, }, Commands: map[string]*ice.Command{ ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Cmd(web.SPIDE, mdb.CREATE, WEIXIN, m.Config(tcp.SERVER)) + m.Cmd(web.SPIDE, mdb.CREATE, WX, m.Conf(ACCESS, kit.Keym(tcp.SERVER))) }}, ACCESS: {Name: "access appid auto ticket token login", Help: "认证", Action: map[string]*ice.Action{ LOGIN: {Name: "login appid appmm token", Help: "登录", Hand: func(m *ice.Message, arg ...string) { @@ -61,7 +61,7 @@ func init() { }}, TOKEN: {Name: "token", Help: "令牌", Hand: func(m *ice.Message, arg ...string) { if now := time.Now().Unix(); m.Config(TOKEN) == "" || now > kit.Int64(m.Config(EXPIRE)) { - msg := m.Cmd(web.SPIDE, WEIXIN, web.SPIDE_GET, "/cgi-bin/token?grant_type=client_credential", + msg := m.Cmd(web.SPIDE, WX, web.SPIDE_GET, "/cgi-bin/token?grant_type=client_credential", APPID, m.Config(APPID), "secret", m.Config(APPMM)) if m.Warn(msg.Append(ERRCODE) != "", msg.Append(ERRCODE), msg.Append(ERRMSG)) { return @@ -74,7 +74,7 @@ func init() { }}, TICKET: {Name: "ticket", Help: "票据", Hand: func(m *ice.Message, arg ...string) { if now := time.Now().Unix(); m.Conf(TICKET) == "" || now > kit.Int64(m.Config(EXPIRES)) { - msg := m.Cmd(web.SPIDE, WEIXIN, web.SPIDE_GET, "/cgi-bin/ticket/getticket?type=jsapi", "access_token", m.Cmdx(ACCESS, TOKEN)) + msg := m.Cmd(web.SPIDE, WX, web.SPIDE_GET, "/cgi-bin/ticket/getticket?type=jsapi", "access_token", m.Cmdx(ACCESS, TOKEN)) if m.Warn(msg.Append(ERRCODE) != "0", msg.Append(ERRCODE), msg.Append(ERRMSG)) { return } @@ -88,7 +88,7 @@ func init() { _wx_config(m, "some") }}, }, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Echo(kit.Formats(m.Confv(ACCESS))) + m.Echo(m.Config(APPID)) }}, }}) } diff --git a/misc/wx/login.go b/misc/wx/login.go index 0274a1ea..7969ff8e 100644 --- a/misc/wx/login.go +++ b/misc/wx/login.go @@ -8,11 +8,20 @@ import ( ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/aaa" "shylinux.com/x/icebergs/base/mdb" - "shylinux.com/x/icebergs/base/web" - "shylinux.com/x/icebergs/core/wiki" kit "shylinux.com/x/toolkits" ) +func _wx_check(m *ice.Message) bool { + check := kit.Sort([]string{m.Conf(ACCESS, "meta.tokens"), m.Option("timestamp"), m.Option("nonce")}) + if sig := kit.Format(sha1.Sum([]byte(strings.Join(check, "")))); m.Warn(sig != m.Option("signature"), ice.ErrNotRight) { + return false // 验证失败 + } + if m.Option("echostr") != "" { + m.RenderResult(m.Option("echostr")) + return false // 绑定验证 + } + return true +} func _wx_parse(m *ice.Message) { data := struct { FromUserName string @@ -40,38 +49,6 @@ func _wx_reply(m *ice.Message, tmpl string) { m.Set(ice.MSG_RESULT).RenderResult(string(res)) } } -func _wx_action(m *ice.Message) { - m.Set(ice.MSG_RESULT).RenderResult() - - m.Echo(` - - -%s - -`, m.Option("ToUserName"), m.Option("FromUserName"), m.Option("CreateTime"), "news") - - count := 0 - m.Table(func(index int, value map[string]string, head []string) { count++ }) - m.Echo(`%d`, count) - - share := m.Cmdx(web.SHARE, mdb.CREATE, kit.MDB_TYPE, web.LOGIN) - - m.Echo(``) - m.Table(func(index int, value map[string]string, head []string) { - m.Echo(` -<![CDATA[%s]]> - - - - -`, value[wiki.TITLE], value[wiki.SPARK], value[wiki.IMAGE], - kit.MergeURL(kit.Format(value[wiki.REFER]), web.SHARE, share)) - }) - m.Echo(``) - m.Echo(``) - - m.Debug("echo: %v", m.Result()) -} const LOGIN = "login" @@ -80,13 +57,8 @@ func init() { LOGIN: {Name: LOGIN, Help: "登录", Value: kit.Data()}, }, Commands: map[string]*ice.Command{ "/login/": {Name: "/login/", Help: "认证", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - check := kit.Sort([]string{m.Conf(ACCESS, "meta.tokens"), m.Option("timestamp"), m.Option("nonce")}) - if sig := kit.Format(sha1.Sum([]byte(strings.Join(check, "")))); m.Warn(sig != m.Option("signature"), ice.ErrNotRight) { - return // 验证失败 - } - if m.Option("echostr") != "" { - m.RenderResult(m.Option("echostr")) - return // 绑定验证 + if !_wx_check(m) { + return // 验签失败 } // 解析数据 diff --git a/misc/wx/menu.go b/misc/wx/menu.go index 4869e38b..86c21e93 100644 --- a/misc/wx/menu.go +++ b/misc/wx/menu.go @@ -3,9 +3,44 @@ package wx import ( ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/mdb" + "shylinux.com/x/icebergs/base/web" + "shylinux.com/x/icebergs/core/wiki" kit "shylinux.com/x/toolkits" ) +func _wx_action(m *ice.Message) { + m.Set(ice.MSG_RESULT).RenderResult() + + m.Echo(` + + +%s + +`, m.Option("ToUserName"), m.Option("FromUserName"), m.Option("CreateTime"), "news") + + count := 0 + m.Table(func(index int, value map[string]string, head []string) { count++ }) + m.Echo(`%d`, count) + + share := m.Cmdx(web.SHARE, mdb.CREATE, kit.MDB_TYPE, web.LOGIN) + + m.Echo(``) + m.Table(func(index int, value map[string]string, head []string) { + m.Echo(` +<![CDATA[%s]]> + + + + +`, value[wiki.TITLE], value[wiki.SPARK], value[wiki.IMAGE], + kit.MergeURL(kit.Format(value[wiki.REFER]), web.SHARE, share)) + }) + m.Echo(``) + m.Echo(``) + + m.Debug("echo: %v", m.Result()) +} + const MENU = "menu" func init() { diff --git a/misc/wx/wx.go b/misc/wx/wx.go index 599f9fa1..4e12282a 100644 --- a/misc/wx/wx.go +++ b/misc/wx/wx.go @@ -8,13 +8,6 @@ import ( const WX = "wx" -var Index = &ice.Context{Name: WX, Help: "公众号", Commands: map[string]*ice.Command{ - ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Load() - }}, - ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Save() - }}, -}} +var Index = &ice.Context{Name: WX, Help: "公众号"} func init() { chat.Index.Register(Index, &web.Frame{}) } diff --git a/misc/wx/wx.shy b/misc/wx/wx.shy index 2193e6fa..01c496c7 100644 --- a/misc/wx/wx.shy +++ b/misc/wx/wx.shy @@ -23,6 +23,9 @@ field "共享" web.share field "会话" aaa.sess field "用户" aaa.user +chapter "企业微信" +field "机器人" web.chat.wework.bot + chapter "项目" field "源代码" web.code.inner args `usr/icebergs/ misc/wx/wx.go` field "趋势图" web.code.git.trend args `icebergs` diff --git a/render.go b/render.go index 0cddcb5d..dd621f79 100644 --- a/render.go +++ b/render.go @@ -26,7 +26,7 @@ func Render(m *Message, cmd string, args ...interface{}) string { break } list := []string{} - for _, k := range kit.Split(strings.ToLower(kit.Join(arg))) { + for _, k := range kit.Split(kit.Join(arg)) { list = append(list, kit.Format(``, k, kit.Select(k, kit.Value(m._cmd.Meta, kit.Keys("_trans", k)), m.Option(MSG_LANGUAGE) != "en"))) } diff --git a/type.go b/type.go index 064ac52b..239e7fb8 100644 --- a/type.go +++ b/type.go @@ -105,6 +105,12 @@ func (c *Context) Merge(s *Context) *Context { if c.Commands == nil { c.Commands = map[string]*Command{} } + if c.Commands[CTX_INIT] == nil { + c.Commands[CTX_INIT] = &Command{Hand: func(m *Message, c *Context, cmd string, arg ...string) { m.Load() }} + } + if c.Commands[CTX_EXIT] == nil { + c.Commands[CTX_EXIT] = &Command{Hand: func(m *Message, c *Context, cmd string, arg ...string) { m.Save() }} + } for k, v := range s.Commands { if o, ok := c.Commands[k]; ok && s != c { func() {