@ferchy/n8n-nodes-aimc-toolkit 0.1.18 → 0.1.21
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 +6 -80
- package/dist/nodes/AimcSocial/AimcSocial.node.js +11 -255
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ AIMC Toolkit is a community node package for n8n with focused nodes:
|
|
|
4
4
|
|
|
5
5
|
- **AIMC Code**: run JavaScript with a practical toolbox of libraries.
|
|
6
6
|
- **AIMC Media**: FFmpeg-powered media operations without extra glue nodes.
|
|
7
|
-
- **AIMC Social Scraper**: scrape content from social media platforms (
|
|
7
|
+
- **AIMC Social Scraper**: scrape content from social media platforms (coming soon).
|
|
8
8
|
|
|
9
9
|
## Why I Built This
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ n8n is powerful, but real workflows often need basic utilities (validation, pars
|
|
|
23
23
|
|
|
24
24
|
- **Fewer nodes, cleaner flows**: consolidate multiple steps into one code node.
|
|
25
25
|
- **Media ready**: convert, compress, merge, and inspect media in one place.
|
|
26
|
-
- **Social scraping**: scrape
|
|
26
|
+
- **Social scraping**: scrape social media platforms with a simple UI (coming soon).
|
|
27
27
|
- **Practical libraries**: parsing, validation, dates, and web utilities built in.
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
@@ -113,7 +113,7 @@ Inputs: items are available as `items` (all) or `item` (single)
|
|
|
113
113
|
Return: return a plain object or array of objects (JSON-safe)
|
|
114
114
|
Libraries (globals): axios, lodash (_), zod (z), joi (Joi), yup, Ajv, validator, dayjs, dateFns, dateFnsTz, moment,
|
|
115
115
|
cheerio, papaparse (Papa), yaml (YAML), xml2js, XMLParser, qs, FormData, uuid, nanoid, bytes, cronParser, ms,
|
|
116
|
-
fuzzy, stringSimilarity, slug, pluralize, jsonDiff, htmlToText, marked, qrcode/QRCode, ytdl,
|
|
116
|
+
fuzzy, stringSimilarity, slug, pluralize, jsonDiff, htmlToText, marked, qrcode/QRCode, ytdl, ffmpeg, ffmpegStatic, ffprobeStatic.
|
|
117
117
|
Helpers: utils.now(), utils.safeJson(), utils.toArray()
|
|
118
118
|
Please return only the JavaScript code for the AIMC Code node.
|
|
119
119
|
```
|
|
@@ -193,85 +193,12 @@ Use **Input Mode = File Path** to avoid loading big files into memory.
|
|
|
193
193
|
|
|
194
194
|
### AIMC Social Scraper
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
**Coming Soon!**
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
> - TikTok's Terms of Service
|
|
200
|
-
> - Applicable laws and regulations in their jurisdiction
|
|
201
|
-
> - Rate limiting and respectful usage practices
|
|
202
|
-
> - Copyright and intellectual property rights
|
|
203
|
-
>
|
|
204
|
-
> **This is not an official API support.** This scraper uses TikTok's web interface to extract media and metadata. Use at your own risk. The authors and maintainers are not responsible for any misuse or violations of terms of service.
|
|
205
|
-
|
|
206
|
-
**Platforms**
|
|
207
|
-
- **TikTok** (fully supported)
|
|
208
|
-
- Instagram, Twitter/X (coming soon)
|
|
198
|
+
Social media scraping functionality is currently under development. This node will support scraping content from various platforms including TikTok, Instagram, Twitter/X, and more.
|
|
209
199
|
|
|
210
200
|
**Note**: YouTube support is already available via the **AIMC Code** node using the `ytdl` library. See the Media Helpers section for details.
|
|
211
201
|
|
|
212
|
-
**TikTok Scrape Types**
|
|
213
|
-
- **User Posts**: Scrape posts from a specific user
|
|
214
|
-
- **Hashtag**: Scrape posts from a hashtag
|
|
215
|
-
- **Trending**: Get trending posts
|
|
216
|
-
- **Single Video**: Get metadata for a specific video
|
|
217
|
-
|
|
218
|
-
**Key Options**
|
|
219
|
-
- **Number of Posts**: How many posts to scrape (1-1000)
|
|
220
|
-
- **Options** (collapsible section):
|
|
221
|
-
- **No Watermark**: Download videos without TikTok watermark
|
|
222
|
-
- **HD Video**: Download in HD quality
|
|
223
|
-
- **By User ID**: Treat username as user ID (for large IDs)
|
|
224
|
-
- **Session Cookie** (Advanced): Only needed if you encounter rate limits - see instructions below
|
|
225
|
-
- **No Watermark**: Download videos without TikTok watermark
|
|
226
|
-
- **HD Video**: Download in HD quality
|
|
227
|
-
- **By User ID**: Treat username as user ID (for large IDs)
|
|
228
|
-
|
|
229
|
-
**Example: Scrape User Posts**
|
|
230
|
-
```
|
|
231
|
-
Platform: TikTok
|
|
232
|
-
Scrape Type: User Posts
|
|
233
|
-
Username: username
|
|
234
|
-
Number of Posts: 50
|
|
235
|
-
Options (expand if needed):
|
|
236
|
-
- No Watermark: true
|
|
237
|
-
- Session Cookie: (only if you hit rate limits)
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
**Example: Scrape Hashtag**
|
|
241
|
-
```
|
|
242
|
-
Platform: TikTok
|
|
243
|
-
Scrape Type: Hashtag
|
|
244
|
-
Hashtag: fyp
|
|
245
|
-
Number of Posts: 100
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
**Example: Get Trending**
|
|
249
|
-
```
|
|
250
|
-
Platform: TikTok
|
|
251
|
-
Scrape Type: Trending
|
|
252
|
-
Number of Posts: 30
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
**Session Cookie (Advanced - Optional)**
|
|
256
|
-
The scraper works **without cookies by default**. Session cookies are only needed if you encounter rate limits. They're hidden in the **Options** section - you don't need to worry about them unless you have issues.
|
|
257
|
-
|
|
258
|
-
**When you might need a session cookie:**
|
|
259
|
-
- You're scraping many posts and getting rate limited
|
|
260
|
-
- You're seeing captcha challenges
|
|
261
|
-
- You need to scrape large amounts of data regularly
|
|
262
|
-
|
|
263
|
-
**To get a session cookie (only if needed):**
|
|
264
|
-
1. Open TikTok in your browser and log in
|
|
265
|
-
2. Open DevTools (F12 or Right-click > Inspect)
|
|
266
|
-
3. Go to **Application** tab > **Cookies** > `https://www.tiktok.com`
|
|
267
|
-
4. Find the `sid_tt` cookie
|
|
268
|
-
5. Copy the entire value (e.g., `sid_tt=abc123def456...`)
|
|
269
|
-
6. Expand **Options** in the node and paste into the Session Cookie field
|
|
270
|
-
|
|
271
|
-
**Tip**: Start without a cookie. Most users won't need it. Only add one if you hit rate limits.
|
|
272
|
-
|
|
273
|
-
**Note**: Session cookies help avoid rate limits and captchas. They're optional but recommended for production use.
|
|
274
|
-
|
|
275
202
|
## Library Reference (AIMC Code)
|
|
276
203
|
|
|
277
204
|
Libraries are available as globals or via `libs.<name>`.
|
|
@@ -344,7 +271,6 @@ Provide Python code and set a variable named `result` to return output.
|
|
|
344
271
|
### Media Helpers
|
|
345
272
|
- `qrcode` (global `QRCode` or `qrcode`): generate QR codes as data URLs or images.
|
|
346
273
|
- `@distube/ytdl-core` (global `ytdl`): download media from supported sources. Use for media ingestion flows.
|
|
347
|
-
- `tiktok-scraper` (global `tiktokScraper` or `TikTokScraper`): scrape TikTok content including users, hashtags, trending topics, videos, and metadata. Supports downloading videos (with/without watermarks), session cookies, and proxies.
|
|
348
274
|
- `fluent-ffmpeg` (global `ffmpeg`): build FFmpeg pipelines in JS when you need custom media logic.
|
|
349
275
|
- `ffmpeg-static` (global `ffmpegStatic`): path to a bundled FFmpeg binary (if available).
|
|
350
276
|
- `ffprobe-static` (global `ffprobeStatic`): path to a bundled ffprobe binary (if available).
|
|
@@ -382,7 +308,7 @@ Environment variables:
|
|
|
382
308
|
The AIMC Social Scraper node uses web scraping techniques to extract data from social media platforms. This is **not** an official API integration and is provided for educational and personal use only.
|
|
383
309
|
|
|
384
310
|
**Important Points:**
|
|
385
|
-
- This scraper uses the web interface of platforms
|
|
311
|
+
- This scraper uses the web interface of platforms, not official APIs
|
|
386
312
|
- Users must comply with each platform's Terms of Service
|
|
387
313
|
- Users must comply with applicable laws and regulations (GDPR, CCPA, etc.)
|
|
388
314
|
- Users are responsible for respecting rate limits and avoiding abusive behavior
|
|
@@ -1,55 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AimcSocial = void 0;
|
|
4
|
-
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
-
let tiktokScraper = null;
|
|
6
|
-
function getTikTokScraper() {
|
|
7
|
-
if (!tiktokScraper) {
|
|
8
|
-
try {
|
|
9
|
-
tiktokScraper = require('tiktok-scraper');
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
throw new Error('TikTok scraper not available. Please ensure tiktok-scraper is installed.');
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return tiktokScraper;
|
|
16
|
-
}
|
|
17
|
-
async function scrapeTikTokUser(username, options) {
|
|
18
|
-
const scraper = getTikTokScraper();
|
|
19
|
-
return await scraper.user(username, {
|
|
20
|
-
number: options.number || 20,
|
|
21
|
-
sessionList: options.sessionList || [],
|
|
22
|
-
by_user_id: options.byUserId || false,
|
|
23
|
-
noWaterMark: options.noWaterMark || false,
|
|
24
|
-
hdVideo: options.hdVideo || false,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
async function scrapeTikTokHashtag(hashtag, options) {
|
|
28
|
-
const scraper = getTikTokScraper();
|
|
29
|
-
return await scraper.hashtag(hashtag, {
|
|
30
|
-
number: options.number || 20,
|
|
31
|
-
sessionList: options.sessionList || [],
|
|
32
|
-
noWaterMark: options.noWaterMark || false,
|
|
33
|
-
hdVideo: options.hdVideo || false,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
async function scrapeTikTokTrend(options) {
|
|
37
|
-
const scraper = getTikTokScraper();
|
|
38
|
-
return await scraper.trend('', {
|
|
39
|
-
number: options.number || 20,
|
|
40
|
-
sessionList: options.sessionList || [],
|
|
41
|
-
noWaterMark: options.noWaterMark || false,
|
|
42
|
-
hdVideo: options.hdVideo || false,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
async function scrapeTikTokVideo(videoUrl, options) {
|
|
46
|
-
const scraper = getTikTokScraper();
|
|
47
|
-
return await scraper.video(videoUrl, {
|
|
48
|
-
sessionList: options.sessionList || [],
|
|
49
|
-
noWaterMark: options.noWaterMark || false,
|
|
50
|
-
hdVideo: options.hdVideo || false,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
4
|
class AimcSocial {
|
|
54
5
|
constructor() {
|
|
55
6
|
this.description = {
|
|
@@ -58,7 +9,7 @@ class AimcSocial {
|
|
|
58
9
|
icon: 'file:aimc-social.svg',
|
|
59
10
|
group: ['transform'],
|
|
60
11
|
version: 1,
|
|
61
|
-
description: 'Scrape content from social media platforms
|
|
12
|
+
description: 'Scrape content from social media platforms. Coming soon!',
|
|
62
13
|
defaults: {
|
|
63
14
|
name: 'AIMC Social Scraper',
|
|
64
15
|
},
|
|
@@ -75,133 +26,7 @@ class AimcSocial {
|
|
|
75
26
|
{ name: 'Twitter/X', value: 'twitter' },
|
|
76
27
|
],
|
|
77
28
|
default: 'tiktok',
|
|
78
|
-
description: 'Select the social media platform to scrape
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
displayName: 'Scrape Type',
|
|
82
|
-
name: 'scrapeType',
|
|
83
|
-
type: 'options',
|
|
84
|
-
options: [
|
|
85
|
-
{ name: 'User Posts', value: 'user' },
|
|
86
|
-
{ name: 'Hashtag', value: 'hashtag' },
|
|
87
|
-
{ name: 'Trending', value: 'trending' },
|
|
88
|
-
{ name: 'Single Video', value: 'video' },
|
|
89
|
-
],
|
|
90
|
-
default: 'user',
|
|
91
|
-
displayOptions: {
|
|
92
|
-
show: {
|
|
93
|
-
platform: ['tiktok'],
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
description: 'What type of content to scrape',
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
displayName: 'Username',
|
|
100
|
-
name: 'username',
|
|
101
|
-
type: 'string',
|
|
102
|
-
default: '',
|
|
103
|
-
required: true,
|
|
104
|
-
displayOptions: {
|
|
105
|
-
show: {
|
|
106
|
-
platform: ['tiktok'],
|
|
107
|
-
scrapeType: ['user'],
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
placeholder: 'username or @username',
|
|
111
|
-
description: 'TikTok username (with or without @ symbol - will be automatically removed)',
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
displayName: 'Hashtag',
|
|
115
|
-
name: 'hashtag',
|
|
116
|
-
type: 'string',
|
|
117
|
-
default: '',
|
|
118
|
-
required: true,
|
|
119
|
-
displayOptions: {
|
|
120
|
-
show: {
|
|
121
|
-
platform: ['tiktok'],
|
|
122
|
-
scrapeType: ['hashtag'],
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
placeholder: 'fyp or #fyp',
|
|
126
|
-
description: 'Hashtag to scrape (with or without # symbol - will be automatically removed)',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
displayName: 'Video URL',
|
|
130
|
-
name: 'videoUrl',
|
|
131
|
-
type: 'string',
|
|
132
|
-
default: '',
|
|
133
|
-
required: true,
|
|
134
|
-
displayOptions: {
|
|
135
|
-
show: {
|
|
136
|
-
platform: ['tiktok'],
|
|
137
|
-
scrapeType: ['video'],
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
placeholder: 'https://www.tiktok.com/@username/video/1234567890',
|
|
141
|
-
description: 'Full URL of the TikTok video',
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
displayName: 'Number of Posts',
|
|
145
|
-
name: 'number',
|
|
146
|
-
type: 'number',
|
|
147
|
-
default: 20,
|
|
148
|
-
typeOptions: {
|
|
149
|
-
minValue: 1,
|
|
150
|
-
maxValue: 1000,
|
|
151
|
-
},
|
|
152
|
-
displayOptions: {
|
|
153
|
-
show: {
|
|
154
|
-
platform: ['tiktok'],
|
|
155
|
-
scrapeType: ['user', 'hashtag', 'trending'],
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
description: 'Number of posts to scrape',
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
displayName: 'Options',
|
|
162
|
-
name: 'options',
|
|
163
|
-
type: 'collection',
|
|
164
|
-
placeholder: 'Add Option',
|
|
165
|
-
default: {},
|
|
166
|
-
displayOptions: {
|
|
167
|
-
show: {
|
|
168
|
-
platform: ['tiktok'],
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
options: [
|
|
172
|
-
{
|
|
173
|
-
displayName: 'No Watermark',
|
|
174
|
-
name: 'noWaterMark',
|
|
175
|
-
type: 'boolean',
|
|
176
|
-
default: false,
|
|
177
|
-
description: 'Whether to download videos without watermark',
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
displayName: 'HD Video',
|
|
181
|
-
name: 'hdVideo',
|
|
182
|
-
type: 'boolean',
|
|
183
|
-
default: false,
|
|
184
|
-
description: 'Whether to download HD quality videos',
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
displayName: 'By User ID',
|
|
188
|
-
name: 'byUserId',
|
|
189
|
-
type: 'boolean',
|
|
190
|
-
default: false,
|
|
191
|
-
description: 'If true, interpret the username as a user ID (only applies to User Posts scrape type)',
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
displayName: 'Session Cookie',
|
|
195
|
-
name: 'sessionCookie',
|
|
196
|
-
type: 'string',
|
|
197
|
-
typeOptions: {
|
|
198
|
-
password: true,
|
|
199
|
-
},
|
|
200
|
-
default: '',
|
|
201
|
-
placeholder: 'sid_tt=abc123...',
|
|
202
|
-
description: '[Advanced] TikTok session cookie to reduce rate limits. Only needed if you encounter rate limits. Leave empty to use without. Format: "sid_tt=abc123def456..."',
|
|
203
|
-
},
|
|
204
|
-
],
|
|
29
|
+
description: 'Select the social media platform to scrape',
|
|
205
30
|
},
|
|
206
31
|
],
|
|
207
32
|
};
|
|
@@ -210,84 +35,15 @@ class AimcSocial {
|
|
|
210
35
|
const items = this.getInputData();
|
|
211
36
|
const returnData = [];
|
|
212
37
|
for (let i = 0; i < items.length; i++) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (scrapeType === 'user') {
|
|
223
|
-
const usernameRaw = this.getNodeParameter('username', i);
|
|
224
|
-
if (!usernameRaw || !usernameRaw.trim()) {
|
|
225
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Username is required. Please enter a TikTok username (without @ symbol).');
|
|
226
|
-
}
|
|
227
|
-
// Remove @ symbol if present and trim whitespace
|
|
228
|
-
const username = usernameRaw.trim().replace(/^@/, '');
|
|
229
|
-
const number = this.getNodeParameter('number', i, 20);
|
|
230
|
-
result = await scrapeTikTokUser.call(this, username, {
|
|
231
|
-
number,
|
|
232
|
-
sessionList,
|
|
233
|
-
byUserId: options.byUserId,
|
|
234
|
-
noWaterMark: options.noWaterMark,
|
|
235
|
-
hdVideo: options.hdVideo,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
else if (scrapeType === 'hashtag') {
|
|
239
|
-
const hashtagRaw = this.getNodeParameter('hashtag', i);
|
|
240
|
-
if (!hashtagRaw || !hashtagRaw.trim()) {
|
|
241
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Hashtag is required. Please enter a TikTok hashtag (without # symbol).');
|
|
242
|
-
}
|
|
243
|
-
// Remove # symbol if present and trim whitespace
|
|
244
|
-
const hashtag = hashtagRaw.trim().replace(/^#/, '');
|
|
245
|
-
const number = this.getNodeParameter('number', i, 20);
|
|
246
|
-
result = await scrapeTikTokHashtag.call(this, hashtag, {
|
|
247
|
-
number,
|
|
248
|
-
sessionList,
|
|
249
|
-
noWaterMark: options.noWaterMark,
|
|
250
|
-
hdVideo: options.hdVideo,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
else if (scrapeType === 'trending') {
|
|
254
|
-
const number = this.getNodeParameter('number', i, 20);
|
|
255
|
-
result = await scrapeTikTokTrend.call(this, {
|
|
256
|
-
number,
|
|
257
|
-
sessionList,
|
|
258
|
-
noWaterMark: options.noWaterMark,
|
|
259
|
-
hdVideo: options.hdVideo,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
else if (scrapeType === 'video') {
|
|
263
|
-
const videoUrl = this.getNodeParameter('videoUrl', i);
|
|
264
|
-
result = await scrapeTikTokVideo.call(this, videoUrl, {
|
|
265
|
-
sessionList,
|
|
266
|
-
noWaterMark: options.noWaterMark,
|
|
267
|
-
hdVideo: options.hdVideo,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Platform "${platform}" is not yet implemented. Currently only TikTok is supported.`);
|
|
273
|
-
}
|
|
274
|
-
returnData.push({
|
|
275
|
-
json: result,
|
|
276
|
-
pairedItem: { item: i },
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
if (this.continueOnFail()) {
|
|
281
|
-
returnData.push({
|
|
282
|
-
json: {
|
|
283
|
-
error: error instanceof Error ? error.message : String(error),
|
|
284
|
-
},
|
|
285
|
-
pairedItem: { item: i },
|
|
286
|
-
});
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
38
|
+
const platform = this.getNodeParameter('platform', i);
|
|
39
|
+
returnData.push({
|
|
40
|
+
json: {
|
|
41
|
+
message: 'AIMC Social Scraper is coming soon!',
|
|
42
|
+
platform: platform,
|
|
43
|
+
note: 'This feature is currently under development. Stay tuned for updates!',
|
|
44
|
+
},
|
|
45
|
+
pairedItem: { item: i },
|
|
46
|
+
});
|
|
291
47
|
}
|
|
292
48
|
return [returnData];
|
|
293
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferchy/n8n-nodes-aimc-toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "AIMC Toolkit nodes for n8n: code execution and media operations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ferchy",
|
|
@@ -88,7 +88,6 @@
|
|
|
88
88
|
"uuid": "^11.1.0",
|
|
89
89
|
"validator": "^13.11.0",
|
|
90
90
|
"xml2js": "^0.6.2",
|
|
91
|
-
"tiktok-scraper": "git+https://github.com/drawrowfly/tiktok-scraper.git",
|
|
92
91
|
"yaml": "^2.8.1",
|
|
93
92
|
"yup": "^1.7.0",
|
|
94
93
|
"zod": "^3.25.76"
|