diff --git a/README.md b/README.md
index 8041d77df2..843969acbf 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解密 [密文]
@@ -768,7 +770,7 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 运势 | 抽签
- - [x] 设置底图[车万 DC4 爱因斯坦 星空列车 樱云之恋 富婆妹 李清歌 公主连结 原神 明日方舟 碧蓝航线 碧蓝幻想 战双 阴阳师 赛马娘 东方归言录 奇异恩典 夏日口袋 ASoul Hololive]
+ - [x] 设置底图[车万 车万2 DC4 爱因斯坦 星空列车 樱云之恋 富婆妹 李清歌 公主连结 原神 明日方舟 碧蓝航线 碧蓝幻想 战双 阴阳师 赛马娘 东方归言录 奇异恩典 夏日口袋 ASoul Hololive]
diff --git a/go.mod b/go.mod
index 4b2cf8ab3b..fdf90ee993 100644
--- a/go.mod
+++ b/go.mod
@@ -59,6 +59,7 @@ require (
github.com/PuerkitoBio/goquery v1.8.0 // indirect
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect
+ github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/antchfx/xpath v1.3.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -76,6 +77,7 @@ require (
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/compress v1.18.4 // indirect
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
diff --git a/go.sum b/go.sum
index 6ec7775d99..5923022227 100644
--- a/go.sum
+++ b/go.sum
@@ -31,6 +31,8 @@ github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5Ua
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
@@ -135,6 +137,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY=
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc=
+github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
+github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
diff --git a/plugin/crypter/handlers.go b/plugin/crypter/handlers.go
index 45a51b805f..ce0bd19e6b 100644
--- a/plugin/crypter/handlers.go
+++ b/plugin/crypter/handlers.go
@@ -2,18 +2,105 @@
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.Sprintf("%v", 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(fmt.Sprintf("%v", 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
+ last := 0
+ for _, loc := range faceTagRe.FindAllStringSubmatchIndex(s, -1) {
+ if loc[0] > last {
+ msg = append(msg, message.Text(s[last:loc[0]]))
+ }
+ id, _ := strconv.Atoi(s[loc[2]:loc[3]])
+ msg = append(msg, message.Face(id))
+ last = loc[1]
+ }
+ if last < len(s) {
+ msg = append(msg, message.Text(s[last:]))
+ }
+ 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 +108,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..f59b02c61a
--- /dev/null
+++ b/plugin/crypter/qqemoji.go
@@ -0,0 +1,131 @@
+// Package crypter QQ表情加解密
+package crypter
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+ "unicode/utf8"
+
+ "github.com/andybalholm/brotli"
+ "github.com/klauspost/compress/zstd"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+const (
+ emojiZeroID = 297
+ emojiOneID = 424
+)
+
+func encodeQQEmoji(text string) message.Message {
+ if text == "" {
+ return message.Message{message.Text("请输入要加密的文本")}
+ }
+
+ data := []byte(text)
+ best, header := data, "0"
+ if br := tryCompress(func(w io.Writer) io.WriteCloser { return brotli.NewWriterLevel(w, brotli.BestCompression) }, data); len(br) > 0 && len(br) < len(best) {
+ best, header = br, "10"
+ }
+ if zs := tryCompress(func(w io.Writer) io.WriteCloser {
+ enc, _ := zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
+ return enc
+ }, data); len(zs) > 0 && len(zs) < len(best) {
+ best, header = zs, "11"
+ }
+
+ var bin strings.Builder
+ bin.WriteString(header)
+ for _, b := range best {
+ 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) < 2 {
+ return "QQ表情密文格式错误"
+ }
+
+ var header int
+ switch {
+ case binary[:2] == "11":
+ header = 2
+ case binary[:2] == "10":
+ header = 2
+ case binary[0] == '0':
+ header = 1
+ default:
+ return "QQ表情密文格式错误"
+ }
+
+ dataBin := binary[header:]
+ if len(dataBin)%8 != 0 {
+ return fmt.Sprintf("QQ表情解密失败:数据长度不正确(%d位)", len(dataBin))
+ }
+
+ data := make([]byte, len(dataBin)/8)
+ for i := range data {
+ for j := 0; j < 8; j++ {
+ if dataBin[i*8+j] == '1' {
+ data[i] |= 1 << (7 - j)
+ }
+ }
+ }
+
+ var out []byte
+ var err error
+ switch binary[:header] {
+ case "0":
+ out = data
+ case "10":
+ r := brotli.NewReader(bytes.NewReader(data))
+ out, err = io.ReadAll(r)
+ case "11":
+ var dec *zstd.Decoder
+ dec, err = zstd.NewReader(bytes.NewReader(data))
+ if err == nil {
+ out, err = io.ReadAll(dec)
+ dec.Close()
+ }
+ }
+ if err != nil {
+ return fmt.Sprintf("QQ表情解压失败: %v", err)
+ }
+ if !utf8.Valid(out) {
+ return "QQ表情解密失败:结果不是有效文本"
+ }
+ return string(out)
+}
+
+func tryCompress(newWriter func(io.Writer) io.WriteCloser, data []byte) []byte {
+ var buf bytes.Buffer
+ w := newWriter(&buf)
+ if _, err := w.Write(data); err != nil {
+ return nil
+ }
+ if err := w.Close(); err != nil {
+ return nil
+ }
+ return buf.Bytes()
+}