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.
Files changed (43) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +3 -0
  3. package/dist/package.json +1 -1
  4. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  5. package/dist/src/platforms/kakaotalk/client.js +15 -0
  6. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  7. package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
  8. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  9. package/dist/src/platforms/kakaotalk/index.js +1 -1
  10. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  11. package/dist/src/platforms/kakaotalk/listener.d.ts +1 -1
  12. package/dist/src/platforms/kakaotalk/listener.d.ts.map +1 -1
  13. package/dist/src/platforms/kakaotalk/listener.js +58 -6
  14. package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
  15. package/dist/src/platforms/kakaotalk/types.d.ts +46 -1
  16. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  17. package/dist/src/platforms/kakaotalk/types.js +22 -0
  18. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  19. package/docs/content/docs/cli/kakaotalk.mdx +4 -3
  20. package/docs/content/docs/sdk/kakaotalk.mdx +2 -0
  21. package/package.json +1 -1
  22. package/skills/agent-channeltalk/SKILL.md +1 -1
  23. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  24. package/skills/agent-discord/SKILL.md +1 -1
  25. package/skills/agent-discordbot/SKILL.md +1 -1
  26. package/skills/agent-instagram/SKILL.md +1 -1
  27. package/skills/agent-kakaotalk/SKILL.md +5 -4
  28. package/skills/agent-line/SKILL.md +1 -1
  29. package/skills/agent-slack/SKILL.md +1 -1
  30. package/skills/agent-slackbot/SKILL.md +1 -1
  31. package/skills/agent-teams/SKILL.md +1 -1
  32. package/skills/agent-telegram/SKILL.md +1 -1
  33. package/skills/agent-telegrambot/SKILL.md +1 -1
  34. package/skills/agent-webex/SKILL.md +1 -1
  35. package/skills/agent-wechatbot/SKILL.md +1 -1
  36. package/skills/agent-whatsapp/SKILL.md +1 -1
  37. package/skills/agent-whatsappbot/SKILL.md +1 -1
  38. package/src/platforms/kakaotalk/client.test.ts +61 -0
  39. package/src/platforms/kakaotalk/client.ts +13 -0
  40. package/src/platforms/kakaotalk/index.ts +6 -0
  41. package/src/platforms/kakaotalk/listener.test.ts +383 -0
  42. package/src/platforms/kakaotalk/listener.ts +69 -12
  43. 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 type {
6
- KakaoTalkListenerEventMap,
7
- KakaoTalkPushGenericEvent,
8
- KakaoTalkPushMemberEvent,
9
- KakaoTalkPushMessageEvent,
10
- KakaoTalkPushReadEvent,
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 event: KakaoTalkPushMessageEvent = {
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: longToString(chatLog.logId),
155
+ log_id: logId,
118
156
  author_id: authorId,
119
- author_name: this.client.lookupAuthorName?.(chatId, authorId) ?? null,
157
+ author_name: authorName,
120
158
  message: chatLog.message as string,
121
- message_type: chatLog.type as number,
122
- sent_at: chatLog.sendAt as number,
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
- this.emitter.emit('message', event)
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