agent-messenger 2.36.0 → 2.37.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/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/chat-classifier.d.ts +2 -1
- package/dist/src/platforms/kakaotalk/chat-classifier.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/chat-classifier.js +8 -6
- package/dist/src/platforms/kakaotalk/chat-classifier.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +21 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +272 -32
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/index.d.ts +3 -3
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +2 -2
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +48 -2
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +13 -1
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/telegram/tdlib.d.ts.map +1 -1
- package/dist/src/platforms/telegram/tdlib.js +5 -2
- package/dist/src/platforms/telegram/tdlib.js.map +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +7 -1
- package/docs/content/docs/sdk/kakaotalk.mdx +29 -1
- 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-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-kakaotalk/references/common-patterns.md +36 -0
- 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-webexbot/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/chat-classifier.test.ts +1 -1
- package/src/platforms/kakaotalk/chat-classifier.ts +10 -7
- package/src/platforms/kakaotalk/client.test.ts +593 -24
- package/src/platforms/kakaotalk/client.ts +337 -35
- package/src/platforms/kakaotalk/index.test.ts +12 -0
- package/src/platforms/kakaotalk/index.ts +7 -1
- package/src/platforms/kakaotalk/types.ts +33 -2
- package/src/platforms/telegram/tdlib-node-test.ts +19 -0
- package/src/platforms/telegram/tdlib-node.test.ts +11 -0
- package/src/platforms/telegram/tdlib.ts +6 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-messenger",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.37.0",
|
|
4
4
|
"description": "Messaging platform interaction skills for AI agents. Interact with Slack, Discord, Microsoft Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, iMessage, Instagram, KakaoTalk, and Channel Talk - send messages, read channels, manage reactions, upload files, and more through simple CLI interfaces.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "agent-messenger",
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-messenger",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.37.0",
|
|
4
4
|
"description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, iMessage, Instagram, KakaoTalk, Channel Talk)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { KakaoChat } from './types.js';
|
|
1
|
+
import type { KakaoChat, KakaoOpenChatType } from './types.js';
|
|
2
2
|
export type KakaoChatKind = 'dm' | 'group' | 'open' | 'unknown';
|
|
3
|
+
export declare function isOpenKakaoChatType(type: unknown): type is KakaoOpenChatType;
|
|
3
4
|
/**
|
|
4
5
|
* Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
|
|
5
6
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-classifier.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"chat-classifier.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAE3D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAO/D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,iBAAiB,CAE5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC,GAAG,aAAa,CAMjG"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// OpenChat-family `type`
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// OpenChat-family `type` values observed on the wire. Login/chat-list data
|
|
2
|
+
// uses numeric codes while CHATINFO may return the protocol string tags
|
|
3
|
+
// `OM` / `OD`; both forms must share this single classification owner.
|
|
4
|
+
const OPEN_CHAT_TYPES = new Set([2, 13, 14, 15, 16, 'OM', 'OD']);
|
|
5
|
+
export function isOpenKakaoChatType(type) {
|
|
6
|
+
return OPEN_CHAT_TYPES.has(type);
|
|
7
|
+
}
|
|
6
8
|
/**
|
|
7
9
|
* Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
|
|
8
10
|
*
|
|
@@ -18,7 +20,7 @@ const OPEN_CHAT_TYPE_CODES = new Set([2, 13, 14, 15, 16]);
|
|
|
18
20
|
* the case defensively.
|
|
19
21
|
*/
|
|
20
22
|
export function classifyKakaoChat(chat) {
|
|
21
|
-
if (
|
|
23
|
+
if (isOpenKakaoChatType(chat.type))
|
|
22
24
|
return 'open';
|
|
23
25
|
// active_members counts the logged-in user, so a 1:1 DM is exactly 2
|
|
24
26
|
// (self + one other) and a "lone" room with only self is 1.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-classifier.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAIA,
|
|
1
|
+
{"version":3,"file":"chat-classifier.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAIA,2EAA2E;AAC3E,wEAAwE;AACxE,uEAAuE;AACvE,MAAM,eAAe,GAAmC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAEhG,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,OAAO,eAAe,CAAC,GAAG,CAAC,IAAyB,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAgD;IAChF,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IACjD,qEAAqE;IACrE,4DAA4D;IAC5D,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AttachmentInput } from './attachment-router.js';
|
|
2
2
|
import { LocoSession } from './protocol/session.js';
|
|
3
3
|
import type { LocoPacket } from './protocol/types.js';
|
|
4
|
-
import { type KakaoChat, type KakaoDeviceType, type KakaoLeaveChatResult, type KakaoMarkReadResult, type KakaoMember, type KakaoMessage, type KakaoProfile, type KakaoReplyTarget, type KakaoSendResult, type KakaoTypingResult } from './types.js';
|
|
4
|
+
import { type KakaoChat, type KakaoDeviceType, type KakaoLeaveChatResult, type KakaoMarkReadResult, type KakaoMember, type KakaoMemberSnapshot, type KakaoMessage, type KakaoMessagePage, type KakaoProfile, type KakaoReplyTarget, type KakaoSendResult, type KakaoTypingResult } from './types.js';
|
|
5
5
|
export type KakaoSessionEvent = {
|
|
6
6
|
type: 'connected';
|
|
7
7
|
userId: string;
|
|
@@ -60,6 +60,14 @@ export declare class KakaoTalkClient {
|
|
|
60
60
|
search?: string;
|
|
61
61
|
resolveTitles?: boolean;
|
|
62
62
|
}): Promise<KakaoChat[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Fetch one chat directly by id via read-only CHATINFO.
|
|
65
|
+
*
|
|
66
|
+
* Unlike `getChats()`, this does not depend on the login-time snapshot or
|
|
67
|
+
* LCHATLIST pagination, so a room first observed from a live push can be
|
|
68
|
+
* resolved without interpreting protocol fields outside this SDK.
|
|
69
|
+
*/
|
|
70
|
+
getChat(chatId: string): Promise<KakaoChat>;
|
|
63
71
|
/**
|
|
64
72
|
* Resolve the user-set room title via CHATINFO. Returns null on any error
|
|
65
73
|
* (network, malformed response, or no TITLE meta present). Designed to be
|
|
@@ -67,10 +75,22 @@ export declare class KakaoTalkClient {
|
|
|
67
75
|
*/
|
|
68
76
|
getChatTitle(chatId: string): Promise<string | null>;
|
|
69
77
|
private fetchChatTitle;
|
|
78
|
+
/**
|
|
79
|
+
* Fetch one forward-only, lossless history page after `from`.
|
|
80
|
+
*
|
|
81
|
+
* The page never keeps only the newest `count` entries. If the protocol
|
|
82
|
+
* returns more entries than requested, the oldest entries are returned and
|
|
83
|
+
* `complete` remains false so callers can continue from `next_cursor`.
|
|
84
|
+
*/
|
|
85
|
+
getMessagePage(chatId: string, options?: {
|
|
86
|
+
count?: number;
|
|
87
|
+
from?: string;
|
|
88
|
+
}): Promise<KakaoMessagePage>;
|
|
70
89
|
getMessages(chatId: string, options?: {
|
|
71
90
|
count?: number;
|
|
72
91
|
from?: string;
|
|
73
92
|
}): Promise<KakaoMessage[]>;
|
|
93
|
+
getMemberSnapshot(chatId: string): Promise<KakaoMemberSnapshot>;
|
|
74
94
|
getMembers(chatId: string): Promise<KakaoMember[]>;
|
|
75
95
|
getMembersByIds(chatId: string, userIds: string[]): Promise<KakaoMember[]>;
|
|
76
96
|
sendMessage(chatId: string, text: string, options?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,eAAe,EAA4C,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import { Long } from 'bson';
|
|
|
5
5
|
import { getConfigDir } from '../../shared/utils/config-dir.js';
|
|
6
6
|
import { warn } from '../../shared/utils/stderr.js';
|
|
7
7
|
import { planAttachments } from './attachment-router.js';
|
|
8
|
+
import { isOpenKakaoChatType } from './chat-classifier.js';
|
|
8
9
|
import { detectImageDimensions } from './image-meta.js';
|
|
9
10
|
import { sha1Hex } from './media-upload.js';
|
|
10
11
|
import { LANG, PC_OS_NAME, getLocoDeviceConfig } from './protocol/config.js';
|
|
@@ -166,6 +167,64 @@ function extractTitle(body) {
|
|
|
166
167
|
const content = titleMeta?.content;
|
|
167
168
|
return typeof content === 'string' && content.length > 0 ? content : null;
|
|
168
169
|
}
|
|
170
|
+
function extractChannelInfo(body, expectedChatId) {
|
|
171
|
+
const info = body.chatInfo;
|
|
172
|
+
if (!info || typeof info !== 'object') {
|
|
173
|
+
throw new Error('CHATINFO response missing chatInfo');
|
|
174
|
+
}
|
|
175
|
+
const responseChatId = longToString(info.chatId);
|
|
176
|
+
if (responseChatId !== expectedChatId) {
|
|
177
|
+
throw new Error('CHATINFO response chatId mismatch');
|
|
178
|
+
}
|
|
179
|
+
return info;
|
|
180
|
+
}
|
|
181
|
+
function requiredNonNegativeInteger(value, field) {
|
|
182
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
183
|
+
throw new Error(`CHATINFO response invalid ${field}`);
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function requiredChatType(value) {
|
|
188
|
+
if (typeof value === 'string' && isOpenKakaoChatType(value)) {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
return requiredNonNegativeInteger(value, 'type');
|
|
192
|
+
}
|
|
193
|
+
function channelInfoToChatData(body, expectedChatId) {
|
|
194
|
+
const info = extractChannelInfo(body, expectedChatId);
|
|
195
|
+
const type = requiredChatType(info.type);
|
|
196
|
+
const activeMembers = requiredNonNegativeInteger(info.activeMembersCount, 'activeMembersCount');
|
|
197
|
+
const unreadCount = info.invalidNewMessageCount === true ? 0 : requiredNonNegativeInteger(info.newMessageCount, 'newMessageCount');
|
|
198
|
+
const displayMembers = Array.isArray(info.displayMembers) ? info.displayMembers : [];
|
|
199
|
+
const memberIds = [];
|
|
200
|
+
const memberNames = [];
|
|
201
|
+
for (const member of displayMembers) {
|
|
202
|
+
if (!member || typeof member !== 'object')
|
|
203
|
+
continue;
|
|
204
|
+
if (member.userId === undefined || typeof member.nickName !== 'string')
|
|
205
|
+
continue;
|
|
206
|
+
memberIds.push(member.userId);
|
|
207
|
+
memberNames.push(member.nickName);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
c: info.chatId,
|
|
211
|
+
t: type,
|
|
212
|
+
a: activeMembers,
|
|
213
|
+
n: unreadCount,
|
|
214
|
+
l: info.lastChatLog ?? null,
|
|
215
|
+
ll: info.lastLogId,
|
|
216
|
+
i: memberIds,
|
|
217
|
+
k: memberNames,
|
|
218
|
+
li: info.li,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function extractChannelMaxLogId(body, expectedChatId) {
|
|
222
|
+
const info = extractChannelInfo(body, expectedChatId);
|
|
223
|
+
const maxLogId = bsonToLong(info.lastLogId);
|
|
224
|
+
if (!maxLogId)
|
|
225
|
+
throw new Error('CHATINFO response missing lastLogId');
|
|
226
|
+
return maxLogId;
|
|
227
|
+
}
|
|
169
228
|
function extractOpenLinkName(body) {
|
|
170
229
|
const ols = body.ols;
|
|
171
230
|
if (!Array.isArray(ols) || ols.length === 0)
|
|
@@ -173,9 +232,8 @@ function extractOpenLinkName(body) {
|
|
|
173
232
|
const ln = ols[0]?.ln;
|
|
174
233
|
return typeof ln === 'string' && ln.length > 0 ? ln : null;
|
|
175
234
|
}
|
|
176
|
-
const OPEN_CHAT_TYPES = new Set(['OM', 'OD']);
|
|
177
235
|
function isOpenChat(chat) {
|
|
178
|
-
return
|
|
236
|
+
return isOpenKakaoChatType(chat.t);
|
|
179
237
|
}
|
|
180
238
|
function getOpenLinkId(chat) {
|
|
181
239
|
return bsonToLong(chat.li) ?? null;
|
|
@@ -359,10 +417,24 @@ function formatMember(member) {
|
|
|
359
417
|
};
|
|
360
418
|
}
|
|
361
419
|
function memberIdKey(member) {
|
|
362
|
-
|
|
420
|
+
const value = member.userId;
|
|
421
|
+
if (typeof value === 'number') {
|
|
422
|
+
return Number.isSafeInteger(value) && value > 0 ? String(value) : null;
|
|
423
|
+
}
|
|
424
|
+
if (!value || typeof value !== 'object' || !('low' in value) || !('high' in value)) {
|
|
363
425
|
return null;
|
|
364
|
-
|
|
365
|
-
|
|
426
|
+
}
|
|
427
|
+
const { low, high } = value;
|
|
428
|
+
if (!Number.isInteger(low) ||
|
|
429
|
+
!Number.isInteger(high) ||
|
|
430
|
+
low < -0x80000000 ||
|
|
431
|
+
low > 0xffffffff ||
|
|
432
|
+
high < -0x80000000 ||
|
|
433
|
+
high > 0xffffffff) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
const numeric = (BigInt(high >>> 0) << 32n) | BigInt(low >>> 0);
|
|
437
|
+
return numeric > 0n && numeric <= BigInt(Number.MAX_SAFE_INTEGER) ? numeric.toString() : null;
|
|
366
438
|
}
|
|
367
439
|
function mergeMemberRecords(primaryMembers, fallbackMembers) {
|
|
368
440
|
const merged = [];
|
|
@@ -385,18 +457,43 @@ function mergeMemberRecords(primaryMembers, fallbackMembers) {
|
|
|
385
457
|
upsert(member);
|
|
386
458
|
return merged;
|
|
387
459
|
}
|
|
388
|
-
function
|
|
389
|
-
if (!
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
460
|
+
function exactMemberRecords(value, source) {
|
|
461
|
+
if (!Array.isArray(value)) {
|
|
462
|
+
throw new Error(`${source} member snapshot missing`);
|
|
463
|
+
}
|
|
464
|
+
const records = [];
|
|
465
|
+
const ids = new Set();
|
|
466
|
+
for (const raw of value) {
|
|
467
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
468
|
+
throw new Error(`${source} member snapshot invalid`);
|
|
469
|
+
}
|
|
470
|
+
const record = raw;
|
|
471
|
+
const id = memberIdKey(record);
|
|
472
|
+
if (!id || ids.has(id)) {
|
|
473
|
+
throw new Error(`${source} member snapshot invalid`);
|
|
474
|
+
}
|
|
475
|
+
ids.add(id);
|
|
476
|
+
records.push(record);
|
|
477
|
+
}
|
|
478
|
+
return { records, ids };
|
|
479
|
+
}
|
|
480
|
+
function sameIds(left, right) {
|
|
481
|
+
return left.size === right.size && [...left].every((id) => right.has(id));
|
|
482
|
+
}
|
|
483
|
+
function subsetIds(subset, superset) {
|
|
484
|
+
return [...subset].every((id) => superset.has(id));
|
|
485
|
+
}
|
|
486
|
+
function channelMemberView(body, expectedChatId) {
|
|
487
|
+
const info = extractChannelInfo(body, expectedChatId);
|
|
488
|
+
const activeMembers = requiredNonNegativeInteger(info.activeMembersCount, 'activeMembersCount');
|
|
489
|
+
const displayMembers = exactMemberRecords(info.displayMembers ?? [], 'CHATINFO');
|
|
490
|
+
if (displayMembers.records.length > activeMembers) {
|
|
491
|
+
throw new Error('CHATINFO member snapshot invalid');
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
activeMembers,
|
|
495
|
+
...displayMembers,
|
|
496
|
+
};
|
|
400
497
|
}
|
|
401
498
|
function formatMessages(logs, count, chatId, nameCache) {
|
|
402
499
|
logs.sort((a, b) => a.sendAt - b.sendAt);
|
|
@@ -410,6 +507,40 @@ function formatMessages(logs, count, chatId, nameCache) {
|
|
|
410
507
|
sent_at: log.sendAt,
|
|
411
508
|
}));
|
|
412
509
|
}
|
|
510
|
+
function formatForwardMessage(log, chatId, nameCache) {
|
|
511
|
+
return {
|
|
512
|
+
log_id: longToString(log.logId),
|
|
513
|
+
type: log.type,
|
|
514
|
+
author_id: log.authorId,
|
|
515
|
+
author_name: nameCache.lookup(chatId, log.authorId),
|
|
516
|
+
message: log.message,
|
|
517
|
+
attachment: parseAttachmentJson(log.attachment),
|
|
518
|
+
sent_at: log.sendAt,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function buildMessagePage(logs, count, cursor, protocolComplete, chatId, nameCache) {
|
|
522
|
+
const cursorValue = BigInt(cursor.toString());
|
|
523
|
+
const unique = new Map();
|
|
524
|
+
for (const log of logs) {
|
|
525
|
+
const logId = longToString(log.logId);
|
|
526
|
+
if (BigInt(logId) <= cursorValue)
|
|
527
|
+
continue;
|
|
528
|
+
if (!unique.has(logId))
|
|
529
|
+
unique.set(logId, log);
|
|
530
|
+
}
|
|
531
|
+
const ordered = [...unique.entries()].sort(([left], [right]) => {
|
|
532
|
+
const a = BigInt(left);
|
|
533
|
+
const b = BigInt(right);
|
|
534
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
535
|
+
});
|
|
536
|
+
const selected = ordered.slice(0, count);
|
|
537
|
+
const nextCursor = selected.at(-1)?.[0] ?? null;
|
|
538
|
+
return {
|
|
539
|
+
messages: selected.map(([, log]) => formatForwardMessage(log, chatId, nameCache)),
|
|
540
|
+
next_cursor: nextCursor,
|
|
541
|
+
complete: protocolComplete && ordered.length <= count,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
413
544
|
function buildReplyExtra(target) {
|
|
414
545
|
return {
|
|
415
546
|
attach_only: false,
|
|
@@ -672,6 +803,43 @@ export class KakaoTalkClient {
|
|
|
672
803
|
}
|
|
673
804
|
});
|
|
674
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* Fetch one chat directly by id via read-only CHATINFO.
|
|
808
|
+
*
|
|
809
|
+
* Unlike `getChats()`, this does not depend on the login-time snapshot or
|
|
810
|
+
* LCHATLIST pagination, so a room first observed from a live push can be
|
|
811
|
+
* resolved without interpreting protocol fields outside this SDK.
|
|
812
|
+
*/
|
|
813
|
+
async getChat(chatId) {
|
|
814
|
+
const parsedChatId = parseChatId(chatId);
|
|
815
|
+
const normalizedChatId = longToString(parsedChatId);
|
|
816
|
+
return this.executeWithReconnect(async ({ session }) => {
|
|
817
|
+
try {
|
|
818
|
+
const response = await session.getChannelInfo(parsedChatId);
|
|
819
|
+
assertLocoOk(response, 'CHATINFO');
|
|
820
|
+
const body = response.body;
|
|
821
|
+
const chat = channelInfoToChatData(body, normalizedChatId);
|
|
822
|
+
this.nameCache.ingest([chat]);
|
|
823
|
+
let title = extractTitle(body);
|
|
824
|
+
if (!title && isOpenChat(chat)) {
|
|
825
|
+
const linkId = getOpenLinkId(chat);
|
|
826
|
+
if (linkId) {
|
|
827
|
+
try {
|
|
828
|
+
const openLinkResponse = await session.getOpenLinkInfo([linkId]);
|
|
829
|
+
title = extractOpenLinkName(openLinkResponse.body);
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
title = null;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return formatChat(chat, title, this.nameCache);
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
throw wrapError(error, 'get_chat_failed');
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
}
|
|
675
843
|
/**
|
|
676
844
|
* Resolve the user-set room title via CHATINFO. Returns null on any error
|
|
677
845
|
* (network, malformed response, or no TITLE meta present). Designed to be
|
|
@@ -714,6 +882,50 @@ export class KakaoTalkClient {
|
|
|
714
882
|
return null;
|
|
715
883
|
}
|
|
716
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Fetch one forward-only, lossless history page after `from`.
|
|
887
|
+
*
|
|
888
|
+
* The page never keeps only the newest `count` entries. If the protocol
|
|
889
|
+
* returns more entries than requested, the oldest entries are returned and
|
|
890
|
+
* `complete` remains false so callers can continue from `next_cursor`.
|
|
891
|
+
*/
|
|
892
|
+
async getMessagePage(chatId, options) {
|
|
893
|
+
const count = options?.count ?? 100;
|
|
894
|
+
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
895
|
+
throw new KakaoTalkError('Message page count must be an integer between 1 and 100', 'invalid_message_page_count');
|
|
896
|
+
}
|
|
897
|
+
const parsedChatId = parseChatId(chatId);
|
|
898
|
+
const normalizedChatId = longToString(parsedChatId);
|
|
899
|
+
const cursor = options?.from ? parseLogId(options.from) : Long.fromNumber(0);
|
|
900
|
+
return this.executeWithReconnect(async ({ session }) => {
|
|
901
|
+
try {
|
|
902
|
+
const response = await session.getChatLogs([parsedChatId], [cursor]);
|
|
903
|
+
assertLocoOk(response, 'MCHATLOGS');
|
|
904
|
+
const batch = (response.body.chatLogs ?? []).filter((log) => longToString(log.chatId) === normalizedChatId);
|
|
905
|
+
if (batch.length > 0) {
|
|
906
|
+
return buildMessagePage(batch, count, cursor, response.body.eof === true, normalizedChatId, this.nameCache);
|
|
907
|
+
}
|
|
908
|
+
// CHATINFO is read-only and supplies a fresh lastLogId even for rooms
|
|
909
|
+
// absent from the login snapshot. This avoids CHATONROOM, which can
|
|
910
|
+
// advance server-side unread state.
|
|
911
|
+
const chatInfoResponse = await session.getChannelInfo(parsedChatId);
|
|
912
|
+
assertLocoOk(chatInfoResponse, 'CHATINFO');
|
|
913
|
+
const maxLogId = extractChannelMaxLogId(chatInfoResponse.body, normalizedChatId);
|
|
914
|
+
if (maxLogId.lessThanOrEqual(cursor)) {
|
|
915
|
+
return { messages: [], next_cursor: null, complete: true };
|
|
916
|
+
}
|
|
917
|
+
const syncResponse = await session.syncMessages(parsedChatId, Math.min(count, 80), cursor, maxLogId);
|
|
918
|
+
assertLocoOk(syncResponse, 'SYNCMSG');
|
|
919
|
+
const syncBatch = (syncResponse.body.chatLogs ?? []).filter((log) => longToString(log.chatId) === normalizedChatId);
|
|
920
|
+
const maxReturned = findMaxLogId(syncBatch, 'logId');
|
|
921
|
+
const protocolComplete = syncResponse.body.isOK === true || (maxReturned !== null && maxReturned.greaterThanOrEqual(maxLogId));
|
|
922
|
+
return buildMessagePage(syncBatch, count, cursor, protocolComplete, normalizedChatId, this.nameCache);
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
throw wrapError(error, 'get_message_page_failed');
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
717
929
|
async getMessages(chatId, options) {
|
|
718
930
|
return this.executeWithReconnect(async ({ session, loginResult }) => {
|
|
719
931
|
try {
|
|
@@ -798,30 +1010,58 @@ export class KakaoTalkClient {
|
|
|
798
1010
|
}
|
|
799
1011
|
});
|
|
800
1012
|
}
|
|
801
|
-
async
|
|
1013
|
+
async getMemberSnapshot(chatId) {
|
|
802
1014
|
const parsedChatId = parseChatId(chatId);
|
|
1015
|
+
const normalizedChatId = longToString(parsedChatId);
|
|
803
1016
|
return this.executeWithReconnect(async ({ session }) => {
|
|
804
1017
|
try {
|
|
805
|
-
const
|
|
806
|
-
assertLocoOk(
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1018
|
+
const firstChatResponse = await session.getChannelInfo(parsedChatId);
|
|
1019
|
+
assertLocoOk(firstChatResponse, 'CHATINFO');
|
|
1020
|
+
const firstChat = channelMemberView(firstChatResponse.body, normalizedChatId);
|
|
1021
|
+
const firstMemberResponse = await session.getAllMembers(parsedChatId);
|
|
1022
|
+
assertLocoOk(firstMemberResponse, 'GETMEM');
|
|
1023
|
+
const firstMembers = exactMemberRecords(firstMemberResponse.body.members ?? [], 'GETMEM');
|
|
1024
|
+
const secondChatResponse = await session.getChannelInfo(parsedChatId);
|
|
1025
|
+
assertLocoOk(secondChatResponse, 'CHATINFO');
|
|
1026
|
+
const secondChat = channelMemberView(secondChatResponse.body, normalizedChatId);
|
|
1027
|
+
const secondMemberResponse = await session.getAllMembers(parsedChatId);
|
|
1028
|
+
assertLocoOk(secondMemberResponse, 'GETMEM');
|
|
1029
|
+
const secondMembers = exactMemberRecords(secondMemberResponse.body.members ?? [], 'GETMEM');
|
|
1030
|
+
if (firstChat.activeMembers !== secondChat.activeMembers ||
|
|
1031
|
+
firstMembers.records.length !== firstChat.activeMembers ||
|
|
1032
|
+
secondMembers.records.length !== secondChat.activeMembers ||
|
|
1033
|
+
!sameIds(firstChat.ids, secondChat.ids) ||
|
|
1034
|
+
!sameIds(firstMembers.ids, secondMembers.ids) ||
|
|
1035
|
+
!subsetIds(secondChat.ids, secondMembers.ids)) {
|
|
1036
|
+
throw new Error('member_snapshot_changed_or_incomplete');
|
|
817
1037
|
}
|
|
818
|
-
return
|
|
1038
|
+
return {
|
|
1039
|
+
chat_id: normalizedChatId,
|
|
1040
|
+
active_members: secondChat.activeMembers,
|
|
1041
|
+
members: mergeMemberRecords(secondMembers.records, secondChat.records).map(formatMember),
|
|
1042
|
+
complete: true,
|
|
1043
|
+
consistency_basis: 'stable_double_read_chatinfo_getmem',
|
|
1044
|
+
};
|
|
819
1045
|
}
|
|
820
1046
|
catch (error) {
|
|
821
|
-
throw wrapError(error, '
|
|
1047
|
+
throw wrapError(error, 'get_member_snapshot_failed');
|
|
822
1048
|
}
|
|
823
1049
|
});
|
|
824
1050
|
}
|
|
1051
|
+
async getMembers(chatId) {
|
|
1052
|
+
try {
|
|
1053
|
+
return (await this.getMemberSnapshot(chatId)).members;
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
if (error instanceof KakaoTalkError && error.code === 'get_member_snapshot_failed') {
|
|
1057
|
+
throw new KakaoTalkError(error.message, 'get_members_failed', {
|
|
1058
|
+
cause: error,
|
|
1059
|
+
serverStatus: error.serverStatus,
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
throw error;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
825
1065
|
async getMembersByIds(chatId, userIds) {
|
|
826
1066
|
if (userIds.length === 0)
|
|
827
1067
|
return [];
|