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.
- package/LICENSE +33 -0
- package/README.md +772 -0
- package/dist/auth/crypto.d.ts +4 -0
- package/dist/auth/crypto.js +69 -0
- package/dist/auth/login.d.ts +53 -0
- package/dist/auth/login.js +559 -0
- package/dist/crypto/v2sl.d.ts +14 -0
- package/dist/crypto/v2sl.js +118 -0
- package/dist/db/kakao-db.d.ts +51 -0
- package/dist/db/kakao-db.js +194 -0
- package/dist/db/kakao-schema-secondary.d.ts +2 -0
- package/dist/db/kakao-schema-secondary.js +57 -0
- package/dist/db/kakao-schema.d.ts +2 -0
- package/dist/db/kakao-schema.js +236 -0
- package/dist/db/kakao-secondary-db.d.ts +9 -0
- package/dist/db/kakao-secondary-db.js +69 -0
- package/dist/index.d.ts +634 -0
- package/dist/index.js +5181 -0
- package/dist/net/booking-client.d.ts +38 -0
- package/dist/net/booking-client.js +202 -0
- package/dist/net/brewery-client.d.ts +148 -0
- package/dist/net/brewery-client.js +419 -0
- package/dist/net/bubble-client.d.ts +45 -0
- package/dist/net/bubble-client.js +64 -0
- package/dist/net/calendar-client.d.ts +41 -0
- package/dist/net/calendar-client.js +80 -0
- package/dist/net/carriage-client.d.ts +56 -0
- package/dist/net/carriage-client.js +426 -0
- package/dist/net/loco-stream.d.ts +9 -0
- package/dist/net/loco-stream.js +39 -0
- package/dist/net/ticket-client.d.ts +11 -0
- package/dist/net/ticket-client.js +30 -0
- package/dist/net/upload-client.d.ts +18 -0
- package/dist/net/upload-client.js +209 -0
- package/dist/protocol/loco-packet.d.ts +19 -0
- package/dist/protocol/loco-packet.js +54 -0
- package/dist/types/attachments.d.ts +17 -0
- package/dist/types/attachments.js +14 -0
- package/dist/types/member-type.d.ts +8 -0
- package/dist/types/member-type.js +10 -0
- package/dist/types/message.d.ts +14 -0
- package/dist/types/message.js +16 -0
- package/dist/types/reaction.d.ts +10 -0
- package/dist/types/reaction.js +12 -0
- package/dist/util/client-msg-id.d.ts +1 -0
- package/dist/util/client-msg-id.js +48 -0
- package/dist/util/media.d.ts +6 -0
- package/dist/util/media.js +173 -0
- 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;
|