tca-js 0.3.0 → 0.3.1

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/README.md CHANGED
@@ -1,133 +1,217 @@
1
- # tca-js — Unofficial TikTok Chat API
1
+ <div align="center">
2
2
 
3
- Read and send TikTok **direct messages** (text, images, reactions) and receive new messages in real time over
4
- websocket — **using session cookies only, no browser needed**. Same idea as [`zca-js`](https://github.com/RFS-ADRENO/zca-js)
5
- for Zalo: it speaks the same protocol as TikTok Web (`tiktok.com/messages`, `tiktok.com/business-suite/messages`).
3
+ # tca-js
6
4
 
7
- > [!WARNING]
8
- > This is an **unofficial** library, not affiliated with or endorsed by TikTok / ByteDance. It relies on undocumented
9
- > internal APIs that can change or break at any time. Automating a TikTok account may violate TikTok's Terms of Service
10
- > and **can get the account restricted or banned** — use it at your own risk, don't spam.
5
+ **Unofficial TikTok Chat API for Node.js**
6
+
7
+ Read and send TikTok direct messages, react, and receive new messages in real time.
8
+
9
+ [![npm](https://img.shields.io/npm/v/tca-js)](https://www.npmjs.com/package/tca-js)
10
+ [![license](https://img.shields.io/github/license/rice2411/tca-js)](LICENSE)
11
+ [![node](https://img.shields.io/node/v/tca-js)](package.json)
11
12
 
12
- ## Install
13
+ </div>
14
+
15
+ > [!WARNING]
16
+ > This project is not affiliated with or endorsed by TikTok / ByteDance. It relies on undocumented APIs that can change
17
+ > at any time. Automating an account may violate TikTok's Terms of Service and can get it restricted — use at your own risk.
18
+
19
+ ## Table of Contents
20
+
21
+ - [Features](#features)
22
+ - [Installation](#installation)
23
+ - [Quick Start](#quick-start)
24
+ - [Authentication](#authentication)
25
+ - [API Reference](#api-reference)
26
+ - [Listener](#listener)
27
+ - [Error Handling](#error-handling)
28
+ - [Contributing](#contributing)
29
+ - [Support Us](#support-us)
30
+ - [License](#license)
31
+
32
+ ## Features
33
+
34
+ - 💬 Send text and image messages
35
+ - 📥 List conversations, page through message history, look up users
36
+ - ⚡ Real-time listener over websocket with auto-reconnect
37
+ - 🖼️ Download and decrypt private images, download stickers and avatars
38
+ - ❤️ Read and set reactions, mark conversations as read
39
+ - 🔐 Cookie-based session — no browser at runtime
40
+ - 📱 Optional QR-code login
41
+ - 📦 Written in TypeScript, ships type definitions, one runtime dependency (`ws`)
42
+
43
+ ## Installation
13
44
 
14
45
  ```bash
15
46
  npm install tca-js
16
47
  ```
17
48
 
18
- Node.js ≥ 22. QR login additionally needs a local Chrome / Chromium (see below); cookie login does not.
49
+ Requires **Node.js 22+**. QR login also needs a local Chrome or Chromium; cookie login does not.
19
50
 
20
- ## Quick start
51
+ ## Quick Start
21
52
 
22
53
  ```ts
23
- import { TikTok, loginQR } from 'tca-js';
54
+ import { TikTok } from 'tca-js';
55
+
56
+ const api = await new TikTok().login({ cookies });
24
57
 
25
- const api = await new TikTok().login(await loginQR({ chromePath, onEvent })); // or saved cookies
26
58
  api.listener.on('message', async (msg) => {
27
59
  if (msg.isSelf) return;
28
- if (msg.image) await api.downloadImage(msg); // decrypted image Buffer
29
- await api.sendMessage(msg, 'Got it, thanks!'); // a message carries its conversation
60
+ await api.sendMessage(msg, `You said: ${msg.text}`);
30
61
  });
31
- api.listener.start(); // reconnects on drop
32
- await api.sendImage(conv, { data: buf, filename: 'photo.jpg' });
62
+
63
+ api.listener.start();
33
64
  ```
34
65
 
35
- ## API
66
+ ## Authentication
36
67
 
37
- | Method | Does |
38
- |---|---|
39
- | `loginQR({chromePath, onEvent, signal})` | QR login through headless Chrome → `Credentials` |
40
- | `new TikTok().login({cookies, deviceId?, userAgent?})` | validate cookies (`passport/web/account/info`), resolve `wid` as `device_id` → `API` |
41
- | `api.getConversations(cursor?)` | 10 conversations per page + latest message; page with `cursor` |
42
- | `api.getMessages(conv, {limit, before})` | messages of a conversation, newest → oldest |
43
- | `api.getUsers(uids)` | display name, `@username`, avatar |
44
- | `api.sendMessage(conv, text)` / `api.sendImage(conv, {data, filename})` | send |
45
- | `api.setReaction(msg, emoji \| null)` | your reaction on a message (one per user, `null` = remove); read via `msg.reactions` |
46
- | `api.downloadSticker(msg.sticker)` | sticker image (public CDN) |
47
- | `api.downloadImage(msg)` | download + decrypt AES-256-GCM (listener messages carry no link — looked up from history) |
48
- | `api.markRead(msg)` · `api.isAlive()` · `api.getCookies()` | |
49
- | `api.listener` | events `connected` · `message` · `command` (read receipts…) · `closed` · `error` |
50
-
51
- Errors: `TikTokError.code` — `SESSION_EXPIRED` (get fresh cookies), `IM_ERROR`, `UPLOAD_FAILED`, `DOWNLOAD_FAILED`, `HTTP_ERROR`.
52
-
53
- ## Session — QR login and cookies
68
+ ### Cookies
69
+
70
+ Log in with the cookies of a `tiktok.com` session. Accepted formats:
71
+
72
+ ```ts
73
+ await new TikTok().login({ cookies: [{ name: 'sessionid', value: '…' }, /* … */] }); // array
74
+ await new TikTok().login({ cookies: { sessionid: '…', ttwid: '…' } }); // object
75
+ await new TikTok().login({ cookies: 'sessionid=…; ttwid=…' }); // header string
76
+ ```
77
+
78
+ Required cookies: `sessionid`, `sid_tt`, `sid_guard`, `uid_tt`, `ttwid`, `odin_tt`, `store-idc`, `tt-target-idc`.
79
+ A session lasts about 6 months, and logging in does not sign out other devices.
80
+
81
+ ### QR code
82
+
83
+ `loginQR` opens a headless Chrome on TikTok's QR page, emits the QR image, and resolves with credentials once the code is
84
+ scanned and confirmed on the phone.
54
85
 
55
86
  ```ts
56
87
  import { loginQR, TikTok } from 'tca-js';
57
- const creds = await loginQR({ chromePath: '/usr/bin/chromium-browser', onEvent: (e) => { /* qr | scanned | confirmed */ } });
58
- const api = await new TikTok().login(creds); // SAVE creds (cookies) to skip scanning next time
88
+
89
+ const credentials = await loginQR({
90
+ chromePath: '/usr/bin/chromium-browser',
91
+ onEvent: (e) => {
92
+ if (e.type === 'qr') showQr(e.image); // data:image/png;base64,…
93
+ },
94
+ });
95
+
96
+ const api = await new TikTok().login(credentials);
97
+ // Save `credentials` to skip scanning next time.
59
98
  ```
60
99
 
61
- - **Reading / sending / websocket only need cookies.** The `X-Gnarly`, `X-Dynosaur`, `X-Bogus`, `msToken` signatures
62
- and `tt-ticket-guard-*` headers the page attaches to IM requests are **not checked** by the server.
63
- - **The QR login API (`passport/web/check_qrconnect`) DOES check signatures** — calling it bare from Node returns
64
- `error_code 7`. So `loginQR` opens **headless Chrome/Chromium** at `tiktok.com/login/qrcode`, lets the page sign its
65
- own requests, and only LISTENS via CDP: the QR image comes from the `get_qrcode` response, status
66
- (`new → scanned → confirmed`, or `expired` → reload for a new QR) from `check_qrconnect`. Once confirmed it reads the
67
- cookies (`Network.getAllCookies`) and closes Chrome. Works with `--no-sandbox` Chromium on Alpine.
68
- - Core cookies: `sessionid`, `sid_tt`, `sid_guard`, `uid_tt`, `ttwid`, `odin_tt`, `store-idc`, `tt-target-idc`.
69
- `sessionid` lives ~6 months. TikTok allows concurrent sessions — logging in here doesn't kick other devices.
70
- - `device_id` = the `wid` TikTok issues for a `ttwid` (`/api/v1/web-cookie-privacy/config`). HTTP IM accepts any id
71
- but **the websocket only accepts the real `wid`** (made-up id → 400).
72
-
73
- ## Protocol (for fixing it when TikTok changes)
74
-
75
- **HTTP IM** — `POST https://im-api-sg.tiktok.com/<path>?aid=1988&…`, protobuf request and response.
76
- `Request {1 cmd, 2 seq, 3 sdk "1.8.5", 5 3, 6 0, 7 build, 8 {<cmd>: body}, 9 device_id, 11 "web", 15 map, 18 1}`,
77
- `Response {1 cmd, 3 status_code, 4 error, 6 {<cmd>: body}}` (`200005` = not logged in).
78
-
79
- | path | cmd | body |
100
+ | Option | Type | Default | Description |
101
+ |---|---|---|---|
102
+ | `chromePath` | `string` | — | Path to Chrome / Chromium |
103
+ | `onEvent` | `(e) => void` | — | `qr` · `scanned` · `confirmed` |
104
+ | `signal` | `AbortSignal` | — | Cancel the login |
105
+ | `timeoutMs` | `number` | `300000` | Total time to wait for a scan |
106
+ | `maxRefresh` | `number` | `3` | Max QR regenerations after expiry |
107
+ | `userAgent` | `string` | — | Browser user agent |
108
+
109
+ ## API Reference
110
+
111
+ `new TikTok(options?)` — `logger?: (level, msg) => void`, `timeoutMs?: number`.
112
+ `.login(credentials)` resolves to an `API` instance.
113
+
114
+ ### Conversations & messages
115
+
116
+ | Method | Returns | Description |
80
117
  |---|---|---|
81
- | `/v1/message/send` | 100 | `{1 convId, 2 1, 3 shortId, 4 content JSON, 5 ext, 6 type, 7 "deprecated", 8 clientMsgId, [13 image, 14 "private_image", 17 image]}` |
82
- | `/v2/message/get_by_user_init` | 203 | `{1 offset}` → `{1 messages[], 2 conversations[], 4 next offset, 5 has more}` |
83
- | `/v1/message/get_by_conversation` | 301 | `{1 convId, 2 1, 3 shortId, 4 1 (older), 5 index anchor, 6 limit}` |
84
- | `/v3/conversation/mark_read` | 2002 (body at 604) | `{1 convId, 2 shortId, 3 1, 4 index, 11 4}` |
85
- | `/v1/media/upload_config` | 2059 | empty → ImageX STS keys |
86
- | `/v1/message/set_property` | 705 | `{1 {1 convId, 2 1, 3 shortId, 4 serverMsgId, 5 clientMsgId, 6 [{1 op 0 add/1 remove, 2 "e:<emoji>", 4 own uid}]}, 2 ticket}` → `{1 status (0 ok, 1 duplicate), 2 version}` |
87
-
88
- `MessageBody {1 convId, 3 serverMsgId, 4 index, 5 shortId, 6 type, 7 sender, 8 content JSON, 9 ext, 10 time ms, 15 reactions, 18 quote, 20 image/sticker}`.
89
- Types: `7` text (`{"aweType":0,"text":…}`), `1802` private image, `1805` sticker, `8` shared video, `1` system, `50001` hidden command.
90
-
91
- **Reactions** (field 15): `{1 "e:<emoji>", 2 {1 [{1 uid, 3 seconds, 4 idempotent_id, 6 µs}]}}`.
92
- The mobile app writes ❤️ as both `e:❤️` and the legacy `e:love` key (web ignores `e:love`) → `tca-js` merges them into ❤️.
93
- Web offers 12 emoji, one reaction per user (add the new key + remove your old one in the same 705 command).
94
- **Stickers** (`1805`, field 20 → 7): `{1 {1 uri, 2 public CDN url, 5 {w,h}}, 200 {1 {2 map a:sticker_id, a:sticker_type (14 stock / 15 custom)…}}}`.
95
- TikTok Web only DISPLAYS stickers, it has no send button — sending stickers is not implemented.
96
- 1-1 `convId` = `0:1:<smaller uid>:<larger uid>`.
97
-
98
- **Websocket** — `wss://im-ws-sg.tiktok.com/ws/v2?aid=1459&fpid=9&access_key=…&device_id=<wid>&ttwid=…`
99
- + cookies. `access_key = md5("9" + "e1bd35ec9db7b8d846de66ed140b1ad9" + wid + "f8a69f1719916z")`.
100
- Frame `{1 seq, 2 logid, 3 5, 4 1, 5 headers, 6 encoding ("__lz4" = block compression), 7 "pb", 8 Response}`;
101
- new message = `Response.cmd 500`, `body[500][5]` is a `MessageBody`. Keep-alive: send text `"hi"` every 15 s.
102
- Frames with header `need_ack=1` → reply `{1 seq, 2 logid, 3 5, 4 1, 5 {is_ack:1, ack_id, ack_code:0}, 9 ack_id}`,
103
- `ack_id = <Date.now() base36>:<x_frontier_traceid>`. The server may push a message several times → dedupe by message id.
104
-
105
- **Sending images** — `upload_config` → ImageX signed with AWS SigV4 (`cn-north-1`/`imagex`): `ApplyImageUpload` →
106
- `POST https://<UploadHost>/upload/v1/<StoreUri>` (headers `Authorization: <Auth>`, `Content-CRC32`,
107
- **`X-Storage-U: <uid>`** — missing it gives 4004) → `CommitImageUpload` with `Functions: Encryption cipher_v2`
108
- → returns `Uri` + `SecretKey` + dimensions → send message type `1802`.
109
- **Receiving images** — `p0-tiktok-dm-private-sg…` links (need cookies), content is AES-256-GCM:
110
- `12-byte IV ‖ ciphertext ‖ 16-byte tag`, key = `decrypt_key` (hex) in the message.
111
-
112
- ## Re-discovering the protocol when it breaks
113
-
114
- Run Chrome with a debug port and capture traffic over CDP (`Network.*` for every target, including the chat iframe
115
- `tiktok.com/messages?scene=business`), then print the protobuf tree without a `.proto`. The web app's message bundle
116
- (`messages-vmok` manifest) ships full protobufjs schemas — the fastest way to find new commands. Requests the browser
117
- sends are the reference: rebuild them with `Writer`, drop the signatures, check the server still accepts them.
118
-
119
- ## Development
118
+ | `getConversations(cursor?)` | `ConversationPage` | 10 conversations per page with their latest messages |
119
+ | `getMessages(conv, { limit?, before? })` | `MessagePage` | Messages of a conversation, newest first |
120
+ | `sendMessage(conv, text)` | `SendResult` | Send a text message |
121
+ | `sendImage(conv, { data, filename })` | `SendResult` | Send an image (`Buffer`) |
122
+ | `setReaction(msg, emoji \| null)` | `void` | Set your reaction; `null` removes it |
123
+ | `markRead(msg)` | `void` | Mark the conversation as read up to `msg` |
124
+
125
+ `conv` is any object with `conversationId` and `conversationShortId` — every message satisfies it, so you can reply
126
+ with `sendMessage(msg, …)`.
127
+
128
+ ### Users & media
129
+
130
+ | Method | Returns | Description |
131
+ |---|---|---|
132
+ | `getUsers(uids)` | `TikTokUser[]` | Nickname, `@username`, avatar |
133
+ | `downloadImage(msg, 'origin' \| 'thumb')` | `Buffer` | Download and decrypt a private image |
134
+ | `downloadSticker(sticker)` | `{ data, type }` | Download a sticker image |
135
+ | `downloadAvatar(user)` | `{ data, type } \| null` | Download a user's avatar |
136
+
137
+ ### Session
138
+
139
+ | Method / property | Description |
140
+ |---|---|
141
+ | `account` | Logged-in account: `uid`, `username`, `screenName`, `avatar` |
142
+ | `getOwnId()` | Own user id |
143
+ | `getCookies()` | Current cookies — persist them to resume the session |
144
+ | `isAlive()` | `true` while the session is still valid |
145
+
146
+ ### Message object
147
+
148
+ | Field | Type | Description |
149
+ |---|---|---|
150
+ | `kind` | `'text' \| 'image' \| 'sticker' \| 'video' \| 'system' \| 'other'` | Message kind |
151
+ | `text` | `string` | Text content |
152
+ | `image` / `sticker` | `object \| null` | Attachment details |
153
+ | `reactions` | `{ emoji, userIds }[]` | Reactions on the message |
154
+ | `senderId` / `isSelf` | `string` / `boolean` | Sender |
155
+ | `createdAt` | `number` | Epoch milliseconds |
156
+
157
+ Full typings are shipped in `dist/index.d.ts`.
158
+
159
+ ## Listener
160
+
161
+ ```ts
162
+ api.listener.on('connected', () => console.log('connected'));
163
+ api.listener.on('message', (msg) => { /* new message */ });
164
+ api.listener.on('command', (msg) => { /* read receipts and other hidden commands */ });
165
+ api.listener.on('closed', (code, reason) => {});
166
+ api.listener.on('error', (err) => {});
167
+
168
+ api.listener.start(); // reconnects automatically; start({ retryOnClose: false }) to disable
169
+ api.listener.stop();
170
+ ```
171
+
172
+ ## Error Handling
173
+
174
+ All library errors are instances of `TikTokError` with a `code`:
175
+
176
+ | Code | Meaning |
177
+ |---|---|
178
+ | `SESSION_EXPIRED` | Cookies are no longer valid — log in again |
179
+ | `IM_ERROR` | TikTok rejected the request |
180
+ | `HTTP_ERROR` · `BAD_RESPONSE` | Network or unexpected response |
181
+ | `UPLOAD_FAILED` · `DOWNLOAD_FAILED` | Media transfer failed |
182
+ | `LOGIN_FAILED` · `LOGIN_ABORTED` | QR login failed or was cancelled |
183
+
184
+ ```ts
185
+ import { TikTokError } from 'tca-js';
186
+
187
+ try {
188
+ await api.sendMessage(conv, 'hi');
189
+ } catch (err) {
190
+ if (err instanceof TikTokError && err.code === 'SESSION_EXPIRED') {
191
+ // refresh credentials
192
+ }
193
+ }
194
+ ```
195
+
196
+ ## Contributing
197
+
198
+ Issues and pull requests are welcome. Protocol notes for maintainers are in [docs/PROTOCOL.md](docs/PROTOCOL.md).
120
199
 
121
200
  ```bash
122
- npm install && npm run build
123
- node scripts/qr-login.mjs <chromePath> <output dir> # try QR login (writes qr.png)
124
- node scripts/smoke.mjs --cookies <cookies.json> [--peer <uid>] [--text "…"] [--image <file>] [--listen 10]
201
+ npm install
202
+ npm run build
203
+ node scripts/smoke.mjs --cookies cookies.json # read-only smoke test against a real account
125
204
  ```
126
205
 
127
- `cookies.json` holds a real session — keep it out of the repo (already in `.gitignore`).
206
+ ## Support Us
207
+
208
+ - ⭐ **Star the repository** if you find it useful
209
+ - 🔄 **Share** it with others
210
+ - 💡 **Contribute** ideas and code
211
+ - ☕ **Buy me a coffee** — VietQR (MB Bank · `0776750418`)
128
212
 
129
- Source comments are in Vietnamese. Issues and PRs welcome in English or Vietnamese.
213
+ <img src=".github/assets/vietqr-mb.png" alt="VietQR MB Bank 0776750418" width="220">
130
214
 
131
215
  ## License
132
216
 
133
- [MIT](LICENSE)
217
+ [MIT](LICENSE) © rice2411
package/dist/api.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import type { Context } from './context';
2
2
  import { Listener } from './listener';
3
3
  import type { ConversationPage, ConversationRef, ImageInput, MessagePage, TikTokAccount, TikTokImage, TikTokMessage, TikTokSticker, TikTokUser } from './types';
4
- /** Kết quả gửi: mã tin phía client (TikTok trả lại đúng mã này). Mã server tới qua listener (`isSelf`). */
4
+ /** Send result: client-side message id (TikTok echoes it back). The server id arrives via the listener (`isSelf`). */
5
5
  export interface SendResult {
6
6
  clientMessageId: string;
7
7
  }
8
8
  /**
9
- * Một phiên TikTok đã đăng nhập — tạo bằng `new TikTok().login(credentials)`.
10
- * Mọi lệnh đi thẳng HTTP/WS tới TikTok bằng cookie, không cần trình duyệt.
9
+ * A logged-in TikTok session — create with `new TikTok().login(credentials)`.
10
+ * All calls go directly over HTTP/WS to TikTok using cookies, no browser needed.
11
11
  */
12
12
  export declare class API {
13
13
  private readonly ctx;
@@ -16,7 +16,7 @@ export declare class API {
16
16
  private readonly media;
17
17
  constructor(ctx: Context, account: TikTokAccount);
18
18
  getOwnId(): string;
19
- /** Cookie hiện tại — lưu lại để lần sau `login` không cần quét QR. */
19
+ /** Current cookies — save them so the next `login` needs no QR scan. */
20
20
  getCookies(): {
21
21
  name: string;
22
22
  value: string;
@@ -25,51 +25,51 @@ export declare class API {
25
25
  deviceId: string;
26
26
  userAgent: string;
27
27
  };
28
- /** Phiên còn sống không — gọi định kỳ để phát hiện hết hạn sớm. */
28
+ /** Whether the session is still alive — call periodically to detect expiry early. */
29
29
  isAlive(): Promise<boolean>;
30
- /** Hội thoại gần đây + tin mới nhất của chúng, lật trang bằng `cursor`. */
30
+ /** Recent conversations + their latest messages, paginated with `cursor`. */
31
31
  getConversations(cursor?: string): Promise<ConversationPage>;
32
- /** Tin của một hội thoại, mới nhất trước. Đọc tiếp tin cũ hơn: `before = <index của tin cũ nhất>`. */
32
+ /** Messages of a conversation, newest first. For older messages: `before = <index of oldest message>`. */
33
33
  getMessages(conv: ConversationRef, opts?: {
34
34
  limit?: number;
35
35
  before?: string;
36
36
  }): Promise<MessagePage>;
37
- /** Hồ sơ người dùng theo uid (tên hiển thị, @username, ảnh đại diện). */
37
+ /** User profiles by uid (display name, @username, avatar). */
38
38
  getUsers(uids: string[]): Promise<TikTokUser[]>;
39
39
  /**
40
- * Tải ảnh đại diện (thử lần lượt các link, bỏ heic). Không gửi cookie / Referer: CDN ảnh khác miền và
41
- * link đã có chữ ký. `null` nếu không link nào tải được.
40
+ * Download an avatar (tries each URL in turn, skips heic). No cookie / Referer sent: the image CDN is cross-origin and
41
+ * URLs are already signed. `null` if no URL could be downloaded.
42
42
  */
43
43
  downloadAvatar(user: Pick<TikTokUser, 'avatars'>): Promise<{
44
44
  data: Buffer;
45
45
  type: string;
46
46
  } | null>;
47
- /** Đánh dấu đã đọc tới tin `msg`. */
47
+ /** Mark as read up to message `msg`. */
48
48
  markRead(msg: ConversationRef & {
49
49
  index: string;
50
50
  }): Promise<void>;
51
- /** Gửi tin chữ vào hội thoại (thường là `msg` vừa nhận — nó mang sẵn mã hội thoại). */
51
+ /** Send a text message to a conversation (usually the just-received `msg` — it carries the conversation ids). */
52
52
  sendMessage(conv: ConversationRef, text: string): Promise<SendResult>;
53
- /** Gửi một ảnh: tải lên kho riêng tư (được mã hoá) rồi gửi tin ảnh. */
53
+ /** Send an image: upload to the private (encrypted) store, then send an image message. */
54
54
  sendImage(conv: ConversationRef, image: ImageInput): Promise<SendResult>;
55
55
  /**
56
- * Đặt reaction CỦA MÌNH trên một tin — như TikTok Web, mỗi người một reaction: `emoji` mới thay cái cũ,
57
- * `null` = gỡ. Truyền nguyên `msg` vừa đọc (cần `reactions` hiện tại để biết gỡ khoá nào).
58
- * Gửi `ModifyMessagePropertyRequestBody` (lệnh 705) — xem README.
56
+ * Set YOUR reaction on a message — like TikTok Web, one reaction per user: a new `emoji` replaces the old one,
57
+ * `null` = remove. Pass the whole `msg` just read (current `reactions` are needed to know which key to remove).
58
+ * Sends `ModifyMessagePropertyRequestBody` (command 705) — see README.
59
59
  */
60
60
  setReaction(msg: TikTokMessage, emoji: string | null): Promise<void>;
61
- /** Tải ảnh sticker (CDN công khai, không mã hoá, không cần cookie). */
61
+ /** Download a sticker image (public CDN, unencrypted, no cookie needed). */
62
62
  downloadSticker(sticker: Pick<TikTokSticker, 'url'>): Promise<{
63
63
  data: Buffer;
64
64
  type: string;
65
65
  }>;
66
- /** Tải + giải mã ảnh của một tin (truyền cả `msg` — tin từ listener thiếu link, hàm tự tra lại). */
66
+ /** Download + decrypt a message's image (pass the whole `msg` — listener messages lack the URL, this looks it up). */
67
67
  downloadImage(source: TikTokMessage | TikTokImage, which?: 'origin' | 'thumb'): Promise<Buffer>;
68
- /** `SendMessageRequestBody`: {1 convId, 2 loại hội thoại, 3 shortId, 4 nội dung, 5 ext, 6 loại tin, 7 ticket, 8 client id, …}. */
68
+ /** `SendMessageRequestBody`: {1 convId, 2 conversation type, 3 shortId, 4 content, 5 ext, 6 message type, 7 ticket, 8 client id, …}. */
69
69
  private send;
70
70
  }
71
- /** Tài khoản của cookie — `null` nếu cookie không còn đăng nhập. */
71
+ /** Account for the cookie — `null` if the cookie is no longer logged in. */
72
72
  export declare function fetchAccount(ctx: Context): Promise<TikTokAccount | null>;
73
- /** `wid` — mã máy TikTok cấp theo cookie `ttwid`; IM + websocket dùng làm `device_id`. */
73
+ /** `wid` — device id TikTok issues per `ttwid` cookie; IM + websocket use it as `device_id`. */
74
74
  export declare function fetchWebId(ctx: Context): Promise<string>;
75
75
  export type { TikTokMessage };