diff --git a/README.md b/README.md index 8041d77df2..a630a5cdb1 100644 --- a/README.md +++ b/README.md @@ -655,6 +655,8 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 齁语解密 [密文] 或 h解密 [密文] - [x] fumo加密 [文本] - [x] fumo解密 [文本] + - [x] qq加密 [文本] + - [x] qq解密 [密文]
diff --git a/plugin/crypter/handlers.go b/plugin/crypter/handlers.go index 45a51b805f..6a96410e70 100644 --- a/plugin/crypter/handlers.go +++ b/plugin/crypter/handlers.go @@ -2,18 +2,104 @@ package crypter import ( + "fmt" + "regexp" + "strconv" + "strings" + "github.com/FloatTech/AnimeAPI/airecord" zero "github.com/wdvxdr1123/ZeroBot" "github.com/wdvxdr1123/ZeroBot/message" ) +var faceTagRe = regexp.MustCompile(`\{\{face:(\d+)\}\}`) + +func parseID(v interface{}) int64 { + n, _ := strconv.ParseInt(fmt.Sprint(v), 10, 64) + return n +} + +func serializeMsg(segs message.Message) string { + var sb strings.Builder + for _, seg := range segs { + switch seg.Type { + case "text": + sb.WriteString(seg.Data["text"]) + case "face": + fmt.Fprintf(&sb, "{{face:%v}}", seg.Data["id"]) + } + } + return sb.String() +} + +func deserializeMsg(s string) message.Message { + var msg message.Message + parts := faceTagRe.Split(s, -1) + matches := faceTagRe.FindAllStringSubmatch(s, -1) + for i, part := range parts { + if part != "" { + msg = append(msg, message.Text(part)) + } + if i < len(matches) { + id, _ := strconv.Atoi(matches[i][1]) + msg = append(msg, message.Face(id)) + } + } + return msg +} + +func getInput(ctx *zero.Ctx, cmds ...string) string { + full := serializeMsg(ctx.Event.Message) + for _, cmd := range cmds { + if idx := strings.Index(full, cmd); idx >= 0 { + return strings.TrimSpace(full[idx+len(cmd):]) + } + } + return "" +} + +func getReplyContent(ctx *zero.Ctx) string { + for _, seg := range ctx.Event.Message { + if seg.Type == "reply" { + if msgID := parseID(seg.Data["id"]); msgID > 0 { + if msg := ctx.GetMessage(msgID); msg.Elements != nil { + return serializeMsg(msg.Elements) + } + } + } + } + return "" +} + +func getReplyFaceIDs(ctx *zero.Ctx) []int { + for _, seg := range ctx.Event.Message { + if seg.Type == "reply" { + if msgID := parseID(seg.Data["id"]); msgID > 0 { + return extractFaceIDs(ctx.GetMessage(msgID).Elements) + } + } + } + return nil +} + +func extractFaceIDs(segs message.Message) []int { + var ids []int + for _, seg := range segs { + if seg.Type == "face" { + if id := int(parseID(seg.Data["id"])); id > 0 { + ids = append(ids, id) + } + } + } + return ids +} + // hou func houEncryptHandler(ctx *zero.Ctx) { - text := ctx.State["regex_matched"].([]string)[1] + text := getInput(ctx, "h加密", "齁语加密") result := encodeHou(text) recCfg := airecord.GetConfig() - record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result) - if record != "" { + if record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result); record != "" { ctx.SendChain(message.Record(record)) } else { ctx.SendChain(message.Text(result)) @@ -21,20 +107,52 @@ func houEncryptHandler(ctx *zero.Ctx) { } func houDecryptHandler(ctx *zero.Ctx) { - text := ctx.State["regex_matched"].([]string)[1] - result := decodeHou(text) - ctx.SendChain(message.Text(result)) + text := getInput(ctx, "h解密", "齁语解密") + if text == "" { + text = getReplyContent(ctx) + } + if text == "" { + ctx.SendChain(message.Text("请输入密文或回复加密消息")) + return + } + ctx.SendChain(deserializeMsg(decodeHou(text))...) } // fumo func fumoEncryptHandler(ctx *zero.Ctx) { - text := ctx.State["regex_matched"].([]string)[1] - result := encryptFumo(text) - ctx.SendChain(message.Text(result)) + ctx.SendChain(message.Text(encryptFumo(getInput(ctx, "fumo加密")))) } func fumoDecryptHandler(ctx *zero.Ctx) { - text := ctx.State["regex_matched"].([]string)[1] - result := decryptFumo(text) - ctx.SendChain(message.Text(result)) + text := getInput(ctx, "fumo解密") + if text == "" { + text = getReplyContent(ctx) + } + if text == "" { + ctx.SendChain(message.Text("请输入密文或回复加密消息")) + return + } + ctx.SendChain(deserializeMsg(decryptFumo(text))...) +} + +// qq表情 +func qqEmojiEncryptHandler(ctx *zero.Ctx) { + text := getInput(ctx, "qq加密") + if text == "" { + ctx.SendChain(message.Text("请输入要加密的文本")) + return + } + ctx.SendChain(encodeQQEmoji(text)...) +} + +func qqEmojiDecryptHandler(ctx *zero.Ctx) { + faceIDs := extractFaceIDs(ctx.Event.Message) + if len(faceIDs) == 0 { + faceIDs = getReplyFaceIDs(ctx) + } + if len(faceIDs) == 0 { + ctx.SendChain(message.Text("请回复QQ表情加密消息进行解密")) + return + } + ctx.SendChain(deserializeMsg(decodeQQEmoji(faceIDs))...) } diff --git a/plugin/crypter/main.go b/plugin/crypter/main.go index caf38668af..c1c539bac8 100644 --- a/plugin/crypter/main.go +++ b/plugin/crypter/main.go @@ -17,15 +17,25 @@ func init() { "- 齁语解密 [密文] 或 h解密 [密文]\n\n" + "- Fumo语加解密:\n" + "- fumo加密 [文本]\n" + - "- fumo解密 [密文]\n\n", + "- fumo解密 [密文]\n\n" + + "- QQ表情加解密:\n" + + "- qq加密 [文本]\n" + + "- qq解密 [密文]\n\n" + + "注意:QQ表情解密建议使用回复,尽量不要复制粘贴\n\n", PublicDataFolder: "Crypter", }) + re := `(?:\[CQ:reply,id=-?\d+\])?` + // hou - engine.OnRegex(`^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler) - engine.OnRegex(`^(?:齁语解密|h解密)\s*(.+)$`).SetBlock(true).Handle(houDecryptHandler) + engine.OnRegex(re + `^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler) + engine.OnRegex(re + `(?:齁语解密|h解密)\s*(.*)$`).SetBlock(true).Handle(houDecryptHandler) // Fumo - engine.OnRegex(`^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler) - engine.OnRegex(`^fumo解密\s*(.+)$`).SetBlock(true).Handle(fumoDecryptHandler) + engine.OnRegex(re + `^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler) + engine.OnRegex(re + `fumo解密\s*(.*)$`).SetBlock(true).Handle(fumoDecryptHandler) + + // QQ表情 + engine.OnRegex(re + `^qq加密\s*(.+)$`).SetBlock(true).Handle(qqEmojiEncryptHandler) + engine.OnRegex(re + `qq解密`).SetBlock(true).Handle(qqEmojiDecryptHandler) } diff --git a/plugin/crypter/qqemoji.go b/plugin/crypter/qqemoji.go new file mode 100644 index 0000000000..7aebd02db7 --- /dev/null +++ b/plugin/crypter/qqemoji.go @@ -0,0 +1,66 @@ +// Package crypter QQ表情加解密 +package crypter + +import ( + "fmt" + "strings" + "unicode/utf8" + + "github.com/wdvxdr1123/ZeroBot/message" +) + +const ( + emojiZeroID = 297 + emojiOneID = 424 +) + +func encodeQQEmoji(text string) message.Message { + if text == "" { + return message.Message{message.Text("请输入要加密的文本")} + } + + var bin strings.Builder + for _, b := range []byte(text) { + fmt.Fprintf(&bin, "%08b", b) + } + + s := bin.String() + msg := make(message.Message, 0, len(s)) + for _, bit := range s { + if bit == '0' { + msg = append(msg, message.Face(emojiZeroID)) + } else { + msg = append(msg, message.Face(emojiOneID)) + } + } + return msg +} + +func decodeQQEmoji(faceIDs []int) string { + var bin strings.Builder + for _, id := range faceIDs { + if id == emojiZeroID { + bin.WriteByte('0') + } else if id == emojiOneID { + bin.WriteByte('1') + } + } + binary := bin.String() + if len(binary) == 0 || len(binary)%8 != 0 { + return "QQ表情密文格式错误" + } + + data := make([]byte, len(binary)/8) + for i := range data { + for j := 0; j < 8; j++ { + if binary[i*8+j] == '1' { + data[i] |= 1 << (7 - j) + } + } + } + + if !utf8.Valid(data) { + return "QQ表情解密失败:结果不是有效文本" + } + return string(data) +}