oicq-icalingua-plus-plus 1.26.5 → 1.26.7
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/index.d.ts +17 -0
- package/lib/client.js +4 -0
- package/lib/core/friendlist.js +2 -3
- package/lib/message/face.js +12 -0
- package/lib/message/parser.js +5 -15
- package/lib/message/ptt.js +2 -0
- package/lib/service.js +117 -89
- package/package.json +3 -3
package/index.d.ts
CHANGED
|
@@ -56,6 +56,23 @@ export interface ConfBot {
|
|
|
56
56
|
|
|
57
57
|
/** 使用NT上线包,部分消息暂未支持 */
|
|
58
58
|
useNT?: boolean,
|
|
59
|
+
|
|
60
|
+
/** 指定APK信息,覆盖默认的协议参数 */
|
|
61
|
+
apk_info?: {
|
|
62
|
+
id?: string,
|
|
63
|
+
name?: string,
|
|
64
|
+
version?: string,
|
|
65
|
+
ver?: string,
|
|
66
|
+
sign?: Buffer,
|
|
67
|
+
buildtime?: number,
|
|
68
|
+
appid?: number,
|
|
69
|
+
subid?: number,
|
|
70
|
+
bitmap?: number,
|
|
71
|
+
sigmap?: number,
|
|
72
|
+
sdkver?: string,
|
|
73
|
+
ssover?: number,
|
|
74
|
+
qua?: string,
|
|
75
|
+
}
|
|
59
76
|
}
|
|
60
77
|
|
|
61
78
|
export interface Statistics {
|
package/lib/client.js
CHANGED
|
@@ -263,11 +263,15 @@ class Client extends EventEmitter {
|
|
|
263
263
|
data_dir: path.join(require.main ? require.main.path : process.cwd(), "data"),
|
|
264
264
|
ffmpeg_path: "ffmpeg",
|
|
265
265
|
ffprobe_path: "ffprobe",
|
|
266
|
+
apk_info: {},
|
|
266
267
|
...config
|
|
267
268
|
};
|
|
268
269
|
this.config = config;
|
|
269
270
|
this.dir = createDataDir(config.data_dir, uin);
|
|
270
271
|
this.apk = getApkInfo(config.platform);
|
|
272
|
+
if (config.apk_info) {
|
|
273
|
+
Object.assign(this.apk, config.apk_info);
|
|
274
|
+
}
|
|
271
275
|
if (this.config.useNT && this.apk.ssover < 20) this.apk.ssover = 20;
|
|
272
276
|
this.logger = log4js.getLogger(`[${platforms[config.platform] ||
|
|
273
277
|
((config.platform % 2 === 1 ? "Android_" : "aPad_") + this.apk.ver)
|
package/lib/core/friendlist.js
CHANGED
|
@@ -85,13 +85,12 @@ async function initFL() {
|
|
|
85
85
|
this.sync_finished = false;
|
|
86
86
|
const tmp = this.fl;
|
|
87
87
|
this.fl = new Map;
|
|
88
|
-
let start = 0, limit =
|
|
88
|
+
let start = 0, limit = 100;
|
|
89
89
|
while (1) {
|
|
90
90
|
try {
|
|
91
91
|
const total = await _initFL.call(this, start, limit, tmp);
|
|
92
92
|
start += limit;
|
|
93
|
-
if (start
|
|
94
|
-
if (start >= total) break;
|
|
93
|
+
if (start > total) break;
|
|
95
94
|
} catch (e) {
|
|
96
95
|
this.logger.debug(e);
|
|
97
96
|
this.logger.warn("加载好友列表出现异常,未加载完成。");
|
package/lib/message/face.js
CHANGED
|
@@ -391,6 +391,18 @@ const map = {
|
|
|
391
391
|
468: "/失眠",
|
|
392
392
|
469: "/坚毅",
|
|
393
393
|
470: "/马到成功",
|
|
394
|
+
472: "/心动",
|
|
395
|
+
474: "/给你一拳",
|
|
396
|
+
475: "/干饭",
|
|
397
|
+
476: "/不是吧",
|
|
398
|
+
477: "/你懂的",
|
|
399
|
+
478: "/对的对的",
|
|
400
|
+
479: "/不对不对",
|
|
401
|
+
480: "/散味儿",
|
|
402
|
+
481: "/学习",
|
|
403
|
+
482: "/热化了",
|
|
404
|
+
483: "/略",
|
|
405
|
+
484: "/比爱心",
|
|
394
406
|
};
|
|
395
407
|
|
|
396
408
|
const FACE_OLD_BUF = Buffer.from([0x00, 0x01, 0x00, 0x04, 0x52, 0xCC, 0xF5, 0xD0]);
|
package/lib/message/parser.js
CHANGED
|
@@ -612,28 +612,18 @@ class Parser {
|
|
|
612
612
|
|
|
613
613
|
data.file = buildImageFileParam(md5, size, width, height, imgType);
|
|
614
614
|
|
|
615
|
-
if (ntPath
|
|
615
|
+
if (ntPath) {
|
|
616
616
|
data.url = `https://${elem[1][2][3]}${ntPath}&spec=0`;
|
|
617
617
|
} else {
|
|
618
618
|
data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toUpperCase()}/0`;
|
|
619
619
|
}
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
newURL = await getNTPicURL.call(this.c, imgInfo, String(this.uid), true);
|
|
625
|
-
} else { //群聊
|
|
626
|
-
newURL = await getNTPicURL.call(this.c, imgInfo, this.gid);
|
|
627
|
-
}
|
|
628
|
-
if (newURL) {
|
|
629
|
-
data.url = newURL;
|
|
630
|
-
}
|
|
620
|
+
if (storeId === 1 && fileid) {
|
|
621
|
+
data.url = await getNTPicURLbyFileid.call(this.c, fileid);
|
|
622
|
+
} else if (storeId === 0) {
|
|
623
|
+
data.url = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${md5.toUpperCase()}/0`;
|
|
631
624
|
}
|
|
632
625
|
// QQNT 图片容错,某些 QQNT 的机器人会发出 appid 异常的图片
|
|
633
626
|
data.url = data.url.replace("multimedia.nt.qq.com.cn", "gchat.qpic.cn"); //这个host下面似乎不检查rkey有效期
|
|
634
|
-
if (!(await checkImgUrl(data.url))) {
|
|
635
|
-
data.url = await getNTPicURLbyFileid.call(this.c, fileid);
|
|
636
|
-
}
|
|
637
627
|
this.isNTImg = true;
|
|
638
628
|
return data;
|
|
639
629
|
}
|
package/lib/message/ptt.js
CHANGED
package/lib/service.js
CHANGED
|
@@ -176,117 +176,145 @@ function highwayUploadStream(readable, obj, ip, port) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
const __ = Buffer.from([41])
|
|
179
|
-
// TODO: 优化内存占用
|
|
180
179
|
function highwayHttpUpload(readable, obj) {
|
|
181
|
-
const agent = new http.Agent({ maxSockets:
|
|
180
|
+
const agent = new http.Agent({ maxSockets: 1 })
|
|
182
181
|
const ip = this.storage.ip
|
|
183
182
|
const port = this.storage.port
|
|
184
183
|
if (!port) throw new Error("没有上传通道,如果你刚刚登录,请等待几秒")
|
|
185
184
|
|
|
186
185
|
console.log(`highway(http) ip:${ip} port:${port}`)
|
|
187
186
|
const url = "http://" + ip + ":" + port + "/cgi-bin/httpconn?htcmd=0x6FF0087&uin=" + this.uin
|
|
188
|
-
|
|
189
|
-
let offset = 0, limit = 524288
|
|
187
|
+
const limit = 524288
|
|
190
188
|
obj.ticket = this.storage.sig_session
|
|
191
189
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let
|
|
190
|
+
const MAX_QUEUED = 3 // 队列中最多保留的分块数,控制内存上限(3×512KB≈1.5MB)
|
|
191
|
+
let seq = 1
|
|
192
|
+
let offset = 0
|
|
193
|
+
let readEnded = false
|
|
194
|
+
|
|
195
|
+
// 顺序队列:每读到一个完整分块就推入,drain 循环逐个串行上传
|
|
196
|
+
const queue = []
|
|
197
|
+
let notify = null // 用于队列非空时唤醒 drain
|
|
198
|
+
let errFlag = null // 记录首个错误,终止后续上传
|
|
199
|
+
let quickDone = false
|
|
200
|
+
|
|
201
|
+
const buildBuf = (chunk, chunkOffset) => {
|
|
202
|
+
const head = pb.encode({
|
|
203
|
+
1: {
|
|
204
|
+
1: 1,
|
|
205
|
+
2: String(this.uin),
|
|
206
|
+
3: "PicUp.DataUp",
|
|
207
|
+
4: seq++,
|
|
208
|
+
5: 0,
|
|
209
|
+
6: this.apk.subid,
|
|
210
|
+
8: obj.cmdid,
|
|
211
|
+
},
|
|
212
|
+
2: {
|
|
213
|
+
1: 0,
|
|
214
|
+
2: obj.size,
|
|
215
|
+
3: chunkOffset,
|
|
216
|
+
4: chunk.length,
|
|
217
|
+
6: obj.ticket,
|
|
218
|
+
8: md5(chunk),
|
|
219
|
+
9: obj.md5,
|
|
220
|
+
10: 0,
|
|
221
|
+
13: 0,
|
|
222
|
+
},
|
|
223
|
+
3: obj.ext,
|
|
224
|
+
4: Date.now()
|
|
225
|
+
})
|
|
226
|
+
const header = Buffer.allocUnsafe(9)
|
|
227
|
+
header.writeUInt8(40)
|
|
228
|
+
header.writeUInt32BE(head.length, 1)
|
|
229
|
+
header.writeUInt32BE(chunk.length, 5)
|
|
230
|
+
return Buffer.concat([header, head, chunk, __])
|
|
231
|
+
}
|
|
196
232
|
|
|
233
|
+
// 读取流 → 分块 → 入队(带背压:队列满时暂停读取)
|
|
197
234
|
readable.on("data", data => {
|
|
235
|
+
if (errFlag || quickDone) return
|
|
198
236
|
let _offset = 0
|
|
199
237
|
while (_offset < data.length) {
|
|
200
238
|
const chunk = data.slice(_offset, limit + _offset)
|
|
201
|
-
const head = pb.encode({
|
|
202
|
-
1: {
|
|
203
|
-
1: 1,
|
|
204
|
-
2: String(this.uin),
|
|
205
|
-
3: "PicUp.DataUp",
|
|
206
|
-
4: seq++,
|
|
207
|
-
5: 0,
|
|
208
|
-
6: this.apk.subid,
|
|
209
|
-
8: obj.cmdid,
|
|
210
|
-
},
|
|
211
|
-
2: {
|
|
212
|
-
1: 0,
|
|
213
|
-
2: obj.size,
|
|
214
|
-
3: offset + _offset,
|
|
215
|
-
4: chunk.length,
|
|
216
|
-
6: obj.ticket,
|
|
217
|
-
8: md5(chunk),
|
|
218
|
-
9: obj.md5,
|
|
219
|
-
10: 0,
|
|
220
|
-
13: 0,
|
|
221
|
-
},
|
|
222
|
-
3: obj.ext,
|
|
223
|
-
4: Date.now()
|
|
224
|
-
})
|
|
225
239
|
_offset += chunk.length
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
_.writeUInt32BE(chunk.length, 5)
|
|
230
|
-
const buf = Buffer.concat([_, head, chunk, __])
|
|
231
|
-
bufs.add(buf)
|
|
240
|
+
const buf = buildBuf(chunk, offset + _offset - chunk.length)
|
|
241
|
+
queue.push({ buf, chunkLen: chunk.length })
|
|
242
|
+
if (notify) { notify(); notify = null }
|
|
232
243
|
}
|
|
233
244
|
offset += data.length
|
|
245
|
+
// 队列积压过多,暂停读取,等上传消化后再恢复
|
|
246
|
+
if (queue.length >= MAX_QUEUED) {
|
|
247
|
+
readable.pause()
|
|
248
|
+
}
|
|
234
249
|
})
|
|
235
250
|
|
|
251
|
+
readable.on("err", err => { errFlag = err })
|
|
252
|
+
|
|
253
|
+
// 串行 drain 循环:逐个从队列取分块上传
|
|
254
|
+
const drain = async () => {
|
|
255
|
+
let uploadedBytes = 0
|
|
256
|
+
while (true) {
|
|
257
|
+
while (queue.length === 0) {
|
|
258
|
+
if (errFlag) return
|
|
259
|
+
if (readEnded) {
|
|
260
|
+
// 流已结束且队列为空,检查是否全部完成
|
|
261
|
+
if (uploadedBytes >= obj.size) return
|
|
262
|
+
}
|
|
263
|
+
// 等待新分块入队
|
|
264
|
+
await new Promise(r => { notify = r })
|
|
265
|
+
}
|
|
266
|
+
const { buf, chunkLen } = queue.shift()
|
|
267
|
+
const r = await axios.post(url, buf, {
|
|
268
|
+
responseType: "arraybuffer",
|
|
269
|
+
httpAgent: agent,
|
|
270
|
+
headers: {
|
|
271
|
+
"Content-Length": String(buf.length),
|
|
272
|
+
"Content-Type": "application/octet-stream"
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
let rsp
|
|
276
|
+
try {
|
|
277
|
+
const respBuf = Buffer.from(r?.data)
|
|
278
|
+
const header = respBuf.slice(9, respBuf.length - 1)
|
|
279
|
+
rsp = pb.decode(header)
|
|
280
|
+
} catch (err) {
|
|
281
|
+
throw err
|
|
282
|
+
}
|
|
283
|
+
if (rsp?.[3] !== 0) {
|
|
284
|
+
throw new Error(`${rsp[3]}, unknown highway error`)
|
|
285
|
+
}
|
|
286
|
+
uploadedBytes += chunkLen
|
|
287
|
+
const percentage = (uploadedBytes / obj.size * 100).toFixed(2)
|
|
288
|
+
this.logger.debug(`highway(http) chunk uploaded (${percentage}%)`)
|
|
289
|
+
if (typeof obj.callback === "function")
|
|
290
|
+
obj.callback(percentage)
|
|
291
|
+
// 快传:服务端已持有全部数据,跳过后续分块
|
|
292
|
+
if (rsp[7]?.toBuffer().length > 0) {
|
|
293
|
+
quickDone = true
|
|
294
|
+
this.logger.debug(`highway(http) can quick upload`)
|
|
295
|
+
this.logger.debug(`highway(http) chunk uploaded (100.00%)`)
|
|
296
|
+
if (typeof obj.callback === "function")
|
|
297
|
+
obj.callback("100.00")
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
// 队列消化后恢复读取
|
|
301
|
+
if (queue.length < MAX_QUEUED && readable.isPaused()) {
|
|
302
|
+
readable.resume()
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
236
307
|
return new Promise((resolve, reject) => {
|
|
237
308
|
readable.on("err", reject)
|
|
238
|
-
.on("end",
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
await axios.post(url, buf, {
|
|
243
|
-
responseType: "arraybuffer",
|
|
244
|
-
httpAgent: agent,
|
|
245
|
-
signal: controller.signal,
|
|
246
|
-
headers: {
|
|
247
|
-
"Content-Length": String(buf.length),
|
|
248
|
-
"Content-Type": "application/octet-stream"
|
|
249
|
-
}
|
|
250
|
-
}).then(r => {
|
|
251
|
-
let percentage, rsp
|
|
252
|
-
try {
|
|
253
|
-
const buf = Buffer.from(r?.data)
|
|
254
|
-
const header = buf.slice(9, buf.length - 1)
|
|
255
|
-
rsp = pb.decode(header)
|
|
256
|
-
} catch (err) {
|
|
257
|
-
this.logger.error(err)
|
|
258
|
-
reject(err)
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
|
-
if (rsp?.[3] !== 0) {
|
|
262
|
-
controller.abort()
|
|
263
|
-
reject(new Error(`${rsp[3]}, unknown highway error"`))
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
++finished
|
|
267
|
-
percentage = (finished / bufs.size * 100).toFixed(2)
|
|
268
|
-
this.logger.debug(`highway(http) chunk uploaded (${percentage}%)`)
|
|
269
|
-
if (typeof obj.callback === "function" && percentage)
|
|
270
|
-
obj.callback(percentage)
|
|
271
|
-
if (finished < bufs.size && rsp[7]?.toBuffer().length > 0) {
|
|
272
|
-
controller.abort()
|
|
273
|
-
flag = true
|
|
274
|
-
this.logger.debug(`highway(http) can quick upload`)
|
|
275
|
-
this.logger.debug(`highway(http) chunk uploaded (100.00%)`)
|
|
276
|
-
if (typeof obj.callback === "function")
|
|
277
|
-
obj.callback("100.00")
|
|
278
|
-
}
|
|
279
|
-
if (finished >= bufs.size && !rsp[7]?.toBuffer().length)
|
|
280
|
-
reject("文件校验未通过,上传失败")
|
|
281
|
-
}).catch(err => {
|
|
282
|
-
if (err instanceof axios.Cancel === false) {
|
|
283
|
-
reject(err)
|
|
284
|
-
}
|
|
285
|
-
flag = true
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
resolve(undefined)
|
|
309
|
+
readable.on("end", () => {
|
|
310
|
+
readEnded = true
|
|
311
|
+
// 流结束时唤醒 drain(如果它正在等待)
|
|
312
|
+
if (notify) { notify(); notify = null }
|
|
289
313
|
})
|
|
314
|
+
drain().then(() => {
|
|
315
|
+
if (errFlag) reject(errFlag)
|
|
316
|
+
else resolve(undefined)
|
|
317
|
+
}).catch(reject)
|
|
290
318
|
})
|
|
291
319
|
}
|
|
292
320
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oicq-icalingua-plus-plus",
|
|
3
3
|
"app_name": "oicq-icalingua-plus-plus",
|
|
4
|
-
"version": "1.26.
|
|
5
|
-
"app_version": "1.26.
|
|
6
|
-
"upday": "2026/
|
|
4
|
+
"version": "1.26.7",
|
|
5
|
+
"app_version": "1.26.7",
|
|
6
|
+
"upday": "2026/07/18",
|
|
7
7
|
"protocol_version": "v11",
|
|
8
8
|
"description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
|
|
9
9
|
"main": "index.js",
|