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 +185 -101
- package/dist/api.d.ts +21 -21
- package/dist/api.js +33 -33
- package/dist/constants.d.ts +17 -17
- package/dist/constants.js +17 -17
- package/dist/context.d.ts +8 -8
- package/dist/context.js +10 -10
- package/dist/errors.d.ts +7 -7
- package/dist/errors.js +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -7
- package/dist/listener.d.ts +8 -8
- package/dist/listener.js +15 -15
- package/dist/lz4.d.ts +2 -2
- package/dist/lz4.js +4 -4
- package/dist/media.d.ts +6 -6
- package/dist/media.js +12 -12
- package/dist/message.d.ts +1 -1
- package/dist/message.js +6 -6
- package/dist/proto.d.ts +9 -9
- package/dist/proto.js +11 -11
- package/dist/qr-login.d.ts +14 -14
- package/dist/qr-login.js +16 -16
- package/dist/types.d.ts +26 -26
- package/package.json +24 -6
package/README.md
CHANGED
|
@@ -1,133 +1,217 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
+
[](https://www.npmjs.com/package/tca-js)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](package.json)
|
|
11
12
|
|
|
12
|
-
|
|
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
|
|
49
|
+
Requires **Node.js 22+**. QR login also needs a local Chrome or Chromium; cookie login does not.
|
|
19
50
|
|
|
20
|
-
## Quick
|
|
51
|
+
## Quick Start
|
|
21
52
|
|
|
22
53
|
```ts
|
|
23
|
-
import { TikTok
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
62
|
+
|
|
63
|
+
api.listener.start();
|
|
33
64
|
```
|
|
34
65
|
|
|
35
|
-
##
|
|
66
|
+
## Authentication
|
|
36
67
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
58
|
-
const
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
123
|
-
|
|
124
|
-
node scripts/smoke.mjs --cookies
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
10
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
28
|
+
/** Whether the session is still alive — call periodically to detect expiry early. */
|
|
29
29
|
isAlive(): Promise<boolean>;
|
|
30
|
-
/**
|
|
30
|
+
/** Recent conversations + their latest messages, paginated with `cursor`. */
|
|
31
31
|
getConversations(cursor?: string): Promise<ConversationPage>;
|
|
32
|
-
/**
|
|
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
|
-
/**
|
|
37
|
+
/** User profiles by uid (display name, @username, avatar). */
|
|
38
38
|
getUsers(uids: string[]): Promise<TikTokUser[]>;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
/**
|
|
47
|
+
/** Mark as read up to message `msg`. */
|
|
48
48
|
markRead(msg: ConversationRef & {
|
|
49
49
|
index: string;
|
|
50
50
|
}): Promise<void>;
|
|
51
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
57
|
-
* `null` =
|
|
58
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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` —
|
|
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 };
|