Skip to content

Commit 07f411f

Browse files
committed
插件crypter添加qq表情加密并支持回应密文进行解密
1 parent 41292ba commit 07f411f

File tree

6 files changed

+286
-17
lines changed

6 files changed

+286
-17
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,8 @@ print("run[CQ:image,file="+j["img"]+"]")
655655
- [x] 齁语解密 [密文] 或 h解密 [密文]
656656
- [x] fumo加密 [文本]
657657
- [x] fumo解密 [文本]
658+
- [x] qq加密 [文本]
659+
- [x] qq解密 [密文]
658660

659661
</details>
660662
<details>

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ require (
5959
github.com/PuerkitoBio/goquery v1.8.0 // indirect
6060
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
6161
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect
62+
github.com/andybalholm/brotli v1.2.0 // indirect
6263
github.com/andybalholm/cascadia v1.3.1 // indirect
6364
github.com/antchfx/xpath v1.3.5 // indirect
6465
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -76,6 +77,7 @@ require (
7677
github.com/jfreymuth/vorbis v1.0.2 // indirect
7778
github.com/jinzhu/inflection v1.0.0 // indirect
7879
github.com/json-iterator/go v1.1.12 // indirect
80+
github.com/klauspost/compress v1.18.4 // indirect
7981
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
8082
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect
8183
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5Ua
3131
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
3232
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
3333
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
34+
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
35+
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
3436
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
3537
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
3638
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
135137
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
136138
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY=
137139
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc=
140+
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
141+
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
138142
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
139143
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
140144
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=

plugin/crypter/handlers.go

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,159 @@
22
package crypter
33

44
import (
5+
"fmt"
6+
"regexp"
7+
"strconv"
8+
"strings"
9+
510
"github.com/FloatTech/AnimeAPI/airecord"
611
zero "github.com/wdvxdr1123/ZeroBot"
712
"github.com/wdvxdr1123/ZeroBot/message"
813
)
914

15+
var faceTagRe = regexp.MustCompile(`\{\{face:(\d+)\}\}`)
16+
17+
func serializeMsg(segs message.Message) string {
18+
var sb strings.Builder
19+
for _, seg := range segs {
20+
switch seg.Type {
21+
case "text":
22+
sb.WriteString(fmt.Sprintf("%v", seg.Data["text"]))
23+
case "face":
24+
fmt.Fprintf(&sb, "{{face:%v}}", seg.Data["id"])
25+
}
26+
}
27+
return sb.String()
28+
}
29+
30+
func deserializeMsg(s string) message.Message {
31+
var msg message.Message
32+
last := 0
33+
for _, loc := range faceTagRe.FindAllStringSubmatchIndex(s, -1) {
34+
if loc[0] > last {
35+
msg = append(msg, message.Text(s[last:loc[0]]))
36+
}
37+
id, _ := strconv.Atoi(s[loc[2]:loc[3]])
38+
msg = append(msg, message.Face(id))
39+
last = loc[1]
40+
}
41+
if last < len(s) {
42+
msg = append(msg, message.Text(s[last:]))
43+
}
44+
return msg
45+
}
46+
47+
func getInput(ctx *zero.Ctx, cmds ...string) string {
48+
full := serializeMsg(ctx.Event.Message)
49+
for _, cmd := range cmds {
50+
if idx := strings.Index(full, cmd); idx >= 0 {
51+
return strings.TrimSpace(full[idx+len(cmd):])
52+
}
53+
}
54+
return ""
55+
}
56+
57+
func getReplyContent(ctx *zero.Ctx) string {
58+
for _, seg := range ctx.Event.Message {
59+
if seg.Type == "reply" {
60+
var msgID int64
61+
fmt.Sscanf(fmt.Sprintf("%v", seg.Data["id"]), "%d", &msgID)
62+
if msgID > 0 {
63+
if msg := ctx.GetMessage(msgID); msg.Elements != nil {
64+
return serializeMsg(msg.Elements)
65+
}
66+
}
67+
}
68+
}
69+
return ""
70+
}
71+
72+
func getReplyFaceIDs(ctx *zero.Ctx) []int {
73+
for _, seg := range ctx.Event.Message {
74+
if seg.Type == "reply" {
75+
var msgID int64
76+
fmt.Sscanf(fmt.Sprintf("%v", seg.Data["id"]), "%d", &msgID)
77+
if msgID > 0 {
78+
return extractFaceIDs(ctx.GetMessage(msgID).Elements)
79+
}
80+
}
81+
}
82+
return nil
83+
}
84+
85+
func extractFaceIDs(segs message.Message) []int {
86+
var ids []int
87+
for _, seg := range segs {
88+
if seg.Type == "face" {
89+
var id int
90+
fmt.Sscanf(fmt.Sprintf("%v", seg.Data["id"]), "%d", &id)
91+
if id > 0 {
92+
ids = append(ids, id)
93+
}
94+
}
95+
}
96+
return ids
97+
}
98+
1099
// hou
11100
func houEncryptHandler(ctx *zero.Ctx) {
12-
text := ctx.State["regex_matched"].([]string)[1]
101+
text := getInput(ctx, "h加密", "齁语加密")
13102
result := encodeHou(text)
14103
recCfg := airecord.GetConfig()
15-
record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result)
16-
if record != "" {
104+
if record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result); record != "" {
17105
ctx.SendChain(message.Record(record))
18106
} else {
19107
ctx.SendChain(message.Text(result))
20108
}
21109
}
22110

23111
func houDecryptHandler(ctx *zero.Ctx) {
24-
text := ctx.State["regex_matched"].([]string)[1]
25-
result := decodeHou(text)
26-
ctx.SendChain(message.Text(result))
112+
text := getInput(ctx, "h解密", "齁语解密")
113+
if text == "" {
114+
text = getReplyContent(ctx)
115+
}
116+
if text == "" {
117+
ctx.SendChain(message.Text("请输入密文或回复加密消息"))
118+
return
119+
}
120+
ctx.SendChain(deserializeMsg(decodeHou(text))...)
27121
}
28122

29123
// fumo
30124
func fumoEncryptHandler(ctx *zero.Ctx) {
31-
text := ctx.State["regex_matched"].([]string)[1]
32-
result := encryptFumo(text)
33-
ctx.SendChain(message.Text(result))
125+
ctx.SendChain(message.Text(encryptFumo(getInput(ctx, "fumo加密"))))
34126
}
35127

36128
func fumoDecryptHandler(ctx *zero.Ctx) {
37-
text := ctx.State["regex_matched"].([]string)[1]
38-
result := decryptFumo(text)
39-
ctx.SendChain(message.Text(result))
129+
text := getInput(ctx, "fumo解密")
130+
if text == "" {
131+
text = getReplyContent(ctx)
132+
}
133+
if text == "" {
134+
ctx.SendChain(message.Text("请输入密文或回复加密消息"))
135+
return
136+
}
137+
ctx.SendChain(deserializeMsg(decryptFumo(text))...)
138+
}
139+
140+
// qq表情
141+
func qqEmojiEncryptHandler(ctx *zero.Ctx) {
142+
text := getInput(ctx, "qq加密")
143+
if text == "" {
144+
ctx.SendChain(message.Text("请输入要加密的文本"))
145+
return
146+
}
147+
ctx.SendChain(encodeQQEmoji(text)...)
148+
}
149+
150+
func qqEmojiDecryptHandler(ctx *zero.Ctx) {
151+
faceIDs := extractFaceIDs(ctx.Event.Message)
152+
if len(faceIDs) == 0 {
153+
faceIDs = getReplyFaceIDs(ctx)
154+
}
155+
if len(faceIDs) == 0 {
156+
ctx.SendChain(message.Text("请回复QQ表情加密消息进行解密"))
157+
return
158+
}
159+
ctx.SendChain(deserializeMsg(decodeQQEmoji(faceIDs))...)
40160
}

plugin/crypter/main.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,25 @@ func init() {
1717
"- 齁语解密 [密文] 或 h解密 [密文]\n\n" +
1818
"- Fumo语加解密:\n" +
1919
"- fumo加密 [文本]\n" +
20-
"- fumo解密 [密文]\n\n",
20+
"- fumo解密 [密文]\n\n" +
21+
"- QQ表情加解密:\n" +
22+
"- qq加密 [文本]\n" +
23+
"- qq解密 [密文]\n\n" +
24+
"注意:QQ表情解密建议使用回复,尽量不要复制粘贴\n\n",
2125
PublicDataFolder: "Crypter",
2226
})
2327

28+
re := `(?:\[CQ:reply,id=-?\d+\])?`
29+
2430
// hou
25-
engine.OnRegex(`^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler)
26-
engine.OnRegex(`^(?:齁语解密|h解密)\s*(.+)$`).SetBlock(true).Handle(houDecryptHandler)
31+
engine.OnRegex(re + `^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler)
32+
engine.OnRegex(re + `(?:齁语解密|h解密)\s*(.*)$`).SetBlock(true).Handle(houDecryptHandler)
2733

2834
// Fumo
29-
engine.OnRegex(`^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler)
30-
engine.OnRegex(`^fumo解密\s*(.+)$`).SetBlock(true).Handle(fumoDecryptHandler)
35+
engine.OnRegex(re + `^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler)
36+
engine.OnRegex(re + `fumo解密\s*(.*)$`).SetBlock(true).Handle(fumoDecryptHandler)
37+
38+
// QQ表情
39+
engine.OnRegex(re + `^qq加密\s*(.+)$`).SetBlock(true).Handle(qqEmojiEncryptHandler)
40+
engine.OnRegex(re + `qq解密`).SetBlock(true).Handle(qqEmojiDecryptHandler)
3141
}

plugin/crypter/qqemoji.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Package crypter QQ表情加解密
2+
package crypter
3+
4+
import (
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"strings"
9+
"unicode/utf8"
10+
11+
"github.com/andybalholm/brotli"
12+
"github.com/klauspost/compress/zstd"
13+
"github.com/wdvxdr1123/ZeroBot/message"
14+
)
15+
16+
const (
17+
emojiZeroID = 297
18+
emojiOneID = 424
19+
)
20+
21+
func encodeQQEmoji(text string) message.Message {
22+
if text == "" {
23+
return message.Message{message.Text("请输入要加密的文本")}
24+
}
25+
26+
data := []byte(text)
27+
best, header := data, "0"
28+
if br := tryCompress(func(w io.Writer) io.WriteCloser { return brotli.NewWriterLevel(w, brotli.BestCompression) }, data); len(br) > 0 && len(br) < len(best) {
29+
best, header = br, "10"
30+
}
31+
if zs := tryCompress(func(w io.Writer) io.WriteCloser {
32+
enc, _ := zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
33+
return enc
34+
}, data); len(zs) > 0 && len(zs) < len(best) {
35+
best, header = zs, "11"
36+
}
37+
38+
var bin strings.Builder
39+
bin.WriteString(header)
40+
for _, b := range best {
41+
fmt.Fprintf(&bin, "%08b", b)
42+
}
43+
44+
s := bin.String()
45+
msg := make(message.Message, 0, len(s))
46+
for _, bit := range s {
47+
if bit == '0' {
48+
msg = append(msg, message.Face(emojiZeroID))
49+
} else {
50+
msg = append(msg, message.Face(emojiOneID))
51+
}
52+
}
53+
return msg
54+
}
55+
56+
func decodeQQEmoji(faceIDs []int) string {
57+
var bin strings.Builder
58+
for _, id := range faceIDs {
59+
if id == emojiZeroID {
60+
bin.WriteByte('0')
61+
} else if id == emojiOneID {
62+
bin.WriteByte('1')
63+
}
64+
}
65+
binary := bin.String()
66+
if len(binary) < 2 {
67+
return "QQ表情密文格式错误"
68+
}
69+
70+
var header int
71+
switch {
72+
case binary[:2] == "11":
73+
header = 2
74+
case binary[:2] == "10":
75+
header = 2
76+
case binary[0] == '0':
77+
header = 1
78+
default:
79+
return "QQ表情密文格式错误"
80+
}
81+
82+
dataBin := binary[header:]
83+
if len(dataBin)%8 != 0 {
84+
return fmt.Sprintf("QQ表情解密失败:数据长度不正确(%d位)", len(dataBin))
85+
}
86+
87+
data := make([]byte, len(dataBin)/8)
88+
for i := range data {
89+
for j := 0; j < 8; j++ {
90+
if dataBin[i*8+j] == '1' {
91+
data[i] |= 1 << (7 - j)
92+
}
93+
}
94+
}
95+
96+
var out []byte
97+
var err error
98+
switch binary[:header] {
99+
case "0":
100+
out = data
101+
case "10":
102+
r := brotli.NewReader(bytes.NewReader(data))
103+
out, err = io.ReadAll(r)
104+
case "11":
105+
var dec *zstd.Decoder
106+
dec, err = zstd.NewReader(bytes.NewReader(data))
107+
if err == nil {
108+
out, err = io.ReadAll(dec)
109+
dec.Close()
110+
}
111+
}
112+
if err != nil {
113+
return fmt.Sprintf("QQ表情解压失败: %v", err)
114+
}
115+
if !utf8.Valid(out) {
116+
return "QQ表情解密失败:结果不是有效文本"
117+
}
118+
return string(out)
119+
}
120+
121+
func tryCompress(newWriter func(io.Writer) io.WriteCloser, data []byte) []byte {
122+
var buf bytes.Buffer
123+
w := newWriter(&buf)
124+
if _, err := w.Write(data); err != nil {
125+
return nil
126+
}
127+
if err := w.Close(); err != nil {
128+
return nil
129+
}
130+
return buf.Bytes()
131+
}

0 commit comments

Comments
 (0)