oicq-icalingua-plus-plus 1.22.13 → 1.22.15
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 +1 -0
- package/lib/client.js +3 -0
- package/lib/message/builder.js +7 -3
- package/lib/message/chat.js +117 -1
- package/lib/service.js +119 -1
- package/package.json +4 -3
package/index.d.ts
CHANGED
|
@@ -934,6 +934,7 @@ export class Client extends EventEmitter {
|
|
|
934
934
|
|
|
935
935
|
/** 私聊 */
|
|
936
936
|
_sendPrivateMsg(user_id: number, message: Sendable, auto_escape?: boolean): Promise<Ret<{ message_id: string }>>;
|
|
937
|
+
sendFile(user_id: number, file: MediaFile, name?: string, process?: (percentage: string) => void): Promise<Ret<{ message_id: string }>>;
|
|
937
938
|
/** 群聊 */
|
|
938
939
|
sendGroupMsg(group_id: number, message: Sendable, auto_escape?: boolean): Promise<Ret<{ message_id: string }>>;
|
|
939
940
|
/** 群临时会话,大多数时候可以使用私聊达到同样效果 */
|
package/lib/client.js
CHANGED
|
@@ -351,6 +351,9 @@ class Client extends EventEmitter {
|
|
|
351
351
|
_sendPrivateMsg(user_id, message = "", auto_escape = false) {
|
|
352
352
|
return this._useProtocol(chat.sendMsg, [user_id, message, auto_escape, 0]);
|
|
353
353
|
}
|
|
354
|
+
sendFile(user_id, file, name, process) {
|
|
355
|
+
return this._useProtocol(chat.sendFile, [user_id, file, name, process]);
|
|
356
|
+
}
|
|
354
357
|
sendGroupMsg(group_id, message = "", auto_escape = false) {
|
|
355
358
|
return this._useProtocol(chat.sendMsg, [group_id, message, auto_escape, 1]);
|
|
356
359
|
}
|
package/lib/message/builder.js
CHANGED
|
@@ -161,10 +161,11 @@ class Builder {
|
|
|
161
161
|
* @param {number} target
|
|
162
162
|
* @param {0|1|2} type //0私聊 1群聊 2讨论组
|
|
163
163
|
*/
|
|
164
|
-
constructor(c, target, type) {
|
|
164
|
+
constructor(c, target, type, file = null) {
|
|
165
165
|
this.c = c;
|
|
166
166
|
this.target = target;
|
|
167
167
|
this.type = type;
|
|
168
|
+
this.file = file;
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
/**
|
|
@@ -845,7 +846,7 @@ class Builder {
|
|
|
845
846
|
if (tasks.length > 0)
|
|
846
847
|
var rsp = await Promise.race(tasks);
|
|
847
848
|
|
|
848
|
-
if (!this.elems.length) {
|
|
849
|
+
if (!this.elems.length && this.file === null) {
|
|
849
850
|
if (rsp) return rsp;
|
|
850
851
|
throw new Error("empty message");
|
|
851
852
|
}
|
|
@@ -867,6 +868,9 @@ class Builder {
|
|
|
867
868
|
} else {
|
|
868
869
|
let user_id = this.target;
|
|
869
870
|
routing = { 1: { 1: user_id } };
|
|
871
|
+
if (this.file !== null) {
|
|
872
|
+
routing = { 15: { 1: user_id, 2: 4 } };
|
|
873
|
+
}
|
|
870
874
|
if (this.c.sl.has(user_id)) {
|
|
871
875
|
try {
|
|
872
876
|
const group_id = this.c.sl.get(user_id).group_id;
|
|
@@ -910,7 +914,7 @@ class Builder {
|
|
|
910
914
|
return pb.encode({
|
|
911
915
|
1: this.routing,
|
|
912
916
|
2: content,
|
|
913
|
-
3: { 1: rich },
|
|
917
|
+
3: this.file !== null ? this.file : { 1: rich },
|
|
914
918
|
4: this.seq,
|
|
915
919
|
5: this.random,
|
|
916
920
|
6: this.type > 0 ? null : this.c.buildSyncCookie(),
|
package/lib/message/chat.js
CHANGED
|
@@ -8,8 +8,11 @@ const { getC2CMsgs, getGroupMsgs, getLastSeq } = require("./history");
|
|
|
8
8
|
const { parseC2CMsg, parseGroupMsg, parseForwardMsg } = require("./parser");
|
|
9
9
|
const common = require("../common");
|
|
10
10
|
const pb = require("../algo/pb");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
11
13
|
const { parseC2CMessageId, parseGroupMessageId, genMessageUuid, genRandom } = common;
|
|
12
14
|
const { ImageBuilder, uploadImages, buildImageFileParam } = require("./image");
|
|
15
|
+
const { highwayHttpUpload } = require("../service");
|
|
13
16
|
|
|
14
17
|
//send msg----------------------------------------------------------------------------------------------------
|
|
15
18
|
|
|
@@ -47,6 +50,119 @@ function sendMsg(target, message, escape, type) {
|
|
|
47
50
|
return builder.buildAndSend(message, escape);
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
async function sendFile(user_id, file, filename, callback) {
|
|
54
|
+
let filesize, filemd5, filesha, filestream
|
|
55
|
+
if (file instanceof Uint8Array) {
|
|
56
|
+
if (!Buffer.isBuffer(file))
|
|
57
|
+
file = Buffer.from(file)
|
|
58
|
+
filesize = file.length
|
|
59
|
+
filemd5 = md5(file), filesha = sha(file)
|
|
60
|
+
filename = filename ? String(filename) : ("file" + filemd5.toString("hex"))
|
|
61
|
+
filestream = Readable.from(file, { objectMode: false, highWaterMark: 524288 })
|
|
62
|
+
} else {
|
|
63
|
+
file = String(file)
|
|
64
|
+
filesize = (await fs.promises.stat(file)).size
|
|
65
|
+
;[filemd5, filesha] = await common.fileHash(file)
|
|
66
|
+
filename = filename ? String(filename) : path.basename(file)
|
|
67
|
+
filestream = fs.createReadStream(file, { highWaterMark: 524288 })
|
|
68
|
+
}
|
|
69
|
+
const body1700 = pb.encode({
|
|
70
|
+
1: 1700,
|
|
71
|
+
2: 6,
|
|
72
|
+
19: {
|
|
73
|
+
10: this.uin,
|
|
74
|
+
20: user_id,
|
|
75
|
+
30: filesize,
|
|
76
|
+
40: filename,
|
|
77
|
+
50: filemd5,
|
|
78
|
+
60: filesha,
|
|
79
|
+
70: "/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/" + filename,
|
|
80
|
+
80: 0,
|
|
81
|
+
90: 0,
|
|
82
|
+
100: 0,
|
|
83
|
+
110: filemd5,
|
|
84
|
+
},
|
|
85
|
+
101: 3,
|
|
86
|
+
102: 104,
|
|
87
|
+
200: 1,
|
|
88
|
+
})
|
|
89
|
+
const payload = await this.sendUni("OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_UPLOAD_V3-1700", body1700)
|
|
90
|
+
const rsp1700 = pb.decode(payload)[19]
|
|
91
|
+
|
|
92
|
+
if (rsp1700[10] !== 0)
|
|
93
|
+
throw new Error(`${rsp1700[10]}, ${rsp1700[20]}`)
|
|
94
|
+
|
|
95
|
+
const fid = rsp1700[90].toBuffer()
|
|
96
|
+
|
|
97
|
+
if (!rsp1700[110]) {
|
|
98
|
+
const ext = pb.encode({
|
|
99
|
+
1: 100,
|
|
100
|
+
2: 2,
|
|
101
|
+
100: {
|
|
102
|
+
100: {
|
|
103
|
+
1: 3,
|
|
104
|
+
100: this.uin,
|
|
105
|
+
200: user_id,
|
|
106
|
+
400: 0,
|
|
107
|
+
700: payload,
|
|
108
|
+
},
|
|
109
|
+
200: {
|
|
110
|
+
100: filesize,
|
|
111
|
+
200: filemd5,
|
|
112
|
+
300: filesha,
|
|
113
|
+
400: filemd5,
|
|
114
|
+
600: fid,
|
|
115
|
+
700: rsp1700[220].toBuffer(),
|
|
116
|
+
},
|
|
117
|
+
300: {
|
|
118
|
+
100: 2,
|
|
119
|
+
200: String(this.apk.subid),
|
|
120
|
+
300: 2,
|
|
121
|
+
400: "d92615c5",
|
|
122
|
+
600: 4,
|
|
123
|
+
},
|
|
124
|
+
400: {
|
|
125
|
+
100: filename,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
200: 1
|
|
129
|
+
})
|
|
130
|
+
await highwayHttpUpload.call(this, filestream, {
|
|
131
|
+
md5: filemd5,
|
|
132
|
+
size: filesize,
|
|
133
|
+
cmdid: 69, // CmdID.OfflineFile,
|
|
134
|
+
ext, callback
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const body800 = pb.encode({
|
|
139
|
+
1: 800,
|
|
140
|
+
2: 7,
|
|
141
|
+
10: {
|
|
142
|
+
10: this.uin,
|
|
143
|
+
20: user_id,
|
|
144
|
+
30: fid,
|
|
145
|
+
},
|
|
146
|
+
101: 3,
|
|
147
|
+
102: 104,
|
|
148
|
+
})
|
|
149
|
+
await this.sendUni("OfflineFilleHandleSvr.pb_ftn_CMD_REQ_UPLOAD_SUCC-800", body800)
|
|
150
|
+
const proto3 = {
|
|
151
|
+
2: {
|
|
152
|
+
1: {
|
|
153
|
+
1: 0,
|
|
154
|
+
3: fid,
|
|
155
|
+
4: filemd5,
|
|
156
|
+
5: filename,
|
|
157
|
+
6: filesize,
|
|
158
|
+
9: 1,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const builder = new Builder(this, user_id, 0, proto3);
|
|
163
|
+
return builder.buildAndSend('', false);
|
|
164
|
+
}
|
|
165
|
+
|
|
50
166
|
//recall----------------------------------------------------------------------------------------------------
|
|
51
167
|
|
|
52
168
|
/**
|
|
@@ -272,5 +388,5 @@ async function preloadImages(files = []) {
|
|
|
272
388
|
module.exports = {
|
|
273
389
|
sendMsg, sendTempMsg, recallMsg, reportReaded,
|
|
274
390
|
getOneMsg, getMsgs, getForwardMsg,
|
|
275
|
-
preloadImages
|
|
391
|
+
preloadImages, sendFile
|
|
276
392
|
};
|
package/lib/service.js
CHANGED
|
@@ -12,6 +12,7 @@ const { randomBytes } = require("crypto");
|
|
|
12
12
|
const tea = require("./algo/tea");
|
|
13
13
|
const pb = require("./algo/pb");
|
|
14
14
|
const { md5, NOOP, BUF0} = require("./common");
|
|
15
|
+
const axios = require("axios").default;
|
|
15
16
|
const MAX_UPLOAD_SIZE = 31457280;
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -150,6 +151,123 @@ function highwayUploadStream(readable, obj, ip, port) {
|
|
|
150
151
|
});
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
const __ = Buffer.from([41])
|
|
155
|
+
|
|
156
|
+
function highwayHttpUpload(readable, obj) {
|
|
157
|
+
const agent = new http.Agent({ maxSockets: 10 })
|
|
158
|
+
const ip = this.storage.ip
|
|
159
|
+
const port = this.storage.port
|
|
160
|
+
if (!port) throw new Error("没有上传通道,如果你刚刚登录,请等待几秒")
|
|
161
|
+
|
|
162
|
+
console.log(`highway(http) ip:${ip} port:${port}`)
|
|
163
|
+
const url = "http://" + ip + ":" + port + "/cgi-bin/httpconn?htcmd=0x6FF0087&uin=" + this.uin
|
|
164
|
+
let seq = 1
|
|
165
|
+
let offset = 0, limit = 524288
|
|
166
|
+
obj.ticket = this.storage.sig_session
|
|
167
|
+
|
|
168
|
+
const tasks = new Set()
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
require("events").setMaxListeners(233, controller.signal);
|
|
171
|
+
let finished = 0
|
|
172
|
+
|
|
173
|
+
readable.on("data", data => {
|
|
174
|
+
let _offset = 0
|
|
175
|
+
while (_offset < data.length) {
|
|
176
|
+
const chunk = data.slice(_offset, limit + _offset)
|
|
177
|
+
const head = pb.encode({
|
|
178
|
+
1: {
|
|
179
|
+
1: 1,
|
|
180
|
+
2: String(this.uin),
|
|
181
|
+
3: "PicUp.DataUp",
|
|
182
|
+
4: seq++,
|
|
183
|
+
5: 0,
|
|
184
|
+
6: this.apk.subid,
|
|
185
|
+
8: obj.cmdid,
|
|
186
|
+
},
|
|
187
|
+
2: {
|
|
188
|
+
1: 0,
|
|
189
|
+
2: obj.size,
|
|
190
|
+
3: offset + _offset,
|
|
191
|
+
4: chunk.length,
|
|
192
|
+
6: obj.ticket,
|
|
193
|
+
8: md5(chunk),
|
|
194
|
+
9: obj.md5,
|
|
195
|
+
10: 0,
|
|
196
|
+
13: 0,
|
|
197
|
+
},
|
|
198
|
+
3: obj.ext,
|
|
199
|
+
4: Date.now()
|
|
200
|
+
})
|
|
201
|
+
_offset += chunk.length
|
|
202
|
+
const _ = Buffer.allocUnsafe(9)
|
|
203
|
+
_.writeUInt8(40)
|
|
204
|
+
_.writeUInt32BE(head.length, 1)
|
|
205
|
+
_.writeUInt32BE(chunk.length, 5)
|
|
206
|
+
const buf = Buffer.concat([_, head, chunk, __])
|
|
207
|
+
const task = new Promise((resolve, reject) => {
|
|
208
|
+
axios.post(url, buf, {
|
|
209
|
+
responseType: "arraybuffer",
|
|
210
|
+
httpAgent: agent,
|
|
211
|
+
//cancelToken: c.token,
|
|
212
|
+
signal: controller.signal,
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Length": String(buf.length)
|
|
215
|
+
}
|
|
216
|
+
}).then(r => {
|
|
217
|
+
let percentage, rsp
|
|
218
|
+
try {
|
|
219
|
+
const buf = Buffer.from(r?.data)
|
|
220
|
+
const header = buf.slice(9, buf.length - 1)
|
|
221
|
+
rsp = pb.decode(header)
|
|
222
|
+
} catch (err) {
|
|
223
|
+
this.logger.error(err)
|
|
224
|
+
reject(err)
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
if (rsp?.[3] !== 0) {
|
|
228
|
+
reject(`${rsp[3]}, unknown highway error"`)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
++finished
|
|
232
|
+
percentage = (finished / tasks.size * 100).toFixed(2)
|
|
233
|
+
this.logger.debug(`highway(http) chunk uploaded (${percentage}%)`)
|
|
234
|
+
if (typeof obj.callback === "function" && percentage)
|
|
235
|
+
obj.callback(percentage)
|
|
236
|
+
if (finished < tasks.size && rsp[7]?.toBuffer().length > 0) {
|
|
237
|
+
controller.abort()
|
|
238
|
+
this.logger.debug(`highway(http) chunk uploaded (100.00%)`)
|
|
239
|
+
if (typeof obj.callback === "function")
|
|
240
|
+
obj.callback("100.00")
|
|
241
|
+
}
|
|
242
|
+
if (finished >= tasks.size && !rsp[7]?.toBuffer().length)
|
|
243
|
+
reject("文件校验未通过,上传失败")
|
|
244
|
+
resolve(undefined)
|
|
245
|
+
}).catch(err => {
|
|
246
|
+
if (err instanceof axios.Cancel === false) {
|
|
247
|
+
reject(err)
|
|
248
|
+
}
|
|
249
|
+
resolve(undefined)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
tasks.add(task)
|
|
253
|
+
}
|
|
254
|
+
offset += data.length
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
readable.on("err", reject)
|
|
259
|
+
.on("end", () => {
|
|
260
|
+
Promise.all(tasks).then(resolve).catch(err => {
|
|
261
|
+
if (err instanceof axios.Cancel === false) {
|
|
262
|
+
controller.abort()
|
|
263
|
+
reject(err)
|
|
264
|
+
}
|
|
265
|
+
resolve(undefined)
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
153
271
|
const ERROR_SIZE_TOO_BIG = new Error("文件体积超过30MB,拒绝下载");
|
|
154
272
|
|
|
155
273
|
class DownloadTransform extends stream.Transform {
|
|
@@ -200,5 +318,5 @@ function downloadFromWeb(url, headers, redirect = 0) {
|
|
|
200
318
|
}
|
|
201
319
|
|
|
202
320
|
module.exports = {
|
|
203
|
-
downloadFromWeb, highwayUploadStream, int32ip2str, MAX_UPLOAD_SIZE,
|
|
321
|
+
downloadFromWeb, highwayUploadStream, int32ip2str, highwayHttpUpload, MAX_UPLOAD_SIZE,
|
|
204
322
|
};
|
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.22.
|
|
5
|
-
"app_version": "1.22.
|
|
6
|
-
"upday": "2022/12/
|
|
4
|
+
"version": "1.22.15",
|
|
5
|
+
"app_version": "1.22.15",
|
|
6
|
+
"upday": "2022/12/24",
|
|
7
7
|
"protocol_version": "v11",
|
|
8
8
|
"description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
|
|
9
9
|
"main": "index.js",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"author": "The Icalingua++ Authors",
|
|
22
22
|
"license": "MPL-2.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"axios": "^1.2.1",
|
|
24
25
|
"jsqr": "^1.4.0",
|
|
25
26
|
"log4js": "^6.3.0",
|
|
26
27
|
"long": "^4.0.0",
|