@fg-lab/amadeus-qq 1.2.1 → 1.2.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/LICENSE +21 -21
- package/README.md +55 -53
- package/README.zh-CN.md +51 -51
- package/REFERENCE.md +49 -49
- package/cordis.patch.yml +6 -6
- package/docs/DATA_MIGRATION.md +26 -26
- package/docs/INSTALL_V1_2.md +22 -22
- package/docs/PUBLISHING.md +28 -26
- package/docs/RELEASE_CHECKLIST.md +23 -23
- package/docs/RELEASE_NOTES.md +38 -38
- package/docs/UPGRADE_POLICY.md +26 -26
- package/docs/verification/IMAGE_READ_FIX_2026-11-07.md +22 -0
- package/docs/verification/LIFE_QQ_BOUNDARY_2026-11-07.md +20 -0
- package/lib/client.js +372 -372
- package/package.json +94 -91
- package/preset.patch.yml +63 -0
- package/scripts/symlink-security.mjs +1 -1
- package/src/account-profile.js +223 -223
- package/src/admin-http.js +361 -361
- package/src/audio.js +428 -428
- package/src/capabilities.js +785 -785
- package/src/client/factory.template.js +82 -82
- package/src/client/logic.js +290 -290
- package/src/config.js +2 -0
- package/src/console/console.css +28 -28
- package/src/console/console.js +3 -0
- package/src/console/index.html +67 -67
- package/src/core.js +21 -4
- package/src/files.js +230 -230
- package/src/index.js +135 -8
- package/src/lease.js +42 -42
- package/src/media.js +66 -3
- package/src/napcat-action-names.js +186 -186
- package/src/native-asr.js +112 -112
- package/src/onebot.js +378 -378
- package/src/outbound-media.js +309 -309
- package/src/owner-workspace.js +394 -394
- package/src/permissions.js +20 -0
- package/src/preset-guard.mjs +16 -2
- package/src/qzone-provider.js +628 -628
- package/src/session-provenance.js +74 -74
- package/src/setup.js +255 -255
- package/src/web-console.js +9 -9
package/src/account-profile.js
CHANGED
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { snapshotControlledImage } from './outbound-media.js'
|
|
4
|
-
|
|
5
|
-
const STATUS_VALUES = new Set([10, 30, 40, 50, 60, 70])
|
|
6
|
-
const MAX_SIGNATURE = 200
|
|
7
|
-
const MAX_NICKNAME = 36
|
|
8
|
-
|
|
9
|
-
function profileError(code) {
|
|
10
|
-
const error = new Error(code)
|
|
11
|
-
error.code = code
|
|
12
|
-
return error
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function cleanText(value, max, code) {
|
|
16
|
-
if (typeof value !== 'string') throw profileError(code)
|
|
17
|
-
const text = value.replace(/[\u0000-\u001f\u007f]/g, '').trim()
|
|
18
|
-
if (text.length === 0 || text.length > max) throw profileError(code)
|
|
19
|
-
return text
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function decimalId(value) {
|
|
23
|
-
const id = typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value
|
|
24
|
-
if (typeof id !== 'string' || !/^[1-9]\d{0,19}$/.test(id)) throw profileError('QQ_PROFILE_RESPONSE_INVALID')
|
|
25
|
-
return id
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function responseInvalid() {
|
|
29
|
-
return profileError('QQ_PROFILE_RESPONSE_INVALID')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function profileFingerprint(scene, action, payload) {
|
|
33
|
-
return createHash('sha256')
|
|
34
|
-
.update(JSON.stringify({
|
|
35
|
-
account: scene.botId,
|
|
36
|
-
scene: scene.peerId,
|
|
37
|
-
action,
|
|
38
|
-
payload,
|
|
39
|
-
}))
|
|
40
|
-
.digest('hex')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function createAccountProfile({ core, transport, authorize, ledger, sendRoot, tempRoot }) {
|
|
44
|
-
if (typeof authorize !== 'function') throw profileError('QQ_PROFILE_UNAUTHORIZED')
|
|
45
|
-
if (!transport || typeof transport.action !== 'function') throw profileError('QQ_PROFILE_UNAVAILABLE')
|
|
46
|
-
|
|
47
|
-
async function check(sessionId, action, write) {
|
|
48
|
-
let scene
|
|
49
|
-
try {
|
|
50
|
-
scene = core.authorizedScene(sessionId)
|
|
51
|
-
} catch {
|
|
52
|
-
throw profileError('QQ_PROFILE_UNAUTHORIZED')
|
|
53
|
-
}
|
|
54
|
-
if (write && scene.kind !== 'private') throw profileError('QQ_PROFILE_OWNER_REQUIRED')
|
|
55
|
-
let allowed = false
|
|
56
|
-
try {
|
|
57
|
-
allowed = await authorize({ action, write, sessionId, scene }) === true
|
|
58
|
-
} catch {
|
|
59
|
-
allowed = false
|
|
60
|
-
}
|
|
61
|
-
if (!allowed) throw profileError('QQ_PROFILE_UNAUTHORIZED')
|
|
62
|
-
return scene
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function call(action, params, write) {
|
|
66
|
-
let data
|
|
67
|
-
try {
|
|
68
|
-
data = await transport.action(action, params, 10_000)
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (!write) throw profileError('QQ_PROFILE_UNAVAILABLE')
|
|
71
|
-
if (error?.code === 'ONEBOT_REJECTED') throw profileError('QQ_PROFILE_REJECTED')
|
|
72
|
-
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
73
|
-
}
|
|
74
|
-
if (data === null || data === undefined) {
|
|
75
|
-
if (write) return {}
|
|
76
|
-
throw responseInvalid()
|
|
77
|
-
}
|
|
78
|
-
if (typeof data !== 'object') throw responseInvalid()
|
|
79
|
-
return data
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function loginInfo(sessionId, write = false) {
|
|
83
|
-
const data = await call('get_login_info', {}, write)
|
|
84
|
-
return { userId: decimalId(data.user_id), nickname: typeof data.nickname === 'string' ? data.nickname.slice(0, MAX_NICKNAME) : '' }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function strangerInfo(sessionId, userId, write = false) {
|
|
88
|
-
const data = await call('get_stranger_info', { user_id: userId, no_cache: true }, write)
|
|
89
|
-
return {
|
|
90
|
-
longNick: typeof data.long_nick === 'string' ? data.long_nick.slice(0, MAX_SIGNATURE) : '',
|
|
91
|
-
status: Number.isFinite(Number(data.status)) ? Number(data.status) : null,
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function userStatus(userId, write = false) {
|
|
96
|
-
const data = await call('nc_get_user_status', { user_id: userId }, write)
|
|
97
|
-
const status = Number(data.status)
|
|
98
|
-
const extStatus = Number(data.ext_status)
|
|
99
|
-
if (!Number.isSafeInteger(status) || !Number.isSafeInteger(extStatus)) throw responseInvalid()
|
|
100
|
-
return { status, extStatus }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function readProfileInternal(sessionId, write = false) {
|
|
104
|
-
const login = await loginInfo(sessionId, write)
|
|
105
|
-
const stranger = await strangerInfo(sessionId, login.userId, write)
|
|
106
|
-
const online = await userStatus(login.userId, write)
|
|
107
|
-
return {
|
|
108
|
-
userId: login.userId,
|
|
109
|
-
nickname: login.nickname,
|
|
110
|
-
signature: stranger.longNick,
|
|
111
|
-
status: online.status,
|
|
112
|
-
extStatus: online.extStatus,
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function writeWithLedger(sessionId, action, payload, actionId, verify) {
|
|
117
|
-
const scene = await check(sessionId, action, true)
|
|
118
|
-
if (!ledger || typeof ledger.run !== 'function') throw profileError('QQ_PROFILE_LEDGER_REQUIRED')
|
|
119
|
-
if (typeof actionId !== 'string' || !/^[\w-]{1,100}$/.test(actionId)) throw profileError('QQ_PROFILE_ACTION_ID_INVALID')
|
|
120
|
-
const fingerprint = profileFingerprint(scene, action, payload)
|
|
121
|
-
return ledger.run({
|
|
122
|
-
actionId,
|
|
123
|
-
fingerprint,
|
|
124
|
-
action,
|
|
125
|
-
sessionId,
|
|
126
|
-
sceneKey: `${scene.botId}:${scene.kind}:${scene.peerId}`,
|
|
127
|
-
execute: async () => {
|
|
128
|
-
await call(action, payload, true)
|
|
129
|
-
try {
|
|
130
|
-
await check(sessionId, action, true)
|
|
131
|
-
} catch {
|
|
132
|
-
// The platform write already happened; do not report a clean failure.
|
|
133
|
-
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
134
|
-
}
|
|
135
|
-
let profile
|
|
136
|
-
try {
|
|
137
|
-
profile = await readProfileInternal(sessionId, true)
|
|
138
|
-
if (!(await verify(profile))) throw profileError('QQ_PROFILE_UNKNOWN')
|
|
139
|
-
} catch (error) {
|
|
140
|
-
if (error?.code === 'QQ_PROFILE_UNKNOWN') throw error
|
|
141
|
-
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
await check(sessionId, action, true)
|
|
145
|
-
} catch {
|
|
146
|
-
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
147
|
-
}
|
|
148
|
-
return profile
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return Object.freeze({
|
|
154
|
-
async readProfile({ sessionId }) {
|
|
155
|
-
await check(sessionId, 'get_login_info', false)
|
|
156
|
-
const profile = await readProfileInternal(sessionId)
|
|
157
|
-
await check(sessionId, 'get_login_info', false)
|
|
158
|
-
return profile
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
async setSignature({ sessionId, signature, actionId }) {
|
|
162
|
-
const value = cleanText(signature, MAX_SIGNATURE, 'QQ_PROFILE_SIGNATURE_INVALID')
|
|
163
|
-
return writeWithLedger(sessionId, 'set_self_longnick', { longNick: value }, actionId,
|
|
164
|
-
profile => profile.signature === value)
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
async setNickname({ sessionId, nickname, actionId }) {
|
|
168
|
-
const value = cleanText(nickname, MAX_NICKNAME, 'QQ_PROFILE_NICKNAME_INVALID')
|
|
169
|
-
return writeWithLedger(sessionId, 'set_qq_profile', { nickname: value }, actionId,
|
|
170
|
-
profile => profile.nickname === value)
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
async setOnlineStatus({ sessionId, status, extStatus, batteryStatus, actionId }) {
|
|
174
|
-
if (!Number.isSafeInteger(status) || !STATUS_VALUES.has(status)) throw profileError('QQ_PROFILE_STATUS_INVALID')
|
|
175
|
-
if (!Number.isSafeInteger(extStatus) || extStatus < 0 || extStatus > 9999) throw profileError('QQ_PROFILE_STATUS_INVALID')
|
|
176
|
-
if (!Number.isSafeInteger(batteryStatus) || batteryStatus < 0 || batteryStatus > 100) throw profileError('QQ_PROFILE_STATUS_INVALID')
|
|
177
|
-
return writeWithLedger(sessionId, 'set_online_status', {
|
|
178
|
-
status, ext_status: extStatus, battery_status: batteryStatus,
|
|
179
|
-
}, actionId, profile => profile.status === status && profile.extStatus === extStatus)
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
async setAvatar({ sessionId, path: relativePath, actionId }) {
|
|
183
|
-
if (typeof sendRoot !== 'string' || !path.isAbsolute(sendRoot)) {
|
|
184
|
-
throw profileError('QQ_PROFILE_AVATAR_ROOT_INVALID')
|
|
185
|
-
}
|
|
186
|
-
const scene = await check(sessionId, 'set_qq_avatar', true)
|
|
187
|
-
if (!ledger || typeof ledger.run !== 'function') throw profileError('QQ_PROFILE_LEDGER_REQUIRED')
|
|
188
|
-
if (typeof actionId !== 'string' || !/^[\w-]{1,100}$/.test(actionId)) throw profileError('QQ_PROFILE_ACTION_ID_INVALID')
|
|
189
|
-
|
|
190
|
-
let snapshot
|
|
191
|
-
try {
|
|
192
|
-
snapshot = await snapshotControlledImage({ sendRoot, relativePath, tempRoot })
|
|
193
|
-
} catch {
|
|
194
|
-
throw profileError('QQ_PROFILE_AVATAR_INVALID')
|
|
195
|
-
}
|
|
196
|
-
const fingerprint = profileFingerprint(scene, 'set_qq_avatar', {
|
|
197
|
-
sha256: snapshot.sha256,
|
|
198
|
-
mediaType: snapshot.mediaType,
|
|
199
|
-
})
|
|
200
|
-
try {
|
|
201
|
-
return await ledger.run({
|
|
202
|
-
actionId,
|
|
203
|
-
fingerprint,
|
|
204
|
-
action: 'set_qq_avatar',
|
|
205
|
-
sessionId,
|
|
206
|
-
sceneKey: `${scene.botId}:${scene.kind}:${scene.peerId}`,
|
|
207
|
-
execute: async () => {
|
|
208
|
-
await call('set_qq_avatar', { file: snapshot.path }, true)
|
|
209
|
-
try {
|
|
210
|
-
await check(sessionId, 'set_qq_avatar', true)
|
|
211
|
-
} catch {
|
|
212
|
-
// The platform accepted the write; a later revocation is unknown.
|
|
213
|
-
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
214
|
-
}
|
|
215
|
-
return { status: 'confirmed', mediaType: snapshot.mediaType, bytes: snapshot.bytes }
|
|
216
|
-
},
|
|
217
|
-
})
|
|
218
|
-
} finally {
|
|
219
|
-
await snapshot.cleanup()
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
})
|
|
223
|
-
}
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { snapshotControlledImage } from './outbound-media.js'
|
|
4
|
+
|
|
5
|
+
const STATUS_VALUES = new Set([10, 30, 40, 50, 60, 70])
|
|
6
|
+
const MAX_SIGNATURE = 200
|
|
7
|
+
const MAX_NICKNAME = 36
|
|
8
|
+
|
|
9
|
+
function profileError(code) {
|
|
10
|
+
const error = new Error(code)
|
|
11
|
+
error.code = code
|
|
12
|
+
return error
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cleanText(value, max, code) {
|
|
16
|
+
if (typeof value !== 'string') throw profileError(code)
|
|
17
|
+
const text = value.replace(/[\u0000-\u001f\u007f]/g, '').trim()
|
|
18
|
+
if (text.length === 0 || text.length > max) throw profileError(code)
|
|
19
|
+
return text
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function decimalId(value) {
|
|
23
|
+
const id = typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value
|
|
24
|
+
if (typeof id !== 'string' || !/^[1-9]\d{0,19}$/.test(id)) throw profileError('QQ_PROFILE_RESPONSE_INVALID')
|
|
25
|
+
return id
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function responseInvalid() {
|
|
29
|
+
return profileError('QQ_PROFILE_RESPONSE_INVALID')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function profileFingerprint(scene, action, payload) {
|
|
33
|
+
return createHash('sha256')
|
|
34
|
+
.update(JSON.stringify({
|
|
35
|
+
account: scene.botId,
|
|
36
|
+
scene: scene.peerId,
|
|
37
|
+
action,
|
|
38
|
+
payload,
|
|
39
|
+
}))
|
|
40
|
+
.digest('hex')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createAccountProfile({ core, transport, authorize, ledger, sendRoot, tempRoot }) {
|
|
44
|
+
if (typeof authorize !== 'function') throw profileError('QQ_PROFILE_UNAUTHORIZED')
|
|
45
|
+
if (!transport || typeof transport.action !== 'function') throw profileError('QQ_PROFILE_UNAVAILABLE')
|
|
46
|
+
|
|
47
|
+
async function check(sessionId, action, write) {
|
|
48
|
+
let scene
|
|
49
|
+
try {
|
|
50
|
+
scene = core.authorizedScene(sessionId)
|
|
51
|
+
} catch {
|
|
52
|
+
throw profileError('QQ_PROFILE_UNAUTHORIZED')
|
|
53
|
+
}
|
|
54
|
+
if (write && scene.kind !== 'private') throw profileError('QQ_PROFILE_OWNER_REQUIRED')
|
|
55
|
+
let allowed = false
|
|
56
|
+
try {
|
|
57
|
+
allowed = await authorize({ action, write, sessionId, scene }) === true
|
|
58
|
+
} catch {
|
|
59
|
+
allowed = false
|
|
60
|
+
}
|
|
61
|
+
if (!allowed) throw profileError('QQ_PROFILE_UNAUTHORIZED')
|
|
62
|
+
return scene
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function call(action, params, write) {
|
|
66
|
+
let data
|
|
67
|
+
try {
|
|
68
|
+
data = await transport.action(action, params, 10_000)
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (!write) throw profileError('QQ_PROFILE_UNAVAILABLE')
|
|
71
|
+
if (error?.code === 'ONEBOT_REJECTED') throw profileError('QQ_PROFILE_REJECTED')
|
|
72
|
+
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
73
|
+
}
|
|
74
|
+
if (data === null || data === undefined) {
|
|
75
|
+
if (write) return {}
|
|
76
|
+
throw responseInvalid()
|
|
77
|
+
}
|
|
78
|
+
if (typeof data !== 'object') throw responseInvalid()
|
|
79
|
+
return data
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function loginInfo(sessionId, write = false) {
|
|
83
|
+
const data = await call('get_login_info', {}, write)
|
|
84
|
+
return { userId: decimalId(data.user_id), nickname: typeof data.nickname === 'string' ? data.nickname.slice(0, MAX_NICKNAME) : '' }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function strangerInfo(sessionId, userId, write = false) {
|
|
88
|
+
const data = await call('get_stranger_info', { user_id: userId, no_cache: true }, write)
|
|
89
|
+
return {
|
|
90
|
+
longNick: typeof data.long_nick === 'string' ? data.long_nick.slice(0, MAX_SIGNATURE) : '',
|
|
91
|
+
status: Number.isFinite(Number(data.status)) ? Number(data.status) : null,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function userStatus(userId, write = false) {
|
|
96
|
+
const data = await call('nc_get_user_status', { user_id: userId }, write)
|
|
97
|
+
const status = Number(data.status)
|
|
98
|
+
const extStatus = Number(data.ext_status)
|
|
99
|
+
if (!Number.isSafeInteger(status) || !Number.isSafeInteger(extStatus)) throw responseInvalid()
|
|
100
|
+
return { status, extStatus }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function readProfileInternal(sessionId, write = false) {
|
|
104
|
+
const login = await loginInfo(sessionId, write)
|
|
105
|
+
const stranger = await strangerInfo(sessionId, login.userId, write)
|
|
106
|
+
const online = await userStatus(login.userId, write)
|
|
107
|
+
return {
|
|
108
|
+
userId: login.userId,
|
|
109
|
+
nickname: login.nickname,
|
|
110
|
+
signature: stranger.longNick,
|
|
111
|
+
status: online.status,
|
|
112
|
+
extStatus: online.extStatus,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function writeWithLedger(sessionId, action, payload, actionId, verify) {
|
|
117
|
+
const scene = await check(sessionId, action, true)
|
|
118
|
+
if (!ledger || typeof ledger.run !== 'function') throw profileError('QQ_PROFILE_LEDGER_REQUIRED')
|
|
119
|
+
if (typeof actionId !== 'string' || !/^[\w-]{1,100}$/.test(actionId)) throw profileError('QQ_PROFILE_ACTION_ID_INVALID')
|
|
120
|
+
const fingerprint = profileFingerprint(scene, action, payload)
|
|
121
|
+
return ledger.run({
|
|
122
|
+
actionId,
|
|
123
|
+
fingerprint,
|
|
124
|
+
action,
|
|
125
|
+
sessionId,
|
|
126
|
+
sceneKey: `${scene.botId}:${scene.kind}:${scene.peerId}`,
|
|
127
|
+
execute: async () => {
|
|
128
|
+
await call(action, payload, true)
|
|
129
|
+
try {
|
|
130
|
+
await check(sessionId, action, true)
|
|
131
|
+
} catch {
|
|
132
|
+
// The platform write already happened; do not report a clean failure.
|
|
133
|
+
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
134
|
+
}
|
|
135
|
+
let profile
|
|
136
|
+
try {
|
|
137
|
+
profile = await readProfileInternal(sessionId, true)
|
|
138
|
+
if (!(await verify(profile))) throw profileError('QQ_PROFILE_UNKNOWN')
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error?.code === 'QQ_PROFILE_UNKNOWN') throw error
|
|
141
|
+
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await check(sessionId, action, true)
|
|
145
|
+
} catch {
|
|
146
|
+
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
147
|
+
}
|
|
148
|
+
return profile
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
async readProfile({ sessionId }) {
|
|
155
|
+
await check(sessionId, 'get_login_info', false)
|
|
156
|
+
const profile = await readProfileInternal(sessionId)
|
|
157
|
+
await check(sessionId, 'get_login_info', false)
|
|
158
|
+
return profile
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
async setSignature({ sessionId, signature, actionId }) {
|
|
162
|
+
const value = cleanText(signature, MAX_SIGNATURE, 'QQ_PROFILE_SIGNATURE_INVALID')
|
|
163
|
+
return writeWithLedger(sessionId, 'set_self_longnick', { longNick: value }, actionId,
|
|
164
|
+
profile => profile.signature === value)
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async setNickname({ sessionId, nickname, actionId }) {
|
|
168
|
+
const value = cleanText(nickname, MAX_NICKNAME, 'QQ_PROFILE_NICKNAME_INVALID')
|
|
169
|
+
return writeWithLedger(sessionId, 'set_qq_profile', { nickname: value }, actionId,
|
|
170
|
+
profile => profile.nickname === value)
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
async setOnlineStatus({ sessionId, status, extStatus, batteryStatus, actionId }) {
|
|
174
|
+
if (!Number.isSafeInteger(status) || !STATUS_VALUES.has(status)) throw profileError('QQ_PROFILE_STATUS_INVALID')
|
|
175
|
+
if (!Number.isSafeInteger(extStatus) || extStatus < 0 || extStatus > 9999) throw profileError('QQ_PROFILE_STATUS_INVALID')
|
|
176
|
+
if (!Number.isSafeInteger(batteryStatus) || batteryStatus < 0 || batteryStatus > 100) throw profileError('QQ_PROFILE_STATUS_INVALID')
|
|
177
|
+
return writeWithLedger(sessionId, 'set_online_status', {
|
|
178
|
+
status, ext_status: extStatus, battery_status: batteryStatus,
|
|
179
|
+
}, actionId, profile => profile.status === status && profile.extStatus === extStatus)
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async setAvatar({ sessionId, path: relativePath, actionId }) {
|
|
183
|
+
if (typeof sendRoot !== 'string' || !path.isAbsolute(sendRoot)) {
|
|
184
|
+
throw profileError('QQ_PROFILE_AVATAR_ROOT_INVALID')
|
|
185
|
+
}
|
|
186
|
+
const scene = await check(sessionId, 'set_qq_avatar', true)
|
|
187
|
+
if (!ledger || typeof ledger.run !== 'function') throw profileError('QQ_PROFILE_LEDGER_REQUIRED')
|
|
188
|
+
if (typeof actionId !== 'string' || !/^[\w-]{1,100}$/.test(actionId)) throw profileError('QQ_PROFILE_ACTION_ID_INVALID')
|
|
189
|
+
|
|
190
|
+
let snapshot
|
|
191
|
+
try {
|
|
192
|
+
snapshot = await snapshotControlledImage({ sendRoot, relativePath, tempRoot })
|
|
193
|
+
} catch {
|
|
194
|
+
throw profileError('QQ_PROFILE_AVATAR_INVALID')
|
|
195
|
+
}
|
|
196
|
+
const fingerprint = profileFingerprint(scene, 'set_qq_avatar', {
|
|
197
|
+
sha256: snapshot.sha256,
|
|
198
|
+
mediaType: snapshot.mediaType,
|
|
199
|
+
})
|
|
200
|
+
try {
|
|
201
|
+
return await ledger.run({
|
|
202
|
+
actionId,
|
|
203
|
+
fingerprint,
|
|
204
|
+
action: 'set_qq_avatar',
|
|
205
|
+
sessionId,
|
|
206
|
+
sceneKey: `${scene.botId}:${scene.kind}:${scene.peerId}`,
|
|
207
|
+
execute: async () => {
|
|
208
|
+
await call('set_qq_avatar', { file: snapshot.path }, true)
|
|
209
|
+
try {
|
|
210
|
+
await check(sessionId, 'set_qq_avatar', true)
|
|
211
|
+
} catch {
|
|
212
|
+
// The platform accepted the write; a later revocation is unknown.
|
|
213
|
+
throw profileError('QQ_PROFILE_UNKNOWN')
|
|
214
|
+
}
|
|
215
|
+
return { status: 'confirmed', mediaType: snapshot.mediaType, bytes: snapshot.bytes }
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
} finally {
|
|
219
|
+
await snapshot.cleanup()
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
}
|