kakaoforge 1.0.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 (49) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +772 -0
  3. package/dist/auth/crypto.d.ts +4 -0
  4. package/dist/auth/crypto.js +69 -0
  5. package/dist/auth/login.d.ts +53 -0
  6. package/dist/auth/login.js +559 -0
  7. package/dist/crypto/v2sl.d.ts +14 -0
  8. package/dist/crypto/v2sl.js +118 -0
  9. package/dist/db/kakao-db.d.ts +51 -0
  10. package/dist/db/kakao-db.js +194 -0
  11. package/dist/db/kakao-schema-secondary.d.ts +2 -0
  12. package/dist/db/kakao-schema-secondary.js +57 -0
  13. package/dist/db/kakao-schema.d.ts +2 -0
  14. package/dist/db/kakao-schema.js +236 -0
  15. package/dist/db/kakao-secondary-db.d.ts +9 -0
  16. package/dist/db/kakao-secondary-db.js +69 -0
  17. package/dist/index.d.ts +634 -0
  18. package/dist/index.js +5181 -0
  19. package/dist/net/booking-client.d.ts +38 -0
  20. package/dist/net/booking-client.js +202 -0
  21. package/dist/net/brewery-client.d.ts +148 -0
  22. package/dist/net/brewery-client.js +419 -0
  23. package/dist/net/bubble-client.d.ts +45 -0
  24. package/dist/net/bubble-client.js +64 -0
  25. package/dist/net/calendar-client.d.ts +41 -0
  26. package/dist/net/calendar-client.js +80 -0
  27. package/dist/net/carriage-client.d.ts +56 -0
  28. package/dist/net/carriage-client.js +426 -0
  29. package/dist/net/loco-stream.d.ts +9 -0
  30. package/dist/net/loco-stream.js +39 -0
  31. package/dist/net/ticket-client.d.ts +11 -0
  32. package/dist/net/ticket-client.js +30 -0
  33. package/dist/net/upload-client.d.ts +18 -0
  34. package/dist/net/upload-client.js +209 -0
  35. package/dist/protocol/loco-packet.d.ts +19 -0
  36. package/dist/protocol/loco-packet.js +54 -0
  37. package/dist/types/attachments.d.ts +17 -0
  38. package/dist/types/attachments.js +14 -0
  39. package/dist/types/member-type.d.ts +8 -0
  40. package/dist/types/member-type.js +10 -0
  41. package/dist/types/message.d.ts +14 -0
  42. package/dist/types/message.js +16 -0
  43. package/dist/types/reaction.d.ts +10 -0
  44. package/dist/types/reaction.js +12 -0
  45. package/dist/util/client-msg-id.d.ts +1 -0
  46. package/dist/util/client-msg-id.js +48 -0
  47. package/dist/util/media.d.ts +6 -0
  48. package/dist/util/media.js +173 -0
  49. package/package.json +40 -0
@@ -0,0 +1,419 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BreweryClient = exports.EventProto = exports.BREWERY_PORT = exports.BREWERY_HOST = void 0;
37
+ const http2 = __importStar(require("http2"));
38
+ const events_1 = require("events");
39
+ const protobuf = __importStar(require("protobufjs"));
40
+ exports.BREWERY_HOST = 'talk-pilsner.kakao.com';
41
+ exports.BREWERY_PORT = 443;
42
+ const APP_VER = '26.1.2';
43
+ /**
44
+ * Protobuf Event schema (from com.kakao.talk.brewery.push.Event)
45
+ *
46
+ * message Event {
47
+ * string path = 1;
48
+ * string type = 2;
49
+ * bytes payload = 3;
50
+ * bytes padding = 4;
51
+ * }
52
+ */
53
+ exports.EventProto = new protobuf.Type('Event')
54
+ .add(new protobuf.Field('path', 1, 'string'))
55
+ .add(new protobuf.Field('type', 2, 'string'))
56
+ .add(new protobuf.Field('payload', 3, 'bytes'))
57
+ .add(new protobuf.Field('padding', 4, 'bytes'));
58
+ new protobuf.Root().add(exports.EventProto);
59
+ /** Length prefix size: 4-byte big-endian uint32 */
60
+ const LEN_PREFIX_SIZE = 4;
61
+ /**
62
+ * Brewery HTTP/2 client for KakaoTalk v26.1.2+.
63
+ *
64
+ * Replaces the legacy LOCO binary protocol with HTTP/2 Retrofit-style
65
+ * communication to talk-pilsner.kakao.com.
66
+ *
67
+ * Usage:
68
+ * const client = new BreweryClient({ oauthToken, lang });
69
+ * await client.connect();
70
+ * client.on('event', (event) => { ... });
71
+ * client.startListen();
72
+ */
73
+ class BreweryClient extends events_1.EventEmitter {
74
+ constructor({ oauthToken, deviceUuid = '', lang = 'ko', appVer = APP_VER }) {
75
+ super();
76
+ this._oauthToken = oauthToken;
77
+ this._deviceUuid = deviceUuid;
78
+ this._lang = lang;
79
+ this._appVer = appVer;
80
+ this._session = null;
81
+ this._listenStream = null;
82
+ this._pingTimer = null;
83
+ this._lastSubscribed = 0;
84
+ this._listenBuffer = Buffer.alloc(0);
85
+ this._connected = false;
86
+ }
87
+ /**
88
+ * Common headers for all brewery requests (PILSNER auth).
89
+ */
90
+ _headers(extra = {}) {
91
+ return {
92
+ 'authorization': `${this._oauthToken}-${this._deviceUuid}`,
93
+ 'talk-agent': `android/${this._appVer}`,
94
+ 'talk-language': this._lang,
95
+ ...extra,
96
+ };
97
+ }
98
+ /**
99
+ * Connect HTTP/2 session to talk-pilsner.kakao.com.
100
+ */
101
+ connect() {
102
+ return new Promise((resolve, reject) => {
103
+ this._session = http2.connect(`https://${exports.BREWERY_HOST}:${exports.BREWERY_PORT}`);
104
+ this._session.on('connect', () => {
105
+ this._connected = true;
106
+ console.log(`[+] HTTP/2 connected to ${exports.BREWERY_HOST}`);
107
+ this.emit('connected');
108
+ resolve();
109
+ });
110
+ this._session.on('error', (err) => {
111
+ console.error('[!] HTTP/2 session error:', err.message);
112
+ this.emit('error', err);
113
+ if (!this._connected)
114
+ reject(err);
115
+ });
116
+ this._session.on('close', () => {
117
+ this._connected = false;
118
+ console.log('[!] HTTP/2 session closed');
119
+ this.emit('disconnected');
120
+ });
121
+ this._session.on('goaway', (errorCode) => {
122
+ console.log(`[!] HTTP/2 GOAWAY received: errorCode=${errorCode}`);
123
+ });
124
+ });
125
+ }
126
+ /**
127
+ * Send an HTTP/2 request and return the response.
128
+ * @param {string} method - HTTP method
129
+ * @param {string} path - URL path
130
+ * @param {Object} [headers] - Extra headers
131
+ * @param {Buffer|string} [body] - Request body
132
+ * @returns {Promise<{status: number, headers: Object, body: Buffer}>}
133
+ */
134
+ request(method, path, { headers = {}, body = null, timeout = 10000 } = {}) {
135
+ return new Promise((resolve, reject) => {
136
+ const reqHeaders = {
137
+ ':method': method,
138
+ ':path': path,
139
+ ...this._headers(headers),
140
+ };
141
+ if (body && typeof body === 'object' && !(body instanceof Buffer)) {
142
+ body = JSON.stringify(body);
143
+ reqHeaders['content-type'] = 'application/json';
144
+ }
145
+ const stream = this._session.request(reqHeaders);
146
+ const chunks = [];
147
+ const timer = setTimeout(() => {
148
+ stream.close();
149
+ reject(new Error(`Request ${method} ${path} timed out`));
150
+ }, timeout);
151
+ stream.on('response', (resHeaders) => {
152
+ stream.on('data', (chunk) => chunks.push(chunk));
153
+ stream.on('end', () => {
154
+ clearTimeout(timer);
155
+ const status = resHeaders[':status'];
156
+ const respBody = Buffer.concat(chunks);
157
+ resolve({ status, headers: resHeaders, body: respBody });
158
+ });
159
+ });
160
+ stream.on('error', (err) => {
161
+ clearTimeout(timer);
162
+ reject(err);
163
+ });
164
+ if (body) {
165
+ stream.write(body);
166
+ }
167
+ stream.end();
168
+ });
169
+ }
170
+ /**
171
+ * Send GET /ping keepalive.
172
+ */
173
+ async ping() {
174
+ try {
175
+ const res = await this.request('GET', '/ping');
176
+ console.log(`[<] PING response: ${res.status}`);
177
+ return res;
178
+ }
179
+ catch (err) {
180
+ console.error('[!] PING error:', err.message);
181
+ throw err;
182
+ }
183
+ }
184
+ /**
185
+ * Start periodic ping (default 10 min = 600000ms, matching app behavior).
186
+ */
187
+ startPing(intervalMs = 600000) {
188
+ this.stopPing();
189
+ this._pingTimer = setInterval(() => this.ping().catch(() => { }), intervalMs);
190
+ }
191
+ stopPing() {
192
+ if (this._pingTimer) {
193
+ clearInterval(this._pingTimer);
194
+ this._pingTimer = null;
195
+ }
196
+ }
197
+ /**
198
+ * Start listening for real-time events via GET /listen.
199
+ * Events are emitted as 'event' with parsed Event protobuf.
200
+ */
201
+ startListen() {
202
+ if (this._listenStream) {
203
+ console.log('[*] Listen stream already active');
204
+ return;
205
+ }
206
+ const reqHeaders = {
207
+ ':method': 'GET',
208
+ ':path': '/listen',
209
+ ...this._headers({
210
+ 'talk-last-subscribed': String(this._lastSubscribed),
211
+ }),
212
+ };
213
+ console.log(`[*] Starting listen stream (lastSubscribed=${this._lastSubscribed})...`);
214
+ this._listenStream = this._session.request(reqHeaders);
215
+ this._listenBuffer = Buffer.alloc(0);
216
+ this._listenStream.on('response', (resHeaders) => {
217
+ const status = resHeaders[':status'];
218
+ console.log(`[+] Listen stream opened: status=${status}`);
219
+ if (status !== 200) {
220
+ console.error(`[!] Listen stream rejected: status=${status}`);
221
+ this._listenStream = null;
222
+ this.emit('listenError', new Error(`Listen returned ${status}`));
223
+ return;
224
+ }
225
+ this.emit('listening');
226
+ });
227
+ this._listenStream.on('data', (chunk) => {
228
+ this._listenBuffer = Buffer.concat([this._listenBuffer, chunk]);
229
+ this._processListenBuffer();
230
+ });
231
+ this._listenStream.on('end', () => {
232
+ console.log('[!] Listen stream ended');
233
+ this._listenStream = null;
234
+ this.emit('listenEnd');
235
+ });
236
+ this._listenStream.on('error', (err) => {
237
+ console.error('[!] Listen stream error:', err.message);
238
+ this._listenStream = null;
239
+ this.emit('listenError', err);
240
+ });
241
+ }
242
+ /**
243
+ * Process buffered listen data.
244
+ * Events arrive as length-delimited protobuf messages (varint length prefix).
245
+ */
246
+ _processListenBuffer() {
247
+ while (this._listenBuffer.length >= LEN_PREFIX_SIZE) {
248
+ // Read 4-byte big-endian length prefix
249
+ const msgLen = this._listenBuffer.readUInt32BE(0);
250
+ // Check if we have the full message
251
+ if (this._listenBuffer.length < LEN_PREFIX_SIZE + msgLen)
252
+ break;
253
+ // Extract the message bytes
254
+ const msgBytes = this._listenBuffer.slice(LEN_PREFIX_SIZE, LEN_PREFIX_SIZE + msgLen);
255
+ this._listenBuffer = this._listenBuffer.slice(LEN_PREFIX_SIZE + msgLen);
256
+ try {
257
+ const event = exports.EventProto.decode(msgBytes);
258
+ const payloadStr = event.payload && event.payload.length > 0
259
+ ? Buffer.from(event.payload).toString('utf8')
260
+ : null;
261
+ let payloadJson = null;
262
+ if (payloadStr) {
263
+ try {
264
+ payloadJson = JSON.parse(payloadStr);
265
+ }
266
+ catch {
267
+ payloadJson = payloadStr;
268
+ }
269
+ }
270
+ const parsed = {
271
+ path: event.path,
272
+ type: event.type,
273
+ payload: payloadJson,
274
+ raw: event,
275
+ };
276
+ this._lastSubscribed = Date.now();
277
+ this.emit('event', parsed);
278
+ // Emit specific event by path
279
+ if (event.path) {
280
+ this.emit(`event:${event.path}`, parsed);
281
+ }
282
+ }
283
+ catch (err) {
284
+ // Only log non-trivial parse errors
285
+ if (msgBytes.length > 1) {
286
+ console.error('[!] Protobuf parse error (%d bytes):', msgBytes.length, err.message);
287
+ console.error('[!] Hex:', msgBytes.slice(0, 100).toString('hex'));
288
+ }
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * Stop the listen stream.
294
+ */
295
+ stopListen() {
296
+ if (this._listenStream) {
297
+ this._listenStream.close();
298
+ this._listenStream = null;
299
+ }
300
+ }
301
+ /**
302
+ * Send push acknowledgment.
303
+ * @param {Object} body - Ack payload
304
+ */
305
+ async pushAck(body) {
306
+ return this.request('POST', '/push-tracker/pushAck', {
307
+ headers: { 'content-type': 'application/json' },
308
+ body: JSON.stringify(body),
309
+ });
310
+ }
311
+ /**
312
+ * Generic GET request to a brewery path.
313
+ */
314
+ async get(path, headers = {}) {
315
+ return this.request('GET', path, { headers });
316
+ }
317
+ /**
318
+ * Generic POST request to a brewery path.
319
+ */
320
+ async post(path, body, headers = {}) {
321
+ return this.request('POST', path, {
322
+ headers: { 'content-type': 'application/json', ...headers },
323
+ body: typeof body === 'string' ? body : JSON.stringify(body),
324
+ });
325
+ }
326
+ /**
327
+ * Parse JSON response body, returning null on failure.
328
+ */
329
+ _parseJson(res) {
330
+ try {
331
+ return JSON.parse(res.body.toString('utf8'));
332
+ }
333
+ catch {
334
+ return null;
335
+ }
336
+ }
337
+ /**
338
+ * Fetch message metadata from a chat using bubble/sync-meta.
339
+ * Used by sub-devices to sync messages via REST polling.
340
+ *
341
+ * @param {number|string} chatId - Chat room ID
342
+ * @param {Object} [opts]
343
+ * @param {number} [opts.cur=0] - Current cursor position (start from this logId)
344
+ * @param {number} [opts.max=0] - Max logId to fetch up to (0 = latest)
345
+ * @param {number} [opts.cnt=50] - Number of messages to fetch
346
+ * @returns {Promise<{content: Array, size: number, last: boolean}>}
347
+ */
348
+ async syncMessages(chatId, { cur = 0, max = 0, cnt = 50 } = {}) {
349
+ const path = `/messaging/chats/${chatId}/bubble/sync-meta?cur=${cur}&max=${max}&cnt=${cnt}`;
350
+ const res = await this.request('GET', path, { timeout: 15000 });
351
+ if (res.status !== 200) {
352
+ throw new Error(`syncMessages failed: status=${res.status}`);
353
+ }
354
+ return this._parseJson(res) || { content: [], size: 0, last: true };
355
+ }
356
+ /**
357
+ * Fetch message metadata for a range of logIds.
358
+ *
359
+ * @param {number|string} chatId - Chat room ID
360
+ * @param {number} from - Start logId
361
+ * @param {number} to - End logId
362
+ * @param {boolean} [desc=false] - Descending order
363
+ * @returns {Promise<{content: Array, size: number, last: boolean}>}
364
+ */
365
+ async getMessages(chatId, from, to, desc = false) {
366
+ const path = `/messaging/chats/${chatId}/bubble/meta?from=${from}&to=${to}&desc=${desc}`;
367
+ const res = await this.request('GET', path, { timeout: 15000 });
368
+ if (res.status !== 200) {
369
+ throw new Error(`getMessages failed: status=${res.status}`);
370
+ }
371
+ return this._parseJson(res) || { content: [], size: 0, last: true };
372
+ }
373
+ /**
374
+ * Fetch chat tab settings (used to detect new messages across chats).
375
+ *
376
+ * @param {number} [revision=0] - Last known revision
377
+ * @returns {Promise<Object>}
378
+ */
379
+ async getChatTabSettings(revision = 0) {
380
+ const path = `/chat/tab/settings?revision=${revision}`;
381
+ const res = await this.request('GET', path, { timeout: 15000 });
382
+ if (res.status !== 200) {
383
+ throw new Error(`getChatTabSettings failed: status=${res.status}`);
384
+ }
385
+ return this._parseJson(res);
386
+ }
387
+ /**
388
+ * Fetch chat room list via GET /messaging/chats.
389
+ * Uses standard PILSNER auth.
390
+ *
391
+ * Returns:
392
+ * { chats: [ChatInfo] }
393
+ * ChatInfo:
394
+ * { chatId, type, title, isEnterAllowed, unreadCount,
395
+ * lastMessageId, lastSeenLogId, displayMembers, ... }
396
+ *
397
+ * @returns {Promise<Object>}
398
+ */
399
+ async getChatRooms() {
400
+ const res = await this.request('GET', '/messaging/chats', { timeout: 15000 });
401
+ if (res.status !== 200) {
402
+ throw new Error(`getChatRooms failed: status=${res.status}`);
403
+ }
404
+ return this._parseJson(res) || { chats: [] };
405
+ }
406
+ /**
407
+ * Disconnect everything.
408
+ */
409
+ disconnect() {
410
+ this.stopPing();
411
+ this.stopListen();
412
+ if (this._session) {
413
+ this._session.close();
414
+ this._session = null;
415
+ }
416
+ this._connected = false;
417
+ }
418
+ }
419
+ exports.BreweryClient = BreweryClient;
@@ -0,0 +1,45 @@
1
+ export declare const BUBBLE_HOST = "talk-pilsner.kakao.com";
2
+ export type BubbleClientOptions = {
3
+ oauthToken: string;
4
+ deviceUuid: string;
5
+ deviceId?: string;
6
+ appVer?: string;
7
+ lang?: string;
8
+ os?: string;
9
+ timeZone?: string;
10
+ hasAccount?: string | boolean;
11
+ adid?: string;
12
+ dtype?: string | number;
13
+ };
14
+ export type ReactionPayload = {
15
+ logId: number | string;
16
+ type: number;
17
+ linkId?: number | string;
18
+ reqId?: number | string;
19
+ };
20
+ export declare class BubbleClient {
21
+ oauthToken: string;
22
+ deviceUuid: string;
23
+ deviceId: string;
24
+ appVer: string;
25
+ lang: string;
26
+ os: string;
27
+ timeZone: string;
28
+ hasAccount: string;
29
+ adid: string;
30
+ dtype: string;
31
+ constructor(opts: BubbleClientOptions);
32
+ _headers(extra?: Record<string, string>): {
33
+ Authorization: string;
34
+ A: string;
35
+ 'User-Agent': string;
36
+ 'talk-agent': string;
37
+ 'talk-language': string;
38
+ TZ: string;
39
+ hasAccount: string;
40
+ ADID: string;
41
+ dtype: string;
42
+ };
43
+ _captureHeaders(res: any): void;
44
+ sendReaction(chatId: number | string, reaction: ReactionPayload): Promise<any>;
45
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BubbleClient = exports.BUBBLE_HOST = void 0;
4
+ const login_1 = require("../auth/login");
5
+ exports.BUBBLE_HOST = 'talk-pilsner.kakao.com';
6
+ const BUBBLE_BASE = '/messaging/chats';
7
+ class BubbleClient {
8
+ constructor(opts) {
9
+ this.oauthToken = opts.oauthToken;
10
+ this.deviceUuid = opts.deviceUuid;
11
+ this.deviceId = opts.deviceId || (0, login_1.buildDeviceId)(opts.deviceUuid);
12
+ this.appVer = opts.appVer || '26.1.2';
13
+ this.lang = opts.lang || 'ko';
14
+ this.os = opts.os || 'android';
15
+ this.timeZone = opts.timeZone || 'Asia/Seoul';
16
+ if (typeof opts.hasAccount === 'boolean') {
17
+ this.hasAccount = opts.hasAccount ? 'true' : 'false';
18
+ }
19
+ else if (typeof opts.hasAccount === 'string') {
20
+ this.hasAccount = opts.hasAccount;
21
+ }
22
+ else {
23
+ this.hasAccount = '';
24
+ }
25
+ this.adid = opts.adid || opts.deviceUuid || '';
26
+ this.dtype = opts.dtype !== undefined && opts.dtype !== null ? String(opts.dtype) : '2';
27
+ }
28
+ _headers(extra = {}) {
29
+ return {
30
+ 'Authorization': (0, login_1.buildAuthorizationHeader)(this.oauthToken, this.deviceId || this.deviceUuid),
31
+ 'A': (0, login_1.buildAHeader)(this.appVer, this.lang),
32
+ 'User-Agent': (0, login_1.buildUserAgent)(this.appVer),
33
+ 'talk-agent': `${this.os}/${this.appVer}`,
34
+ 'talk-language': this.lang,
35
+ 'TZ': this.timeZone,
36
+ 'hasAccount': this.hasAccount,
37
+ 'ADID': this.adid,
38
+ 'dtype': this.dtype,
39
+ ...extra,
40
+ };
41
+ }
42
+ _captureHeaders(res) {
43
+ const headers = res?.headers || {};
44
+ const hasAccount = headers['hasaccount'] ?? headers['hasAccount'];
45
+ if (hasAccount !== undefined && hasAccount !== null) {
46
+ this.hasAccount = Array.isArray(hasAccount) ? String(hasAccount[0] ?? '') : String(hasAccount);
47
+ }
48
+ }
49
+ async sendReaction(chatId, reaction) {
50
+ const payload = {
51
+ logId: reaction.logId,
52
+ type: reaction.type,
53
+ reqId: reaction.reqId ?? Date.now(),
54
+ };
55
+ if (reaction.linkId !== undefined && reaction.linkId !== null && reaction.linkId !== '') {
56
+ payload.linkId = reaction.linkId;
57
+ }
58
+ const path = `${BUBBLE_BASE}/${encodeURIComponent(String(chatId))}/bubble/reactions`;
59
+ const res = await (0, login_1.httpsPostJson)(exports.BUBBLE_HOST, path, payload, this._headers());
60
+ this._captureHeaders(res);
61
+ return res;
62
+ }
63
+ }
64
+ exports.BubbleClient = BubbleClient;
@@ -0,0 +1,41 @@
1
+ export declare const CALENDAR_HOST = "talk-pilsner.kakao.com";
2
+ export type CalendarClientOptions = {
3
+ oauthToken: string;
4
+ deviceUuid: string;
5
+ deviceId?: string;
6
+ appVer?: string;
7
+ lang?: string;
8
+ os?: string;
9
+ timeZone?: string;
10
+ hasAccount?: string | boolean;
11
+ adid?: string;
12
+ dtype?: string | number;
13
+ };
14
+ export declare class CalendarClient {
15
+ oauthToken: string;
16
+ deviceUuid: string;
17
+ deviceId: string;
18
+ appVer: string;
19
+ lang: string;
20
+ os: string;
21
+ timeZone: string;
22
+ hasAccount: string;
23
+ adid: string;
24
+ dtype: string;
25
+ constructor(opts: CalendarClientOptions);
26
+ _headers(extra?: Record<string, string>): {
27
+ Authorization: string;
28
+ A: string;
29
+ 'User-Agent': string;
30
+ 'talk-agent': string;
31
+ 'talk-language': string;
32
+ TZ: string;
33
+ hasAccount: string;
34
+ ADID: string;
35
+ dtype: string;
36
+ };
37
+ _captureHeaders(res: any): void;
38
+ createEvent(event: any, { referer, templateId, originalEId }?: any): Promise<any>;
39
+ connectEvent(eId: string, chatId: number | string, referer?: string): Promise<any>;
40
+ shareMessage(eId: string, referer?: string): Promise<any>;
41
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CalendarClient = exports.CALENDAR_HOST = void 0;
4
+ const login_1 = require("../auth/login");
5
+ exports.CALENDAR_HOST = 'talk-pilsner.kakao.com';
6
+ const CALENDAR_BASE = '/calendar/talk';
7
+ function buildQuery(params) {
8
+ const parts = [];
9
+ for (const [key, value] of Object.entries(params)) {
10
+ if (value === undefined || value === null || value === '')
11
+ continue;
12
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
13
+ }
14
+ return parts.length > 0 ? `?${parts.join('&')}` : '';
15
+ }
16
+ class CalendarClient {
17
+ constructor(opts) {
18
+ this.oauthToken = opts.oauthToken;
19
+ this.deviceUuid = opts.deviceUuid;
20
+ this.deviceId = opts.deviceId || (0, login_1.buildDeviceId)(opts.deviceUuid);
21
+ this.appVer = opts.appVer || '26.1.2';
22
+ this.lang = opts.lang || 'ko';
23
+ this.os = opts.os || 'android';
24
+ this.timeZone = opts.timeZone || 'Asia/Seoul';
25
+ if (typeof opts.hasAccount === 'boolean') {
26
+ this.hasAccount = opts.hasAccount ? 'true' : 'false';
27
+ }
28
+ else if (typeof opts.hasAccount === 'string') {
29
+ this.hasAccount = opts.hasAccount;
30
+ }
31
+ else {
32
+ this.hasAccount = '';
33
+ }
34
+ this.adid = opts.adid || opts.deviceUuid || '';
35
+ this.dtype = opts.dtype !== undefined && opts.dtype !== null ? String(opts.dtype) : '2';
36
+ }
37
+ _headers(extra = {}) {
38
+ return {
39
+ 'Authorization': (0, login_1.buildAuthorizationHeader)(this.oauthToken, this.deviceId || this.deviceUuid),
40
+ 'A': (0, login_1.buildAHeader)(this.appVer, this.lang),
41
+ 'User-Agent': (0, login_1.buildUserAgent)(this.appVer),
42
+ 'talk-agent': `${this.os}/${this.appVer}`,
43
+ 'talk-language': this.lang,
44
+ 'TZ': this.timeZone,
45
+ 'hasAccount': this.hasAccount,
46
+ 'ADID': this.adid,
47
+ 'dtype': this.dtype,
48
+ ...extra,
49
+ };
50
+ }
51
+ _captureHeaders(res) {
52
+ const headers = res?.headers || {};
53
+ const hasAccount = headers['hasaccount'] ?? headers['hasAccount'];
54
+ if (hasAccount !== undefined && hasAccount !== null) {
55
+ this.hasAccount = Array.isArray(hasAccount) ? String(hasAccount[0] ?? '') : String(hasAccount);
56
+ }
57
+ }
58
+ async createEvent(event, { referer, templateId, originalEId } = {}) {
59
+ const query = buildQuery({ referer, templateId, originalEId });
60
+ const path = `${CALENDAR_BASE}/events${query}`;
61
+ const res = await (0, login_1.httpsPostJson)(exports.CALENDAR_HOST, path, event, this._headers());
62
+ this._captureHeaders(res);
63
+ return res;
64
+ }
65
+ async connectEvent(eId, chatId, referer) {
66
+ const query = buildQuery({ eId, chatId, referer });
67
+ const path = `${CALENDAR_BASE}/chat/connectEvent${query}`;
68
+ const res = await (0, login_1.httpsPostJson)(exports.CALENDAR_HOST, path, {}, this._headers());
69
+ this._captureHeaders(res);
70
+ return res;
71
+ }
72
+ async shareMessage(eId, referer) {
73
+ const query = buildQuery({ eId, referer });
74
+ const path = `${CALENDAR_BASE}/chat/shareMessage${query}`;
75
+ const res = await (0, login_1.httpsGet)(exports.CALENDAR_HOST, path, this._headers());
76
+ this._captureHeaders(res);
77
+ return res;
78
+ }
79
+ }
80
+ exports.CalendarClient = CalendarClient;