@juzi/wechaty-puppet-service 1.0.113 → 1.0.115
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/dist/cjs/src/client/call-raw-payload-parser.spec.d.ts +3 -0
- package/dist/cjs/src/client/call-raw-payload-parser.spec.d.ts.map +1 -0
- package/dist/cjs/src/client/call-raw-payload-parser.spec.js +128 -0
- package/dist/cjs/src/client/call-raw-payload-parser.spec.js.map +1 -0
- package/dist/cjs/src/client/payload-store.js +2 -2
- package/dist/cjs/src/client/payload-store.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.d.ts +21 -0
- package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
- package/dist/cjs/src/client/puppet-service.js +230 -1
- package/dist/cjs/src/client/puppet-service.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.spec.js.map +1 -1
- package/dist/cjs/src/package-json.js +4 -4
- package/dist/cjs/src/package-json.js.map +1 -1
- package/dist/cjs/src/pure-functions/timestamp.d.ts +15 -1
- package/dist/cjs/src/pure-functions/timestamp.d.ts.map +1 -1
- package/dist/cjs/src/pure-functions/timestamp.js +10 -1
- package/dist/cjs/src/pure-functions/timestamp.js.map +1 -1
- package/dist/cjs/src/pure-functions/timestamp.spec.d.ts +3 -0
- package/dist/cjs/src/pure-functions/timestamp.spec.d.ts.map +1 -0
- package/dist/cjs/src/pure-functions/timestamp.spec.js +48 -0
- package/dist/cjs/src/pure-functions/timestamp.spec.js.map +1 -0
- package/dist/cjs/src/server/event-stream-manager.d.ts.map +1 -1
- package/dist/cjs/src/server/event-stream-manager.js +7 -0
- package/dist/cjs/src/server/event-stream-manager.js.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.d.ts.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.js +296 -2
- package/dist/cjs/src/server/puppet-implementation.js.map +1 -1
- package/dist/cjs/src/utils/call-media-mapping.d.ts +17 -0
- package/dist/cjs/src/utils/call-media-mapping.d.ts.map +1 -0
- package/dist/cjs/src/utils/call-media-mapping.js +60 -0
- package/dist/cjs/src/utils/call-media-mapping.js.map +1 -0
- package/dist/cjs/src/utils/call-media-mapping.spec.d.ts +3 -0
- package/dist/cjs/src/utils/call-media-mapping.spec.d.ts.map +1 -0
- package/dist/cjs/src/utils/call-media-mapping.spec.js +65 -0
- package/dist/cjs/src/utils/call-media-mapping.spec.js.map +1 -0
- package/dist/esm/src/client/call-raw-payload-parser.spec.d.ts +3 -0
- package/dist/esm/src/client/call-raw-payload-parser.spec.d.ts.map +1 -0
- package/dist/esm/src/client/call-raw-payload-parser.spec.js +103 -0
- package/dist/esm/src/client/call-raw-payload-parser.spec.js.map +1 -0
- package/dist/esm/src/client/payload-store.js +2 -2
- package/dist/esm/src/client/payload-store.js.map +1 -1
- package/dist/esm/src/client/puppet-service.d.ts +21 -0
- package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
- package/dist/esm/src/client/puppet-service.js +232 -3
- package/dist/esm/src/client/puppet-service.js.map +1 -1
- package/dist/esm/src/client/puppet-service.spec.js +1 -1
- package/dist/esm/src/client/puppet-service.spec.js.map +1 -1
- package/dist/esm/src/package-json.js +4 -4
- package/dist/esm/src/package-json.js.map +1 -1
- package/dist/esm/src/pure-functions/timestamp.d.ts +15 -1
- package/dist/esm/src/pure-functions/timestamp.d.ts.map +1 -1
- package/dist/esm/src/pure-functions/timestamp.js +9 -1
- package/dist/esm/src/pure-functions/timestamp.js.map +1 -1
- package/dist/esm/src/pure-functions/timestamp.spec.d.ts +3 -0
- package/dist/esm/src/pure-functions/timestamp.spec.d.ts.map +1 -0
- package/dist/esm/src/pure-functions/timestamp.spec.js +46 -0
- package/dist/esm/src/pure-functions/timestamp.spec.js.map +1 -0
- package/dist/esm/src/server/event-stream-manager.d.ts.map +1 -1
- package/dist/esm/src/server/event-stream-manager.js +7 -0
- package/dist/esm/src/server/event-stream-manager.js.map +1 -1
- package/dist/esm/src/server/puppet-implementation.d.ts.map +1 -1
- package/dist/esm/src/server/puppet-implementation.js +296 -2
- package/dist/esm/src/server/puppet-implementation.js.map +1 -1
- package/dist/esm/src/utils/call-media-mapping.d.ts +17 -0
- package/dist/esm/src/utils/call-media-mapping.d.ts.map +1 -0
- package/dist/esm/src/utils/call-media-mapping.js +32 -0
- package/dist/esm/src/utils/call-media-mapping.js.map +1 -0
- package/dist/esm/src/utils/call-media-mapping.spec.d.ts +3 -0
- package/dist/esm/src/utils/call-media-mapping.spec.d.ts.map +1 -0
- package/dist/esm/src/utils/call-media-mapping.spec.js +40 -0
- package/dist/esm/src/utils/call-media-mapping.spec.js.map +1 -0
- package/package.json +4 -4
- package/src/client/call-raw-payload-parser.spec.ts +131 -0
- package/src/client/payload-store.ts +2 -2
- package/src/client/puppet-service.spec.ts +3 -1
- package/src/client/puppet-service.ts +438 -4
- package/src/package-json.ts +4 -4
- package/src/pure-functions/timestamp.spec.ts +56 -0
- package/src/pure-functions/timestamp.ts +19 -0
- package/src/server/event-stream-manager.ts +7 -0
- package/src/server/puppet-implementation.ts +384 -3
- package/src/utils/call-media-mapping.spec.ts +53 -0
- package/src/utils/call-media-mapping.ts +35 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
|
|
3
|
+
import { test } from 'tstest'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
millisecondsFromTimestamp,
|
|
7
|
+
millisecondsFromTimestampObject,
|
|
8
|
+
timestampFromMilliseconds,
|
|
9
|
+
} from './timestamp.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 1. ms -> Timestamp -> ms round-trips for millisecond-aligned values.
|
|
13
|
+
*/
|
|
14
|
+
test('timestampFromMilliseconds <-> millisecondsFromTimestamp round-trip', async t => {
|
|
15
|
+
const cases = [
|
|
16
|
+
0,
|
|
17
|
+
1_000,
|
|
18
|
+
1_700_000_000_000,
|
|
19
|
+
1_700_000_000_123,
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
for (const ms of cases) {
|
|
23
|
+
const roundTripped = millisecondsFromTimestamp(timestampFromMilliseconds(ms))
|
|
24
|
+
t.equal(roundTripped, ms, `round-trip identity for ${ms}ms`)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 2. The plain-object variant agrees with the instance variant for the same
|
|
30
|
+
* Timestamp (this is the `toObject()` shape used by callRawPayloadParser).
|
|
31
|
+
*/
|
|
32
|
+
test('millisecondsFromTimestampObject matches millisecondsFromTimestamp', async t => {
|
|
33
|
+
const cases = [
|
|
34
|
+
0,
|
|
35
|
+
1_000,
|
|
36
|
+
1_700_000_000_123,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for (const ms of cases) {
|
|
40
|
+
const timestamp = timestampFromMilliseconds(ms)
|
|
41
|
+
const fromInstance = millisecondsFromTimestamp(timestamp)
|
|
42
|
+
const fromObject = millisecondsFromTimestampObject(timestamp.toObject())
|
|
43
|
+
t.equal(fromObject, fromInstance, `instance and object conversion agree for ${ms}ms`)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 3. Sub-millisecond nanos are rounded to the nearest millisecond.
|
|
49
|
+
*/
|
|
50
|
+
test('millisecondsFromTimestampObject rounds nanos to nearest millisecond', async t => {
|
|
51
|
+
t.equal(millisecondsFromTimestampObject({ seconds: 1, nanos: 0 }), 1000, 'no nanos')
|
|
52
|
+
t.equal(millisecondsFromTimestampObject({ seconds: 1, nanos: 499_999 }), 1000, '0.499ms rounds down')
|
|
53
|
+
t.equal(millisecondsFromTimestampObject({ seconds: 1, nanos: 500_000 }), 1001, '0.5ms rounds up')
|
|
54
|
+
t.equal(millisecondsFromTimestampObject({ seconds: 1, nanos: 999_999 }), 1001, '0.999ms rounds up')
|
|
55
|
+
t.equal(millisecondsFromTimestampObject({ seconds: 0, nanos: 123_000_000 }), 123, '123ms from nanos')
|
|
56
|
+
})
|
|
@@ -22,7 +22,26 @@ function millisecondsFromTimestamp (timestamp: ReturnType<typeof timestampFromMi
|
|
|
22
22
|
return seconds * 1000 + nanos / 1000000
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Plain `{ seconds, nanos }` shape produced by a protobuf `Message.toObject()`
|
|
27
|
+
* for a `google.protobuf.Timestamp` field.
|
|
28
|
+
*/
|
|
29
|
+
type TimestampObject = {
|
|
30
|
+
seconds: number,
|
|
31
|
+
nanos: number,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Same conversion as `millisecondsFromTimestamp`, but for the plain
|
|
36
|
+
* `{ seconds, nanos }` shape produced by `Message.toObject()` rather than a
|
|
37
|
+
* Timestamp class instance. Nanos are rounded to the nearest millisecond.
|
|
38
|
+
*/
|
|
39
|
+
function millisecondsFromTimestampObject (timestamp: TimestampObject): number {
|
|
40
|
+
return timestamp.seconds * 1000 + Math.round(timestamp.nanos / 1000000)
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
export {
|
|
26
44
|
millisecondsFromTimestamp,
|
|
45
|
+
millisecondsFromTimestampObject,
|
|
27
46
|
timestampFromMilliseconds,
|
|
28
47
|
}
|
|
@@ -322,6 +322,13 @@ class EventStreamManager {
|
|
|
322
322
|
this.offCallbackList.push(off)
|
|
323
323
|
break
|
|
324
324
|
}
|
|
325
|
+
case 'call': {
|
|
326
|
+
const listener = (payload: PUPPET.payloads.EventCall) => this.grpcEmit(grpcPuppet.EventType.EVENT_TYPE_CALL, payload)
|
|
327
|
+
this.puppet.on('call', listener)
|
|
328
|
+
const off = () => this.puppet.off('call', listener)
|
|
329
|
+
this.offCallbackList.push(off)
|
|
330
|
+
break
|
|
331
|
+
}
|
|
325
332
|
case 'reset':
|
|
326
333
|
// the `reset` event should be dealed internally, should not send out
|
|
327
334
|
break
|
|
@@ -44,8 +44,39 @@ import { log } from '../config.js'
|
|
|
44
44
|
import { grpcError } from './grpc-error.js'
|
|
45
45
|
import { EventStreamManager } from './event-stream-manager.js'
|
|
46
46
|
import { OptionalBooleanUnwrapper, OptionalBooleanWrapper, callRecordPayloadToPb, channelPayloadToPb, chatHistoryPayloadToPb, postPbToPayload, urlLinkPayloadToPb, channelCardPayloadToPb } from '../utils/pb-payload-helper.js'
|
|
47
|
+
import { grpcCallTypeToPuppetMedia, puppetCallMediaTypeToGrpc } from '../utils/call-media-mapping.js'
|
|
47
48
|
import { TextContentType } from '@juzi/wechaty-puppet/types'
|
|
48
49
|
|
|
50
|
+
type MessageBatchSendResponse = Awaited<ReturnType<PUPPET.impls.PuppetInterface['messageBatchSendText']>>
|
|
51
|
+
|
|
52
|
+
type GrpcBatchResult = {
|
|
53
|
+
setConversationId(value: string): void,
|
|
54
|
+
setError(value: string): void,
|
|
55
|
+
setId(value: string): void,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type GrpcBatchResponse<Result extends GrpcBatchResult> = {
|
|
59
|
+
setResultsList(value: Result[]): void,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const setGrpcBatchResults = <Result extends GrpcBatchResult> (
|
|
63
|
+
response : GrpcBatchResponse<Result>,
|
|
64
|
+
ResultClass : new () => Result,
|
|
65
|
+
payload : MessageBatchSendResponse,
|
|
66
|
+
): void => {
|
|
67
|
+
response.setResultsList(payload.results.map(result => {
|
|
68
|
+
const pbResult = new ResultClass()
|
|
69
|
+
pbResult.setConversationId(result.conversationId)
|
|
70
|
+
if (result.id) {
|
|
71
|
+
pbResult.setId(result.id)
|
|
72
|
+
}
|
|
73
|
+
if (result.error) {
|
|
74
|
+
pbResult.setError(result.error)
|
|
75
|
+
}
|
|
76
|
+
return pbResult
|
|
77
|
+
}))
|
|
78
|
+
}
|
|
79
|
+
|
|
49
80
|
function puppetImplementation (
|
|
50
81
|
puppet : PUPPET.impls.PuppetInterface,
|
|
51
82
|
FileBoxUuid : typeof FileBox,
|
|
@@ -1251,6 +1282,180 @@ function puppetImplementation (
|
|
|
1251
1282
|
}
|
|
1252
1283
|
},
|
|
1253
1284
|
|
|
1285
|
+
messageBatchSendText: async (call, callback) => {
|
|
1286
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendText()')
|
|
1287
|
+
|
|
1288
|
+
try {
|
|
1289
|
+
const results = await puppet.messageBatchSendText(
|
|
1290
|
+
call.request.getConversationIdsList(),
|
|
1291
|
+
call.request.getText(),
|
|
1292
|
+
call.request.getBatchTaskId(),
|
|
1293
|
+
)
|
|
1294
|
+
const response = new grpcPuppet.MessageBatchSendTextResponse()
|
|
1295
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendTextResponse.Result, results)
|
|
1296
|
+
return callback(null, response)
|
|
1297
|
+
} catch (e) {
|
|
1298
|
+
return grpcError('messageBatchSendText', e, callback)
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
|
|
1302
|
+
messageBatchSendFile: async (call, callback) => {
|
|
1303
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendFile()')
|
|
1304
|
+
|
|
1305
|
+
try {
|
|
1306
|
+
const results = await puppet.messageBatchSendFile(
|
|
1307
|
+
call.request.getConversationIdsList(),
|
|
1308
|
+
FileBoxUuid.fromJSON(call.request.getFileBox()),
|
|
1309
|
+
call.request.getBatchTaskId(),
|
|
1310
|
+
)
|
|
1311
|
+
const response = new grpcPuppet.MessageBatchSendFileResponse()
|
|
1312
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendFileResponse.Result, results)
|
|
1313
|
+
return callback(null, response)
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
return grpcError('messageBatchSendFile', e, callback)
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
|
|
1319
|
+
messageBatchForward: async (call, callback) => {
|
|
1320
|
+
log.verbose('PuppetServiceImpl', 'messageBatchForward()')
|
|
1321
|
+
|
|
1322
|
+
try {
|
|
1323
|
+
const messageIds = call.request.getMessageIdsList()
|
|
1324
|
+
const results = await puppet.messageBatchForward(
|
|
1325
|
+
call.request.getConversationIdsList(),
|
|
1326
|
+
messageIds.length > 0 ? messageIds : call.request.getMessageId(),
|
|
1327
|
+
call.request.getBatchTaskId(),
|
|
1328
|
+
)
|
|
1329
|
+
const response = new grpcPuppet.MessageBatchForwardResponse()
|
|
1330
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchForwardResponse.Result, results)
|
|
1331
|
+
return callback(null, response)
|
|
1332
|
+
} catch (e) {
|
|
1333
|
+
return grpcError('messageBatchForward', e, callback)
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
|
|
1337
|
+
messageBatchSendContact: async (call, callback) => {
|
|
1338
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendContact()')
|
|
1339
|
+
|
|
1340
|
+
try {
|
|
1341
|
+
const results = await puppet.messageBatchSendContact(
|
|
1342
|
+
call.request.getConversationIdsList(),
|
|
1343
|
+
call.request.getContactId(),
|
|
1344
|
+
call.request.getBatchTaskId(),
|
|
1345
|
+
)
|
|
1346
|
+
const response = new grpcPuppet.MessageBatchSendContactResponse()
|
|
1347
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendContactResponse.Result, results)
|
|
1348
|
+
return callback(null, response)
|
|
1349
|
+
} catch (e) {
|
|
1350
|
+
return grpcError('messageBatchSendContact', e, callback)
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
|
|
1354
|
+
messageBatchSendUrl: async (call, callback) => {
|
|
1355
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendUrl()')
|
|
1356
|
+
|
|
1357
|
+
try {
|
|
1358
|
+
const payload = call.request.getUrlLink()?.toObject()
|
|
1359
|
+
if (!payload) {
|
|
1360
|
+
throw new Error('no url link payload found')
|
|
1361
|
+
}
|
|
1362
|
+
const results = await puppet.messageBatchSendUrl(
|
|
1363
|
+
call.request.getConversationIdsList(),
|
|
1364
|
+
payload,
|
|
1365
|
+
call.request.getBatchTaskId(),
|
|
1366
|
+
)
|
|
1367
|
+
const response = new grpcPuppet.MessageBatchSendUrlResponse()
|
|
1368
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendUrlResponse.Result, results)
|
|
1369
|
+
return callback(null, response)
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
return grpcError('messageBatchSendUrl', e, callback)
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
1374
|
+
|
|
1375
|
+
messageBatchSendMiniProgram: async (call, callback) => {
|
|
1376
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendMiniProgram()')
|
|
1377
|
+
|
|
1378
|
+
try {
|
|
1379
|
+
const payload = call.request.getMiniProgram()?.toObject()
|
|
1380
|
+
if (!payload) {
|
|
1381
|
+
throw new Error('no mini program payload found')
|
|
1382
|
+
}
|
|
1383
|
+
const results = await puppet.messageBatchSendMiniProgram(
|
|
1384
|
+
call.request.getConversationIdsList(),
|
|
1385
|
+
payload,
|
|
1386
|
+
call.request.getBatchTaskId(),
|
|
1387
|
+
)
|
|
1388
|
+
const response = new grpcPuppet.MessageBatchSendMiniProgramResponse()
|
|
1389
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendMiniProgramResponse.Result, results)
|
|
1390
|
+
return callback(null, response)
|
|
1391
|
+
} catch (e) {
|
|
1392
|
+
return grpcError('messageBatchSendMiniProgram', e, callback)
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
|
|
1396
|
+
messageBatchSendLocation: async (call, callback) => {
|
|
1397
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendLocation()')
|
|
1398
|
+
|
|
1399
|
+
try {
|
|
1400
|
+
const payload = call.request.getLocation()?.toObject()
|
|
1401
|
+
if (!payload) {
|
|
1402
|
+
throw new Error('no location payload found')
|
|
1403
|
+
}
|
|
1404
|
+
const results = await puppet.messageBatchSendLocation(
|
|
1405
|
+
call.request.getConversationIdsList(),
|
|
1406
|
+
payload,
|
|
1407
|
+
call.request.getBatchTaskId(),
|
|
1408
|
+
)
|
|
1409
|
+
const response = new grpcPuppet.MessageBatchSendLocationResponse()
|
|
1410
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendLocationResponse.Result, results)
|
|
1411
|
+
return callback(null, response)
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
return grpcError('messageBatchSendLocation', e, callback)
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
|
|
1417
|
+
messageBatchSendChannel: async (call, callback) => {
|
|
1418
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendChannel()')
|
|
1419
|
+
|
|
1420
|
+
try {
|
|
1421
|
+
const payload = call.request.getChannel()?.toObject()
|
|
1422
|
+
if (!payload) {
|
|
1423
|
+
throw new Error('no channel payload found')
|
|
1424
|
+
}
|
|
1425
|
+
const results = await puppet.messageBatchSendChannel(
|
|
1426
|
+
call.request.getConversationIdsList(),
|
|
1427
|
+
payload,
|
|
1428
|
+
call.request.getBatchTaskId(),
|
|
1429
|
+
)
|
|
1430
|
+
const response = new grpcPuppet.MessageBatchSendChannelResponse()
|
|
1431
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendChannelResponse.Result, results)
|
|
1432
|
+
return callback(null, response)
|
|
1433
|
+
} catch (e) {
|
|
1434
|
+
return grpcError('messageBatchSendChannel', e, callback)
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
|
|
1438
|
+
messageBatchSendChannelCard: async (call, callback) => {
|
|
1439
|
+
log.verbose('PuppetServiceImpl', 'messageBatchSendChannelCard()')
|
|
1440
|
+
|
|
1441
|
+
try {
|
|
1442
|
+
const payload = call.request.getChannelCard()?.toObject()
|
|
1443
|
+
if (!payload) {
|
|
1444
|
+
throw new Error('no channel card payload found')
|
|
1445
|
+
}
|
|
1446
|
+
const results = await puppet.messageBatchSendChannelCard(
|
|
1447
|
+
call.request.getConversationIdsList(),
|
|
1448
|
+
payload,
|
|
1449
|
+
call.request.getBatchTaskId(),
|
|
1450
|
+
)
|
|
1451
|
+
const response = new grpcPuppet.MessageBatchSendChannelCardResponse()
|
|
1452
|
+
setGrpcBatchResults(response, grpcPuppet.MessageBatchSendChannelCardResponse.Result, results)
|
|
1453
|
+
return callback(null, response)
|
|
1454
|
+
} catch (e) {
|
|
1455
|
+
return grpcError('messageBatchSendChannelCard', e, callback)
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
|
|
1254
1459
|
messageSendUrl: async (call, callback) => {
|
|
1255
1460
|
log.verbose('PuppetServiceImpl', 'messageSendUrl()')
|
|
1256
1461
|
|
|
@@ -1494,9 +1699,11 @@ function puppetImplementation (
|
|
|
1494
1699
|
const inviteOnly = call.request.getInviteOnly()
|
|
1495
1700
|
const quoteIds = call.request.getQuoteIdsList()
|
|
1496
1701
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1702
|
+
if (contactIds.length > 1) {
|
|
1703
|
+
await puppet.roomAddV2(roomId, contactIds, inviteOnly, quoteIds)
|
|
1704
|
+
} else {
|
|
1705
|
+
await puppet.roomAdd(roomId, contactId || contactIds[0] || '', inviteOnly, quoteIds)
|
|
1706
|
+
}
|
|
1500
1707
|
|
|
1501
1708
|
return callback(null, new grpcPuppet.RoomAddResponse())
|
|
1502
1709
|
|
|
@@ -2752,6 +2959,180 @@ function puppetImplementation (
|
|
|
2752
2959
|
|
|
2753
2960
|
},
|
|
2754
2961
|
|
|
2962
|
+
callInvite: async (call, callback) => {
|
|
2963
|
+
log.verbose('PuppetServiceImpl', 'callInvite()')
|
|
2964
|
+
|
|
2965
|
+
try {
|
|
2966
|
+
const contactIds = call.request.getContactIdsList()
|
|
2967
|
+
if (contactIds.length === 0) {
|
|
2968
|
+
throw new Error('callInvite: contact_ids is required')
|
|
2969
|
+
}
|
|
2970
|
+
const media = grpcCallTypeToPuppetMedia(call.request.getMedia())
|
|
2971
|
+
if (media === undefined) {
|
|
2972
|
+
throw new Error('callInvite: media is required (got CALL_TYPE_UNKNOWN on the wire)')
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
const callId = await puppet.callInvite(contactIds, media)
|
|
2976
|
+
if (!callId) {
|
|
2977
|
+
throw new Error('callInvite: puppet returned empty callId')
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
const response = new grpcPuppet.CallInviteResponse()
|
|
2981
|
+
response.setCallId(callId)
|
|
2982
|
+
|
|
2983
|
+
return callback(null, response)
|
|
2984
|
+
} catch (e) {
|
|
2985
|
+
return grpcError('callInvite', e, callback)
|
|
2986
|
+
}
|
|
2987
|
+
},
|
|
2988
|
+
|
|
2989
|
+
callAdd: async (call, callback) => {
|
|
2990
|
+
log.verbose('PuppetServiceImpl', 'callAdd()')
|
|
2991
|
+
|
|
2992
|
+
try {
|
|
2993
|
+
const callId = call.request.getCallId()
|
|
2994
|
+
if (!callId) {
|
|
2995
|
+
throw new Error('callAdd: call_id is required')
|
|
2996
|
+
}
|
|
2997
|
+
const contactIds = call.request.getContactIdsList()
|
|
2998
|
+
if (contactIds.length === 0) {
|
|
2999
|
+
throw new Error('callAdd: contact_ids is required')
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
await puppet.callAdd(callId, contactIds)
|
|
3003
|
+
|
|
3004
|
+
return callback(null, new grpcPuppet.CallAddResponse())
|
|
3005
|
+
} catch (e) {
|
|
3006
|
+
return grpcError('callAdd', e, callback)
|
|
3007
|
+
}
|
|
3008
|
+
},
|
|
3009
|
+
|
|
3010
|
+
callAccept: async (call, callback) => {
|
|
3011
|
+
log.verbose('PuppetServiceImpl', 'callAccept()')
|
|
3012
|
+
|
|
3013
|
+
try {
|
|
3014
|
+
const callId = call.request.getCallId()
|
|
3015
|
+
if (!callId) {
|
|
3016
|
+
throw new Error('callAccept: call_id is required')
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
await puppet.callAccept(callId)
|
|
3020
|
+
|
|
3021
|
+
return callback(null, new grpcPuppet.CallAcceptResponse())
|
|
3022
|
+
} catch (e) {
|
|
3023
|
+
return grpcError('callAccept', e, callback)
|
|
3024
|
+
}
|
|
3025
|
+
},
|
|
3026
|
+
|
|
3027
|
+
callReject: async (call, callback) => {
|
|
3028
|
+
log.verbose('PuppetServiceImpl', 'callReject()')
|
|
3029
|
+
|
|
3030
|
+
try {
|
|
3031
|
+
const callId = call.request.getCallId()
|
|
3032
|
+
if (!callId) {
|
|
3033
|
+
throw new Error('callReject: call_id is required')
|
|
3034
|
+
}
|
|
3035
|
+
const reason = call.request.getReason() || undefined
|
|
3036
|
+
|
|
3037
|
+
await puppet.callReject(callId, reason)
|
|
3038
|
+
|
|
3039
|
+
return callback(null, new grpcPuppet.CallRejectResponse())
|
|
3040
|
+
} catch (e) {
|
|
3041
|
+
return grpcError('callReject', e, callback)
|
|
3042
|
+
}
|
|
3043
|
+
},
|
|
3044
|
+
|
|
3045
|
+
callCancel: async (call, callback) => {
|
|
3046
|
+
log.verbose('PuppetServiceImpl', 'callCancel()')
|
|
3047
|
+
|
|
3048
|
+
try {
|
|
3049
|
+
const callId = call.request.getCallId()
|
|
3050
|
+
if (!callId) {
|
|
3051
|
+
throw new Error('callCancel: call_id is required')
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
await puppet.callCancel(callId)
|
|
3055
|
+
|
|
3056
|
+
return callback(null, new grpcPuppet.CallCancelResponse())
|
|
3057
|
+
} catch (e) {
|
|
3058
|
+
return grpcError('callCancel', e, callback)
|
|
3059
|
+
}
|
|
3060
|
+
},
|
|
3061
|
+
|
|
3062
|
+
callHangup: async (call, callback) => {
|
|
3063
|
+
log.verbose('PuppetServiceImpl', 'callHangup()')
|
|
3064
|
+
|
|
3065
|
+
try {
|
|
3066
|
+
const callId = call.request.getCallId()
|
|
3067
|
+
if (!callId) {
|
|
3068
|
+
throw new Error('callHangup: call_id is required')
|
|
3069
|
+
}
|
|
3070
|
+
const reason = call.request.getReason() || undefined
|
|
3071
|
+
|
|
3072
|
+
await puppet.callHangup(callId, reason)
|
|
3073
|
+
|
|
3074
|
+
return callback(null, new grpcPuppet.CallHangupResponse())
|
|
3075
|
+
} catch (e) {
|
|
3076
|
+
return grpcError('callHangup', e, callback)
|
|
3077
|
+
}
|
|
3078
|
+
},
|
|
3079
|
+
|
|
3080
|
+
callMediaEndpoint: async (call, callback) => {
|
|
3081
|
+
log.verbose('PuppetServiceImpl', 'callMediaEndpoint()')
|
|
3082
|
+
|
|
3083
|
+
try {
|
|
3084
|
+
const callId = call.request.getCallId()
|
|
3085
|
+
if (!callId) {
|
|
3086
|
+
throw new Error('callMediaEndpoint: call_id is required')
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
const payload = await puppet.callMediaEndpoint(callId)
|
|
3090
|
+
|
|
3091
|
+
const response = new grpcPuppet.CallMediaEndpointResponse()
|
|
3092
|
+
response.setUrl(payload.url)
|
|
3093
|
+
response.setToken(payload.token)
|
|
3094
|
+
if (payload.expiresAt !== undefined) {
|
|
3095
|
+
response.setExpiresAt(timestampFromMilliseconds(payload.expiresAt))
|
|
3096
|
+
}
|
|
3097
|
+
if (payload.protocol) {
|
|
3098
|
+
response.setProtocol(payload.protocol)
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
return callback(null, response)
|
|
3102
|
+
} catch (e) {
|
|
3103
|
+
return grpcError('callMediaEndpoint', e, callback)
|
|
3104
|
+
}
|
|
3105
|
+
},
|
|
3106
|
+
|
|
3107
|
+
callPayload: async (call, callback) => {
|
|
3108
|
+
log.verbose('PuppetServiceImpl', 'callPayload()')
|
|
3109
|
+
|
|
3110
|
+
try {
|
|
3111
|
+
const callId = call.request.getId()
|
|
3112
|
+
if (!callId) {
|
|
3113
|
+
throw new Error('callPayload: id is required')
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
const payload = await puppet.callPayload(callId)
|
|
3117
|
+
|
|
3118
|
+
const response = new grpcPuppet.CallPayloadResponse()
|
|
3119
|
+
response.setId(payload.id)
|
|
3120
|
+
if (payload.starter) {
|
|
3121
|
+
response.setStarter(payload.starter)
|
|
3122
|
+
}
|
|
3123
|
+
response.setParticipantsList(payload.participants)
|
|
3124
|
+
response.setMedia(puppetCallMediaTypeToGrpc(payload.media))
|
|
3125
|
+
response.setStartTime(timestampFromMilliseconds(payload.startTime))
|
|
3126
|
+
if (payload.endTime !== undefined) {
|
|
3127
|
+
response.setEndTime(timestampFromMilliseconds(payload.endTime))
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
return callback(null, response)
|
|
3131
|
+
} catch (e) {
|
|
3132
|
+
return grpcError('callPayload', e, callback)
|
|
3133
|
+
}
|
|
3134
|
+
},
|
|
3135
|
+
|
|
2755
3136
|
upload: async (call, callback) => {
|
|
2756
3137
|
log.verbose('PuppetServiceImpl', 'upload()')
|
|
2757
3138
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
|
|
3
|
+
import { test } from 'tstest'
|
|
4
|
+
|
|
5
|
+
import * as PUPPET from '@juzi/wechaty-puppet'
|
|
6
|
+
import { puppet as grpcPuppet } from '@juzi/wechaty-grpc'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
puppetCallMediaTypeToGrpc,
|
|
10
|
+
grpcCallTypeToPuppetMedia,
|
|
11
|
+
} from './call-media-mapping.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 1. CallMediaType Audio and Video round-trip: PUPPET -> gRPC -> PUPPET === original.
|
|
15
|
+
*/
|
|
16
|
+
test('CallMediaType: Audio and Video survive a round-trip through gRPC', async t => {
|
|
17
|
+
const mediaCases = [
|
|
18
|
+
PUPPET.types.CallMediaType.Audio,
|
|
19
|
+
PUPPET.types.CallMediaType.Video,
|
|
20
|
+
] as const
|
|
21
|
+
|
|
22
|
+
for (const media of mediaCases) {
|
|
23
|
+
const grpcValue = puppetCallMediaTypeToGrpc(media)
|
|
24
|
+
const roundTripped = grpcCallTypeToPuppetMedia(grpcValue)
|
|
25
|
+
t.equal(roundTripped, media, `round-trip should be identity for CallMediaType.${media}`)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 2. Each direction maps to the expected concrete enum member.
|
|
31
|
+
*/
|
|
32
|
+
test('puppetCallMediaTypeToGrpc: Audio -> VOICE, Video -> VIDEO', async t => {
|
|
33
|
+
t.equal(puppetCallMediaTypeToGrpc(PUPPET.types.CallMediaType.Audio), grpcPuppet.CallType.CALL_TYPE_VOICE, 'Audio maps to CALL_TYPE_VOICE')
|
|
34
|
+
t.equal(puppetCallMediaTypeToGrpc(PUPPET.types.CallMediaType.Video), grpcPuppet.CallType.CALL_TYPE_VIDEO, 'Video maps to CALL_TYPE_VIDEO')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 3. CALL_TYPE_UNKNOWN (wire zero value) maps to undefined so callers can reject it explicitly.
|
|
39
|
+
*/
|
|
40
|
+
test('grpcCallTypeToPuppetMedia: CALL_TYPE_UNKNOWN returns undefined', async t => {
|
|
41
|
+
const result = grpcCallTypeToPuppetMedia(grpcPuppet.CallType.CALL_TYPE_UNKNOWN)
|
|
42
|
+
t.equal(result, undefined, 'CALL_TYPE_UNKNOWN should map to undefined')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 4. Unknown numeric values not in the proto enum throw rather than silently returning garbage.
|
|
47
|
+
*/
|
|
48
|
+
test('grpcCallTypeToPuppetMedia: unknown value 999 throws', async t => {
|
|
49
|
+
t.throws(
|
|
50
|
+
() => grpcCallTypeToPuppetMedia(999),
|
|
51
|
+
'should throw for unknown CallType value 999',
|
|
52
|
+
)
|
|
53
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as PUPPET from '@juzi/wechaty-puppet'
|
|
2
|
+
import { puppet as grpcPuppet } from '@juzi/wechaty-grpc'
|
|
3
|
+
|
|
4
|
+
type GrpcCallType = grpcPuppet.CallTypeMap[keyof grpcPuppet.CallTypeMap]
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PUPPET CallMediaType (string enum) -> gRPC CallType (number enum).
|
|
8
|
+
* The `never` check in default guarantees exhaustiveness at compile time,
|
|
9
|
+
* while the throw still guards against untyped callers at runtime.
|
|
10
|
+
* media is required per upstream contract: every call invite must carry a media type.
|
|
11
|
+
*/
|
|
12
|
+
export function puppetCallMediaTypeToGrpc (media: PUPPET.types.CallMediaType): GrpcCallType {
|
|
13
|
+
switch (media) {
|
|
14
|
+
case PUPPET.types.CallMediaType.Audio: return grpcPuppet.CallType.CALL_TYPE_VOICE
|
|
15
|
+
case PUPPET.types.CallMediaType.Video: return grpcPuppet.CallType.CALL_TYPE_VIDEO
|
|
16
|
+
default: {
|
|
17
|
+
const exhaustive: never = media
|
|
18
|
+
throw new Error(`puppetCallMediaTypeToGrpc: unknown CallMediaType "${String(exhaustive)}"`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* gRPC CallType (number enum) -> PUPPET CallMediaType (string enum).
|
|
25
|
+
* CALL_TYPE_UNKNOWN (wire zero value) maps to undefined so callers can reject it explicitly.
|
|
26
|
+
*/
|
|
27
|
+
export function grpcCallTypeToPuppetMedia (grpcType: number): PUPPET.types.CallMediaType | undefined {
|
|
28
|
+
switch (grpcType) {
|
|
29
|
+
case grpcPuppet.CallType.CALL_TYPE_UNKNOWN: return undefined
|
|
30
|
+
case grpcPuppet.CallType.CALL_TYPE_VOICE: return PUPPET.types.CallMediaType.Audio
|
|
31
|
+
case grpcPuppet.CallType.CALL_TYPE_VIDEO: return PUPPET.types.CallMediaType.Video
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`grpcCallTypeToPuppetMedia: unknown gRPC CallType value ${grpcType}`)
|
|
34
|
+
}
|
|
35
|
+
}
|