dsh-qq-tools 0.9.5 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -11
- package/cordis.patch.yml +1 -1
- package/lib/config.js +96 -0
- package/lib/images.js +420 -0
- package/lib/index.js +2 -1
- package/lib/mount-all.js +59 -0
- package/lib/observation.js +24 -0
- package/lib/onebot.js +32 -0
- package/lib/prompts.js +56 -0
- package/lib/qq-state.js +51 -0
- package/lib/render.js +49 -0
- package/lib/session.js +201 -0
- package/lib/shared.js +26 -3377
- package/lib/tools/avatar.js +54 -0
- package/lib/tools/faces.js +296 -0
- package/lib/tools/friends.js +559 -0
- package/lib/tools/groups.js +220 -0
- package/lib/tools/poke.js +41 -0
- package/lib/tools/private.js +190 -0
- package/lib/tools/qzone.js +569 -0
- package/lib/utils.js +73 -0
- package/lib/watchers/mentions.js +167 -0
- package/lib/watchers/poke.js +87 -0
- package/lib/watchers/private.js +95 -0
- package/lib/watchers/ws.js +61 -0
- package/package.json +98 -103
- package/tools/qq-favorite-faces/index.js +11 -10
- package/tools/qq-favorite-faces/locale/en.json +1 -1
- package/tools/qq-favorite-faces/locale/zh.json +1 -1
- package/tools/qq-friend-management/index.js +28 -28
- package/tools/qq-get-group-history/index.js +10 -8
- package/tools/qq-get-group-members/index.js +9 -8
- package/tools/qq-list-groups/index.js +9 -8
- package/tools/qq-private-chat/index.js +16 -20
- package/tools/qq-qzone/index.js +22 -22
- package/tools/qq-send-face/index.js +11 -8
- package/tools/qq-send-face/locale/en.json +1 -1
- package/tools/qq-send-face/locale/zh.json +1 -1
- package/tools/qq-send-group-message/index.js +13 -10
- package/tools/qq-send-poke/index.js +13 -10
- package/tools/qq-set-avatar/index.js +9 -8
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// 改头像工具。
|
|
2
|
+
|
|
3
|
+
import { loadCfg } from '../config.js'
|
|
4
|
+
import { onebot } from '../onebot.js'
|
|
5
|
+
import { renderTool } from '../render.js'
|
|
6
|
+
import { imageSourceOf, imageSourceFromMessage, describeSource } from '../images.js'
|
|
7
|
+
|
|
8
|
+
// ---------- 头像 ----------
|
|
9
|
+
// set_qq_avatar 是 SnowLuma 扩展,file 和图片接口一样吃 http(s)/base64:///file://。
|
|
10
|
+
// 换头像会立刻对所有好友和群友可见,描述里明确要求只在用户提出时调用。
|
|
11
|
+
export function makeSetAvatarTool() {
|
|
12
|
+
return {
|
|
13
|
+
name: 'qq_set_avatar',
|
|
14
|
+
description:
|
|
15
|
+
'改自己的 QQ 头像(SnowLuma set_qq_avatar)。' +
|
|
16
|
+
'file 传 http(s) 链接、base64://、data:image/... 或本机文件路径;' +
|
|
17
|
+
'也可以用 message_id + index 从聊天记录里的某张图片取图。' +
|
|
18
|
+
'只有用户明确要求换头像时才用;换完不用再重复说明。',
|
|
19
|
+
parameters: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
file: { type: 'string', description: '图片:http(s) 链接、base64://、data:image/... 或本机路径;和 message_id 二选一' },
|
|
23
|
+
message_id: { type: 'string', description: '从这条消息里取图当头像;和 file 二选一' },
|
|
24
|
+
index: { type: 'number', description: 'message_id 里第几张图片,从 0 开始,默认 0' },
|
|
25
|
+
},
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
output: { schema: { type: 'object', additionalProperties: true }, render: renderTool },
|
|
29
|
+
async execute(args) {
|
|
30
|
+
const cfg = loadCfg()
|
|
31
|
+
const fileRaw = String((args && args.file) || '').trim()
|
|
32
|
+
const hasMessage = args && args.message_id != null && args.message_id !== ''
|
|
33
|
+
if (fileRaw && hasMessage) return { ok: false, error: 'file 和 message_id 只能选一个' }
|
|
34
|
+
if (!fileRaw && !hasMessage) return { ok: false, error: '要换头像就传 file 或 message_id' }
|
|
35
|
+
try {
|
|
36
|
+
let source = imageSourceOf(fileRaw)
|
|
37
|
+
if (!source && hasMessage) {
|
|
38
|
+
const got = await imageSourceFromMessage(args.message_id, args.index, cfg)
|
|
39
|
+
if (got.error) return { ok: false, error: got.error }
|
|
40
|
+
source = got.source
|
|
41
|
+
}
|
|
42
|
+
if (!source) return { ok: false, error: '拿不到头像图片' }
|
|
43
|
+
await onebot('set_qq_avatar', { file: source }, cfg)
|
|
44
|
+
return { ok: true, avatar: true, source: describeSource(source) }
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return { ok: false, error: String((error && error.message) || error) }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
render(_args, v) {
|
|
50
|
+
if (!v || !v.ok) return [{ type: 'text', text: '换头像失败:' + String((v && v.error) || '未知错误') }]
|
|
51
|
+
return [{ type: 'text', text: 'QQ 头像已更新(' + (v.source || '图片') + ')。' }]
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// 收藏表情工具:发、列、收藏/取消收藏/改备注。
|
|
2
|
+
|
|
3
|
+
import { loadCfg, allowedGroup, allowedPrivate } from '../config.js'
|
|
4
|
+
import { onebot } from '../onebot.js'
|
|
5
|
+
import { clean } from '../utils.js'
|
|
6
|
+
import { renderTool } from '../render.js'
|
|
7
|
+
import { imageSourceOf, imageSourceFromMessage } from '../images.js'
|
|
8
|
+
|
|
9
|
+
// ---------- 表情:只走 QQ 收藏表情(SnowLuma 自定义表情) ----------
|
|
10
|
+
// 发表情走 SnowLuma 的 send_custom_face,不走 OneBot 11 的 face 段,也不用系统表情编号。
|
|
11
|
+
// 收藏表情还用到这些扩展接口:
|
|
12
|
+
// fetch_custom_face_detail / add_custom_face / delete_custom_face /
|
|
13
|
+
// modify_custom_face / move_custom_face_to_front / send_custom_face
|
|
14
|
+
// 这些接口是 SnowLuma 扩展;本插件只支持 SnowLuma,其他 OneBot 实现没有这些接口。
|
|
15
|
+
const CUSTOM_FACE_COUNT = 200
|
|
16
|
+
|
|
17
|
+
function customFaceOf(entry) {
|
|
18
|
+
return {
|
|
19
|
+
emoji_id: String((entry && (entry.emoji_id || entry.emojiId || entry.resId)) || ''),
|
|
20
|
+
desc: clean(entry && entry.desc),
|
|
21
|
+
url: String((entry && entry.url) || ''),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchCustomFaces(cfg, count = CUSTOM_FACE_COUNT) {
|
|
26
|
+
const data = await onebot('fetch_custom_face_detail', { count }, cfg)
|
|
27
|
+
return (Array.isArray(data) ? data : []).map(customFaceOf).filter((f) => f.emoji_id)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// preview 只取这一张表情的图,不走发送;列表平时只有备注,省 token。
|
|
33
|
+
async function previewFace(cfg, emojiId, admitImages) {
|
|
34
|
+
if (typeof admitImages !== 'function') {
|
|
35
|
+
return { ok: false, error: '当前宿主不支持图片预览' }
|
|
36
|
+
}
|
|
37
|
+
let faces = []
|
|
38
|
+
try {
|
|
39
|
+
faces = await fetchCustomFaces(cfg, CUSTOM_FACE_COUNT)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return { ok: false, error: String((error && error.message) || error) }
|
|
42
|
+
}
|
|
43
|
+
const hit = faces.find((f) => f.emoji_id === emojiId)
|
|
44
|
+
if (!hit) return { ok: false, error: '收藏夹里没有这个 emoji_id(先用 qq_list_faces 对一下)' }
|
|
45
|
+
if (!/^https?:/i.test(hit.url)) return { ok: false, error: '这个表情没有可预览的图片地址' }
|
|
46
|
+
const images = await admitImages([{
|
|
47
|
+
data: { url: hit.url },
|
|
48
|
+
cfg,
|
|
49
|
+
name: `qq-face-${hit.emoji_id}.jpg`,
|
|
50
|
+
caption: `收藏表情预览 ${hit.desc || hit.emoji_id}`,
|
|
51
|
+
}])
|
|
52
|
+
if (!images.length) return { ok: false, error: '预览图没能送进来(下载失败或宿主不支持图片)' }
|
|
53
|
+
return { ok: true, preview: true, emoji_id: emojiId, desc: hit.desc, images }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function makeSendFaceTool(onSent, admitImages) {
|
|
57
|
+
return {
|
|
58
|
+
name: 'qq_send_face',
|
|
59
|
+
description:
|
|
60
|
+
'发一个你 QQ 收藏夹里的自定义表情(emoji_id)。' +
|
|
61
|
+
'群聊传 group_id;私聊传 user_id(可选 group_id 表示临时会话来源群)。' +
|
|
62
|
+
'emoji_id 从 qq_list_faces 的收藏列表拿。' +
|
|
63
|
+
'正式发之前先传 preview=true:只返回这一张表情图让你确认,不发送;确认合适再去掉 preview 真发。' +
|
|
64
|
+
'可以顺带一句话 text;文字会先单独发一条。',
|
|
65
|
+
parameters: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
group_id: { type: 'number', description: '群号;私聊时如果是从群里发起的临时会话,也可以传来源群号' },
|
|
69
|
+
user_id: { type: 'number', description: '对方 QQ 号;传了就是私聊,和只传 group_id 的群聊区分开' },
|
|
70
|
+
emoji_id: { type: 'string', description: 'QQ 收藏表情的 emoji_id(SnowLuma),必填' },
|
|
71
|
+
preview: { type: 'boolean', description: 'true = 只看这张表情的预览图,不发送' },
|
|
72
|
+
text: { type: 'string', description: '跟着表情一起发的文字,可选,不要 Markdown' },
|
|
73
|
+
reply_to: { type: 'string', description: '要回复的消息 ID,可选' },
|
|
74
|
+
},
|
|
75
|
+
required: ['emoji_id'],
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
},
|
|
78
|
+
output: { schema: { type: 'object', additionalProperties: true }, render: renderTool },
|
|
79
|
+
async execute(args) {
|
|
80
|
+
const cfg = loadCfg()
|
|
81
|
+
const hasUser = args && args.user_id != null && args.user_id !== ''
|
|
82
|
+
const userId = hasUser ? Number(args.user_id) : 0
|
|
83
|
+
const groupId = args && args.group_id != null && args.group_id !== '' ? Number(args.group_id) : 0
|
|
84
|
+
const emojiId = String((args && args.emoji_id) || '').trim()
|
|
85
|
+
const text = String((args && args.text) || '').trim()
|
|
86
|
+
const previewOnly = args && args.preview === true
|
|
87
|
+
if (!emojiId) return { ok: false, error: '要发表情就传 emoji_id(从 qq_list_faces 拿)' }
|
|
88
|
+
if (previewOnly) return previewFace(cfg, emojiId, admitImages)
|
|
89
|
+
if (hasUser) {
|
|
90
|
+
if (!Number.isFinite(userId)) return { ok: false, error: 'user_id 不是有效 QQ 号' }
|
|
91
|
+
if (!allowedPrivate(userId, cfg)) return { ok: false, error: '这个 QQ 不在允许列表里' }
|
|
92
|
+
if (groupId && !allowedGroup(groupId, cfg)) return { ok: false, error: '这个群不在允许列表里' }
|
|
93
|
+
} else {
|
|
94
|
+
if (!Number.isFinite(groupId) || !groupId) return { ok: false, error: 'group_id 和 user_id 至少传一个' }
|
|
95
|
+
if (!allowedGroup(groupId, cfg)) return { ok: false, error: '这个群不在允许列表里' }
|
|
96
|
+
}
|
|
97
|
+
const target = hasUser
|
|
98
|
+
? { user_id: userId, ...(groupId ? { group_id: groupId } : {}) }
|
|
99
|
+
: { group_id: groupId }
|
|
100
|
+
try {
|
|
101
|
+
let textMessageId = null
|
|
102
|
+
if (text) {
|
|
103
|
+
const action = hasUser ? 'send_private_msg' : 'send_group_msg'
|
|
104
|
+
const t = await onebot(
|
|
105
|
+
action,
|
|
106
|
+
{ ...target, message: [{ type: 'text', data: { text: text.slice(0, 1000) } }] },
|
|
107
|
+
cfg,
|
|
108
|
+
)
|
|
109
|
+
textMessageId = t && t.message_id
|
|
110
|
+
}
|
|
111
|
+
const body = { emoji_id: emojiId, ...target }
|
|
112
|
+
const replyTo = args && args.reply_to
|
|
113
|
+
if (replyTo != null && replyTo !== '') body.reply_to = String(replyTo)
|
|
114
|
+
const data = await onebot('send_custom_face', body, cfg)
|
|
115
|
+
try { if (!hasUser && typeof onSent === 'function') onSent(groupId, '发表情') } catch (error) {}
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
kind: 'custom',
|
|
119
|
+
chat: hasUser ? 'private' : 'group',
|
|
120
|
+
group_id: hasUser ? (groupId || null) : groupId,
|
|
121
|
+
user_id: hasUser ? userId : null,
|
|
122
|
+
message_id: data && data.message_id,
|
|
123
|
+
text_message_id: textMessageId,
|
|
124
|
+
emoji_id: emojiId,
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return { ok: false, error: String((error && error.message) || error) }
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
render(_args, v) {
|
|
131
|
+
if (!v || !v.ok) return [{ type: 'text', text: '发表情失败:' + String((v && v.error) || '未知错误') }]
|
|
132
|
+
if (v.preview) {
|
|
133
|
+
const blocks = [{ type: 'text', text: '收藏表情预览 ' + (v.desc ? '「' + v.desc + '」' : '') + '(emoji_id=' + v.emoji_id + '):合适就再去掉 preview 正式发。' }]
|
|
134
|
+
for (const img of Array.isArray(v.images) ? v.images : []) {
|
|
135
|
+
if (img && img.caption) blocks.push({ type: 'text', text: img.caption })
|
|
136
|
+
if (img && img.block) blocks.push(img.block)
|
|
137
|
+
}
|
|
138
|
+
return blocks
|
|
139
|
+
}
|
|
140
|
+
const where = v.chat === 'private'
|
|
141
|
+
? (v.group_id ? '群 ' + v.group_id + ' 的临时会话 QQ ' + v.user_id : 'QQ ' + v.user_id)
|
|
142
|
+
: '群 ' + v.group_id
|
|
143
|
+
const parts = []
|
|
144
|
+
if (v.text_message_id) parts.push('文字已发(消息 ' + v.text_message_id + ')')
|
|
145
|
+
parts.push('收藏表情 ' + v.emoji_id + ' 已发到' + where + '(消息 ' + (v.message_id || '?') + ')')
|
|
146
|
+
return [{ type: 'text', text: parts.join(';') + '。' }]
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function makeListFacesTool() {
|
|
152
|
+
return {
|
|
153
|
+
name: 'qq_list_faces',
|
|
154
|
+
description:
|
|
155
|
+
'看你 QQ 收藏夹里的表情:每个只返回 emoji_id 和文字备注,不发图片。' +
|
|
156
|
+
'按备注找就传 query;不传就列收藏夹。' +
|
|
157
|
+
'收藏/取消收藏用 qq_favorite_face;发表情用 qq_send_face。',
|
|
158
|
+
parameters: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
query: { type: 'string', description: '按备注或 emoji_id 搜收藏表情;不传就列全部' },
|
|
162
|
+
count: { type: 'number', description: '最多列多少个收藏表情,默认 200,最多 500' },
|
|
163
|
+
},
|
|
164
|
+
additionalProperties: false,
|
|
165
|
+
},
|
|
166
|
+
output: { schema: { type: 'object', additionalProperties: true }, render: renderTool },
|
|
167
|
+
async execute(args) {
|
|
168
|
+
const cfg = loadCfg()
|
|
169
|
+
const q = String((args && args.query) || '').trim().toLowerCase()
|
|
170
|
+
const count = Math.max(1, Math.min(Number(args && args.count) || CUSTOM_FACE_COUNT, 500))
|
|
171
|
+
let favorites = []
|
|
172
|
+
let favoritesError = ''
|
|
173
|
+
try {
|
|
174
|
+
favorites = await fetchCustomFaces(cfg, count)
|
|
175
|
+
} catch (error) {
|
|
176
|
+
favoritesError = String((error && error.message) || error)
|
|
177
|
+
}
|
|
178
|
+
const matched = q
|
|
179
|
+
? favorites.filter((f) => (f.desc + ' ' + f.emoji_id).toLowerCase().includes(q))
|
|
180
|
+
: favorites
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
query: q,
|
|
184
|
+
favorites: matched.slice(0, count).map((f) => ({ emoji_id: f.emoji_id, desc: f.desc })),
|
|
185
|
+
favorites_total: favorites.length,
|
|
186
|
+
favorites_error: favoritesError,
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
render(_args, v) {
|
|
190
|
+
if (!v || !v.ok) return [{ type: 'text', text: '看表情失败:' + String((v && v.error) || '未知错误') }]
|
|
191
|
+
const lines = []
|
|
192
|
+
if (v.favorites.length) {
|
|
193
|
+
lines.push(v.query ? '匹配的收藏表情:' : '你 QQ 收藏夹里的表情:')
|
|
194
|
+
v.favorites.forEach((f, i) => {
|
|
195
|
+
lines.push(`${i + 1}. ${f.desc || '(无备注)'} emoji_id=${f.emoji_id}`)
|
|
196
|
+
})
|
|
197
|
+
if (v.favorites_total > v.favorites.length) {
|
|
198
|
+
lines.push(`(拿到 ${v.favorites_total} 个,只列了前 ${v.favorites.length} 个)`)
|
|
199
|
+
}
|
|
200
|
+
} else if (v.favorites_error) {
|
|
201
|
+
lines.push('收藏表情读不到(SnowLuma 自定义表情接口不可用):' + String(v.favorites_error).slice(0, 200))
|
|
202
|
+
} else if (v.query) {
|
|
203
|
+
lines.push('没有匹配备注的收藏表情,换个词或直接列收藏夹。')
|
|
204
|
+
} else {
|
|
205
|
+
lines.push('QQ 收藏夹里还没有表情。用 qq_favorite_face 从图片或消息收藏。')
|
|
206
|
+
}
|
|
207
|
+
return [{ type: 'text', text: lines.join('\n') }]
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function makeFavoriteFaceTool() {
|
|
213
|
+
return {
|
|
214
|
+
name: 'qq_favorite_face',
|
|
215
|
+
description:
|
|
216
|
+
'管理你 QQ 账号的收藏表情(SnowLuma):收藏一张图片、取消收藏、改备注、移到最前。' +
|
|
217
|
+
'收藏来源可以是 http(s) 图片链接、base64://、本机文件路径,或某条消息里的图片(message_id + index)。' +
|
|
218
|
+
'收藏时尽量传 desc:qq_list_faces 只返回备注,desc 直接简短描述画面内容(比如「锅铲敲地」),不写后面就不知道这张表情该什么时候用。' +
|
|
219
|
+
'emoji_id 从 qq_list_faces 的收藏列表拿;发表情用 qq_send_face。',
|
|
220
|
+
parameters: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
file: { type: 'string', description: '要收藏的图片:http(s) 链接、base64:// 或本机文件路径;和 message_id 二选一' },
|
|
224
|
+
message_id: { type: 'string', description: '从这条消息里取图片收藏;和 file 二选一' },
|
|
225
|
+
index: { type: 'number', description: 'message_id 里第几张图片,从 0 开始,默认 0' },
|
|
226
|
+
emoji_id: { type: 'string', description: '已收藏表情的 emoji_id;取消收藏、改备注、移到最前时用' },
|
|
227
|
+
desc: { type: 'string', description: '收藏表情的备注:直接简短描述画面内容,比如「闭眼安详」「锅铲敲地」「穿冬装的肥鱼」;收藏时强烈建议传,配合 emoji_id 可修改' },
|
|
228
|
+
remove: { type: 'boolean', description: 'true = 取消收藏(需要 emoji_id)' },
|
|
229
|
+
move_to_front: { type: 'boolean', description: 'true = 移到收藏夹最前(需要 emoji_id)' },
|
|
230
|
+
},
|
|
231
|
+
additionalProperties: false,
|
|
232
|
+
},
|
|
233
|
+
output: { schema: { type: 'object', additionalProperties: true }, render: renderTool },
|
|
234
|
+
async execute(args) {
|
|
235
|
+
const cfg = loadCfg()
|
|
236
|
+
const emojiId = String((args && args.emoji_id) || '').trim()
|
|
237
|
+
const desc = clean(args && args.desc)
|
|
238
|
+
const remove = args && args.remove === true
|
|
239
|
+
const moveToFront = args && args.move_to_front === true
|
|
240
|
+
if (remove && moveToFront) return { ok: false, error: 'remove 和 move_to_front 只能选一个' }
|
|
241
|
+
try {
|
|
242
|
+
if (remove || moveToFront) {
|
|
243
|
+
if (!emojiId) return { ok: false, error: (remove ? '取消收藏' : '移到最前') + '需要 emoji_id' }
|
|
244
|
+
if (remove) {
|
|
245
|
+
await onebot('delete_custom_face', { emoji_id: emojiId }, cfg)
|
|
246
|
+
return { ok: true, removed: true, emoji_id: emojiId }
|
|
247
|
+
}
|
|
248
|
+
await onebot('move_custom_face_to_front', { emoji_id: emojiId }, cfg)
|
|
249
|
+
return { ok: true, moved: true, emoji_id: emojiId }
|
|
250
|
+
}
|
|
251
|
+
if (emojiId) {
|
|
252
|
+
if (args && args.desc == null) return { ok: false, error: '改备注需要同时传 emoji_id 和 desc' }
|
|
253
|
+
await onebot('modify_custom_face', { emoji_id: emojiId, desc }, cfg)
|
|
254
|
+
return { ok: true, updated: true, emoji_id: emojiId, desc }
|
|
255
|
+
}
|
|
256
|
+
let source = imageSourceOf(args && args.file)
|
|
257
|
+
if (!source && args && args.message_id != null && args.message_id !== '') {
|
|
258
|
+
const got = await imageSourceFromMessage(args.message_id, args.index, cfg)
|
|
259
|
+
if (got.error) return { ok: false, error: got.error }
|
|
260
|
+
source = got.source
|
|
261
|
+
}
|
|
262
|
+
if (!source) return { ok: false, error: '要收藏就传 file 或 message_id' }
|
|
263
|
+
const data = await onebot('add_custom_face', { file: source }, cfg)
|
|
264
|
+
const newId = String((data && (data.emoji_id || data.emojiId || data.resId)) || '')
|
|
265
|
+
let descSet = true
|
|
266
|
+
let warning = ''
|
|
267
|
+
if (desc) {
|
|
268
|
+
if (newId) {
|
|
269
|
+
try {
|
|
270
|
+
await onebot('modify_custom_face', { emoji_id: newId, desc }, cfg)
|
|
271
|
+
} catch (error) {
|
|
272
|
+
descSet = false
|
|
273
|
+
warning = String((error && error.message) || error)
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
descSet = false
|
|
277
|
+
warning = '没有拿到新表情的 emoji_id,备注未设置'
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { ok: true, added: true, emoji_id: newId, desc, desc_set: descSet, warning, source }
|
|
281
|
+
} catch (error) {
|
|
282
|
+
return { ok: false, error: String((error && error.message) || error) }
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
render(_args, v) {
|
|
286
|
+
if (!v || !v.ok) return [{ type: 'text', text: '收藏表情失败:' + String((v && v.error) || '未知错误') }]
|
|
287
|
+
if (v.removed) return [{ type: 'text', text: `已取消收藏表情 ${v.emoji_id}。` }]
|
|
288
|
+
if (v.moved) return [{ type: 'text', text: `已把表情 ${v.emoji_id} 移到收藏夹最前。` }]
|
|
289
|
+
if (v.updated) return [{ type: 'text', text: `已把表情 ${v.emoji_id} 的备注改成「${v.desc || '空'}」。` }]
|
|
290
|
+
let text = `已收藏表情${v.desc ? '「' + v.desc + '」' : ''}(emoji_id: ${v.emoji_id || '?'})。`
|
|
291
|
+
if (v.desc && v.desc_set === false) text += ` 备注没设置成功:${String(v.warning || '').slice(0, 200)}`
|
|
292
|
+
if (!v.desc) text += ' 这次没写备注;建议用 emoji_id + desc 补一条,直接简短描述画面内容,不然后面挑表情时只有编号可看。'
|
|
293
|
+
return [{ type: 'text', text }]
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
}
|