diff --git a/README.md b/README.md index 4f9c2d3d..3dd5b388 100644 --- a/README.md +++ b/README.md @@ -67,16 +67,16 @@ zerobot [-h] [-t token] [-u url] [-n nickname] [-p prefix] [-d|w] [-g 监听地 - [x] /服务详情 - [x] @Bot 插件冲突检测 (会在本群发送一条消息并在约 1s 后撤回以检测其它同类 bot 中已启用的插件并禁用) - **定时指令触发器** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/job"` - - [x] 记录以"完全匹配关键词"触发的指令 - - [x] 取消以"完全匹配关键词"触发的指令 + - [x] 记录以"完全匹配关键词"触发的(代表我执行的)指令 + - [x] 取消以"完全匹配关键词"触发的(代表我执行的)指令 - [x] 记录在"cron"触发的指令 - [x] 取消在"cron"触发的指令 - [x] 查看所有触发指令 - [x] 查看在"cron"触发的指令 - - [x] 查看以"完全匹配关键词"触发的指令 + - [x] 查看以"完全匹配关键词"触发的(代表我执行的)指令 - [x] 注入指令结果:任意指令 - [x] 执行指令:任意指令 - - 注:任意指令可以使用形如`?::参数1提示语::1!`,`?::参数2提示语::2!`,`?::?可选参数3提示语,不回答将填入空值::2!`的未定参数,在注入时一一匹配 + - 注:任意指令可以使用形如`?::参数1提示语::1!`,`?::参数2提示语::2!`,`?::?可选参数3提示语,不回答将填入空值::3!`,`!::从url获取的参数::4!`,`!::?可选的从url获取的参数,出错将填入空值::5!`的未定参数,在注入时一一匹配 - 一些示例 > 每日9:30推送摸鱼人日历 ``` @@ -107,8 +107,17 @@ if random() > 0.5: print('您最终会选?::请输入您的选择1::1!') else: print('您最终会选?::请输入您的选择2::2!') 简易的选择困难症助手 ``` -![register](https://user-images.githubusercontent.com/41315874/157266929-1c7ab727-0ae5-445a-bf69-fa895045fd1d.png) -![run](https://user-images.githubusercontent.com/41315874/157266938-06c64b81-0734-47b0-b558-ed51c81b5f3f.png) +> 自行编写随机b站404页趣图插件 +```python +记录以"随机b站404页趣图"触发的代表我执行的指令 +注入指令结果:>runcoderaw py +import json +j = json.loads(r'''!::https://api.iyk0.com/bili_chart::1!''') +print("run[CQ:image,file="+j["img"]+"]") +随机b站404页趣图 +``` +![随机b站404页趣图](https://user-images.githubusercontent.com/41315874/157371451-c09ad3bb-c61a-4a42-9c47-fab3305bc0f8.png) + - **聊天** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chat"` - [x] [BOT名字] - [x] [戳一戳BOT] diff --git a/plugin/job/main.go b/plugin/job/main.go index 0a905139..10a13485 100644 --- a/plugin/job/main.go +++ b/plugin/job/main.go @@ -15,6 +15,7 @@ import ( "github.com/FloatTech/zbputils/ctxext" "github.com/FloatTech/zbputils/process" "github.com/FloatTech/zbputils/vevent" + "github.com/FloatTech/zbputils/web" "github.com/fumiama/cron" "github.com/sirupsen/logrus" "github.com/tidwall/gjson" @@ -65,6 +66,12 @@ func init() { matchers[c.ID] = getmatcher(m) return nil } + if strings.HasPrefix(c.Cron, "sm:") { + m := en.OnFullMatch(c.Cron[3:] /* skip fm: */).SetBlock(true) + m.Handle(superuserhandler(c)) + matchers[c.ID] = getmatcher(m) + return nil + } eid, err := process.CronTab.AddFunc(c.Cron, inject(id, []byte(c.Cmd))) if err != nil { return err @@ -107,6 +114,22 @@ func init() { } ctx.SendChain(message.Text("成功!")) }) + en.OnRegex(`^记录以"(.*)"触发的代表我执行的指令$`, zero.SuperUserPermission, islonotnil, isfirstregmatchnotnil, logevent).SetBlock(true).Handle(func(ctx *zero.Ctx) { + cron := "sm:" + ctx.State["regex_matched"].([]string)[1] + command := ctx.State["job_raw_event"].(string) + logrus.Debugln("[job] get cmd:", command) + c := &cmd{ + ID: idof(cron, command), + Cron: cron, + Cmd: command, + } + err := registercmd(ctx.Event.SelfID, c) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + ctx.SendChain(message.Text("成功!")) + }) en.OnRegex(`^取消在"(.*)"触发的指令$`, ctxext.UserOrGrpAdmin, islonotnil, isfirstregmatchnotnil).SetBlock(true).Handle(func(ctx *zero.Ctx) { cron := ctx.State["regex_matched"].([]string)[1] err := rmcmd(ctx.Event.SelfID, ctx.Event.UserID, cron) @@ -116,8 +139,15 @@ func init() { } ctx.SendChain(message.Text("成功!")) }) - en.OnRegex(`^取消以"(.*)"触发的指令$`, zero.SuperUserPermission, islonotnil, isfirstregmatchnotnil).SetBlock(true).Handle(func(ctx *zero.Ctx) { - cron := "fm:" + ctx.State["regex_matched"].([]string)[1] + en.OnRegex(`^取消以"(.*)"触发的(代表我执行的)?指令$`, zero.SuperUserPermission, islonotnil, isfirstregmatchnotnil).SetBlock(true).Handle(func(ctx *zero.Ctx) { + issu := ctx.State["regex_matched"].([]string)[2] != "" + cron := "" + if issu { + cron = "sm:" + } else { + cron = "fm:" + } + cron += ctx.State["regex_matched"].([]string)[1] err := delcmd(ctx.Event.SelfID, cron) if err != nil { ctx.SendChain(message.Text("ERROR:", err)) @@ -168,10 +198,17 @@ func init() { } ctx.SendChain(message.Text(lst)) }) - en.OnRegex(`^查看以"(.*)"触发的指令$`, zero.SuperUserPermission, islonotnil, isfirstregmatchnotnil).SetBlock(true).Handle(func(ctx *zero.Ctx) { + en.OnRegex(`^查看以"(.*)"触发的(代表我执行的)?指令$`, zero.SuperUserPermission, islonotnil, isfirstregmatchnotnil).SetBlock(true).Handle(func(ctx *zero.Ctx) { c := &cmd{} ids := strconv.FormatInt(ctx.Event.SelfID, 36) - cron := "fm:" + ctx.State["regex_matched"].([]string)[1] + issu := ctx.State["regex_matched"].([]string)[2] != "" + cron := "" + if issu { + cron = "sm:" + } else { + cron = "fm:" + } + cron += ctx.State["regex_matched"].([]string)[1] mu.Lock() defer mu.Unlock() n, err := db.Count(ids) @@ -193,7 +230,9 @@ func init() { en.OnPrefix("执行指令:", ctxext.UserOrGrpAdmin, islonotnil, func(ctx *zero.Ctx) bool { return ctx.State["args"].(string) != "" }, parseArgs).SetBlock(true).Handle(func(ctx *zero.Ctx) { - inject(ctx.Event.SelfID, binary.StringToBytes(strings.ReplaceAll(ctx.Event.RawEvent.Raw, "执行指令:", "")))() + ev := strings.ReplaceAll(ctx.Event.RawEvent.Raw, "执行指令:", "") + logrus.Debugln("[job] inject:", ev) + inject(ctx.Event.SelfID, binary.StringToBytes(ev))() }) en.OnPrefix("注入指令结果:", ctxext.UserOrGrpAdmin, islonotnil, func(ctx *zero.Ctx) bool { return ctx.State["args"].(string) != "" @@ -263,8 +302,12 @@ func addcmd(bot int64, c *cmd) error { func registercmd(bot int64, c *cmd) error { mu.Lock() defer mu.Unlock() - m := en.OnFullMatch(c.Cron[3:] /* skip fm: */).SetBlock(true) - m.Handle(generalhandler(c)) + m := en.OnFullMatch(c.Cron[3:] /* skip fm: or sm: */).SetBlock(true) + if strings.HasPrefix(c.Cron, "sm:") { + m.Handle(superuserhandler(c)) + } else { + m.Handle(generalhandler(c)) + } matchers[c.ID] = getmatcher(m) return db.Insert(strconv.FormatInt(bot, 36), c) } @@ -288,6 +331,32 @@ func generalhandler(c *cmd) zero.Handler { } } +func superuserhandler(c *cmd) zero.Handler { + e := &zero.Event{Sender: new(zero.User)} + err := json.Unmarshal(binary.StringToBytes(c.Cmd), e) + return func(ctx *zero.Ctx) { + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + ctx.Event.UserID = e.UserID + ctx.Event.RawMessage = e.RawMessage + ctx.Event.Sender = e.Sender + ctx.Event.NativeMessage = e.NativeMessage + vev, cl := binary.OpenWriterF(func(w *binary.Writer) { + err = json.NewEncoder(w).Encode(ctx.Event) + }) + if err != nil { + cl() + ctx.SendChain(message.Text("ERROR:", err)) + return + } + logrus.Debugln("[job] inject:", binary.BytesToString(vev)) + inject(ctx.Event.SelfID, vev)() + cl() + } +} + func rmcmd(bot, caller int64, cron string) error { c := &cmd{} mu.Lock() @@ -345,7 +414,8 @@ func delcmd(bot int64, cron string) error { } func parseArgs(ctx *zero.Ctx) bool { - if !strings.Contains(ctx.State["args"].(string), "?::") { + cmds := ctx.State["args"].(string) + if !strings.Contains(cmds, "?::") && !strings.Contains(cmds, "!::") { return true } args := make(map[int]string) @@ -394,6 +464,66 @@ func parseArgs(ctx *zero.Ctx) bool { } ctx.Event.RawEvent.Raw = ctx.Event.RawEvent.Raw[:start] + arr + ctx.Event.RawEvent.Raw[numend+1:] } + args = make(map[int]string) + for strings.Contains(ctx.Event.RawEvent.Raw, "!::") { + start := strings.Index(ctx.Event.RawEvent.Raw, "!::") + msgend := strings.Index(ctx.Event.RawEvent.Raw[start+3:], "::") + if msgend < 0 { + ctx.SendChain(message.Text("ERROR:找不到结束的::")) + return false + } + msgend += start + 3 + numend := strings.Index(ctx.Event.RawEvent.Raw[msgend+2:], "!") + if numend <= 0 { + ctx.SendChain(message.Text("ERROR:找不到结束的!")) + return false + } + numend += msgend + 2 + logrus.Debugln("[job]", start, msgend, numend) + u := ctx.Event.RawEvent.Raw[start+3 : msgend] + if u == "" { + return false + } + arg, err := strconv.Atoi(ctx.Event.RawEvent.Raw[msgend+2 : numend]) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return false + } + arr, ok := args[arg] + if !ok { + isnilable := u[0] == '?' + if isnilable { + u = u[1:] + if u == "" { + return false + } + } + b, err := web.GetData(u) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + if !isnilable { + return false + } + } + if len(b) > 0 { + type fakejson struct { + Arg string `json:"arg"` + } + f := fakejson{Arg: binary.BytesToString(b)} + w := binary.SelectWriter() + defer binary.PutWriter(w) + json.NewEncoder(w).Encode(&f) + arr = w.String()[8 : w.Len()-3] + args[arg] = arr + } + } + w := binary.SelectWriter() + w.WriteString(ctx.Event.RawEvent.Raw[:start]) + w.WriteString(arr) + w.WriteString(ctx.Event.RawEvent.Raw[numend+1:]) + ctx.Event.RawEvent.Raw = string(w.Bytes()) + binary.PutWriter(w) + } return true } diff --git a/plugin/manager/manager.go b/plugin/manager/manager.go index 21d38fb7..ce913587 100644 --- a/plugin/manager/manager.go +++ b/plugin/manager/manager.go @@ -512,11 +512,8 @@ func init() { // 插件主体 // 运行 CQ 码 engine.OnRegex(`^run(.*)$`, zero.SuperUserPermission).SetBlock(true). Handle(func(ctx *zero.Ctx) { - var cmd = ctx.State["regex_matched"].([]string)[1] - cmd = strings.ReplaceAll(cmd, "[", "[") - cmd = strings.ReplaceAll(cmd, "]", "]") // 可注入,权限为主人 - ctx.Send(cmd) + ctx.Send(message.UnescapeCQCodeText(ctx.State["regex_matched"].([]string)[1])) }) // 根据 gist 自动同意加群 // 加群请在github新建一个gist,其文件名为本群群号的字符串的md5(小写),内容为一行,是当前unix时间戳(10分钟内有效)。 diff --git a/plugin/runcode/code_runner.go b/plugin/runcode/code_runner.go index 047e3d16..3507b5d6 100644 --- a/plugin/runcode/code_runner.go +++ b/plugin/runcode/code_runner.go @@ -117,7 +117,7 @@ func init() { ) } else { // 执行运行 - block := message.UnescapeCQCodeText(ctx.State["regex_matched"].([]string)[3]) + block := message.UnescapeCQText(ctx.State["regex_matched"].([]string)[3]) switch block { case "help": ctx.SendChain(