agent-messenger 2.15.1 → 2.17.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 +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/attachment-router.d.ts +25 -0
- package/dist/src/platforms/kakaotalk/attachment-router.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/attachment-router.js +29 -0
- package/dist/src/platforms/kakaotalk/attachment-router.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts +14 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +216 -0
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/message.js +49 -0
- package/dist/src/platforms/kakaotalk/commands/message.js.map +1 -1
- package/dist/src/platforms/kakaotalk/image-meta.d.ts +7 -0
- package/dist/src/platforms/kakaotalk/image-meta.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/image-meta.js +153 -0
- package/dist/src/platforms/kakaotalk/image-meta.js.map +1 -0
- package/dist/src/platforms/kakaotalk/index.d.ts +8 -2
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +5 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/media-upload.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/media-upload.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/media-upload.js +44 -0
- package/dist/src/platforms/kakaotalk/media-upload.js.map +1 -0
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts +1 -0
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +11 -0
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.d.ts +25 -0
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.js +99 -0
- package/dist/src/platforms/kakaotalk/protocol/media-uploader.js.map +1 -0
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts +6 -0
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +61 -0
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +44 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +9 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/slackbot/types.d.ts +4 -0
- package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +47 -2
- package/docs/content/docs/sdk/kakaotalk.mdx +32 -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 +62 -4
- package/skills/agent-kakaotalk/references/common-patterns.md +50 -11
- 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/attachment-router.test.ts +102 -0
- package/src/platforms/kakaotalk/attachment-router.ts +50 -0
- package/src/platforms/kakaotalk/client.ts +315 -8
- package/src/platforms/kakaotalk/commands/message.ts +66 -0
- package/src/platforms/kakaotalk/image-meta.test.ts +90 -0
- package/src/platforms/kakaotalk/image-meta.ts +176 -0
- package/src/platforms/kakaotalk/index.ts +13 -0
- package/src/platforms/kakaotalk/media-upload.ts +44 -0
- package/src/platforms/kakaotalk/protocol/connection.ts +11 -0
- package/src/platforms/kakaotalk/protocol/media-uploader.ts +129 -0
- package/src/platforms/kakaotalk/protocol/session.ts +67 -0
- package/src/platforms/kakaotalk/types.ts +57 -0
- package/src/platforms/slackbot/types.ts +16 -0
- package/src/platforms/telegrambot/cli.ts +0 -0
|
@@ -12,8 +12,13 @@ export type {
|
|
|
12
12
|
KakaoDeviceType,
|
|
13
13
|
KakaoEmoticonKind,
|
|
14
14
|
KakaoEmoticonMessageType,
|
|
15
|
+
KakaoFileExtra,
|
|
16
|
+
KakaoLoginResult,
|
|
17
|
+
KakaoMarkReadResult,
|
|
15
18
|
KakaoMember,
|
|
16
19
|
KakaoMessage,
|
|
20
|
+
KakaoMultiPhotoExtra,
|
|
21
|
+
KakaoPhotoExtra,
|
|
17
22
|
KakaoProfile,
|
|
18
23
|
KakaoSendResult,
|
|
19
24
|
KakaoTalkListenerEventMap,
|
|
@@ -27,9 +32,11 @@ export type {
|
|
|
27
32
|
export {
|
|
28
33
|
KAKAO_EMOTICON_KIND_BY_TYPE,
|
|
29
34
|
KAKAO_EMOTICON_MESSAGE_TYPES,
|
|
35
|
+
KAKAO_MESSAGE_TYPE,
|
|
30
36
|
KakaoAccountCredentialsSchema,
|
|
31
37
|
KakaoChatSchema,
|
|
32
38
|
KakaoConfigSchema,
|
|
39
|
+
KakaoMarkReadResultSchema,
|
|
33
40
|
KakaoMemberSchema,
|
|
34
41
|
KakaoMessageSchema,
|
|
35
42
|
KakaoProfileSchema,
|
|
@@ -39,3 +46,9 @@ export {
|
|
|
39
46
|
KakaoTalkPushMessageEventSchema,
|
|
40
47
|
KakaoTalkPushReadEventSchema,
|
|
41
48
|
} from './types'
|
|
49
|
+
export { attemptLogin, generateDeviceUuid, loginFlow, registerDevice, requestPasscode } from './auth/kakao-login'
|
|
50
|
+
export type { LoginCredentials } from './auth/kakao-login'
|
|
51
|
+
export { sha1Hex } from './media-upload'
|
|
52
|
+
export { detectImageDimensions } from './image-meta'
|
|
53
|
+
export type { AttachmentInput, AttachmentPlan, ResolvedAttachment, SingleAttachmentKind } from './attachment-router'
|
|
54
|
+
export { planAttachments, resolveAttachment } from './attachment-router'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SHA-1 hex (uppercase, 40 chars) — required as the `cs` (checksum) field
|
|
2
|
+
// on SHIP requests and on the inbound photo `extra` JSON. Verified by inbound
|
|
3
|
+
// capture of a real KakaoTalk client photo (2026-05).
|
|
4
|
+
export async function sha1Hex(data: Uint8Array): Promise<string> {
|
|
5
|
+
const hashBuf = await crypto.subtle.digest('SHA-1', data)
|
|
6
|
+
return Array.from(new Uint8Array(hashBuf))
|
|
7
|
+
.map((b) => b.toString(16).padStart(2, '0').toUpperCase())
|
|
8
|
+
.join('')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MIME_BY_EXT: Record<string, string> = {
|
|
12
|
+
jpg: 'image/jpeg',
|
|
13
|
+
jpeg: 'image/jpeg',
|
|
14
|
+
png: 'image/png',
|
|
15
|
+
gif: 'image/gif',
|
|
16
|
+
webp: 'image/webp',
|
|
17
|
+
mp4: 'video/mp4',
|
|
18
|
+
mov: 'video/quicktime',
|
|
19
|
+
webm: 'video/webm',
|
|
20
|
+
mkv: 'video/x-matroska',
|
|
21
|
+
m4v: 'video/x-m4v',
|
|
22
|
+
m4a: 'audio/m4a',
|
|
23
|
+
mp3: 'audio/mpeg',
|
|
24
|
+
wav: 'audio/wav',
|
|
25
|
+
ogg: 'audio/ogg',
|
|
26
|
+
pdf: 'application/pdf',
|
|
27
|
+
csv: 'text/csv',
|
|
28
|
+
txt: 'text/plain',
|
|
29
|
+
json: 'application/json',
|
|
30
|
+
zip: 'application/zip',
|
|
31
|
+
doc: 'application/msword',
|
|
32
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
33
|
+
xls: 'application/vnd.ms-excel',
|
|
34
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
35
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
36
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function guessMimeFromFilename(filename: string): string {
|
|
40
|
+
const dot = filename.lastIndexOf('.')
|
|
41
|
+
if (dot < 0 || dot === filename.length - 1) return 'application/octet-stream'
|
|
42
|
+
const ext = filename.slice(dot + 1).toLowerCase()
|
|
43
|
+
return MIME_BY_EXT[ext] ?? 'application/octet-stream'
|
|
44
|
+
}
|
|
@@ -50,6 +50,17 @@ export class LocoConnection {
|
|
|
50
50
|
await this.write(handshakePacket)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Writes raw bytes onto the LOCO stream after applying the same AES-128-GCM
|
|
54
|
+
// framing as encrypted packets — used by the media-upload POST step where
|
|
55
|
+
// the protocol expects the file payload to follow the POST request inside
|
|
56
|
+
// the same encrypted channel (chunked into N frames automatically by the
|
|
57
|
+
// crypto layer's 4-byte-size + nonce + ciphertext + tag wrapping).
|
|
58
|
+
async writeRaw(data: Buffer): Promise<void> {
|
|
59
|
+
if (!this.crypto) throw new Error('crypto not initialised')
|
|
60
|
+
const encrypted = this.crypto.encrypt(data)
|
|
61
|
+
await this.write(encrypted)
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
async sendPacket(method: string, body: Record<string, unknown> = {}): Promise<LocoPacket> {
|
|
54
65
|
const packetId = ++this.packetIdCounter
|
|
55
66
|
const packet: LocoPacket = {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Long } from 'bson'
|
|
2
|
+
|
|
3
|
+
import type { KakaoDeviceType } from '../types'
|
|
4
|
+
import { MCCMNC, getLocoDeviceConfig } from './config'
|
|
5
|
+
import { LocoConnection } from './connection'
|
|
6
|
+
import type { LocoPacket } from './types'
|
|
7
|
+
|
|
8
|
+
export interface UploadToLocoOptions {
|
|
9
|
+
shipToken: string
|
|
10
|
+
shipHost: string
|
|
11
|
+
shipPort: number
|
|
12
|
+
chatId: Long
|
|
13
|
+
msgType: number
|
|
14
|
+
userId: string
|
|
15
|
+
filename: string
|
|
16
|
+
data: Uint8Array
|
|
17
|
+
width?: number
|
|
18
|
+
height?: number
|
|
19
|
+
deviceType: KakaoDeviceType
|
|
20
|
+
onProgress?: (sent: number, total: number) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UploadToLocoResult {
|
|
24
|
+
completePacket: LocoPacket | null
|
|
25
|
+
postStatusCode: number
|
|
26
|
+
postOffset: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_NETWORK_TYPE = 0
|
|
30
|
+
const DEFAULT_COMPLETE_TIMEOUT_MS = 60_000
|
|
31
|
+
|
|
32
|
+
// Drives the SHIP → connect → POST → stream → COMPLETE pipeline that KakaoTalk
|
|
33
|
+
// uses for chat-media uploads. The caller has already done SHIP on the main
|
|
34
|
+
// session and received {k, vh, p}; this opens a dedicated TCP+LOCO connection
|
|
35
|
+
// to (vh, p), sends POST with the ticket + chat metadata, streams the raw file
|
|
36
|
+
// bytes, and waits for the server's COMPLETE push that triggers the actual
|
|
37
|
+
// chat-message registration.
|
|
38
|
+
//
|
|
39
|
+
// Field names (u/k/t/s/c/mid/w/h/mm/nt/os/av/f/ns) match the APK's
|
|
40
|
+
// `SR/g0.java` (PostJob) verbatim. The COMPLETE handshake is a server-pushed
|
|
41
|
+
// packet whose body contains the resulting chatLog struct.
|
|
42
|
+
export async function uploadMediaToLoco(opts: UploadToLocoOptions): Promise<UploadToLocoResult> {
|
|
43
|
+
return runPostStreamComplete(opts, 'POST')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Single-entry MPOST upload — runs the same connect → POST → stream → COMPLETE
|
|
47
|
+
// pipeline as uploadMediaToLoco but with MPOST as the opcode (used after MSHIP
|
|
48
|
+
// for each entry in a multi-photo batch). Callers fan-out across all entries
|
|
49
|
+
// in parallel and then issue one FORWARD to register the gallery message.
|
|
50
|
+
export async function uploadMultiMediaEntry(opts: UploadToLocoOptions): Promise<UploadToLocoResult> {
|
|
51
|
+
return runPostStreamComplete(opts, 'MPOST')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function runPostStreamComplete(opts: UploadToLocoOptions, opcode: 'POST' | 'MPOST'): Promise<UploadToLocoResult> {
|
|
55
|
+
const device = getLocoDeviceConfig(opts.deviceType)
|
|
56
|
+
|
|
57
|
+
const conn = new LocoConnection()
|
|
58
|
+
await conn.connectSecure(opts.shipHost, opts.shipPort)
|
|
59
|
+
|
|
60
|
+
const totalSize = opts.data.byteLength
|
|
61
|
+
let completeResolve: ((p: LocoPacket | null) => void) | null = null
|
|
62
|
+
let completeTimeout: ReturnType<typeof setTimeout> | null = null
|
|
63
|
+
const completePromise = new Promise<LocoPacket | null>((resolve) => {
|
|
64
|
+
completeResolve = resolve
|
|
65
|
+
completeTimeout = setTimeout(() => {
|
|
66
|
+
completeTimeout = null
|
|
67
|
+
resolve(null)
|
|
68
|
+
}, DEFAULT_COMPLETE_TIMEOUT_MS)
|
|
69
|
+
})
|
|
70
|
+
conn.onPush((push) => {
|
|
71
|
+
if (push.method === 'COMPLETE' && completeResolve) {
|
|
72
|
+
if (completeTimeout) {
|
|
73
|
+
clearTimeout(completeTimeout)
|
|
74
|
+
completeTimeout = null
|
|
75
|
+
}
|
|
76
|
+
completeResolve(push)
|
|
77
|
+
completeResolve = null
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const postBody: Record<string, unknown> = {
|
|
83
|
+
k: opts.shipToken,
|
|
84
|
+
s: totalSize,
|
|
85
|
+
t: opts.msgType,
|
|
86
|
+
u: Long.fromString(opts.userId),
|
|
87
|
+
os: device.os,
|
|
88
|
+
av: device.appVersion,
|
|
89
|
+
nt: DEFAULT_NETWORK_TYPE,
|
|
90
|
+
mm: MCCMNC,
|
|
91
|
+
}
|
|
92
|
+
if (opcode === 'POST') {
|
|
93
|
+
postBody.f = opts.filename
|
|
94
|
+
postBody.c = opts.chatId
|
|
95
|
+
postBody.mid = Long.ONE
|
|
96
|
+
postBody.ns = true
|
|
97
|
+
if (typeof opts.width === 'number') postBody.w = opts.width
|
|
98
|
+
if (typeof opts.height === 'number') postBody.h = opts.height
|
|
99
|
+
} else {
|
|
100
|
+
postBody.dt = 0
|
|
101
|
+
postBody.scp = 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const postResp = await conn.sendPacket(opcode, postBody)
|
|
105
|
+
const postOffsetRaw = (postResp.body as Record<string, unknown>).o
|
|
106
|
+
const postOffset = typeof postOffsetRaw === 'number' ? postOffsetRaw : 0
|
|
107
|
+
|
|
108
|
+
const bytesToSend = opts.data.subarray(postOffset)
|
|
109
|
+
if (bytesToSend.length > 0) {
|
|
110
|
+
await conn.writeRaw(Buffer.from(bytesToSend))
|
|
111
|
+
opts.onProgress?.(totalSize, totalSize)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const completePacket = await completePromise
|
|
115
|
+
const postStatusCode = postResp.statusCode
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
completePacket,
|
|
119
|
+
postStatusCode,
|
|
120
|
+
postOffset,
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
if (completeTimeout) {
|
|
124
|
+
clearTimeout(completeTimeout)
|
|
125
|
+
completeTimeout = null
|
|
126
|
+
}
|
|
127
|
+
conn.close()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -126,6 +126,73 @@ export class LocoSession {
|
|
|
126
126
|
})
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Sends a WRITE with non-text message_type plus the JSON-stringified `extra`
|
|
130
|
+
// payload that KakaoTalk clients render as the attachment (photo, file, etc).
|
|
131
|
+
// See types.ts → KakaoPhotoExtra / KakaoFileExtra for the per-type shape.
|
|
132
|
+
async sendAttachment(chatId: Long, type: number, extra: Record<string, unknown>, caption = ''): Promise<LocoPacket> {
|
|
133
|
+
if (!this.connection) throw new Error('Not connected')
|
|
134
|
+
return this.connection.sendPacket('WRITE', {
|
|
135
|
+
chatId,
|
|
136
|
+
msg: caption,
|
|
137
|
+
type,
|
|
138
|
+
noSeen: false,
|
|
139
|
+
extra: JSON.stringify(extra),
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// SHIP — request a media-upload ticket. Reserves a slot on a media LOCO
|
|
144
|
+
// server and returns the token (k), host (vh), and port (p) the client must
|
|
145
|
+
// connect to next. Sent on the main session.
|
|
146
|
+
async shipMedia(chatId: Long, type: number, size: number, checksum: string, extension: string): Promise<LocoPacket> {
|
|
147
|
+
if (!this.connection) throw new Error('Not connected')
|
|
148
|
+
const body: Record<string, unknown> = {
|
|
149
|
+
c: chatId,
|
|
150
|
+
t: type,
|
|
151
|
+
s: Long.fromNumber(size),
|
|
152
|
+
cs: checksum,
|
|
153
|
+
}
|
|
154
|
+
if (extension.length > 0) body.e = extension
|
|
155
|
+
return this.connection.sendPacket('SHIP', body)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// MSHIP — multi-file equivalent of SHIP. Per-file fields become parallel
|
|
159
|
+
// arrays (sl/csl/el) and the response carries kl/vhl/pl arrays the caller
|
|
160
|
+
// must fan out across — one MPOST connection per entry.
|
|
161
|
+
async shipMultiMedia(
|
|
162
|
+
chatId: Long,
|
|
163
|
+
type: number,
|
|
164
|
+
sizes: number[],
|
|
165
|
+
checksums: string[],
|
|
166
|
+
extensions: string[],
|
|
167
|
+
): Promise<LocoPacket> {
|
|
168
|
+
if (!this.connection) throw new Error('Not connected')
|
|
169
|
+
return this.connection.sendPacket('MSHIP', {
|
|
170
|
+
c: chatId,
|
|
171
|
+
t: type,
|
|
172
|
+
sl: sizes.map((s) => Long.fromNumber(s)),
|
|
173
|
+
csl: checksums,
|
|
174
|
+
el: extensions,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// FORWARD — used after MPOST: registers a multi-attachment chatlog as one
|
|
179
|
+
// message. Same shape as WRITE but the server routes the attachment to
|
|
180
|
+
// multi-media rendering (galleries, multi-photo posts).
|
|
181
|
+
async forwardChat(chatId: Long, type: number, extra: Record<string, unknown>, caption = ''): Promise<LocoPacket> {
|
|
182
|
+
if (!this.connection) throw new Error('Not connected')
|
|
183
|
+
return this.connection.sendPacket('FORWARD', {
|
|
184
|
+
chatId,
|
|
185
|
+
msg: caption,
|
|
186
|
+
type,
|
|
187
|
+
noSeen: false,
|
|
188
|
+
extra: JSON.stringify(extra),
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getConnection(): LocoConnection | null {
|
|
193
|
+
return this.connection
|
|
194
|
+
}
|
|
195
|
+
|
|
129
196
|
async syncMessages(chatId: Long, count = 20, cursor?: Long, maxLogId?: Long): Promise<LocoPacket> {
|
|
130
197
|
if (!this.connection) throw new Error('Not connected')
|
|
131
198
|
return this.connection.sendPacket('SYNCMSG', {
|
|
@@ -119,6 +119,63 @@ export interface KakaoSendResult {
|
|
|
119
119
|
sent_at: number
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// LOCO message_type values. Source: KakaoTalk APK 26.4.2 + typeclaw inbound parser.
|
|
123
|
+
export const KAKAO_MESSAGE_TYPE = {
|
|
124
|
+
TEXT: 1,
|
|
125
|
+
PHOTO: 2,
|
|
126
|
+
VIDEO: 3,
|
|
127
|
+
AUDIO: 5,
|
|
128
|
+
FILE: 18,
|
|
129
|
+
MULTIPHOTO: 27,
|
|
130
|
+
} as const
|
|
131
|
+
|
|
132
|
+
// PHOTO `extra` keys verified by inbound capture of a real KakaoTalk client
|
|
133
|
+
// photo message (2026-05). The bare minimum the receiver needs to render
|
|
134
|
+
// the inline preview is k/s/w/h/mt/cs — url and thumbnailUrl are server-issued
|
|
135
|
+
// pre-signed URLs that the recipient regenerates locally from k, so we don't
|
|
136
|
+
// supply them on outbound.
|
|
137
|
+
export interface KakaoPhotoExtra {
|
|
138
|
+
k: string
|
|
139
|
+
s: number
|
|
140
|
+
w: number
|
|
141
|
+
h: number
|
|
142
|
+
mt: string
|
|
143
|
+
cs: string
|
|
144
|
+
url?: string
|
|
145
|
+
thumbnailUrl?: string
|
|
146
|
+
thumbnailWidth?: number
|
|
147
|
+
thumbnailHeight?: number
|
|
148
|
+
expire?: number
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface KakaoFileExtra {
|
|
152
|
+
k: string
|
|
153
|
+
s: number
|
|
154
|
+
name: string
|
|
155
|
+
mt: string
|
|
156
|
+
cs: string
|
|
157
|
+
expire?: number
|
|
158
|
+
url?: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// MULTIPHOTO extra — each per-file field of KakaoPhotoExtra becomes a
|
|
162
|
+
// parallel array. Verified by inbound capture of a real multi-photo message
|
|
163
|
+
// (2026-05). csl uses lowercase hex (unlike PHOTO's cs which is uppercase).
|
|
164
|
+
export interface KakaoMultiPhotoExtra {
|
|
165
|
+
kl: string[]
|
|
166
|
+
wl: number[]
|
|
167
|
+
hl: number[]
|
|
168
|
+
mtl: string[]
|
|
169
|
+
sl: number[]
|
|
170
|
+
csl: string[]
|
|
171
|
+
cmtl?: string[]
|
|
172
|
+
imageUrls?: string[]
|
|
173
|
+
thumbnailUrls?: string[]
|
|
174
|
+
thumbnailWidths?: number[]
|
|
175
|
+
thumbnailHeights?: number[]
|
|
176
|
+
expire?: number
|
|
177
|
+
}
|
|
178
|
+
|
|
122
179
|
export interface KakaoMarkReadResult {
|
|
123
180
|
success: boolean
|
|
124
181
|
status_code: number
|
|
@@ -334,6 +334,18 @@ export interface SlackSocketModeMessageEvent {
|
|
|
334
334
|
event_ts?: string
|
|
335
335
|
edited?: { user: string; ts: string }
|
|
336
336
|
hidden?: boolean
|
|
337
|
+
// Set on every reply within a thread; identifies the author of the message
|
|
338
|
+
// the thread is rooted at. Useful for deciding whether a reply targets the
|
|
339
|
+
// bot, another human, or an unknown parent.
|
|
340
|
+
parent_user_id?: string
|
|
341
|
+
// Client-generated UUID on user-authored messages, stable across Slack-side
|
|
342
|
+
// resends of the same gesture. Primary dedupe key for the "one user action
|
|
343
|
+
// surfaces as two events" case.
|
|
344
|
+
client_msg_id?: string
|
|
345
|
+
// Attachments delivered inline on the same message event. Slack does not
|
|
346
|
+
// fire a separate file_share envelope for messages we receive over Socket
|
|
347
|
+
// Mode, so consumers reading attachments off `message` events look here.
|
|
348
|
+
files?: SlackFile[]
|
|
337
349
|
[key: string]: unknown
|
|
338
350
|
}
|
|
339
351
|
|
|
@@ -345,6 +357,10 @@ export interface SlackSocketModeAppMentionEvent {
|
|
|
345
357
|
ts: string
|
|
346
358
|
thread_ts?: string
|
|
347
359
|
event_ts?: string
|
|
360
|
+
// `app_mention` envelopes do not always carry `client_msg_id`, but typing
|
|
361
|
+
// it keeps the promotion to a message-shaped event lossless if Slack ever
|
|
362
|
+
// starts sending it on this event.
|
|
363
|
+
client_msg_id?: string
|
|
348
364
|
[key: string]: unknown
|
|
349
365
|
}
|
|
350
366
|
|
|
File without changes
|