1
0
forked from x/icebergs
This commit is contained in:
shylinux 2020-10-23 11:22:31 +08:00
parent 750ca5e19c
commit 5cc142e009
19 changed files with 467 additions and 457 deletions

View File

@ -80,8 +80,9 @@ func UserRole(m *ice.Message, username interface{}) (role string) {
func UserLogin(m *ice.Message, username, password string) bool {
if _user_login(m, username, password) {
m.Option(ice.MSG_USERNAME, username)
m.Option(ice.MSG_USERNICK, UserNick(m, username))
m.Option(ice.MSG_USERROLE, UserRole(m, username))
m.Log_AUTH(USERROLE, m.Option(ice.MSG_USERROLE), USERNAME, m.Option(ice.MSG_USERNAME), PASSWORD, strings.Repeat("*", len(password)))
m.Log_AUTH(USERROLE, m.Option(ice.MSG_USERROLE), USERNICK, m.Option(ice.MSG_USERNICK), USERNAME, m.Option(ice.MSG_USERNAME), PASSWORD, strings.Repeat("*", len(password)))
return true
}
return false
@ -89,6 +90,14 @@ func UserLogin(m *ice.Message, username, password string) bool {
const (
AVATAR = "avatar"
GENDER = "gender"
MOBILE = "mobile"
EMAIL = "email"
CITY = "city"
COUNTRY = "country"
PROVINCE = "province"
LANGUAGE = "language"
USER_CREATE = "user.create"
)

View File

@ -55,14 +55,24 @@ func _serve_main(m *ice.Message, w http.ResponseWriter, r *http.Request) bool {
if r.URL.Path == "/" && r.FormValue(SHARE) != "" {
m.W = w
s := m.Cmd(SHARE, mdb.SELECT, kit.MDB_HASH, r.FormValue(SHARE))
if s.Append(kit.MDB_TYPE) == "login" {
Render(m, COOKIE, aaa.SessCreate(m, s.Append(aaa.USERNAME), s.Append(aaa.USERROLE)))
http.Redirect(w, r, kit.MergeURL(r.URL.String(), SHARE, ""), http.StatusTemporaryRedirect)
m.W = nil
return false
defer func() { m.W = nil }()
if s := m.Cmd(SHARE, mdb.SELECT, kit.MDB_HASH, r.FormValue(SHARE)); s.Append(kit.MDB_TYPE) == "login" {
defer func() { http.Redirect(w, r, kit.MergeURL(r.URL.String(), SHARE, ""), http.StatusTemporaryRedirect) }()
msg := m.Spawn()
if c, e := r.Cookie(ice.MSG_SESSID); e == nil && c.Value != "" {
if aaa.SessCheck(msg, c.Value); msg.Option(ice.MSG_USERNAME) != "" {
return false // 复用会话
}
}
msg.Option(ice.MSG_USERUA, r.Header.Get("User-Agent"))
msg.Option(ice.MSG_USERIP, r.Header.Get(ice.MSG_USERIP))
Render(msg, COOKIE, aaa.SessCreate(msg, s.Append(aaa.USERNAME), s.Append(aaa.USERROLE)))
return false // 新建会话
}
m.W = nil
return true
}

View File

@ -90,7 +90,7 @@ func init() {
kit.MDB_TIME, m.Time(m.Conf(SHARE, "meta.expire")), arg)
}},
mdb.SELECT: {Name: "select hash", Help: "查询", Hand: func(m *ice.Message, arg ...string) {
m.Option(mdb.FIELDS, "time,hash,userrole,username,river,storm,type,name,text")
m.Option(mdb.FIELDS, "time,userrole,username,river,storm,type,name,text")
m.Cmdy(mdb.SELECT, SHARE, "", mdb.HASH, kit.MDB_HASH, m.Option(kit.MDB_HASH))
}},
mdb.REMOVE: {Name: "remove", Help: "删除", Hand: func(m *ice.Message, arg ...string) {
@ -99,7 +99,7 @@ func init() {
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Option(mdb.FIELDS, kit.Select("time,hash,userrole,username,river,storm,type,name,text", mdb.DETAIL, len(arg) > 0))
m.Cmdy(mdb.SELECT, SHARE, "", mdb.HASH, kit.MDB_HASH, arg)
m.PushAction("删除")
m.PushAction(mdb.REMOVE)
}},
"/share/": {Name: "/share/", Help: "共享链", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Option(mdb.FIELDS, kit.Select("time,hash,userrole,username,river,storm,type,name,text"))

View File

@ -302,8 +302,10 @@ func init() {
var data interface{}
m.Assert(json.NewDecoder(res.Body).Decode(&data))
m.Optionv("content_data", data)
data = kit.KeyValue(map[string]interface{}{}, "", data)
m.Info("res: %s", kit.Format(data))
m.Debug("res: %s", kit.Formats(data))
m.Push("", data)
}
})

View File

@ -39,6 +39,9 @@ func init() {
"pack": {Name: "pack", Help: "打包", Hand: func(m *ice.Message, arg ...string) {
m.Cmdy("web.code.webpack", "create")
}},
"wx": {Name: "wx", Help: "微信", Hand: func(m *ice.Message, arg ...string) {
m.Cmdy("web.chat.wx.access", "config")
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Echo(m.Conf(HEADER, TITLE))
}},

View File

@ -6,7 +6,6 @@ import (
kit "github.com/shylinux/toolkits"
"math"
"net/url"
)
func distance(lat1, long1, lat2, long2 float64) float64 {
@ -35,8 +34,8 @@ func init() {
LOCATION: {Name: LOCATION, Help: "地理位置", Value: kit.Data(kit.MDB_SHORT, kit.MDB_TEXT)},
},
Commands: map[string]*ice.Command{
LOCATION: {Name: "location text auto create@location", Help: "地理位置", Action: map[string]*ice.Action{
mdb.CREATE: {Name: "insert type=text name address latitude longitude", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
LOCATION: {Name: "location text auto create@getLocation", Help: "地理位置", Action: map[string]*ice.Action{
mdb.CREATE: {Name: "create type=text name address latitude longitude", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
_trans(arg, map[string]string{"address": "text"})
m.Cmdy(mdb.INSERT, LOCATION, "", mdb.HASH, arg)
}},
@ -44,7 +43,7 @@ func init() {
m.Cmdy(mdb.MODIFY, LOCATION, "", mdb.HASH, kit.MDB_HASH, m.Option(kit.MDB_HASH), arg)
}},
mdb.REMOVE: {Name: "remove", Help: "删除", Hand: func(m *ice.Message, arg ...string) {
m.Cmdy(mdb.DELETE, LOCATION, "", mdb.HASH, kit.MDB_HASH, m.Option(kit.MDB_HASH))
m.Cmdy(mdb.DELETE, LOCATION, "", mdb.HASH, kit.MDB_TEXT, m.Option(kit.MDB_TEXT))
}},
mdb.EXPORT: {Name: "export", Help: "导出", Hand: func(m *ice.Message, arg ...string) {
m.Cmdy(mdb.EXPORT, LOCATION, "", mdb.HASH)
@ -58,13 +57,7 @@ func init() {
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Option(mdb.FIELDS, kit.Select("time,type,name,text,longitude,latitude", mdb.DETAIL, len(arg) > 0))
m.Cmdy(mdb.SELECT, LOCATION, "", mdb.HASH, kit.MDB_TEXT, arg)
m.Table(func(index int, value map[string]string, head []string) {
m.PushRender(kit.MDB_LINK, "a", "百度地图", kit.Format(
"https://map.baidu.com/search/%s/@12958750.085,4825785.55,16z?querytype=s&da_src=shareurl&wd=%s",
url.QueryEscape(kit.Format(value[kit.MDB_TEXT])), url.QueryEscape(kit.Format(value[kit.MDB_TEXT])),
))
})
m.PushAction(mdb.REMOVE)
m.PushAction("openLocation", mdb.REMOVE)
}},
},
}, nil)

View File

@ -256,7 +256,7 @@ func init() {
m.Richs(USER, nil, value[aaa.USERNAME], func(key string, val map[string]interface{}) {
val = kit.GetMeta(val)
m.Push(aaa.USERNICK, val[aaa.USERNICK])
m.PushRender(aaa.AVATAR, "img", kit.Format(val["avatar_url"]), kit.Select("60", "240", m.Option(mdb.FIELDS) == mdb.DETAIL))
m.PushRender(aaa.AVATAR, "img", kit.Format(val[aaa.AVATAR]), kit.Select("60", "240", m.Option(mdb.FIELDS) == mdb.DETAIL))
})
})

View File

@ -15,8 +15,8 @@ func init() {
SCAN: {Name: SCAN, Help: "扫码", Value: kit.Data(kit.MDB_SHORT, kit.MDB_TEXT)},
},
Commands: map[string]*ice.Command{
SCAN: {Name: "scan hash auto create@scan", Help: "扫码", Action: map[string]*ice.Action{
mdb.CREATE: {Name: "create type=text name=hi text:textarea=hi", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
SCAN: {Name: "scan hash auto create@scanQRCode scanQRCode0=应用扫码", Help: "扫码", Action: map[string]*ice.Action{
mdb.CREATE: {Name: "create type=text name=hi text:textarea=hi", Help: "扫码", Hand: func(m *ice.Message, arg ...string) {
m.Cmdy(mdb.INSERT, SCAN, "", mdb.HASH, arg)
}},
mdb.REMOVE: {Name: "remove", Help: "删除", Hand: func(m *ice.Message, arg ...string) {

View File

@ -16,9 +16,12 @@ func init() {
)},
},
Commands: map[string]*ice.Command{
FEEL: {Name: "feel path auto upload 上一页 下一页 下载 参数", Help: "影音媒体", Meta: kit.Dict(
FEEL: {Name: "feel path auto choose@chooseImage upload 上一页 下一页 下载 参数", Help: "影音媒体", Meta: kit.Dict(
"display", "/plugin/local/wiki/feel.js",
), Action: map[string]*ice.Action{
"choose": {Name: "choose", Help: "本机照片", Hand: func(m *ice.Message, arg ...string) {
_wiki_upload(m, FEEL, m.Option(kit.MDB_PATH))
}},
web.UPLOAD: {Name: "upload", Help: "上传", Hand: func(m *ice.Message, arg ...string) {
_wiki_upload(m, FEEL, m.Option(kit.MDB_PATH))
}},

View File

@ -94,8 +94,12 @@ func (m *Message) Push(key string, value interface{}, arg ...interface{}) *Messa
}
fallthrough
default:
if v = kit.Value(value, k); v == nil {
v = kit.Value(val, k)
if v = value[k]; v == nil {
if v = kit.Value(value, k); v == nil {
if v = val[k]; v == nil {
v = kit.Value(val, k)
}
}
}
}

View File

@ -66,7 +66,6 @@ func (m *Message) PushRender(key, view, name string, arg ...string) *Message {
if m.Option(MSG_USERUA) == "" {
return m
}
if strings.Contains(m.Option(MSG_USERUA), "curl") {
return m
}

View File

@ -35,7 +35,7 @@ func _alpha_find(m *ice.Message, method, word string) *ice.Message {
}
func _alpha_load(m *ice.Message, file, name string) {
// 清空数据
meta := m.Confm(ALPHA, "meta")
meta := m.Confm(ALPHA, kit.MDB_META)
m.Assert(os.RemoveAll(path.Join(kit.Format(meta[kit.MDB_STORE]), name)))
m.Conf(ALPHA, name, "")
@ -47,8 +47,7 @@ func _alpha_load(m *ice.Message, file, name string) {
kit.MDB_LEAST, meta[kit.MDB_LEAST],
))
m.Cmd(mdb.IMPORT, ALPHA, name, kit.MDB_LIST,
m.Cmd(web.CACHE, "catch", "csv", file+".csv").Append(kit.MDB_FILE))
m.Cmd(mdb.IMPORT, ALPHA, name, kit.MDB_LIST, file)
// 保存词库
m.Conf(ALPHA, kit.Keys(name, "meta.limit"), 0)
@ -67,28 +66,16 @@ var Index = &ice.Context{Name: ALPHA, 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() }},
ALPHA: {Name: "alpha method=word,line word auto", Help: "英汉", Action: map[string]*ice.Action{
mdb.IMPORT: {Name: "import file=usr/word-dict/ecdict name", Help: "加载词库", Hand: func(m *ice.Message, arg ...string) {
_alpha_load(m, m.Option(kit.MDB_FILE), kit.Select(path.Base(m.Option(kit.MDB_FILE)), m.Option(kit.MDB_NAME)))
}},
mdb.SEARCH: {Name: "search", Help: "搜索", Hand: func(m *ice.Message, arg ...string) {
_alpha_find(m.Spawn(), "word", arg[1]).Table(func(index int, value map[string]string, head []string) {
m.Push("file", "")
m.Push("line", arg[1])
m.Push("text", value["definition"]+"\n"+value["translation"])
})
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
_alpha_find(m, arg[0], arg[1])
}},
ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Load()
m.Cmd(mdb.SEARCH, mdb.CREATE, ALPHA, ALPHA, c.Cap(ice.CTX_FOLLOW))
}},
ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Save()
}},
},
}

View File

@ -139,6 +139,7 @@ func _input_load(m *ice.Message, file string, libs ...string) {
}
const (
ZONE = "zone"
FILE = "file"
CODE = "code"
TEXT = "text"
@ -163,17 +164,17 @@ var Index = &ice.Context{Name: INPUT, 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(INPUT) }},
ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Save() }},
WUBI: {Name: "wubi method=word,line code= auto", Help: "五笔", Action: map[string]*ice.Action{
mdb.INSERT: {Name: "insert zone=person text= code= weight=", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
_input_push(m, kit.Select("person", m.Option("zone")), m.Option("text"), m.Option("code"), m.Option("weight"))
WUBI: {Name: "wubi method=word,line code auto", Help: "五笔", Action: map[string]*ice.Action{
mdb.INSERT: {Name: "insert zone=person text code weight", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
_input_push(m, kit.Select("person", m.Option(ZONE)), m.Option(TEXT), m.Option(CODE), m.Option(WEIGHT))
}},
mdb.EXPORT: {Name: "export file=usr/wubi-dict/person zone=person", Help: "导出", Hand: func(m *ice.Message, arg ...string) {
// _input_save(m, kit.Select("usr/wubi-dict/person", m.Option("file")), m.Option("zone"))
}},
mdb.IMPORT: {Name: "import file=usr/wubi-dict/person zone=", Help: "导入", Hand: func(m *ice.Message, arg ...string) {
_input_load(m, kit.Select("usr/wubi-dict/person", m.Option("file")), m.Option("zone"))
_input_load(m, kit.Select("usr/wubi-dict/person", m.Option(FILE)), m.Option(ZONE))
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
_input_find(m, arg[0], arg[1], m.Option("cache.limit"))

View File

@ -7,6 +7,7 @@ import (
"github.com/shylinux/icebergs/base/mdb"
"github.com/shylinux/icebergs/base/web"
"github.com/shylinux/icebergs/core/chat"
"github.com/shylinux/icebergs/core/wiki"
"github.com/shylinux/toolkits"
"encoding/json"
@ -16,19 +17,15 @@ import (
"time"
)
func raw(m *ice.Message, url string, arg ...interface{}) interface{} {
m.Option("header", "Authorization", "Bearer "+m.Cmdx(APP, "token", "bot"), "Content-Type", "application/json")
data := kit.UnMarshal(m.Cmdx(web.SPIDE, LARK, "raw", http.MethodGet, url, arg))
m.Debug(kit.Formats(data))
return data
func _lark_get(m *ice.Message, bot string, arg ...interface{}) *ice.Message {
m.Option(web.SPIDE_HEADER, "Authorization", "Bearer "+m.Cmdx(APP, TOKEN, bot), web.ContentType, web.ContentJSON)
return m.Cmd(web.SPIDE, LARK, http.MethodGet, arg)
}
func post(m *ice.Message, bot string, arg ...interface{}) {
m.Richs(APP, nil, bot, func(key string, value map[string]interface{}) {
m.Option("header", "Authorization", "Bearer "+m.Cmdx(APP, "token", bot), "Content-Type", "application/json")
m.Cmdy(web.SPIDE, LARK, arg)
})
func _lark_post(m *ice.Message, bot string, arg ...interface{}) *ice.Message {
m.Option(web.SPIDE_HEADER, "Authorization", "Bearer "+m.Cmdx(APP, TOKEN, bot), web.ContentType, web.ContentJSON)
return m.Cmd(web.SPIDE, LARK, arg)
}
func parse(m *ice.Message) {
func _lark_parse(m *ice.Message) {
data := m.Optionv("content_data")
if data == nil {
json.NewDecoder(m.R.Body).Decode(&data)
@ -40,153 +37,143 @@ func parse(m *ice.Message) {
switch d := v.(type) {
case map[string]interface{}:
for k, v := range d {
m.Add("option", k, kit.Format(v))
m.Add(ice.MSG_OPTION, k, kit.Format(v))
}
default:
for _, v := range kit.Simple(v) {
m.Add("option", "msg."+k, kit.Format(v))
m.Add(ice.MSG_OPTION, "msg."+k, kit.Format(v))
}
}
}
}
}
m.Info("msg: %v", kit.Formats(data))
m.Debug("msg: %v", kit.Format(data))
}
const (
ADD_BOT = "add_bot"
P2P_CHAT_CREATE = "p2p_chat_create"
ADD_BOT = "add_bot"
)
const (
SHIP_ID = "ship_id"
OPEN_ID = "open_id"
CHAT_ID = "chat_id"
USER_OPEN_ID = "user_open_id"
OPEN_CHAT_ID = "open_chat_id"
USER_OPEN_ID = "user_open_id"
)
const (
APP = "app"
SHIP = "ship"
USERS = "users"
GROUP = "group"
LOGIN = "login"
APPID = "appid"
APPMM = "appmm"
TOKEN = "token"
EXPIRE = "expire"
)
const (
APP = "app"
COMPANY = "company"
EMPLOYEE = "employee"
GROUP = "group"
SEND = "send"
FORM = "form"
DUTY = "duty"
HOME = "home"
FORM = "form"
TALK = "talk"
RAND = "rand"
HOME = "home"
DATE = "date"
META = "meta"
WORD = "word"
LARK = "lark"
)
var Index = &ice.Context{Name: "lark", Help: "机器人",
const LARK = "lark"
var Index = &ice.Context{Name: LARK, Help: "机器人",
Configs: map[string]*ice.Config{
APP: {Name: APP, Help: "服务配置", Value: kit.Data(kit.MDB_SHORT, kit.MDB_NAME,
LARK, "https://open.feishu.cn", DUTY, "", "welcome", kit.Dict(
LARK, "https://open.feishu.cn", DUTY, "", "template", kit.Dict(
ADD_BOT, "我来也~", P2P_CHAT_CREATE, "让我们做好朋友吧~",
),
)},
SHIP: {Name: SHIP, Help: "组织配置", Value: kit.Data(kit.MDB_SHORT, SHIP_ID)},
USERS: {Name: USERS, Help: "用户配置", Value: kit.Data(kit.MDB_SHORT, OPEN_ID)},
HOME: {Name: HOME, Help: "卡片配置", Value: kit.Data(kit.MDB_SHORT, OPEN_ID)},
META: {Name: META, Help: "卡片配置", Value: kit.Data(
kit.MDB_SHORT, "url",
)},
COMPANY: {Name: COMPANY, Help: "组织配置", Value: kit.Data(kit.MDB_SHORT, SHIP_ID)},
EMPLOYEE: {Name: EMPLOYEE, Help: "用户配置", Value: kit.Data(kit.MDB_SHORT, OPEN_ID)},
},
Commands: map[string]*ice.Command{
ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Load()
m.Cmd(web.SPIDE, mdb.CREATE, LARK, m.Conf(APP, "meta.lark"))
m.Cmd(web.SPIDE, mdb.CREATE, LARK, m.Conf(APP, kit.Keys(kit.MDB_META, LARK)))
m.Cmd(DUTY, "boot", m.Conf(cli.RUNTIME, "boot.hostname"), m.Time())
}},
ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Save(APP, SHIP, USERS, META)
m.Save()
}},
APP: {Name: "app [name] auto", Help: "应用", Action: map[string]*ice.Action{
"login": {Name: "login name id mm", Hand: func(m *ice.Message, arg ...string) {
m.Rich(APP, nil, kit.Dict("name", arg[0], "id", arg[1], "mm", arg[2]))
APP: {Name: "app name auto token login", Help: "应用", Action: map[string]*ice.Action{
LOGIN: {Name: "login name appid appmm", Help: "登录", Hand: func(m *ice.Message, arg ...string) {
m.Cmdy(mdb.INSERT, m.Prefix(APP), "", mdb.HASH, arg)
}},
"token": {Name: "token name", Hand: func(m *ice.Message, arg ...string) {
m.Richs(APP, nil, arg[0], func(key string, value map[string]interface{}) {
if now := time.Now().Unix(); kit.Format(value["token"]) == "" || kit.Int64(value["expire"]) < now {
m.Cmdy(web.SPIDE, LARK, "/open-apis/auth/v3/tenant_access_token/internal/", "app_id", value["id"], "app_secret", value["mm"])
value["expire"] = kit.Int64(m.Append("expire")) + now
value["token"] = m.Append("tenant_access_token")
m.Set(ice.MSG_RESULT)
}
m.Echo("%s", value["token"])
})
}},
}, Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
m.Richs(APP, nil, kit.Select(kit.MDB_FOREACH, arg, 0), func(key string, value map[string]interface{}) {
if len(arg) == 0 || arg[0] == "" {
m.Push(key, value, []string{"time", "name", "id", "expire"})
return
TOKEN: {Name: "token name", Help: "令牌", Hand: func(m *ice.Message, arg ...string) {
m.Option(mdb.FIELDS, "time,appid,appmm,token,expire")
msg := m.Cmd(mdb.SELECT, m.Prefix(APP), "", mdb.HASH, kit.MDB_NAME, m.Option(kit.MDB_NAME))
if now := time.Now().Unix(); msg.Append(TOKEN) == "" || now > kit.Int64(msg.Append(EXPIRE)) {
sub := m.Cmd(web.SPIDE, LARK, "/open-apis/auth/v3/tenant_access_token/internal/",
"app_id", msg.Append(APPID), "app_secret", msg.Append(APPMM))
m.Cmd(mdb.MODIFY, m.Prefix(APP), "", mdb.HASH, kit.MDB_NAME, m.Option(kit.MDB_NAME),
TOKEN, msg.Append(TOKEN, sub.Append("tenant_access_token")), EXPIRE, now+kit.Int64(sub.Append(EXPIRE)))
}
m.Push("detail", value)
})
m.Echo(msg.Append(TOKEN))
}},
}, Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
m.Option(mdb.FIELDS, kit.Select("time,name,appid,token,expire", mdb.DETAIL, len(arg) > 0))
m.Cmdy(mdb.SELECT, m.Prefix(APP), "", mdb.HASH, kit.MDB_NAME, arg)
}},
SHIP: {Name: "ship ship_id open_id text", Help: "组织", Action: map[string]*ice.Action{
COMPANY: {Name: "company ship_id open_id text auto", Help: "组织", Action: map[string]*ice.Action{
"info": {Name: "info ship_id", Hand: func(m *ice.Message, arg ...string) {
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
data := raw(m, "/open-apis/contact/v1/department/detail/batch_get",
"department_ids", arg[0])
kit.Fetch(kit.Value(data, "data.department_infos"), func(index int, value map[string]interface{}) {
m.Push("name", value)
})
msg := _lark_get(m, "bot", "/open-apis/contact/v1/department/detail/batch_get", "department_ids", m.Option(SHIP_ID))
kit.Fetch(kit.Value(msg.Optionv("content_data"), "data.department_infos"), func(index int, value map[string]interface{}) {
m.Push("", value)
})
}},
"users": {Name: "users ship_id", Hand: func(m *ice.Message, arg ...string) {
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
data := raw(m, "/open-apis/contact/v1/department/user/list",
"department_id", arg[0], "page_size", "100", "fetch_child", "true")
"list": {Name: "list ship_id", Hand: func(m *ice.Message, arg ...string) {
msg := _lark_get(m, "bot", "/open-apis/contact/v1/department/user/list",
"department_id", m.Option(SHIP_ID), "page_size", "100", "fetch_child", "true")
kit.Fetch(kit.Value(data, "data.user_list"), func(index int, value map[string]interface{}) {
msg := m.Cmd(USERS, value[OPEN_ID])
// m.Push("avatar", m.Cmdx(mdb.RENDER, web.RENDER.IMG, msg.Append("avatar_72")))
m.Push("gender", kit.Select("男", "女", msg.Append("gender") == "2"))
m.Push(kit.MDB_NAME, msg.Append(kit.MDB_NAME))
m.Push("description", msg.Append("description"))
m.Push(OPEN_ID, msg.Append(OPEN_ID))
})
kit.Fetch(kit.Value(msg.Optionv("content_data"), "data.user_list"), func(index int, value map[string]interface{}) {
msg := m.Cmd(EMPLOYEE, value[OPEN_ID])
m.PushRender(aaa.AVATAR, "img", msg.Append("avatar_72"))
m.Push(aaa.GENDER, kit.Select("女", "男", msg.Append(aaa.GENDER) == "1"))
m.Push(kit.MDB_NAME, msg.Append(kit.MDB_NAME))
m.Push(kit.MDB_TEXT, msg.Append("description"))
m.Push(OPEN_ID, msg.Append(OPEN_ID))
})
}},
}, Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
if len(arg) == 0 {
// 组织列表
data := raw(m, "/open-apis/contact/v1/scope/get/")
kit.Fetch(kit.Value(data, "data.authed_departments"), func(index int, value string) {
if len(arg) == 0 { // 组织列表
msg := _lark_get(m, "bot", "/open-apis/contact/v1/scope/get/")
kit.Fetch(kit.Value(msg.Optionv("content_data"), "data.authed_departments"), func(index int, value string) {
m.Push(SHIP_ID, value)
msg := m.Cmd(m.Prefix(SHIP), "info", value)
msg := m.Cmd(m.Prefix(COMPANY), "info", value)
m.Push(kit.MDB_NAME, msg.Append(kit.MDB_NAME))
m.Push("member_count", msg.Append("member_count"))
m.Push(kit.MDB_COUNT, msg.Append("member_count"))
m.Push(CHAT_ID, msg.Append(CHAT_ID))
})
m.Sort(kit.MDB_NAME)
return
} else if len(arg) == 1 { // 员工列表
m.Cmdy(m.Prefix(COMPANY), "list", arg[0])
} else if len(arg) == 2 { // 员工详情
m.Cmdy(EMPLOYEE, arg[1])
} else { // 员工通知
m.Cmdy(m.Prefix(SEND), OPEN_ID, arg[1], arg[2:])
}
if len(arg) == 1 {
// 用户列表
m.Cmdy(m.Prefix(SHIP), USERS, arg[0])
return
}
// 用户通知
m.Cmdy(m.Prefix(SEND), OPEN_ID, arg[1], arg[2:])
}},
USERS: {Name: "users open_id|mobile|email", Help: "用户", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
EMPLOYEE: {Name: "employee open_id|mobile|email auto", Help: "员工", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
if len(arg) == 0 {
return
}
if strings.HasPrefix(arg[0], "ou_") {
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
data := raw(m, "/open-apis/contact/v1/user/batch_get", "open_ids", arg[0])
kit.Fetch(kit.Value(data, "data.user_infos"), func(index int, value map[string]interface{}) {
m.Push("name", value)
})
msg := _lark_get(m, "bot", "/open-apis/contact/v1/user/batch_get", "open_ids", arg[0])
kit.Fetch(kit.Value(msg.Optionv("content_data"), "data.user_infos"), func(index int, value map[string]interface{}) {
m.Push(mdb.DETAIL, value)
})
return
}
@ -195,55 +182,49 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
for i := 0; i < len(arg); i++ {
us = append(us, kit.Select("mobiles", "emails", strings.Contains(arg[i], "@")), arg[i])
}
post(m, "bot", http.MethodGet, "/open-apis/user/v1/batch_get_id", us)
_lark_get(m, "bot", "/open-apis/user/v1/batch_get_id", us)
for i := 0; i < len(arg); i++ {
m.Echo(m.Append(kit.Keys("data.mobile_users", arg[i], "0.open_id")))
}
}},
GROUP: {Name: "group chat_id open_id text", Help: "群组", Action: map[string]*ice.Action{
"users": {Name: "users id", Hand: func(m *ice.Message, arg ...string) {
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
data := raw(m, "/open-apis/chat/v4/info", "chat_id", arg[0])
kit.Fetch(kit.Value(data, "data.members"), func(index int, value map[string]interface{}) {
msg := m.Cmd(USERS, value[OPEN_ID])
// m.Push("avatar", m.Cmdx(mdb.RENDER, web.RENDER.IMG, msg.Append("avatar_72")))
m.Push("gender", kit.Select("男", "女", msg.Append("gender") == "2"))
m.Push(kit.MDB_NAME, msg.Append(kit.MDB_NAME))
m.Push("description", msg.Append("description"))
m.Push(OPEN_ID, msg.Append(OPEN_ID))
})
GROUP: {Name: "group chat_id open_id text auto", Help: "群组", Action: map[string]*ice.Action{
"list": {Name: "list chat_id", Hand: func(m *ice.Message, arg ...string) {
msg := _lark_get(m, "bot", "/open-apis/chat/v4/info", "chat_id", m.Option(CHAT_ID))
kit.Fetch(kit.Value(msg.Optionv("content_data"), "data.members"), func(index int, value map[string]interface{}) {
msg := m.Cmd(EMPLOYEE, value[OPEN_ID])
m.PushRender(aaa.AVATAR, "img", msg.Append("avatar_72"))
m.Push(aaa.GENDER, kit.Select("女", "男", msg.Append(aaa.GENDER) == "1"))
m.Push(kit.MDB_NAME, msg.Append(kit.MDB_NAME))
m.Push(kit.MDB_TEXT, msg.Append("description"))
m.Push(OPEN_ID, msg.Append(OPEN_ID))
})
}},
}, Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
if len(arg) == 0 {
// 群组列表
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
m.Option("header", "Authorization", "Bearer "+m.Cmdx(APP, "token", "bot"), "Content-Type", "application/json")
data := raw(m, "/open-apis/chat/v4/list")
kit.Fetch(kit.Value(data, "data.groups"), func(index int, value map[string]interface{}) {
m.Push(CHAT_ID, value[CHAT_ID])
// m.Push("avatar", m.Cmdx(mdb.RENDER, web.RENDER.IMG, value["avatar"]))
m.Push(kit.MDB_NAME, value[kit.MDB_NAME])
m.Push("description", value["description"])
m.Push(OPEN_ID, value["owner_open_id"])
})
if len(arg) == 0 { // 群组列表
msg := _lark_get(m, "bot", "/open-apis/chat/v4/list")
kit.Fetch(kit.Value(msg.Optionv("content_data"), "data.groups"), func(index int, value map[string]interface{}) {
m.Push(CHAT_ID, value[CHAT_ID])
m.PushRender(aaa.AVATAR, "img", kit.Format(value[aaa.AVATAR]), "72")
m.Push(kit.MDB_NAME, value[kit.MDB_NAME])
m.Push(kit.MDB_TEXT, value["description"])
m.Push(OPEN_ID, value["owner_open_id"])
})
m.Sort(kit.MDB_NAME)
return
} else if len(arg) == 1 { // 组员列表
m.Cmdy(m.Prefix(GROUP), "list", arg[0])
} else if len(arg) == 2 { // 组员详情
m.Cmdy(EMPLOYEE, arg[1])
} else { // 组员通知
m.Cmdy(m.Prefix(SEND), CHAT_ID, arg[0], arg[2:])
}
if len(arg) == 1 {
// 用户列表
m.Cmdy(m.Prefix(GROUP), USERS, arg[0])
return
}
// 用户通知
m.Cmdy(m.Prefix(SEND), CHAT_ID, arg[0], arg[2:])
}},
SEND: {Name: "send [chat_id|open_id|user_id|email] user [title] text", Help: "消息", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
SEND: {Name: "send [chat_id|open_id|user_id|email] target [title] text", Help: "消息", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
var form = kit.Dict("content", kit.Dict())
switch arg[0] {
case CHAT_ID, OPEN_ID, "user_id", "email":
form[arg[0]], arg = arg[1], arg[2:]
@ -270,108 +251,48 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
content, line = append(content, line), []interface{}{}
continue
}
line = append(line, map[string]interface{}{
"tag": "text", "text": v + " ",
})
line = append(line, map[string]interface{}{"tag": "text", "text": v + " "})
}
content = append(content, line)
kit.Value(form, "msg_type", "post")
kit.Value(form, "content.post", map[string]interface{}{
"zh_cn": map[string]interface{}{
"title": arg[0],
"content": content,
},
"zh_cn": map[string]interface{}{"title": arg[0], "content": content},
})
}
post(m, "bot", "/open-apis/message/v4/send/", "data", kit.Formats(form))
}},
TALK: {Name: "talk text", Help: "聊天", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
// 用户权限
m.Option(ice.MSG_USERZONE, LARK)
m.Option(ice.MSG_USERNAME, m.Option(OPEN_ID))
m.Option(ice.MSG_USERNICK, aaa.UserNick(m, m.Option(ice.MSG_USERNAME)))
m.Option(ice.MSG_USERROLE, aaa.UserRole(m, m.Option(ice.MSG_USERNAME)))
m.Info("%s: %s", m.Option(ice.MSG_USERROLE), m.Option(ice.MSG_USERNAME))
cmd := kit.Split(arg[0])
if !m.Right(cmd) {
// 群组权限
m.Option(ice.MSG_USERNAME, m.Option(OPEN_CHAT_ID))
m.Option(ice.MSG_USERROLE, aaa.UserRole(m, m.Option(ice.MSG_USERNAME)))
m.Info("%s: %s", m.Option(ice.MSG_USERROLE), m.Option(ice.MSG_USERNAME))
if !m.Right(cmd) {
// 没有权限
m.Cmd(DUTY, m.Option(OPEN_CHAT_ID), m.Option("text_without_at_bot"))
m.Cmd(HOME)
return
}
}
if len(cmd) == 0 {
m.Cmd(HOME)
return
}
// 执行命令
msg := m.Cmd(cmd)
if m.Hand = false; !msg.Hand {
msg = m.Cmd(cli.SYSTEM, cmd)
}
if m.Hand = true; msg.Result() == "" {
msg.Table()
}
m.Echo(msg.Result())
_lark_post(m, "bot", "/open-apis/message/v4/send/", web.SPIDE_DATA, kit.Format(form))
}},
DUTY: {Name: "duty [title] text", Help: "通告", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
m.Cmdy(SEND, m.Conf(APP, "meta.duty"), arg)
}},
RAND: {Name: "rand", Help: "随机", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
msg := m.Cmd(GROUP, USERS, m.Option(OPEN_CHAT_ID))
list := msg.Appendv("name")
if strings.Contains(m.Option("content"), "誰") {
m.Echo(strings.Replace(m.Option("content"), "誰", list[rand.Intn(len(list))], 1))
return
}
m.Echo(list[rand.Intn(len(list))])
}},
HOME: {Name: "home", Help: "首页", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
list := []string{}
HOME: {Name: "home river storm title", Help: "首页", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
name := kit.Select(m.Option(ice.MSG_USERNAME), m.Option(ice.MSG_USERNICK))
if len(name) > 10 {
name = name[:10]
}
name += "的应用列表"
link := "https://shylinux.com"
name += "的" + kit.Select("应用列表", arg, 2)
text := ""
link, list := m.Conf(web.SHARE, "meta.domain"), []string{}
if len(arg) == 0 {
m.Option("_source", ".")
m.Cmd("web.chat./river").Table(func(index int, val map[string]string, head []string) {
m.Option(ice.MSG_RIVER, val["key"])
m.Cmd("web.chat./storm").Table(func(index int, value map[string]string, head []string) {
list = append(list, val["name"]+"."+value["name"], "cmd", "home "+val["key"]+" "+value["key"])
m.Cmd("web.chat./river", val[kit.MDB_HASH], chat.TOOL).Table(func(index int, value map[string]string, head []string) {
list = append(list, kit.Keys(val[kit.MDB_NAME], value[kit.MDB_NAME]),
kit.SSH_CMD, kit.Format([]string{"home", val[kit.MDB_HASH], value[kit.MDB_HASH], val[kit.MDB_NAME] + "." + value[kit.MDB_NAME]}))
})
})
} else {
m.Option(ice.MSG_RIVER, arg[0])
m.Option(ice.MSG_STORM, arg[1])
m.Richs(chat.RIVER, nil, arg[0], func(key string, val map[string]interface{}) {
m.Richs(chat.RIVER, kit.Keys(kit.MDB_HASH, arg[0], chat.TOOL), arg[1], func(key string, value map[string]interface{}) {
text = kit.Keys(kit.Value(val, "meta.name"), kit.Value(value, "meta.name"))
})
link = kit.MergeURL(link, chat.RIVER, arg[0], chat.STORM, arg[1])
m.Cmd("web.chat./river", arg[0], chat.TOOL, arg[1]).Table(func(index int, value map[string]string, head []string) {
list = append(list, value[kit.SSH_CMD], kit.SSH_CMD, kit.Keys(value[kit.SSH_CTX], value[kit.SSH_CMD]))
})
m.Cmd("web.chat./action").Table(func(index int, value map[string]string, head []string) {
list = append(list, value["name"], "cmd", kit.Keys(value["group"], value["index"]))
})
link = "https://shylinux.com?river=" + arg[0] + "&storm=" + arg[1]
}
m.Cmd(FORM, CHAT_ID, m.Option(OPEN_CHAT_ID), name, text,
"网页应用", "url", link, list,
)
m.Cmd(FORM, CHAT_ID, m.Option(OPEN_CHAT_ID), name, text, "打开网页", "url", link, list)
}},
FORM: {Name: "form chat_id|open_id|user_id|email user title [text [confirm|value|url arg...]]...", Help: "消息", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
FORM: {Name: "form chat_id|open_id|user_id|email target title text [confirm|value|url arg...]...", Help: "消息", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
var form = map[string]interface{}{"content": map[string]interface{}{}}
switch arg[0] {
case CHAT_ID, OPEN_ID, "user_id", "email":
@ -390,10 +311,9 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
actions := []interface{}{}
for i := 2; i < len(arg); i++ {
button := map[string]interface{}{
"tag": "button", "text": map[string]interface{}{
"type": "default", "tag": "button", "text": map[string]interface{}{
"tag": "plain_text", "content": arg[i],
},
"type": "default",
}
content := arg[i]
@ -404,9 +324,7 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
"text": map[string]interface{}{"tag": "lark_md", "content": arg[i+3]},
}, i+3
case "value":
button[arg[i+1]], i = map[string]interface{}{
arg[i+2]: arg[i+3],
}, i+3
button[arg[i+1]], i = map[string]interface{}{arg[i+2]: arg[i+3]}, i+3
case "url":
button[arg[i+1]], i = arg[i+2], i+2
default:
@ -423,66 +341,70 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
actions = append(actions, button)
}
elements = append(elements, map[string]interface{}{
"tag": "action", "actions": actions,
})
elements = append(elements, map[string]interface{}{"tag": "action", "actions": actions})
kit.Value(form, "msg_type", "interactive")
kit.Value(form, "card", map[string]interface{}{
"config": map[string]interface{}{
"wide_screen_mode": true,
},
"config": map[string]interface{}{"wide_screen_mode": true},
"header": map[string]interface{}{
"title": map[string]interface{}{
"tag": "lark_md", "content": arg[0],
},
"title": map[string]interface{}{"tag": "lark_md", "content": arg[0]},
},
"elements": elements,
})
post(m, "bot", "/open-apis/message/v4/send/", "data", kit.Formats(form))
_lark_post(m, "bot", "/open-apis/message/v4/send/", web.SPIDE_DATA, kit.Formats(form))
}},
TALK: {Name: "talk text", Help: "聊天", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
m.Option(ice.MSG_USERZONE, LARK)
cmds := kit.Split(strings.Join(arg, " "))
if aaa.UserLogin(m, m.Option(OPEN_ID), ""); !m.Right(cmds) {
if aaa.UserLogin(m, m.Option(OPEN_CHAT_ID), ""); !m.Right(cmds) {
m.Cmd(DUTY, m.Option(OPEN_CHAT_ID), m.Option("text_without_at_bot"))
m.Cmd(HOME)
return // 没有权限
}
}
if cmds[0] == HOME {
m.Cmd(HOME, cmds[1:])
return // 没有命令
}
// 执行命令
if msg := m.Cmd(cmds); msg.Hand == true {
if m.Copy(msg); len(m.Resultv()) == 0 {
m.Table()
}
} else {
m.Cmdy(cli.SYSTEM, cmds)
}
}},
RAND: {Name: "rand", Help: "随机", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
msg := m.Cmd(GROUP, EMPLOYEE, m.Option(OPEN_CHAT_ID))
list := msg.Appendv("name")
if strings.Contains(m.Option("content"), "誰") {
m.Echo(strings.Replace(m.Option("content"), "誰", list[rand.Intn(len(list))], 1))
return
}
m.Echo(list[rand.Intn(len(list))])
}},
// DATE: {Name: "date id", Help: "日历", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
// m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
// if len(arg) == 0 {
// data := raw(m, "/open-apis/calendar/v3/calendar_list")
// kit.Fetch(kit.Value(data, "data"), func(index int, value map[string]interface{}) {
// m.Push("", value)
// })
// return
// }
// data := raw(m, "/open-apis/calendar/v3/calendars/"+arg[0]+"/events")
// kit.Fetch(kit.Value(data, "data"), func(index int, value map[string]interface{}) {
// m.Push("", value)
// })
// })
// }},
// META: {Name: "meta", Help: "日历", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
// m.Richs(META, nil, kit.MDB_FOREACH, func(key string, value map[string]interface{}) {
// m.Push("image", m.Cmdx(mdb.RENDER, web.RENDER.IMG, value["url"]))
// })
// }},
web.LOGIN: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {}},
"/msg": {Name: "/msg", Help: "聊天消息", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
data := m.Optionv(ice.MSG_USERDATA)
if kit.Value(data, "action") != nil {
m.Option(ice.MSG_USERUA, "MicroMessenger")
m.Option(ice.MSG_USERUA, "")
kit.Fetch(kit.Value(data, "action.value"), func(key string, value string) { m.Option(key, value) })
kit.Fetch(kit.Value(data, "action.value"), func(key string, value string) {
m.Option(key, value)
})
m.Cmdy(TALK, m.Option("cmd"))
m.Cmd(SEND, CHAT_ID, m.Option(OPEN_CHAT_ID), m.Result())
m.Cmdy(TALK, kit.Parse(nil, "", kit.Split(m.Option(kit.SSH_CMD))...))
m.Cmd(SEND, CHAT_ID, m.Option(OPEN_CHAT_ID), m.Option(wiki.TITLE)+" "+m.Option(kit.SSH_CMD), m.Result())
return
}
switch parse(m); m.Option("msg.type") {
case "url_verification":
// 绑定验证
m.Option(ice.MSG_OUTPUT, ice.RENDER_RESULT)
m.Echo(kit.Format(map[string]interface{}{"challenge": m.Option("msg.challenge")}))
switch _lark_parse(m); m.Option("msg.type") {
case "url_verification": // 绑定验证
m.Render(ice.RENDER_RESULT, kit.Format(kit.Dict("challenge", m.Option("msg.challenge"))))
case "event_callback":
switch m.Option("type") {
@ -491,24 +413,24 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
case P2P_CHAT_CREATE, ADD_BOT:
// 创建对话
if m.Options(OPEN_CHAT_ID) {
m.Cmdy(SEND, m.Option(OPEN_CHAT_ID), m.Conf(APP, kit.Dict(kit.MDB_META, "welcome", m.Option("type"))))
m.Cmdy(SEND, m.Option(OPEN_CHAT_ID), m.Conf(APP, kit.Keys(kit.MDB_META, "template", m.Option("type"))))
}
default:
switch m.Option("msg_type") {
case "location":
case "image":
m.Rich(META, nil, kit.Dict(
"url", m.Option("image_url"),
"width", m.Option("image_width"),
"height", m.Option("image_height"),
))
// m.Rich(META, nil, kit.Dict(
// "url", m.Option("image_url"),
// "width", m.Option("image_width"),
// "height", m.Option("image_height"),
// ))
default:
if m.Options(OPEN_CHAT_ID) {
if m.Cmdy(TALK, strings.TrimSpace(m.Option("text_without_at_bot"))); len(m.Resultv()) > 0 {
m.Cmd(SEND, m.Option(OPEN_CHAT_ID), m.Result())
}
} else {
m.Cmd(DUTY, m.Option("msg.type"), kit.Formats(data))
m.Cmd(DUTY, m.Option("type"), kit.Formats(data))
}
}
}
@ -518,33 +440,27 @@ var Index = &ice.Context{Name: "lark", Help: "机器人",
}},
"/sso": {Name: "/sso", Help: "网页", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) {
if m.Options("code") {
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
data := kit.UnMarshal(m.Cmdx(web.SPIDE, LARK, "raw", "/open-apis/authen/v1/access_token",
"code", m.Option("code"), "grant_type", "authorization_code",
"app_access_token", m.Cmdx(APP, "token", "bot"),
))
msg := m.Cmd(web.SPIDE, LARK, "/open-apis/authen/v1/access_token", "grant_type", "authorization_code",
"code", m.Option("code"), "app_access_token", m.Cmdx(APP, "token", "bot"))
m.Option(aaa.USERZONE, LARK)
m.Option(ice.MSG_USERROLE, aaa.ROOT)
user := kit.Format(kit.Value(data, "data.open_id"))
web.RenderCookie(m, aaa.SessCreate(m, user, aaa.UserRole(m, user)))
m.Render("redirect", m.Conf(web.SHARE, "meta.domain"))
m.Option(aaa.USERZONE, LARK)
user := msg.Append("data.open_id")
web.RenderCookie(m, aaa.SessCreate(m, user, aaa.UserRole(m, user)))
m.Render("redirect", m.Conf(web.SHARE, "meta.domain"))
m.Option(aaa.USERNAME, user)
msg := m.Cmd(USERS, user)
m.Cmd(aaa.USER, mdb.MODIFY, aaa.USERZONE, LARK, aaa.USERNICK, msg.Append("name"),
"mobile", msg.Append("mobile"), "avatar_url", msg.Append("avatar_url"),
"gender", kit.Select("女", "男", msg.Append("gender") == "1"),
"country", msg.Append("country"), "city", msg.Append("city"),
)
})
msg = m.Cmd(EMPLOYEE, m.Option(aaa.USERNAME, user))
m.Cmd(aaa.USER, mdb.MODIFY, aaa.USERZONE, LARK, aaa.USERNICK, msg.Append(kit.MDB_NAME),
aaa.AVATAR, msg.Append("avatar_url"), aaa.GENDER, kit.Select("女", "男", msg.Append(aaa.GENDER) == "1"),
aaa.COUNTRY, msg.Append(aaa.COUNTRY), aaa.CITY, msg.Append(aaa.CITY),
aaa.MOBILE, msg.Append(aaa.MOBILE),
)
return
}
m.Richs(APP, nil, "bot", func(key string, value map[string]interface{}) {
m.Render("redirect", kit.MergeURL2(m.Conf(APP, "meta.lark"), "/open-apis/authen/v1/index"),
"app_id", value["id"], "redirect_uri", kit.MergeURL2(m.Conf(web.SHARE, "meta.domain"), "/chat/lark/sso"),
// "app_id", value["id"], "redirect_uri", "https://shylinux.com/chat/lark/sso",
m.Option(mdb.FIELDS, "time,appid,appmm,token,expire")
m.Cmd(mdb.SELECT, m.Prefix(APP), "", mdb.HASH, kit.MDB_NAME, "bot").Table(func(index int, value map[string]string, head []string) {
m.Render("redirect", kit.MergeURL2(m.Conf(APP, kit.Keys(kit.MDB_META, LARK)), "/open-apis/authen/v1/index"),
"app_id", value[APPID], "redirect_uri", kit.MergeURL2(m.Conf(web.SHARE, "meta.domain"), "/chat/lark/sso"),
)
})
}},

View File

@ -1,19 +1,25 @@
title "飞书机器人"
refer "官网" `
官网 https://www.feishu.cn/
应用管理 https://open.feishu.cn/app
后台管理 https://www.feishu.cn/admin
文档 https://open.feishu.cn/document/uQjL04CN/ucDOz4yN4MjL3gzM
源码 https://github.com/shylinux/icebergs/blob/master/misc/lark/lark.go
后台管理 https://www.feishu.cn/admin
应用管理 https://open.feishu.cn/app
`
image qrcode `https://www.feishu.cn/`
chapter "应用"
field "ship" web.chat.lark.ship
field "app" web.chat.lark.app
field "company" web.chat.lark.company
# field "employee" web.chat.lark.employee
field "group" web.chat.lark.group
# field "send" web.chat.lark.send
# field "duty" web.chat.lark.duty
chapter "权限"
field user aaa.user
field sess aaa.sess
chapter "项目"
section "icebergs"
field "icebergs_统计" web.code.git.trend args `[ icebergs ]` action `{ height 200 speed 20 }`
field "icebergs_源码" web.code.inner args `[ usr/icebergs misc/lark/lark.go 74 ]`
field "icebergs" web.code.git.trend args `icebergs`
field "icebergs" web.code.inner args `usr/icebergs/ misc/lark/lark.go 1`

View File

@ -10,51 +10,60 @@ import (
)
const (
LOGIN = "login"
LOGIN = "login"
APPID = "appid"
APPMM = "appmm"
ACCESS = "access"
OPENID = "openid"
WEIXIN = "weixin"
)
const MP = "mp"
var Index = &ice.Context{Name: MP, Help: "小程序",
Configs: map[string]*ice.Config{
LOGIN: {Name: LOGIN, Help: "认证", Value: kit.Data(
"auth", "/sns/jscode2session?grant_type=authorization_code",
"weixin", "https://api.weixin.qq.com",
"appid", "", "appmm", "", "token", "",
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.Load()
m.Cmd(web.SPIDE, mdb.CREATE, "weixin", m.Conf(LOGIN, "meta.weixin"))
m.Cmd(web.SPIDE, mdb.CREATE, WEIXIN, m.Conf(LOGIN, kit.Keys(kit.MDB_META, WEIXIN)))
}},
ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Save()
}},
ACCESS: {Name: "access appid auto login", Help: "认证", Action: map[string]*ice.Action{
LOGIN: {Name: "login appid appmm", Help: "登录", Hand: func(m *ice.Message, arg ...string) {
m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID), m.Option(APPID))
m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPMM), m.Option(APPMM))
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Push(APPID, m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID)))
}},
"/login/": {Name: "/login/", Help: "登录", Action: map[string]*ice.Action{
"code": {Name: "code", Help: "登录", Hand: func(m *ice.Message, arg ...string) {
msg := m.Cmd(web.SPIDE, "weixin", web.SPIDE_GET, m.Conf(LOGIN, "meta.auth"), "js_code", m.Option("code"),
"appid", m.Conf(LOGIN, "meta.appid"), "secret", m.Conf(LOGIN, "meta.appmm"))
"/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("code"),
APPID, m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID)), "secret", m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPMM)))
// 用户登录
m.Option(ice.MSG_USERZONE, MP)
m.Echo(aaa.SessCreate(msg, msg.Append("openid"), aaa.UserRole(msg, msg.Append("openid"))))
m.Echo(aaa.SessCreate(msg, msg.Append(OPENID), aaa.UserRole(msg, msg.Append(OPENID))))
}},
"info": {Name: "info", Help: "信息", Hand: func(m *ice.Message, arg ...string) {
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"),
"avatar_url", m.Option("avatarUrl"),
"gender", kit.Select("女", "男", m.Option("gender") == "1"),
"country", m.Option("country"), "city", m.Option("city"),
"language", m.Option("language"),
"province", m.Option("province"),
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),
)
}},
"scan": {Name: "scan", Help: "scan", Hand: func(m *ice.Message, arg ...string) {
if m.Option(web.SHARE) != "" {
if m.Option(chat.RIVER) != "" {
m.Cmdy(chat.AUTH, mdb.INSERT)
}
chat.SCAN: {Name: "scan", Help: "扫码", Hand: func(m *ice.Message, arg ...string) {
if m.Option(web.SHARE) != "" && m.Option(chat.RIVER) != "" {
m.Cmdy(chat.AUTH, mdb.INSERT)
} else {
m.Cmdy(chat.SCAN)
}
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {

View File

@ -6,13 +6,19 @@ refer "" `
源码 https://github.com/shylinux/icebergs/blob/master/misc/mp/mp.go
`
image qrcode `https://weixin.qq.com`
spark
chapter "应用"
field "asset" web.mall.asset args `[ ]`
field "task" web.team.task args `[ ]`
field location web.chat.location
field paste web.chat.paste
field scan web.chat.scan
field feel web.chat.feel
chapter "权限"
field access web.chat.mp.access
field user aaa.user
field sess aaa.sess
chapter "项目"
section "icebergs"
field "icebergs_统计" web.code.git.trend args `[ icebergs ]` action `{ height 200 speed 20 }`
field "icebergs_源码" web.code.inner args `[ usr/icebergs misc/mp/mp.go 1 ]`
field "icebergs" web.code.git.trend args `icebergs`
field "icebergs" web.code.inner args `usr/icebergs/ misc/mp/mp.go 1`

View File

@ -7,55 +7,70 @@ import (
"github.com/shylinux/icebergs/base/mdb"
"github.com/shylinux/icebergs/base/web"
"github.com/shylinux/icebergs/core/chat"
"github.com/shylinux/icebergs/core/wiki"
"github.com/shylinux/toolkits"
"crypto/sha1"
"encoding/hex"
"encoding/xml"
"sort"
"fmt"
"strings"
"time"
)
func parse(m *ice.Message) {
func _wx_sign(m *ice.Message, nonce, stamp string) string {
b := sha1.Sum([]byte(strings.Join(kit.Sort([]string{
fmt.Sprintf("noncestr=%s", nonce),
fmt.Sprintf("timestamp=%s", stamp),
fmt.Sprintf("url=%s", m.Option(ice.MSG_USERWEB)),
fmt.Sprintf("jsapi_ticket=%s", m.Cmdx(ACCESS, TICKET)),
}), "&")))
return hex.EncodeToString(b[:])
}
func _wx_config(m *ice.Message, nonce string) {
m.Option(APPID, m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID)))
m.Option("signature", _wx_sign(m, m.Option("noncestr", nonce), m.Option("timestamp", kit.Format(time.Now().Unix()))))
}
func _wx_parse(m *ice.Message) {
data := struct {
ToUserName string
FromUserName string
CreateTime int
ToUserName string
CreateTime int64
MsgId int64
MsgType string
Content string
Event string
}{}
xml.NewDecoder(m.R.Body).Decode(&data)
m.Option("ToUserName", data.ToUserName)
m.Debug("data: %#v", data)
m.Option("FromUserName", data.FromUserName)
m.Option("ToUserName", data.ToUserName)
m.Option("CreateTime", data.CreateTime)
m.Option("MsgId", data.MsgId)
m.Option("MsgType", data.MsgType)
m.Option("Content", data.Content)
m.Option("Event", data.Event)
}
func reply(m *ice.Message) {
m.Render(m.Conf("login", "meta.template.text"))
func _wx_reply(m *ice.Message, tmpl string) {
m.Render(m.Conf(LOGIN, kit.Keys("meta.template", tmpl)))
}
func action(m *ice.Message) {
func _wx_action(m *ice.Message) {
m.Option(ice.MSG_OUTPUT, ice.RENDER_RESULT)
m.Echo(`<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<ToUserName><![CDATA[%s]]></ToUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
`, m.Option("FromUserName"), m.Option("ToUserName"), m.Option("CreateTime"))
<MsgType><![CDATA[%s]]></MsgType>
`, 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(`<ArticleCount>%d</ArticleCount>
`, count)
m.Table(func(index int, value map[string]string, head []string) { count++ })
m.Echo(`<ArticleCount>%d</ArticleCount>`, count)
m.Echo(`<Articles>
`)
m.Echo(`<Articles>`)
m.Table(func(index int, value map[string]string, head []string) {
m.Echo(`<item>
<Title><![CDATA[%s]]></Title>
@ -63,99 +78,148 @@ func action(m *ice.Message) {
<PicUrl><![CDATA[%s]]></PicUrl>
<Url><![CDATA[%s]]></Url>
</item>
`, value["name"], value["text"], value["view"], value["link"])
`, value[wiki.TITLE], value[wiki.SPARK], value[wiki.IMAGE], value[wiki.REFER])
})
m.Echo(`</Articles>`)
m.Echo(`</xml>`)
m.Echo(`</Articles>
`)
m.Echo(`</xml>
`)
m.Info("what %v", m.Result())
m.Debug("echo: %v", m.Result())
}
const (
LOGIN = "login"
APPID = "appid"
APPMM = "appmm"
TOKEN = "token"
TICKET = "ticket"
ACCESS = "access"
CONFIG = "config"
WEIXIN = "weixin"
)
const WX = "wx"
var Index = &ice.Context{Name: "wx", Help: "公众号",
Caches: map[string]*ice.Cache{},
var Index = &ice.Context{Name: WX, Help: "公众号",
Configs: map[string]*ice.Config{
"login": {Name: "login", Help: "认证", Value: kit.Data(
"auth", "/sns/jscode2session?grant_type=authorization_code",
"weixin", "https://api.weixin.qq.com",
"appid", "", "appmm", "", "token", "",
"template", kit.Dict(
"text", `<xml>
<ToUserName><![CDATA[{{.Option "FromUserName"}}]]></ToUserName>
LOGIN: {Name: LOGIN, Help: "认证", Value: kit.Data(
WEIXIN, "https://api.weixin.qq.com", APPID, "", APPMM, "", TOKEN, "",
"template", kit.Dict("text", `<xml>
<FromUserName><![CDATA[{{.Option "ToUserName"}}]]></FromUserName>
<ToUserName><![CDATA[{{.Option "FromUserName"}}]]></ToUserName>
<CreateTime>{{.Option "CreateTime"}}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{{.Append "reply"}}]]></Content>
</xml>`,
),
<Content><![CDATA[{{.Result}}]]></Content>
</xml>`),
"menu", []interface{}{
kit.Dict("name", "home", "text", "主页", "view", "https://shylinux.com/static/volcanos/favicon.ico", "link", "https://shylinux.com"),
kit.Dict("name", "sub", "text", "工具", "view", "https://shylinux.com/static/volcanos/favicon.ico", "link", "https://shylinux.com"),
kit.Dict(wiki.TITLE, "主页", wiki.SPARK, "点击进入", wiki.IMAGE, "https://shylinux.com/static/volcanos/favicon.ico", wiki.REFER, "https://shylinux.com"),
kit.Dict(wiki.TITLE, "产品", wiki.SPARK, "工具", wiki.IMAGE, "https://shylinux.com/static/volcanos/favicon.ico", wiki.REFER, "https://shylinux.com?river=product"),
kit.Dict(wiki.TITLE, "研发", wiki.SPARK, "工具", wiki.IMAGE, "https://shylinux.com/static/volcanos/favicon.ico", wiki.REFER, "https://shylinux.com?river=project"),
},
)},
},
Commands: map[string]*ice.Command{
ice.CTX_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Load("login")
m.Cmd(web.SPIDE, mdb.CREATE, "weixin", m.Conf("login", "meta.weixin"))
m.Load()
m.Cmd(web.SPIDE, mdb.CREATE, WEIXIN, m.Conf(LOGIN, kit.Keys(kit.MDB_META, WEIXIN)))
}},
ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Save("login")
m.Save()
}},
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) {
m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID), m.Option(APPID))
m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPMM), m.Option(APPMM))
m.Conf(LOGIN, kit.Keys(kit.MDB_META, TOKEN), m.Option(TOKEN))
}},
TOKEN: {Name: "token", Help: "令牌", Hand: func(m *ice.Message, arg ...string) {
if now := time.Now().Unix(); m.Conf(LOGIN, "meta.access.token") == "" || now > kit.Int64(m.Conf(LOGIN, "meta.access.expire")) {
msg := m.Cmd(web.SPIDE, "weixin", web.SPIDE_GET, "/cgi-bin/token?grant_type=client_credential",
APPID, m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID)), "secret", m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPMM)))
m.Conf(LOGIN, "meta.access.token", msg.Append("access_token"))
m.Conf(LOGIN, "meta.access.expire", now+kit.Int64(msg.Append("expires_in")))
}
m.Echo(m.Conf(LOGIN, "meta.access.token"))
}},
TICKET: {Name: "ticket", Help: "票据", Hand: func(m *ice.Message, arg ...string) {
if now := time.Now().Unix(); m.Conf(LOGIN, "meta.access.ticket") == "" || now > kit.Int64(m.Conf(LOGIN, "meta.access.expires")) {
msg := m.Cmd(web.SPIDE, "weixin", web.SPIDE_GET, "/cgi-bin/ticket/getticket?type=jsapi",
"access_token", m.Cmdx(ACCESS, TOKEN))
m.Conf(LOGIN, "meta.access.ticket", msg.Append(TICKET))
m.Conf(LOGIN, "meta.access.expires", now+kit.Int64(msg.Append("expires_in")))
}
m.Echo(m.Conf(LOGIN, "meta.access.ticket"))
}},
CONFIG: {Name: "config", Help: "配置", Hand: func(m *ice.Message, arg ...string) {
_wx_config(m, "some")
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
m.Push(APPID, m.Conf(LOGIN, kit.Keys(kit.MDB_META, APPID)))
}},
"menu": {Name: "menu name auto", Help: "菜单", Action: map[string]*ice.Action{
mdb.CREATE: {Name: "create", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
share := m.Cmdx(web.SHARE, mdb.CREATE, kit.MDB_TYPE, "login")
kit.Fetch(m.Confv(LOGIN, "meta.menu"), func(index int, value map[string]interface{}) {
m.Push("", value, kit.Split("title,spark,image"))
m.Push(wiki.REFER, kit.MergeURL(kit.Format(value[wiki.REFER]), web.SHARE, share))
})
}},
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
kit.Fetch(m.Confv(LOGIN, "meta.menu"), func(index int, value map[string]interface{}) {
m.Push("", value, kit.Split("title,spark,image,refer"))
})
}},
"/login/": {Name: "/login/", Help: "认证", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
check := []string{m.Conf("login", "meta.token"), m.Option("timestamp"), m.Option("nonce")}
sort.Strings(check)
if b := sha1.Sum([]byte(strings.Join(check, ""))); m.Warn(m.Option("signature") != hex.EncodeToString(b[:]), "signature error") {
// 验证失败
return
check := kit.Sort([]string{m.Conf(LOGIN, "meta.token"), m.Option("timestamp"), m.Option("nonce")})
if b := sha1.Sum([]byte(strings.Join(check, ""))); m.Warn(m.Option("signature") != hex.EncodeToString(b[:]), ice.ErrNotAuth) {
return // 验证失败
}
if m.Option("echostr") != "" { // 绑定验证
m.Render(m.Option("echostr"))
return
if m.Option("echostr") != "" {
m.Render(ice.RENDER_RESULT, m.Option("echostr"))
return // 绑定验证
}
// 解析数据
parse(m)
_wx_parse(m)
// 用户登录
m.Option(ice.MSG_USERZONE, WX)
m.Option(ice.MSG_SESSID, aaa.SessCreate(m, m.Append("FromUserName"), aaa.UserRole(m, m.Append("FromUserName"))))
aaa.UserLogin(m, m.Append("FromUserName"), "")
switch m.Option("MsgType") {
case "event":
switch m.Option("Event") {
case "subscribe":
// 应用列表
_wx_action(m.Cmdy("menu", mdb.CREATE))
case "unsubscribe":
}
case "text":
if cmds := kit.Split(m.Option("Content")); !m.Right(cmds) {
action(m.Cmdy("menu"))
if cmds := kit.Split(m.Option("Content")); m.Warn(!m.Right(cmds), ice.ErrNotAuth) {
_wx_action(m.Cmdy("menu", mdb.CREATE))
break // 没有权限
} else {
switch cmds[0] {
case "menu":
action(m.Cmdy("menu"))
// 应用列表
_wx_action(m.Cmdy("menu", mdb.CREATE))
default:
// 执行命令
msg := m.Cmd(cmds)
if m.Hand = false; !msg.Hand {
msg = m.Cmd(cli.SYSTEM, cmds)
}
if msg.Result() == "" {
msg.Table()
if m.Cmdy(cmds); len(m.Appendv(ice.MSG_APPEND)) == 0 && len(m.Resultv()) == 0 {
m.Cmdy(cli.SYSTEM, cmds)
} else if len(m.Resultv()) == 0 {
m.Table()
}
// 返回结果
reply(m.Push("reply", msg.Result()))
_wx_reply(m, "text")
}
}
}
}},
"menu": {Name: "menu", Help: "菜单", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
kit.Fetch(m.Confv("login", "meta.menu"), func(index int, value map[string]interface{}) {
m.Push("", value, []string{"name", "text", "view"})
m.Push("link", kit.MergeURL(kit.Format(value["link"]), ice.MSG_SESSID, m.Option(ice.MSG_SESSID)))
})
}},
},
}

View File

@ -6,24 +6,22 @@ refer "" `
源码 https://github.com/shylinux/icebergs/blob/master/misc/wx/wx.go
`
image qrcode `https://weixin.qq.com`
spark
chapter "应用"
field location web.chat.location
field paste web.chat.paste
field scan web.chat.scan
field feel web.chat.feel
field "阅读器" web.code.inner args `[ src/ main.shy ]`
field "编辑器" web.code.vimer args `[ src/ main.go ]`
field "资料库" web.chat.meet.miss args `[ ]`
field "粘贴板" web.chat.paste.paste args `[ ]`
field "相册集" web.wiki.feel args `[ ]`
field "思维导图" web.wiki.draw.draw args `[ src/ main.svg ]`
field "任务" web.team.task args `[ ]`
field "任务" web.team.plan args `[ ]`
field "资产" web.mall.asset args `[ ]`
chapter "权限"
field access web.chat.wx.access
field menu web.chat.wx.menu
field share web.share
field user aaa.user
field sess aaa.sess
chapter "项目"
section "icebergs"
field "icebergs 统计" web.code.git.trend args `[ icebergs ]` action `{ height 200 speed 20 }`
field "icebergs 源码" web.code.inner args `[ usr/icebergs misc/wx/wx.go 1 ]`
field "icebergs" web.code.git.trend args `icebergs`
field "icebergs" web.code.inner args `usr/icebergs/ misc/wx/wx.go 1`