agent-messenger 2.14.1 → 2.15.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -0
- package/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +15 -0
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +1 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.d.ts +1 -1
- package/dist/src/platforms/kakaotalk/listener.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.js +58 -6
- package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +46 -1
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +22 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +4 -3
- package/docs/content/docs/sdk/kakaotalk.mdx +2 -0
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +5 -4
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/kakaotalk/client.test.ts +61 -0
- package/src/platforms/kakaotalk/client.ts +13 -0
- package/src/platforms/kakaotalk/index.ts +6 -0
- package/src/platforms/kakaotalk/listener.test.ts +383 -0
- package/src/platforms/kakaotalk/listener.ts +69 -12
- package/src/platforms/kakaotalk/types.ts +48 -0
|
@@ -4,6 +4,7 @@ import type { KakaoSessionEvent, KakaoSessionEventHandler, KakaoPushHandler, Kak
|
|
|
4
4
|
import { KakaoTalkListener } from './listener'
|
|
5
5
|
import type { LocoPacket } from './protocol/types'
|
|
6
6
|
import type {
|
|
7
|
+
KakaoTalkPushEmoticonEvent,
|
|
7
8
|
KakaoTalkPushGenericEvent,
|
|
8
9
|
KakaoTalkPushMemberEvent,
|
|
9
10
|
KakaoTalkPushMessageEvent,
|
|
@@ -176,6 +177,388 @@ describe('KakaoTalkListener', () => {
|
|
|
176
177
|
})
|
|
177
178
|
})
|
|
178
179
|
|
|
180
|
+
describe('emoticon events', () => {
|
|
181
|
+
it('emits emoticon on MSG push with type=20 (animated sticker)', async () => {
|
|
182
|
+
const { listener: l, client } = createListener()
|
|
183
|
+
listener = l
|
|
184
|
+
|
|
185
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
186
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
187
|
+
|
|
188
|
+
await listener.start()
|
|
189
|
+
client.emitPush('MSG', {
|
|
190
|
+
chatId: { high: 0, low: 100 },
|
|
191
|
+
chatLog: {
|
|
192
|
+
logId: { high: 0, low: 200 },
|
|
193
|
+
authorId: 42,
|
|
194
|
+
message: '',
|
|
195
|
+
type: 20,
|
|
196
|
+
sendAt: 1700000000,
|
|
197
|
+
attachment: '{"path":"4412724.emot_001.webp","emoticonItemPath":"4412724.emot_001.webp","name":"(emoticon)"}',
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
expect(emoticons.length).toBe(1)
|
|
202
|
+
expect(emoticons[0].type).toBe('EMOTICON')
|
|
203
|
+
expect(emoticons[0].chat_id).toBe('100')
|
|
204
|
+
expect(emoticons[0].log_id).toBe('200')
|
|
205
|
+
expect(emoticons[0].author_id).toBe(42)
|
|
206
|
+
expect(emoticons[0].message_type).toBe(20)
|
|
207
|
+
expect(emoticons[0].emoticon_kind).toBe('sticker_ani')
|
|
208
|
+
expect(emoticons[0].pack_id).toBe('4412724')
|
|
209
|
+
expect(emoticons[0].sticker_path).toBe('4412724.emot_001.webp')
|
|
210
|
+
expect(emoticons[0].sent_at).toBe(1700000000)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('emits emoticon on MSG push with type=12 (static sticker)', async () => {
|
|
214
|
+
const { listener: l, client } = createListener()
|
|
215
|
+
listener = l
|
|
216
|
+
|
|
217
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
218
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
219
|
+
|
|
220
|
+
await listener.start()
|
|
221
|
+
client.emitPush('MSG', {
|
|
222
|
+
chatId: { high: 0, low: 100 },
|
|
223
|
+
chatLog: {
|
|
224
|
+
logId: { high: 0, low: 201 },
|
|
225
|
+
authorId: 42,
|
|
226
|
+
message: '',
|
|
227
|
+
type: 12,
|
|
228
|
+
sendAt: 1700000001,
|
|
229
|
+
attachment: '{"path":"2222149.emot_004.png","emoticonItemPath":"2222149.emot_004.png","name":"(emoticon)"}',
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
expect(emoticons.length).toBe(1)
|
|
234
|
+
expect(emoticons[0].emoticon_kind).toBe('sticker')
|
|
235
|
+
expect(emoticons[0].pack_id).toBe('2222149')
|
|
236
|
+
expect(emoticons[0].sticker_path).toBe('2222149.emot_004.png')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it.each([
|
|
240
|
+
[6, 'ditem_emoticon' as const],
|
|
241
|
+
[22, 'actioncon' as const],
|
|
242
|
+
[25, 'sticker_gif' as const],
|
|
243
|
+
])('maps message_type=%i to emoticon_kind=%s', async (msgType, expectedKind) => {
|
|
244
|
+
const { listener: l, client } = createListener()
|
|
245
|
+
listener = l
|
|
246
|
+
|
|
247
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
248
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
249
|
+
|
|
250
|
+
await listener.start()
|
|
251
|
+
client.emitPush('MSG', {
|
|
252
|
+
chatId: { high: 0, low: 100 },
|
|
253
|
+
chatLog: {
|
|
254
|
+
logId: { high: 0, low: 202 },
|
|
255
|
+
authorId: 42,
|
|
256
|
+
message: '',
|
|
257
|
+
type: msgType,
|
|
258
|
+
sendAt: 1700000002,
|
|
259
|
+
attachment: '{"path":"9999999.emot_001.webp"}',
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
expect(emoticons[0].emoticon_kind).toBe(expectedKind)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('still emits the message event for back-compat when emoticon arrives', async () => {
|
|
267
|
+
const { listener: l, client } = createListener()
|
|
268
|
+
listener = l
|
|
269
|
+
|
|
270
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
271
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
272
|
+
listener.on('message', (event) => messages.push(event))
|
|
273
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
274
|
+
|
|
275
|
+
await listener.start()
|
|
276
|
+
client.emitPush('MSG', {
|
|
277
|
+
chatId: { high: 0, low: 100 },
|
|
278
|
+
chatLog: {
|
|
279
|
+
logId: { high: 0, low: 203 },
|
|
280
|
+
authorId: 42,
|
|
281
|
+
message: '',
|
|
282
|
+
type: 12,
|
|
283
|
+
sendAt: 1700000003,
|
|
284
|
+
attachment: '{"path":"2222149.emot_004.png"}',
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
expect(messages.length).toBe(1)
|
|
289
|
+
expect(messages[0].message_type).toBe(12)
|
|
290
|
+
expect(emoticons.length).toBe(1)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('does not emit emoticon for non-sticker types', async () => {
|
|
294
|
+
const { listener: l, client } = createListener()
|
|
295
|
+
listener = l
|
|
296
|
+
|
|
297
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
298
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
299
|
+
|
|
300
|
+
await listener.start()
|
|
301
|
+
client.emitPush('MSG', {
|
|
302
|
+
chatId: { high: 0, low: 100 },
|
|
303
|
+
chatLog: {
|
|
304
|
+
logId: { high: 0, low: 204 },
|
|
305
|
+
authorId: 42,
|
|
306
|
+
message: 'hello',
|
|
307
|
+
type: 1,
|
|
308
|
+
sendAt: 1700000004,
|
|
309
|
+
attachment: '{}',
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
expect(emoticons.length).toBe(0)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('emits emoticon with null fields when attachment is malformed JSON', async () => {
|
|
317
|
+
const { listener: l, client } = createListener()
|
|
318
|
+
listener = l
|
|
319
|
+
|
|
320
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
321
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
322
|
+
|
|
323
|
+
await listener.start()
|
|
324
|
+
client.emitPush('MSG', {
|
|
325
|
+
chatId: { high: 0, low: 100 },
|
|
326
|
+
chatLog: {
|
|
327
|
+
logId: { high: 0, low: 205 },
|
|
328
|
+
authorId: 42,
|
|
329
|
+
message: '',
|
|
330
|
+
type: 12,
|
|
331
|
+
sendAt: 1700000005,
|
|
332
|
+
attachment: 'not-json',
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
expect(emoticons.length).toBe(1)
|
|
337
|
+
expect(emoticons[0].sticker_path).toBeNull()
|
|
338
|
+
expect(emoticons[0].pack_id).toBeNull()
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it('emits emoticon with null fields when attachment is missing', async () => {
|
|
342
|
+
const { listener: l, client } = createListener()
|
|
343
|
+
listener = l
|
|
344
|
+
|
|
345
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
346
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
347
|
+
|
|
348
|
+
await listener.start()
|
|
349
|
+
client.emitPush('MSG', {
|
|
350
|
+
chatId: { high: 0, low: 100 },
|
|
351
|
+
chatLog: {
|
|
352
|
+
logId: { high: 0, low: 206 },
|
|
353
|
+
authorId: 42,
|
|
354
|
+
message: '',
|
|
355
|
+
type: 20,
|
|
356
|
+
sendAt: 1700000006,
|
|
357
|
+
},
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
expect(emoticons.length).toBe(1)
|
|
361
|
+
expect(emoticons[0].sticker_path).toBeNull()
|
|
362
|
+
expect(emoticons[0].pack_id).toBeNull()
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('falls back to emoticonItemPath when path is absent', async () => {
|
|
366
|
+
const { listener: l, client } = createListener()
|
|
367
|
+
listener = l
|
|
368
|
+
|
|
369
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
370
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
371
|
+
|
|
372
|
+
await listener.start()
|
|
373
|
+
client.emitPush('MSG', {
|
|
374
|
+
chatId: { high: 0, low: 100 },
|
|
375
|
+
chatLog: {
|
|
376
|
+
logId: { high: 0, low: 207 },
|
|
377
|
+
authorId: 42,
|
|
378
|
+
message: '',
|
|
379
|
+
type: 12,
|
|
380
|
+
sendAt: 1700000007,
|
|
381
|
+
attachment: '{"emoticonItemPath":"3333.emot_009.png"}',
|
|
382
|
+
},
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
expect(emoticons[0].sticker_path).toBe('3333.emot_009.png')
|
|
386
|
+
expect(emoticons[0].pack_id).toBe('3333')
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('does not throw or leak invalid types when attachment.path is a non-string', async () => {
|
|
390
|
+
const { listener: l, client } = createListener()
|
|
391
|
+
listener = l
|
|
392
|
+
|
|
393
|
+
const emoticons: KakaoTalkPushEmoticonEvent[] = []
|
|
394
|
+
listener.on('emoticon', (event) => emoticons.push(event))
|
|
395
|
+
|
|
396
|
+
await listener.start()
|
|
397
|
+
client.emitPush('MSG', {
|
|
398
|
+
chatId: { high: 0, low: 100 },
|
|
399
|
+
chatLog: {
|
|
400
|
+
logId: { high: 0, low: 208 },
|
|
401
|
+
authorId: 42,
|
|
402
|
+
message: '',
|
|
403
|
+
type: 12,
|
|
404
|
+
sendAt: 1700000008,
|
|
405
|
+
attachment: '{"path":12345,"emoticonItemPath":null,"name":{"nested":"obj"}}',
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
expect(emoticons.length).toBe(1)
|
|
410
|
+
expect(emoticons[0].sticker_path).toBeNull()
|
|
411
|
+
expect(emoticons[0].pack_id).toBeNull()
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
describe('attachment field', () => {
|
|
416
|
+
it('parses chatLog.attachment JSON into a structured field', async () => {
|
|
417
|
+
const { listener: l, client } = createListener()
|
|
418
|
+
listener = l
|
|
419
|
+
|
|
420
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
421
|
+
listener.on('message', (event) => messages.push(event))
|
|
422
|
+
|
|
423
|
+
await listener.start()
|
|
424
|
+
client.emitPush('MSG', {
|
|
425
|
+
chatId: { high: 0, low: 100 },
|
|
426
|
+
chatLog: {
|
|
427
|
+
logId: { high: 0, low: 200 },
|
|
428
|
+
authorId: 42,
|
|
429
|
+
message: '사진',
|
|
430
|
+
type: 2,
|
|
431
|
+
sendAt: 1700000000,
|
|
432
|
+
attachment:
|
|
433
|
+
'{"k":"abc/photo.jpg","w":1320,"h":2868,"s":438315,"mt":"image/jpeg","url":"https://talk.kakaocdn.net/p"}',
|
|
434
|
+
},
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
expect(messages[0].attachment).toEqual({
|
|
438
|
+
k: 'abc/photo.jpg',
|
|
439
|
+
w: 1320,
|
|
440
|
+
h: 2868,
|
|
441
|
+
s: 438315,
|
|
442
|
+
mt: 'image/jpeg',
|
|
443
|
+
url: 'https://talk.kakaocdn.net/p',
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('returns null when attachment is "{}" (text messages have no payload)', async () => {
|
|
448
|
+
const { listener: l, client } = createListener()
|
|
449
|
+
listener = l
|
|
450
|
+
|
|
451
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
452
|
+
listener.on('message', (event) => messages.push(event))
|
|
453
|
+
|
|
454
|
+
await listener.start()
|
|
455
|
+
client.emitPush('MSG', {
|
|
456
|
+
chatId: { high: 0, low: 100 },
|
|
457
|
+
chatLog: {
|
|
458
|
+
logId: { high: 0, low: 201 },
|
|
459
|
+
authorId: 42,
|
|
460
|
+
message: 'hi',
|
|
461
|
+
type: 1,
|
|
462
|
+
sendAt: 1700000001,
|
|
463
|
+
attachment: '{}',
|
|
464
|
+
},
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
expect(messages[0].attachment).toBeNull()
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('returns null when attachment is absent', async () => {
|
|
471
|
+
const { listener: l, client } = createListener()
|
|
472
|
+
listener = l
|
|
473
|
+
|
|
474
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
475
|
+
listener.on('message', (event) => messages.push(event))
|
|
476
|
+
|
|
477
|
+
await listener.start()
|
|
478
|
+
client.emitPush('MSG', {
|
|
479
|
+
chatId: { high: 0, low: 100 },
|
|
480
|
+
chatLog: {
|
|
481
|
+
logId: { high: 0, low: 202 },
|
|
482
|
+
authorId: 42,
|
|
483
|
+
message: 'hi',
|
|
484
|
+
type: 1,
|
|
485
|
+
sendAt: 1700000002,
|
|
486
|
+
},
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
expect(messages[0].attachment).toBeNull()
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
it('returns null when attachment is an empty string', async () => {
|
|
493
|
+
const { listener: l, client } = createListener()
|
|
494
|
+
listener = l
|
|
495
|
+
|
|
496
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
497
|
+
listener.on('message', (event) => messages.push(event))
|
|
498
|
+
|
|
499
|
+
await listener.start()
|
|
500
|
+
client.emitPush('MSG', {
|
|
501
|
+
chatId: { high: 0, low: 100 },
|
|
502
|
+
chatLog: {
|
|
503
|
+
logId: { high: 0, low: 203 },
|
|
504
|
+
authorId: 42,
|
|
505
|
+
message: '',
|
|
506
|
+
type: 1,
|
|
507
|
+
sendAt: 1700000003,
|
|
508
|
+
attachment: '',
|
|
509
|
+
},
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
expect(messages[0].attachment).toBeNull()
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('returns null when attachment is malformed JSON', async () => {
|
|
516
|
+
const { listener: l, client } = createListener()
|
|
517
|
+
listener = l
|
|
518
|
+
|
|
519
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
520
|
+
listener.on('message', (event) => messages.push(event))
|
|
521
|
+
|
|
522
|
+
await listener.start()
|
|
523
|
+
client.emitPush('MSG', {
|
|
524
|
+
chatId: { high: 0, low: 100 },
|
|
525
|
+
chatLog: {
|
|
526
|
+
logId: { high: 0, low: 204 },
|
|
527
|
+
authorId: 42,
|
|
528
|
+
message: '',
|
|
529
|
+
type: 1,
|
|
530
|
+
sendAt: 1700000004,
|
|
531
|
+
attachment: 'not-json{',
|
|
532
|
+
},
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
expect(messages[0].attachment).toBeNull()
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
it('returns null when attachment is a JSON array (non-object)', async () => {
|
|
539
|
+
const { listener: l, client } = createListener()
|
|
540
|
+
listener = l
|
|
541
|
+
|
|
542
|
+
const messages: KakaoTalkPushMessageEvent[] = []
|
|
543
|
+
listener.on('message', (event) => messages.push(event))
|
|
544
|
+
|
|
545
|
+
await listener.start()
|
|
546
|
+
client.emitPush('MSG', {
|
|
547
|
+
chatId: { high: 0, low: 100 },
|
|
548
|
+
chatLog: {
|
|
549
|
+
logId: { high: 0, low: 205 },
|
|
550
|
+
authorId: 42,
|
|
551
|
+
message: '',
|
|
552
|
+
type: 1,
|
|
553
|
+
sendAt: 1700000005,
|
|
554
|
+
attachment: '[1,2,3]',
|
|
555
|
+
},
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
expect(messages[0].attachment).toBeNull()
|
|
559
|
+
})
|
|
560
|
+
})
|
|
561
|
+
|
|
179
562
|
describe('member events', () => {
|
|
180
563
|
it('emits member_joined on NEWMEM push', async () => {
|
|
181
564
|
const { listener: l, client } = createListener()
|
|
@@ -2,12 +2,15 @@ import { EventEmitter } from 'events'
|
|
|
2
2
|
|
|
3
3
|
import type { KakaoSessionEvent, KakaoTalkClient } from './client'
|
|
4
4
|
import type { LocoPacket } from './protocol/types'
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import {
|
|
6
|
+
KAKAO_EMOTICON_KIND_BY_TYPE,
|
|
7
|
+
type KakaoEmoticonMessageType,
|
|
8
|
+
type KakaoTalkListenerEventMap,
|
|
9
|
+
type KakaoTalkPushEmoticonEvent,
|
|
10
|
+
type KakaoTalkPushGenericEvent,
|
|
11
|
+
type KakaoTalkPushMemberEvent,
|
|
12
|
+
type KakaoTalkPushMessageEvent,
|
|
13
|
+
type KakaoTalkPushReadEvent,
|
|
11
14
|
} from './types'
|
|
12
15
|
|
|
13
16
|
type EventKey = keyof KakaoTalkListenerEventMap
|
|
@@ -20,6 +23,34 @@ function longToString(v: unknown): string {
|
|
|
20
23
|
return String(v ?? 0)
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
function isEmoticonType(type: number): type is KakaoEmoticonMessageType {
|
|
27
|
+
return type in KAKAO_EMOTICON_KIND_BY_TYPE
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseAttachmentJson(raw: unknown): Record<string, unknown> | null {
|
|
31
|
+
if (typeof raw !== 'string' || raw.length === 0) return null
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(raw) as unknown
|
|
34
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null
|
|
35
|
+
const attachment = parsed as Record<string, unknown>
|
|
36
|
+
return Object.keys(attachment).length > 0 ? attachment : null
|
|
37
|
+
} catch {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function nonEmptyString(value: unknown): string | null {
|
|
43
|
+
return typeof value === 'string' && value.length > 0 ? value : null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractPackIdFromPath(path: string | null): string | null {
|
|
47
|
+
if (!path) return null
|
|
48
|
+
const dotIndex = path.indexOf('.')
|
|
49
|
+
if (dotIndex <= 0) return null
|
|
50
|
+
const head = path.slice(0, dotIndex)
|
|
51
|
+
return /^\d+$/.test(head) ? head : null
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
export class KakaoTalkListener {
|
|
24
55
|
private client: KakaoTalkClient
|
|
25
56
|
private running = false
|
|
@@ -111,17 +142,43 @@ export class KakaoTalkListener {
|
|
|
111
142
|
const chatLog = body.chatLog as Record<string, unknown>
|
|
112
143
|
const chatId = longToString(body.chatId)
|
|
113
144
|
const authorId = chatLog.authorId as number
|
|
114
|
-
const
|
|
145
|
+
const logId = longToString(chatLog.logId)
|
|
146
|
+
const messageType = chatLog.type as number
|
|
147
|
+
const authorName = this.client.lookupAuthorName?.(chatId, authorId) ?? null
|
|
148
|
+
const sentAt = chatLog.sendAt as number
|
|
149
|
+
|
|
150
|
+
const attachment = parseAttachmentJson(chatLog.attachment)
|
|
151
|
+
|
|
152
|
+
const messageEvent: KakaoTalkPushMessageEvent = {
|
|
115
153
|
type: 'MSG',
|
|
116
154
|
chat_id: chatId,
|
|
117
|
-
log_id:
|
|
155
|
+
log_id: logId,
|
|
118
156
|
author_id: authorId,
|
|
119
|
-
author_name:
|
|
157
|
+
author_name: authorName,
|
|
120
158
|
message: chatLog.message as string,
|
|
121
|
-
message_type:
|
|
122
|
-
|
|
159
|
+
message_type: messageType,
|
|
160
|
+
attachment,
|
|
161
|
+
sent_at: sentAt,
|
|
162
|
+
}
|
|
163
|
+
this.emitter.emit('message', messageEvent)
|
|
164
|
+
|
|
165
|
+
if (isEmoticonType(messageType)) {
|
|
166
|
+
const stickerPath = nonEmptyString(attachment?.path) ?? nonEmptyString(attachment?.emoticonItemPath)
|
|
167
|
+
const emoticonEvent: KakaoTalkPushEmoticonEvent = {
|
|
168
|
+
type: 'EMOTICON',
|
|
169
|
+
chat_id: chatId,
|
|
170
|
+
log_id: logId,
|
|
171
|
+
author_id: authorId,
|
|
172
|
+
author_name: authorName,
|
|
173
|
+
message_type: messageType,
|
|
174
|
+
emoticon_kind: KAKAO_EMOTICON_KIND_BY_TYPE[messageType],
|
|
175
|
+
pack_id: extractPackIdFromPath(stickerPath),
|
|
176
|
+
sticker_path: stickerPath,
|
|
177
|
+
sent_at: sentAt,
|
|
178
|
+
}
|
|
179
|
+
this.emitter.emit('emoticon', emoticonEvent)
|
|
123
180
|
}
|
|
124
|
-
|
|
181
|
+
|
|
125
182
|
this.emitter.emit('kakaotalk_event', { type: method, ...body })
|
|
126
183
|
break
|
|
127
184
|
}
|
|
@@ -90,6 +90,7 @@ export interface KakaoMessage {
|
|
|
90
90
|
author_id: number
|
|
91
91
|
author_name: string | null
|
|
92
92
|
message: string
|
|
93
|
+
attachment: Record<string, unknown> | null
|
|
93
94
|
sent_at: number
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -148,6 +149,7 @@ export const KakaoMessageSchema = z.object({
|
|
|
148
149
|
author_id: z.number(),
|
|
149
150
|
author_name: z.string().nullable(),
|
|
150
151
|
message: z.string(),
|
|
152
|
+
attachment: z.record(z.string(), z.unknown()).nullable(),
|
|
151
153
|
sent_at: z.number(),
|
|
152
154
|
})
|
|
153
155
|
|
|
@@ -237,6 +239,36 @@ export interface KakaoTalkPushMessageEvent {
|
|
|
237
239
|
author_name: string | null
|
|
238
240
|
message: string
|
|
239
241
|
message_type: number
|
|
242
|
+
attachment: Record<string, unknown> | null
|
|
243
|
+
sent_at: number
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export type KakaoEmoticonKind = 'sticker' | 'sticker_ani' | 'actioncon' | 'sticker_gif' | 'ditem_emoticon'
|
|
247
|
+
|
|
248
|
+
export const KAKAO_EMOTICON_KIND_BY_TYPE = {
|
|
249
|
+
6: 'ditem_emoticon',
|
|
250
|
+
12: 'sticker',
|
|
251
|
+
20: 'sticker_ani',
|
|
252
|
+
22: 'actioncon',
|
|
253
|
+
25: 'sticker_gif',
|
|
254
|
+
} as const satisfies Record<number, KakaoEmoticonKind>
|
|
255
|
+
|
|
256
|
+
export type KakaoEmoticonMessageType = keyof typeof KAKAO_EMOTICON_KIND_BY_TYPE
|
|
257
|
+
|
|
258
|
+
export const KAKAO_EMOTICON_MESSAGE_TYPES = Object.keys(KAKAO_EMOTICON_KIND_BY_TYPE).map(
|
|
259
|
+
Number,
|
|
260
|
+
) as KakaoEmoticonMessageType[]
|
|
261
|
+
|
|
262
|
+
export interface KakaoTalkPushEmoticonEvent {
|
|
263
|
+
type: 'EMOTICON'
|
|
264
|
+
chat_id: string
|
|
265
|
+
log_id: string
|
|
266
|
+
author_id: number
|
|
267
|
+
author_name: string | null
|
|
268
|
+
message_type: KakaoEmoticonMessageType
|
|
269
|
+
emoticon_kind: KakaoEmoticonKind
|
|
270
|
+
pack_id: string | null
|
|
271
|
+
sticker_path: string | null
|
|
240
272
|
sent_at: number
|
|
241
273
|
}
|
|
242
274
|
|
|
@@ -260,12 +292,14 @@ export interface KakaoTalkPushGenericEvent {
|
|
|
260
292
|
|
|
261
293
|
export type KakaoTalkPushEvent =
|
|
262
294
|
| KakaoTalkPushMessageEvent
|
|
295
|
+
| KakaoTalkPushEmoticonEvent
|
|
263
296
|
| KakaoTalkPushMemberEvent
|
|
264
297
|
| KakaoTalkPushReadEvent
|
|
265
298
|
| KakaoTalkPushGenericEvent
|
|
266
299
|
|
|
267
300
|
export interface KakaoTalkListenerEventMap {
|
|
268
301
|
message: [event: KakaoTalkPushMessageEvent]
|
|
302
|
+
emoticon: [event: KakaoTalkPushEmoticonEvent]
|
|
269
303
|
member_joined: [event: KakaoTalkPushMemberEvent]
|
|
270
304
|
member_left: [event: KakaoTalkPushMemberEvent]
|
|
271
305
|
read: [event: KakaoTalkPushReadEvent]
|
|
@@ -283,6 +317,20 @@ export const KakaoTalkPushMessageEventSchema = z.object({
|
|
|
283
317
|
author_name: z.string().nullable(),
|
|
284
318
|
message: z.string(),
|
|
285
319
|
message_type: z.number(),
|
|
320
|
+
attachment: z.record(z.string(), z.unknown()).nullable(),
|
|
321
|
+
sent_at: z.number(),
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
export const KakaoTalkPushEmoticonEventSchema = z.object({
|
|
325
|
+
type: z.literal('EMOTICON'),
|
|
326
|
+
chat_id: z.string(),
|
|
327
|
+
log_id: z.string(),
|
|
328
|
+
author_id: z.number(),
|
|
329
|
+
author_name: z.string().nullable(),
|
|
330
|
+
message_type: z.union([z.literal(6), z.literal(12), z.literal(20), z.literal(22), z.literal(25)]),
|
|
331
|
+
emoticon_kind: z.enum(['sticker', 'sticker_ani', 'actioncon', 'sticker_gif', 'ditem_emoticon']),
|
|
332
|
+
pack_id: z.string().nullable(),
|
|
333
|
+
sticker_path: z.string().nullable(),
|
|
286
334
|
sent_at: z.number(),
|
|
287
335
|
})
|
|
288
336
|
|