mirror of
https://github.com/hansputera/tiktok-dl.git
synced 2026-04-05 19:51:57 +02:00
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
public
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"google"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
* @hansputera
|
||||
packages/core/src/musicalyDownProvider.ts @OhYoonHee
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,4 +1,14 @@
|
||||
.vercel
|
||||
node_modules/
|
||||
.env.local
|
||||
.env
|
||||
package-lock.json
|
||||
.turbo
|
||||
.yarn/cache
|
||||
.yarn/sdks
|
||||
.yarn/plugins
|
||||
.yarn/patches
|
||||
.yarn/*.gz
|
||||
*.rdb
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint
|
||||
npm run lint
|
||||
npm run format
|
||||
|
||||
942
.yarn/releases/yarn-4.9.3.cjs
vendored
Executable file
942
.yarn/releases/yarn-4.9.3.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
7
.yarnrc.yml
Normal file
7
.yarnrc.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.3.cjs
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM node:current-alpine3.15
|
||||
|
||||
RUN apk update
|
||||
|
||||
# Prepare
|
||||
RUN mkdir -p /home/tiktok-dl
|
||||
COPY . /home/tiktok-dl
|
||||
WORKDIR /home/tiktok-dl
|
||||
|
||||
# Build
|
||||
RUN yarn install
|
||||
RUN yarn build
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["yarn", "start"]
|
||||
22
README.md
22
README.md
@@ -3,16 +3,18 @@
|
||||
Video TikTok Downloader using 🧰 NodeJS with Watermark and Non-Watermark!
|
||||
|
||||
## 〽️ Technology/Dependencies
|
||||
- [Turbo Repo](https://turborepo.org)
|
||||
- [Got](https://npmjs.com/got)
|
||||
- [Ow](https://npmjs.com/ow)
|
||||
- [VM2](https://npmjs.com/vm2)
|
||||
- [Next.js](https://nextjs.org)
|
||||
- [WindiCSS](https://windicss.org)
|
||||
|
||||
## Develop 👷
|
||||
## Development 👷
|
||||
- Make sure you have [yarn](https://yarnpkg.com) installed, and you are using the [yarn berry](https://yarnpkg.com/getting-started/migration#step-by-step) version.
|
||||
- Clone/fork into a directory you want.
|
||||
- Install all dependencies correctly (`yarn install` or `npm install`)
|
||||
- Install vercel cli too with `npm install vercel -g` or `yarn global add vercel`
|
||||
- Ran `vercel dev` for development (default port is: `3000`)
|
||||
> Use `vercel --prod` for production use 😎
|
||||
- Install all dependencies correctly (`yarn install`)
|
||||
- Ran `yarn dev` (default web port: `3000`)
|
||||
|
||||
# Configuration 🔑
|
||||
|
||||
@@ -26,7 +28,10 @@ Video TikTok Downloader using 🧰 NodeJS with Watermark and Non-Watermark!
|
||||
3. Add environment variable with `REDIS_URL` as the name, and fill the value using your redis url.
|
||||
|
||||
## ☁️ Endpoints
|
||||
Check [this](https://docs.tiktok-dl.tslab.site) out.
|
||||
Check [this](https://docs.tdl.besecure.eu.org) out.
|
||||
|
||||
## :bug: Bugs Report
|
||||
Feel free to open an issue if you have a problem or find a bug. That would be very helpful!
|
||||
|
||||
## 🔥 Credits + Source
|
||||
|
||||
@@ -36,13 +41,10 @@ Check [this](https://docs.tiktok-dl.tslab.site) out.
|
||||
- [SaveFrom](https://id.savefrom.net)
|
||||
- [TTDownloader](https://ttdownloader.com)
|
||||
- [Musically Down](https://musicaldown.com)
|
||||
- [DLTik](https://dltik.com/)
|
||||
- [TTSave](https://ttsave.app)
|
||||
- [DDDTik](https://dddtik.com)
|
||||
- [TikDown](https://tikdown.org)
|
||||
- [DownTik](https://downtik.net)
|
||||
- [LoveTik](https://lovetik.com)
|
||||
- [Tokup](https://tokup.app)
|
||||
- [TikTokDownloaderOne](https://tiktokdownloader.one)
|
||||
- [GetVidTik](https://getvidtik.com)
|
||||
|
||||
🧗♀️ Contribution(s) are welcome!
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import type {VercelRequest, VercelResponse} from '@vercel/node';
|
||||
import ow from 'ow';
|
||||
import {getProvider, Providers} from '../lib/providers';
|
||||
import {BaseProvider} from '../lib/providers/baseProvider';
|
||||
import {rotateProvider} from '../lib/rotator';
|
||||
import {ratelimitMiddleware} from '../middleware/ratelimit';
|
||||
|
||||
const providersType = Providers.map((p) => p.resourceName());
|
||||
|
||||
export default async (req: VercelRequest, res: VercelResponse) => {
|
||||
try {
|
||||
ow(req.method === 'POST' ? req.body : req.query, ow.object.partialShape({
|
||||
'url': ow.string.url.validate((v) => ({
|
||||
'validator': /^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.*)?\/(.*)?$/gi
|
||||
.test(v),
|
||||
'message': 'Expected (.*).tiktok.com',
|
||||
})),
|
||||
'type': ow.optional.string.validate((v) => ({
|
||||
'validator': typeof v === 'string' &&
|
||||
providersType.includes(v.toLowerCase()),
|
||||
'message': 'Invalid Provider, available provider is: ' +
|
||||
Providers.map((x) => x.resourceName()).join(', '),
|
||||
})),
|
||||
'nocache': req.method === 'POST' ?
|
||||
ow.optional.boolean : ow.optional.string,
|
||||
'rotateOnError': req.method === 'POST' ?
|
||||
ow.optional.boolean : ow.optional.string,
|
||||
}));
|
||||
|
||||
const provider = getProvider((req.query && req.query.type || req.body && req.body.type) ?? 'random');
|
||||
if (!provider) {
|
||||
return res.status(400).json({
|
||||
'error': 'Invalid provider',
|
||||
'providers': providersType,
|
||||
});
|
||||
}
|
||||
const result = await rotateProvider(
|
||||
provider as BaseProvider, req.query.url || req.body.url,
|
||||
req.method === 'POST' ?
|
||||
req.body.url : req.query.url, req.method === 'POST' ?
|
||||
req.body.rotateOnError :
|
||||
!!req.query.rotateOnError);
|
||||
await ratelimitMiddleware(req);
|
||||
return res.status(200).json(result);
|
||||
} catch (e) {
|
||||
return res.status(400).json({
|
||||
'error': (e as Error).message,
|
||||
});
|
||||
}
|
||||
};
|
||||
11
api/index.ts
11
api/index.ts
@@ -1,11 +0,0 @@
|
||||
import type {VercelRequest, VercelResponse} from '@vercel/node';
|
||||
|
||||
export default async (_: VercelRequest, res: VercelResponse) => {
|
||||
res.json({
|
||||
'index': 'Hello world!',
|
||||
'endpoints': {
|
||||
'ping': '/api/ping',
|
||||
'download': '/api/download',
|
||||
},
|
||||
});
|
||||
};
|
||||
16
api/ping.ts
16
api/ping.ts
@@ -1,16 +0,0 @@
|
||||
import type {VercelRequest, VercelResponse} from '@vercel/node';
|
||||
import {fetch} from '../lib';
|
||||
|
||||
export default async (_: VercelRequest, res: VercelResponse) => {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const response = await fetch('./');
|
||||
res.status(200).json({
|
||||
status: response.statusCode,
|
||||
took: Date.now() - start,
|
||||
data: response.statusMessage || 'Nothing.',
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).json({status: null, data: null, took: null});
|
||||
}
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import type {VercelRequest, VercelResponse} from '@vercel/node';
|
||||
import {Providers} from '../lib';
|
||||
|
||||
export default async (_: VercelRequest, res: VercelResponse) => {
|
||||
const providers = Providers.map((p) => ({
|
||||
'name': p.resourceName(),
|
||||
'url': p.client.defaults.options.prefixUrl,
|
||||
'maintenance': p.maintenance,
|
||||
}));
|
||||
|
||||
return res.send(providers);
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import {VercelRequest, VercelResponse} from '@vercel/node';
|
||||
import {matchLink} from '../lib/providers/util';
|
||||
import {client} from '../lib/redis';
|
||||
|
||||
export default async (_: VercelRequest, res: VercelResponse) => {
|
||||
const keys = await client.keys('*');
|
||||
|
||||
res.status(200).json(keys.filter((x) => matchLink(x)));
|
||||
};
|
||||
6
apps/web/.eslintrc.js
Normal file
6
apps/web/.eslintrc.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const eslintConfigs = require('tiktok-dl-config/eslint.typescript');
|
||||
|
||||
module.exports = {
|
||||
...eslintConfigs,
|
||||
extends: eslintConfigs.extends.concat(['plugin:@next/next/recommended']),
|
||||
};
|
||||
2
apps/web/.gitignore
vendored
Normal file
2
apps/web/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.next
|
||||
*.rdb
|
||||
3
apps/web/.prettierignore
Normal file
3
apps/web/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
*.json
|
||||
.next
|
||||
|
||||
1
apps/web/.prettierrc.js
Normal file
1
apps/web/.prettierrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('tiktok-dl-config/prettier');
|
||||
20
apps/web/components/Body.tsx
Normal file
20
apps/web/components/Body.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Body Component.
|
||||
* @param {{children: JSX.Element}} param0 BodyComponent props.
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export const BodyComponent = ({
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<section className="min-h-screen bg-light-200">
|
||||
<div className="md:container md:mx-auto">{children}</div>
|
||||
</section>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
15
apps/web/components/Footer.tsx
Normal file
15
apps/web/components/Footer.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Footer = () => (
|
||||
<React.Fragment>
|
||||
<p className="text-lg">
|
||||
© {new Date().getFullYear()}{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/hansputera/tiktok-dl.git"
|
||||
>
|
||||
TikTok-DL Project
|
||||
</a>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
);
|
||||
187
apps/web/components/FormInput.tsx
Normal file
187
apps/web/components/FormInput.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React from 'react';
|
||||
import useSWR, {Fetcher} from 'swr';
|
||||
import {ExtractedInfo} from 'tiktok-dl-core';
|
||||
import {getTikTokURL} from '../lib/url';
|
||||
import {VideoComponent} from './Video';
|
||||
|
||||
// // ERRORS ///
|
||||
/**
|
||||
* @class InvalidUrlError
|
||||
*/
|
||||
class InvalidUrlError extends Error {
|
||||
/**
|
||||
* @param {string} msg error message.
|
||||
*/
|
||||
constructor(msg?: string) {
|
||||
super(msg);
|
||||
this.name = 'INVALID_URL';
|
||||
}
|
||||
}
|
||||
|
||||
export type ExtractedInfoWithProvider = ExtractedInfo & {
|
||||
provider: string;
|
||||
_url: string;
|
||||
};
|
||||
|
||||
interface StateData {
|
||||
submitted: boolean;
|
||||
error?: string | Error;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const fetcher: Fetcher<ExtractedInfoWithProvider, string> = (...args) =>
|
||||
fetch(...args).then((r) => r.json());
|
||||
|
||||
/**
|
||||
* FormInput Component.
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export const FormInputComponent = (): JSX.Element => {
|
||||
const [state, setState] = React.useState<StateData>({
|
||||
submitted: false,
|
||||
error: undefined,
|
||||
url: '',
|
||||
});
|
||||
const {data, mutate} = useSWR(
|
||||
(!state.error || !(state.error as string).length) &&
|
||||
/^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.+)$/gi.test(state.url)
|
||||
? [
|
||||
'/api/download',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({url: state.url}),
|
||||
},
|
||||
]
|
||||
: null,
|
||||
fetcher,
|
||||
{
|
||||
loadingTimeout: 5_000,
|
||||
refreshInterval: 60_000,
|
||||
revalidateOnMount: false,
|
||||
onSuccess: () =>
|
||||
setState({
|
||||
...state,
|
||||
submitted: false,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
!/^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.+)$/gi.test(
|
||||
state.url,
|
||||
) &&
|
||||
state.url.length
|
||||
) {
|
||||
setState({
|
||||
...state,
|
||||
error: new InvalidUrlError('Invalid TikTok URL'),
|
||||
});
|
||||
} else {
|
||||
// submit event trigger.
|
||||
if (state.submitted && !state.error) {
|
||||
mutate();
|
||||
}
|
||||
|
||||
try {
|
||||
const u = getTikTokURL(state.url);
|
||||
if (!u) {
|
||||
setState({
|
||||
...state,
|
||||
error: new InvalidUrlError('Invalid TikTok URL'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
url: u,
|
||||
});
|
||||
} catch {
|
||||
setState({
|
||||
...state,
|
||||
error: new InvalidUrlError('Invalid TikTok URL'),
|
||||
});
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
error: undefined,
|
||||
});
|
||||
}
|
||||
}, [state.submitted, state.url]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<section className="inline-block">
|
||||
<h1 className="text-lg leading-relaxed">
|
||||
Fill TikTok's Video URL below:
|
||||
</h1>
|
||||
<p className="text-red-400 font-sans font-semibold">
|
||||
{state.error instanceof Error
|
||||
? state.error.name.concat(
|
||||
': '.concat(state.error.message),
|
||||
)
|
||||
: state.error
|
||||
? state.error
|
||||
: ''}
|
||||
</p>
|
||||
<form
|
||||
className="flex flex-col md:flex-row"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (!state.url.length) {
|
||||
setState({
|
||||
...state,
|
||||
error: 'Please fill the URL!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
!state.error &&
|
||||
setState({
|
||||
...state,
|
||||
submitted: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
type="url"
|
||||
onChange={(event) =>
|
||||
setState({
|
||||
...state,
|
||||
url: event.target.value,
|
||||
})
|
||||
}
|
||||
value={state.url}
|
||||
placeholder="e.g: "
|
||||
className="p-3 border border-gray-300 font-sans h-auto w-auto outline-solid-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="p-3 lg:ml-2 mt-1 bg-sky-400 uppercase text-white shadow-sm"
|
||||
disabled={state.submitted}
|
||||
>
|
||||
download
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section className="mt-3 mb-3">
|
||||
{state.submitted && !data && (
|
||||
<p className={'text-base font-sans text-blue-500'}>
|
||||
Wait a minute
|
||||
</p>
|
||||
)}
|
||||
{data && data.video && data.video.urls.length && (
|
||||
<VideoComponent data={data} />
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormInputComponent;
|
||||
36
apps/web/components/Header.tsx
Normal file
36
apps/web/components/Header.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Next Head Component.
|
||||
* @param {{title: string, children: JSX.Element }} param0 NextHeadComponent args.
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export const NextHeadComponent = ({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title?: string;
|
||||
children?: JSX.Element;
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<title>{title ?? 'TikTok Downloader'}</title>
|
||||
<meta
|
||||
name="title"
|
||||
content="TikTok Downloader | Non&Watermark Support"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="An Open-Source Project where it could download TikTok's Video without annoying ads!"
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="tiktok-downloader, tiktokdl, tiktok, download video tiktok, tiktok no watermark"
|
||||
/>
|
||||
<meta
|
||||
name="author"
|
||||
content="Hanif Dwy Putra S <github.com/hansputera>"
|
||||
/>
|
||||
<meta name="robots" content="index, follow" />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
38
apps/web/components/Video.tsx
Normal file
38
apps/web/components/Video.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import type {ExtractedInfoWithProvider} from './FormInput';
|
||||
|
||||
export const VideoComponent = ({data}: {data: ExtractedInfoWithProvider}) => {
|
||||
const copyUrl = (url: string) => {
|
||||
navigator.clipboard.writeText(url);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.alert('URL Copied');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
This video is downloaded from{' '}
|
||||
<span className="font-semibold">{data.provider}</span>.
|
||||
{data.caption && <pre>{data.caption}</pre>}
|
||||
<div className="md:grid md:grid-cols-3 md:gap-4">
|
||||
<video
|
||||
controls={true}
|
||||
autoPlay={false}
|
||||
className="rounded-md h-64 w-80"
|
||||
>
|
||||
<source src={data.video?.urls[0]} />
|
||||
</video>
|
||||
<div className="flex flex-row font-sans basis-8 mt-2">
|
||||
{data.video?.urls.map((url, index) => (
|
||||
<button
|
||||
key={index.toString()}
|
||||
className="mr-1 bg-teal-400 md:p-2 p-1 rounded-md shadow"
|
||||
onClick={() => copyUrl(url)}
|
||||
>
|
||||
LINK {index + 1}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
export const rateLimitConfig = {
|
||||
'enable': true, // you can set it to 'false'
|
||||
/**
|
||||
enable: true, // you can set it to 'false'
|
||||
/**
|
||||
* How much rate limit count per x second(s)
|
||||
* -is allowed? Default: 60 requests
|
||||
*/
|
||||
'maxRatelimitPerXSeconds': 60,
|
||||
/**
|
||||
maxRatelimitPerXSeconds: 60,
|
||||
/**
|
||||
* Every x second(s), the ratelimit data will removed.
|
||||
* So, their ratelimit data will removed.
|
||||
* PS: Data stored on redis.
|
||||
*/
|
||||
'ratelimitTime': 60,
|
||||
ratelimitTime: 120,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -18,3 +18,9 @@ export const rateLimitConfig = {
|
||||
* Default: 1 hour
|
||||
*/
|
||||
export const providerCache = 3600;
|
||||
|
||||
/**
|
||||
* How much rotate retries is allowed?
|
||||
* Default: 5x
|
||||
*/
|
||||
export const maxRotateCount = 3;
|
||||
23
apps/web/errors/httpError.ts
Normal file
23
apps/web/errors/httpError.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @class HttpError
|
||||
*/
|
||||
export class HttpError extends Error {
|
||||
public statusCode = 400;
|
||||
/**
|
||||
* @constructor
|
||||
* @param {string} message HttpError message,
|
||||
*/
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set http status code.
|
||||
* @param {number} code HttpError status code.
|
||||
* @return {HttpError}
|
||||
*/
|
||||
setCode(code: number): HttpError {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
1
apps/web/errors/index.ts
Normal file
1
apps/web/errors/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './httpError';
|
||||
3
apps/web/lib/index.ts
Normal file
3
apps/web/lib/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './redis';
|
||||
export * from './rotator';
|
||||
export * from './url';
|
||||
5
apps/web/lib/redis.ts
Normal file
5
apps/web/lib/redis.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
export const client = new Redis(
|
||||
process.env.REDIS_URL ?? 'redis://:@localhost:6379',
|
||||
);
|
||||
90
apps/web/lib/rotator.ts
Normal file
90
apps/web/lib/rotator.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import {maxRotateCount, providerCache} from '../config';
|
||||
import {BaseProvider, ExtractedInfo, getRandomProvider} from 'tiktok-dl-core';
|
||||
import {client as redisClient} from './redis';
|
||||
|
||||
/**
|
||||
* Rotate provider.
|
||||
* @param {BaseProvider} provider Provider instance
|
||||
* @param {string} url Video TikTok URL
|
||||
* @param {boolean?} skipOnError Rotate when error
|
||||
* @param {Record<string,string>} params Advanced provider fetch options
|
||||
* @param {number} retryCount Retry count.
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
export const rotateProvider = async (
|
||||
provider: BaseProvider,
|
||||
url: string,
|
||||
skipOnError: boolean = true,
|
||||
params?: Record<string, string>,
|
||||
retryCount: number = 0,
|
||||
): Promise<ExtractedInfo & {provider: string}> => {
|
||||
if (retryCount >= maxRotateCount) {
|
||||
return {
|
||||
error: 'MAX_ROTATE_ALLOWED',
|
||||
provider: retryCount.toString().concat(' providers'),
|
||||
};
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
await redisClient.del(url);
|
||||
}
|
||||
|
||||
// console.log(provider.resourceName());
|
||||
if (provider.maintenance) {
|
||||
return await rotateProvider(getRandomProvider(), url, skipOnError);
|
||||
}
|
||||
|
||||
const cachedData = await redisClient.get(url);
|
||||
if (!cachedData) {
|
||||
try {
|
||||
const data = await provider.fetch(url, params ?? {});
|
||||
if (data.error) {
|
||||
if (!skipOnError) {
|
||||
return {
|
||||
error: data.error,
|
||||
provider: provider.resourceName(),
|
||||
};
|
||||
}
|
||||
retryCount++;
|
||||
// switching to other provider
|
||||
return await rotateProvider(
|
||||
getRandomProvider(),
|
||||
url,
|
||||
skipOnError,
|
||||
params,
|
||||
retryCount,
|
||||
);
|
||||
} else if (data.video && !data.video.urls.length) {
|
||||
retryCount++;
|
||||
return await rotateProvider(
|
||||
getRandomProvider(),
|
||||
url,
|
||||
skipOnError,
|
||||
params,
|
||||
retryCount,
|
||||
);
|
||||
} else {
|
||||
redisClient.set(
|
||||
url,
|
||||
JSON.stringify({
|
||||
...data,
|
||||
provider: provider.resourceName(),
|
||||
}),
|
||||
'EX',
|
||||
providerCache,
|
||||
);
|
||||
return {...data, provider: provider.resourceName()};
|
||||
}
|
||||
} catch (e) {
|
||||
if (skipOnError) {
|
||||
return await rotateProvider(getRandomProvider(), url);
|
||||
} else {
|
||||
return {
|
||||
error: (e as Error).message,
|
||||
provider: provider.resourceName(),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
};
|
||||
17
apps/web/lib/url.ts
Normal file
17
apps/web/lib/url.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/** Get TikTok Video URL.
|
||||
* @param {string} url Video
|
||||
* @return {string}
|
||||
*/
|
||||
export function getTikTokURL(url: string): string | undefined {
|
||||
try {
|
||||
if (/^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.+)$/gi.test(url)) {
|
||||
const u = new URL(url);
|
||||
u.search = ''; // cleanup params
|
||||
return u.href;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
31
apps/web/middleware/apply.ts
Normal file
31
apps/web/middleware/apply.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {NextApiRequest, NextApiResponse} from 'next';
|
||||
|
||||
type Middleware = (
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse,
|
||||
) => Promise<undefined>;
|
||||
|
||||
export const applyRoute = (
|
||||
route: (req: NextApiRequest, res: NextApiResponse) => Promise<void>,
|
||||
middlewares: Middleware[],
|
||||
) => {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const middleware:
|
||||
| {
|
||||
message: string;
|
||||
status: number;
|
||||
}
|
||||
| undefined = await Promise.all(middlewares.map((m) => m(req, res)))
|
||||
.catch((e) => ({
|
||||
message: e.message,
|
||||
status: 500,
|
||||
}))
|
||||
.then(() => undefined);
|
||||
if (middleware)
|
||||
return res.status(middleware.status).json({
|
||||
message: middleware.message,
|
||||
statusCode: middleware.status,
|
||||
});
|
||||
else return route(req, res);
|
||||
};
|
||||
};
|
||||
33
apps/web/middleware/ratelimit.ts
Normal file
33
apps/web/middleware/ratelimit.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type {NextApiRequest, NextApiResponse} from 'next';
|
||||
import {rateLimitConfig} from '../config';
|
||||
import {HttpError} from '../errors';
|
||||
import {client} from '../lib';
|
||||
|
||||
export const ratelimitMiddleware = async (
|
||||
req: NextApiRequest,
|
||||
_res: NextApiResponse,
|
||||
) => {
|
||||
const ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'];
|
||||
if (!rateLimitConfig.enable || process.env.NODE_ENV === 'development')
|
||||
return undefined;
|
||||
else if (!ip)
|
||||
throw new HttpError("Couldn't find your real ip address!").setCode(401);
|
||||
const result = await client.get('rate-'.concat(ip.toString()));
|
||||
if (result) {
|
||||
if (parseInt(result) > rateLimitConfig.maxRatelimitPerXSeconds) {
|
||||
throw new HttpError(
|
||||
'Please try again, you are getting ratelimit!',
|
||||
).setCode(429);
|
||||
}
|
||||
client.incr('rate-'.concat(ip.toString()));
|
||||
return undefined;
|
||||
} else {
|
||||
client.set(
|
||||
'rate-'.concat(ip.toString()),
|
||||
'1',
|
||||
'EX',
|
||||
rateLimitConfig.ratelimitTime,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
5
apps/web/next-env.d.ts
vendored
Normal file
5
apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
20
apps/web/next.config.js
Normal file
20
apps/web/next.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const webpack = require('webpack');
|
||||
const withTM = require('next-transpile-modules')(['tiktok-dl-core']);
|
||||
const WindiCSSWebpackPlugin = require('windicss-webpack-plugin');
|
||||
|
||||
const {parsed: cusEnv} = require('dotenv').config({
|
||||
path: require('path').resolve(__dirname, '..', '..', '.env'),
|
||||
});
|
||||
|
||||
module.exports = withTM({
|
||||
reactStrictMode: true,
|
||||
experimental: {esmExternals: true},
|
||||
webpack(config) {
|
||||
// adding windicss plugin
|
||||
config.plugins.push(new WindiCSSWebpackPlugin());
|
||||
if (typeof cusEnv !== 'undefined') {
|
||||
config.plugins.push(new webpack.EnvironmentPlugin(cusEnv));
|
||||
}
|
||||
return config;
|
||||
},
|
||||
});
|
||||
35
apps/web/package.json
Normal file
35
apps/web/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "tiktok-dl-web",
|
||||
"description": "TikTok-DL Project Website",
|
||||
"version": "1.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "16.6.1",
|
||||
"ioredis": "5.7.0",
|
||||
"next": "12.2.4",
|
||||
"ow": "0.28.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"swr": "1.3.0",
|
||||
"tiktok-dl-config": "*",
|
||||
"tiktok-dl-core": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"lint": "next lint",
|
||||
"format": "prettier . --write",
|
||||
"start": "next start --port process.env.PORT"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "12.2.4",
|
||||
"@types/node": "17.0.35",
|
||||
"@types/react": "18.0.17",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"next-transpile-modules": "9.0.0",
|
||||
"prettier": "2.7.1",
|
||||
"tiktok-dl-config": "*",
|
||||
"typescript": "^5.9.2",
|
||||
"windicss-webpack-plugin": "1.7.5"
|
||||
}
|
||||
}
|
||||
11
apps/web/pages/_app.tsx
Normal file
11
apps/web/pages/_app.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'windi.css';
|
||||
import type {AppProps} from 'next/app';
|
||||
|
||||
/**
|
||||
* TikTokApp
|
||||
* @param {AppProps} arg0 App properties.
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export default function TikTokApp({Component, pageProps}: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
28
apps/web/pages/_document.tsx
Normal file
28
apps/web/pages/_document.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import Document, {Head, Html, Main, NextScript} from 'next/document';
|
||||
import {BodyComponent} from '../components/Body';
|
||||
import {NextHeadComponent} from '../components/Header';
|
||||
|
||||
/**
|
||||
* @class TikTokDocument
|
||||
*/
|
||||
export default class TikTokDocument extends Document {
|
||||
/**
|
||||
* Render TikTokDocument
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<NextHeadComponent />
|
||||
</Head>
|
||||
<body>
|
||||
<BodyComponent>
|
||||
<Main />
|
||||
</BodyComponent>
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
103
apps/web/pages/about.tsx
Normal file
103
apps/web/pages/about.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import useSWR from 'swr';
|
||||
import Head from 'next/head';
|
||||
|
||||
export default () => {
|
||||
const {data, error} = useSWR(
|
||||
'https://api.github.com/repos/hansputera/tiktok-dl',
|
||||
(...args) => fetch(...args).then((r) => r.json()),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>About</title>
|
||||
</Head>
|
||||
<section className="p-5">
|
||||
<h1 className="text-center text-2xl">About TikTok-DL</h1>
|
||||
|
||||
<div className="mt-3">
|
||||
{error ? (
|
||||
<p className="text-red-500">{error}</p>
|
||||
) : (
|
||||
data && (
|
||||
<>
|
||||
<p className="text-center font-sans">
|
||||
{data.description}
|
||||
</p>
|
||||
<ul className="mb-3 mt-3">
|
||||
<li>
|
||||
This project is based on{' '}
|
||||
<span className="font-semibold">
|
||||
{data.license.name}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Currently, we have{' '}
|
||||
{data.stargazers_count.toLocaleString()}{' '}
|
||||
stars, {data.forks.toLocaleString()}{' '}
|
||||
forks, and {data.open_issues} opened
|
||||
issues.
|
||||
</li>
|
||||
<li>
|
||||
Also, this project is created at{' '}
|
||||
<span className="font-semibold">
|
||||
{new Date(
|
||||
data.created_at,
|
||||
).toLocaleDateString('en-US', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
timeZone: 'Asia/Jakarta',
|
||||
})}
|
||||
</span>{' '}
|
||||
by{' '}
|
||||
<span className="font-medium">
|
||||
{data.owner.login}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Last update:{' '}
|
||||
<span className="font-semibold">
|
||||
{new Date(
|
||||
data.updated_at,
|
||||
).toLocaleDateString('en-US', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
timeZone: 'Asia/Jakarta',
|
||||
})}
|
||||
</span>
|
||||
</li>
|
||||
</ul>{' '}
|
||||
If you want see the source code,{' '}
|
||||
<a
|
||||
href={data.html_url}
|
||||
className="font-semibold text-blue-500"
|
||||
target="_blank"
|
||||
>
|
||||
click here
|
||||
</a>
|
||||
. And feel free to open an{' '}
|
||||
<a
|
||||
href={data.html_url.concat(
|
||||
'/issues/new/choose',
|
||||
)}
|
||||
className="text-blue-500"
|
||||
target="_blank"
|
||||
>
|
||||
issue
|
||||
</a>{' '}
|
||||
if you have a problem or find a bug. Thank you!
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
87
apps/web/pages/api/download.ts
Normal file
87
apps/web/pages/api/download.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type {NextApiRequest, NextApiResponse} from 'next';
|
||||
import ow from 'ow';
|
||||
import {getProvider, Providers, BaseProvider} from 'tiktok-dl-core';
|
||||
import {getTikTokURL} from '../../lib';
|
||||
import {rotateProvider} from '../../lib/rotator';
|
||||
import {applyRoute} from '../../middleware/apply';
|
||||
import {ratelimitMiddleware} from '../../middleware/ratelimit';
|
||||
|
||||
export default applyRoute(
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
try {
|
||||
if (req.method === 'POST' && typeof req.body === 'string') {
|
||||
req.body = JSON.parse(req.body);
|
||||
}
|
||||
const providersType = Providers.map((p) => p.resourceName());
|
||||
ow(
|
||||
req.body || req.query,
|
||||
ow.object.partialShape({
|
||||
url: ow.string.url.validate((v) => ({
|
||||
validator: !!getTikTokURL(v),
|
||||
message: 'Expected (.*).tiktok.com',
|
||||
})),
|
||||
type: ow.optional.string.validate((v) => ({
|
||||
validator:
|
||||
typeof v === 'string' &&
|
||||
providersType.includes(v.toLowerCase()),
|
||||
message:
|
||||
'Invalid Provider, available provider is: ' +
|
||||
Providers.map((x) => x.resourceName()).join(', '),
|
||||
})),
|
||||
rotateOnError:
|
||||
req.method === 'POST'
|
||||
? ow.optional.boolean
|
||||
: ow.optional.string,
|
||||
}),
|
||||
);
|
||||
|
||||
let provider = getProvider(
|
||||
(req.query.type || req.body.type) ?? 'random',
|
||||
);
|
||||
|
||||
if (!provider) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid provider',
|
||||
providers: providersType,
|
||||
});
|
||||
}
|
||||
|
||||
const params = provider.getParams();
|
||||
if (
|
||||
params &&
|
||||
provider.resourceName() ===
|
||||
(req.query.type?.toString() || req.body.type)?.toLowerCase()
|
||||
) {
|
||||
ow(req.query || req.body, ow.object.partialShape(params));
|
||||
} else if (params) {
|
||||
provider = getProvider('random');
|
||||
}
|
||||
|
||||
const url = getTikTokURL(req.query.url || req.body.url);
|
||||
|
||||
const result = await rotateProvider(
|
||||
provider as BaseProvider,
|
||||
url!,
|
||||
req.method === 'POST'
|
||||
? req.body.rotateOnError
|
||||
: !!req.query.rotateOnError,
|
||||
params
|
||||
? Object.fromEntries(
|
||||
Object.keys(params!).map((p) => [
|
||||
p,
|
||||
(req.query[p] as string) ?? req.body[p],
|
||||
]),
|
||||
)
|
||||
: {},
|
||||
);
|
||||
return res.status(200).json(result);
|
||||
} catch (e) {
|
||||
return res
|
||||
.status((e as Error).name === 'ArgumentError' ? 400 : 500)
|
||||
.json({
|
||||
error: (e as Error).name + '|' + (e as Error).message,
|
||||
});
|
||||
}
|
||||
},
|
||||
[ratelimitMiddleware],
|
||||
);
|
||||
5
apps/web/pages/api/index.ts
Normal file
5
apps/web/pages/api/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type {NextApiRequest, NextApiResponse} from 'next';
|
||||
|
||||
export default async (_: NextApiRequest, res: NextApiResponse) => {
|
||||
return res.redirect('https://github.com/hansputera/tiktok-dl.git');
|
||||
};
|
||||
24
apps/web/pages/api/providers.ts
Normal file
24
apps/web/pages/api/providers.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type {NextApiRequest, NextApiResponse} from 'next';
|
||||
import {Providers} from 'tiktok-dl-core';
|
||||
import {ratelimitMiddleware} from '../../middleware/ratelimit';
|
||||
import type {Shape} from 'ow';
|
||||
import {applyRoute} from '../../middleware/apply';
|
||||
|
||||
export default applyRoute(
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const providers = Providers.map((p) => ({
|
||||
name: p.resourceName(),
|
||||
url: p.client?.defaults.options.prefixUrl,
|
||||
maintenance: p.maintenance,
|
||||
params: p.getParams()
|
||||
? Object.keys(p.getParams()!).map((x) => ({
|
||||
name: x,
|
||||
type: (p.getParams()![x] as Shape).type,
|
||||
}))
|
||||
: {},
|
||||
}));
|
||||
|
||||
return res.send(providers);
|
||||
},
|
||||
[ratelimitMiddleware],
|
||||
);
|
||||
14
apps/web/pages/api/stored-links.ts
Normal file
14
apps/web/pages/api/stored-links.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type {NextApiRequest, NextApiResponse} from 'next';
|
||||
import {client} from '../../lib';
|
||||
import {ratelimitMiddleware} from '../../middleware/ratelimit';
|
||||
import {matchLink} from 'tiktok-dl-core';
|
||||
import {applyRoute} from '../../middleware/apply';
|
||||
|
||||
export default applyRoute(
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await ratelimitMiddleware(req, res);
|
||||
const keys = await client.keys('*');
|
||||
return res.status(200).json(keys.filter((x) => matchLink(x)));
|
||||
},
|
||||
[ratelimitMiddleware],
|
||||
);
|
||||
25
apps/web/pages/index.tsx
Normal file
25
apps/web/pages/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import {Footer} from '../components/Footer';
|
||||
|
||||
const FormInputComponentDynamic = dynamic(
|
||||
() => import('../components/FormInput'),
|
||||
{
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<section className="p-5">
|
||||
<h1 className="align-middle text-4xl font-sans font-medium">
|
||||
TikTok-DL{' '}
|
||||
<span className="font-normal md:break-words text-2xl">
|
||||
Download TikTok Video without watermark and free ads.
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<FormInputComponentDynamic />
|
||||
<Footer />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
37
apps/web/tsconfig.json
Normal file
37
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "dist",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"incremental": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
}
|
||||
8
apps/web/windi.config.js
Normal file
8
apps/web/windi.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import {defineConfig} from 'windicss/helpers';
|
||||
|
||||
export default defineConfig({
|
||||
extract: {
|
||||
include: ['**/*.{jsx,tsx,css}'],
|
||||
exclude: ['node_modules', '.git', '.next'],
|
||||
},
|
||||
});
|
||||
19
docker-compose.yaml
Normal file
19
docker-compose.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- REDIS_URL=redis://redis_db:6379
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- redis_db
|
||||
image: "hansputera/tiktok-dl:latest"
|
||||
redis_db:
|
||||
image: "redis:alpine"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
31
experiments/savetik.out.js
Normal file
31
experiments/savetik.out.js
Normal file
File diff suppressed because one or more lines are too long
29
experiments/snaptik.out.js
Normal file
29
experiments/snaptik.out.js
Normal file
File diff suppressed because one or more lines are too long
15
lib/fetch.ts
15
lib/fetch.ts
@@ -1,15 +0,0 @@
|
||||
import got, {ExtendOptions} from 'got';
|
||||
|
||||
export const fetch = got.extend({
|
||||
prefixUrl: 'https://www.tiktok.com',
|
||||
dnsCache: true,
|
||||
});
|
||||
|
||||
|
||||
export const getFetch = (baseUrl: string, options?: ExtendOptions) =>
|
||||
got.extend({
|
||||
prefixUrl: baseUrl,
|
||||
dnsCache: true,
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './fetch';
|
||||
export * from './providers';
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
|
||||
/**
|
||||
* @class DLTikProvider
|
||||
*/
|
||||
export class DLTikProvider extends BaseProvider {
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'dltik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://dltik.com');
|
||||
|
||||
public maintenance = {
|
||||
'reason': 'My prediction is that DLTik needs an active session to use.',
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// getting verification token
|
||||
const response = await this.client('./#url=' +
|
||||
encodeURIComponent(url));
|
||||
const token = (
|
||||
response.body.match(/type="hidden" value="([^""]+)"/) as string[]
|
||||
)[1];
|
||||
|
||||
const dlResponse = await this.client.post('./', {
|
||||
'form': {
|
||||
'm': 'getlink',
|
||||
'url': `https://m.tiktok.com/v/${
|
||||
(/predownload\('([0-9]+)'\)/gi.exec(response.body) as string[])[1]
|
||||
}.html`,
|
||||
'__RequestVerificationToken': token,
|
||||
},
|
||||
'headers': {
|
||||
'Origin': this.client.defaults.options.prefixUrl,
|
||||
'Referer': response.url,
|
||||
'Cookie': response.headers['set-cookie']?.toString(),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(dlResponse.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - Raw
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const json = JSON.parse(html);
|
||||
if (!json.status) {
|
||||
return {
|
||||
'error': json.message,
|
||||
};
|
||||
} else {
|
||||
// if (json.data.videoId === '7013188037203070234') {
|
||||
// return {
|
||||
// 'error': 'Invalid url',
|
||||
// };
|
||||
// }
|
||||
return {
|
||||
'video': {
|
||||
'id': json.data.videoId,
|
||||
'urls': [json.data.watermarkVideoUrl, json.data.destinationUrl],
|
||||
'thumb': json.data.dynamicCover,
|
||||
},
|
||||
'music': {
|
||||
'url': json.data.musicUrl,
|
||||
},
|
||||
'caption': json.data.desc,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '..';
|
||||
import {matchLink} from './util';
|
||||
|
||||
/**
|
||||
* @class DDDTikProvider
|
||||
*/
|
||||
export class DDDTikProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'dddtik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://dddtik.com');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url Tiktok video url
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client.post('./down.php', {
|
||||
'form': {
|
||||
'url': url,
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const urls = matchLink(html) as string[];
|
||||
urls.pop();
|
||||
|
||||
const t = urls[1];
|
||||
return {
|
||||
'video': {
|
||||
'urls': urls.filter((u) => u !== t),
|
||||
'thumb': t,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '..';
|
||||
import {matchCustomDownload} from './util';
|
||||
|
||||
/**
|
||||
* @class DownTikProvider
|
||||
*/
|
||||
export class DownTikProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'downtik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://downtik.net');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client('./');
|
||||
|
||||
const token = (
|
||||
response.body.match(/id="token" value="([^""]+)"/) as string[]
|
||||
)[1];
|
||||
|
||||
const responseAction = await this.client.post(
|
||||
'./action.php', {
|
||||
'form': {
|
||||
'url': url,
|
||||
'token': token,
|
||||
},
|
||||
'headers': {
|
||||
'cookie': response.headers['set-cookie']?.toString(),
|
||||
'Referer': 'https://downtik.net/',
|
||||
'Origin': 'https://downtik.net',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (JSON.parse(responseAction.body).error) {
|
||||
return {
|
||||
'error': JSON.parse(responseAction.body).message,
|
||||
};
|
||||
};
|
||||
|
||||
return this.extract(JSON.parse(responseAction.body).data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const urls = matchCustomDownload('downtik', html);
|
||||
|
||||
return {
|
||||
'music': {
|
||||
'url': urls.pop() as string,
|
||||
},
|
||||
'video': {
|
||||
'thumb': urls?.shift(),
|
||||
'urls': urls as string[],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '../fetch';
|
||||
|
||||
/**
|
||||
* @class DownloadOne
|
||||
*/
|
||||
export class DownloadOne extends BaseProvider {
|
||||
/**
|
||||
* Get provider name
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'ttdownloaderone';
|
||||
}
|
||||
|
||||
public client = getFetch('http://tiktokdownloader.one');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* Fetch ttdownloader.one
|
||||
* @param {string} url Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(
|
||||
url: string,
|
||||
): Promise<ExtractedInfo> {
|
||||
// getting the token
|
||||
const response = await this.client('./');
|
||||
|
||||
const token = (/name="_token_" content="(.*)"/gi
|
||||
.exec(response.body) as string[])[1];
|
||||
|
||||
const dlResponse = await this.client(
|
||||
'./api/v1/fetch?url=' + url, {
|
||||
'headers': {
|
||||
'TOKEN': token,
|
||||
'Referer': 'http://tiktokdownloader.one/',
|
||||
'Origin': 'http://tiktokdownloader.one',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (dlResponse.statusCode !== 200) {
|
||||
return {
|
||||
'error': 'Probably the video doesn\'t exist',
|
||||
};
|
||||
}
|
||||
|
||||
return this.extract(dlResponse.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract page from ttdownloader.one site
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const json = JSON.parse(html);
|
||||
|
||||
return {
|
||||
'video': {
|
||||
'urls': [
|
||||
json.url,
|
||||
json.url_nwm,
|
||||
],
|
||||
'thumb': json.cover,
|
||||
'id': json.video_id,
|
||||
},
|
||||
'music': {
|
||||
'url': json.music.url,
|
||||
'title': json.music.title,
|
||||
'cover': json.music.cover,
|
||||
'author': json.music.author,
|
||||
},
|
||||
'author': {
|
||||
'id': json.user.name,
|
||||
'username': json.user.username,
|
||||
'thumb': json.user.cover,
|
||||
},
|
||||
'caption': json.caption,
|
||||
'updatedAt': json.updatedAt ?? '-',
|
||||
'uploadedAt': json.uploaded_at,
|
||||
'commentsCount': json.stats.comment,
|
||||
'sharesCount': json.stats.shares,
|
||||
'likesCount': json.stats.likes,
|
||||
'playsCount': json.stats.play,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import type {BaseProvider} from './baseProvider';
|
||||
|
||||
import {MusicalyDown} from './musicalyDownProvider';
|
||||
import {SnaptikProvider} from './snaptikProvider';
|
||||
import {TikmateProvider} from './tikmateProvider';
|
||||
import {TTDownloader} from './ttDownloaderProvider';
|
||||
import {TTSave} from './ttSaveProvider';
|
||||
import {DLTikProvider} from './DLTikProvider';
|
||||
import {SaveFromProvider} from './saveFromProvider';
|
||||
import {SaveTikProvider} from './saveTikProvider';
|
||||
import {TikDownProvider} from './tikDownProvider';
|
||||
import {DownTikProvider} from './downTikProvider';
|
||||
import {LoveTikProvider} from './loveTikProvider';
|
||||
import {DDDTikProvider} from './dddTikProvider';
|
||||
import {TokupProvider} from './tokupProvider';
|
||||
import {DownloadOne} from './downloaderOneProvider';
|
||||
|
||||
export const Providers: BaseProvider[] = [
|
||||
new SnaptikProvider(),
|
||||
new TikmateProvider(),
|
||||
new MusicalyDown(),
|
||||
new TTDownloader(),
|
||||
new TTSave(), // won't work because we coudn't receive the cookie.
|
||||
new DLTikProvider(),
|
||||
new SaveFromProvider(),
|
||||
new SaveTikProvider(),
|
||||
new TikDownProvider(),
|
||||
new DownTikProvider(), // SaveTik Mirror
|
||||
new LoveTikProvider(),
|
||||
new DDDTikProvider(),
|
||||
new TokupProvider(), // ttsave alternative
|
||||
new DownloadOne(),
|
||||
];
|
||||
|
||||
export const getRandomProvider = () => Providers[
|
||||
Math.floor(Math.random() * Providers.length)
|
||||
];
|
||||
|
||||
export const getProvider = (name: string) => name.toLowerCase() !== 'random' ?
|
||||
Providers.find(
|
||||
(p) => p.resourceName() === name.toLowerCase(),
|
||||
) : getRandomProvider();
|
||||
@@ -1,67 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '..';
|
||||
|
||||
/**
|
||||
* @class LoveTikProvider
|
||||
*/
|
||||
export class LoveTikProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'lovetik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://lovetik.com');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client.post(
|
||||
'./api/ajax/search', {
|
||||
'form': {
|
||||
'query': url,
|
||||
},
|
||||
'headers': {
|
||||
'Origin': 'https://lovetik.com/',
|
||||
'Referer': 'https://lovetik.com/',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} jsonString
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(jsonString: string): ExtractedInfo {
|
||||
const json = JSON.parse(jsonString);
|
||||
|
||||
if (json.mess) {
|
||||
return {
|
||||
'error': json.mess,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'music': {
|
||||
'url': json.links.pop().a,
|
||||
},
|
||||
'video': {
|
||||
'thumb': json.cover,
|
||||
'urls': json.links.map((l: Record<string, unknown>) => l.a),
|
||||
},
|
||||
'author': {
|
||||
'username': json.author.replace(/(<([^>]+)>)/ig, ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
|
||||
/**
|
||||
* @class MusicalyDown
|
||||
*/
|
||||
export class MusicalyDown extends BaseProvider {
|
||||
public client = getFetch('https://musicaldown.com/id');
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'musicalydown';
|
||||
}
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video Tiktok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const res = await this.client('./', {
|
||||
'headers': {
|
||||
'Accept': '*/*',
|
||||
'Referer': this.client.defaults.options.prefixUrl,
|
||||
'Origin': this.client.defaults.options.prefixUrl,
|
||||
},
|
||||
});
|
||||
const tokens = (
|
||||
res.body.match(
|
||||
/input name="([^""]+)" type="hidden" value="([^""]+)"/) as string[]
|
||||
);
|
||||
const response = await this.client.post('./download', {
|
||||
form: {
|
||||
[(
|
||||
res.body.match(/input name="([^"]+)/) as string[]
|
||||
)[1]]: url,
|
||||
[tokens[1]]: tokens[2],
|
||||
'verify': 1,
|
||||
},
|
||||
headers: {
|
||||
'Cookie': res.headers['set-cookie']?.toString(),
|
||||
'Accept': '*/*',
|
||||
'Referer': this.client.defaults.options.prefixUrl,
|
||||
'Origin': this.client.defaults.options.prefixUrl,
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - Raw HTML
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
public extract(html: string): ExtractedInfo {
|
||||
const matchUrls = (html
|
||||
.match(/<a.*?target="_blank".*?href="(.*?)".*?<\/a>/gi) as string[]);
|
||||
const urls = matchUrls.map((url) =>
|
||||
/<a.*?target="_blank".*?href="(.*?)".*?<\/a>/gi.exec(url)?.[1] as string);
|
||||
return {
|
||||
'video': {
|
||||
'urls': urls,
|
||||
'thumb': /img class="responsive-img" src="(.*?)"/gi.exec(html)?.[1],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {deObfuscateSaveFromScript} from './util';
|
||||
|
||||
/**
|
||||
* @class saveFromProvider
|
||||
*/
|
||||
export class SaveFromProvider extends BaseProvider {
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'savefrom';
|
||||
}
|
||||
|
||||
public client = getFetch('https://worker-as.sf-tools.com');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client.post('./savefrom.php', {
|
||||
'form': {
|
||||
'sf_url': url,
|
||||
'sf_submit': '',
|
||||
'new': '2',
|
||||
'lang': 'id',
|
||||
'country': 'id',
|
||||
'os': 'Ubuntu',
|
||||
'browser': 'Firefox',
|
||||
'channel': 'Downloader',
|
||||
'sf-nomad': '1',
|
||||
},
|
||||
'headers': {
|
||||
'Origin': 'https://id.savefrom.net',
|
||||
'Referer': 'https://id.savefrom.net',
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - HTML Raw
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const deobfuscated = deObfuscateSaveFromScript(html);
|
||||
const json = JSON.parse(
|
||||
(deobfuscated.match(/\({(.*)}\)/) as string[])[0]
|
||||
.replace(/(\(|\))/g, ''),
|
||||
);
|
||||
return {
|
||||
'video': {
|
||||
'thumb': json.thumb,
|
||||
'id': json.id,
|
||||
'urls': json.url.map((x: { url: string; }) => x.url),
|
||||
'duration': json.meta.duration,
|
||||
'title': json.meta.title,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '..';
|
||||
import {matchCustomDownload} from './util';
|
||||
|
||||
/**
|
||||
* @class SaveTikProvider
|
||||
*/
|
||||
export class SaveTikProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'savetik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://savetik.net');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client('./');
|
||||
|
||||
const token = (
|
||||
response.body.match(/id="token" value="([^""]+)"/) as string[]
|
||||
)[1];
|
||||
|
||||
const responseAction = await this.client.post(
|
||||
'./action.php', {
|
||||
'form': {
|
||||
'url': url,
|
||||
'token': token,
|
||||
},
|
||||
'headers': {
|
||||
'cookie': response.headers['set-cookie']?.toString(),
|
||||
'Referer': 'https://savetik.net/',
|
||||
'Origin': 'https://savetik.net',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (JSON.parse(responseAction.body).error) {
|
||||
return {
|
||||
'error': JSON.parse(responseAction.body).message,
|
||||
};
|
||||
};
|
||||
|
||||
return this.extract(JSON.parse(responseAction.body).data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const urls = matchCustomDownload('savetik', html);
|
||||
|
||||
return {
|
||||
'music': {
|
||||
'url': urls?.pop() as string,
|
||||
},
|
||||
'video': {
|
||||
'thumb': urls?.shift(),
|
||||
'urls': urls as string[],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {deObfuscate, matchLink} from './util';
|
||||
|
||||
/**
|
||||
* @class SnaptikProvider
|
||||
*/
|
||||
export class SnaptikProvider extends BaseProvider {
|
||||
public client = getFetch('https://snaptik.app/en');
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'snaptik';
|
||||
}
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - TikTok Video URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client('./abc.php', {
|
||||
searchParams: {
|
||||
'url': url,
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract information from raw html
|
||||
* @param {string} html - Raw HTML
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const results = matchLink(deObfuscate(html));
|
||||
if (!results || !results.length) throw new Error('Broken');
|
||||
|
||||
return {
|
||||
'video': {
|
||||
'thumb': results?.shift(),
|
||||
'urls': [...new Set(results)],
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '..';
|
||||
import {matchLink} from './util';
|
||||
|
||||
/**
|
||||
* @class TikDownProvider
|
||||
*/
|
||||
export class TikDownProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'tikdown';
|
||||
}
|
||||
|
||||
public client = getFetch('https://tikdown.org');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client('./');
|
||||
|
||||
const token = (
|
||||
response.body.match(
|
||||
/name="_token" value="([^""]+)"/) as string[]
|
||||
)[1];
|
||||
|
||||
const responseAjax = await this.client.post(
|
||||
'./getAjax', {
|
||||
'form': {
|
||||
'url': url,
|
||||
'_token': token,
|
||||
},
|
||||
'headers': {
|
||||
'x-csrf-token': token,
|
||||
'cookie': response.headers['set-cookie']?.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!JSON.parse(responseAjax.body).status) {
|
||||
return {
|
||||
'error': 'Something was wrong',
|
||||
};
|
||||
}
|
||||
return this.extract(JSON.parse(responseAjax.body).html);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const urls = matchLink(html) as string[];
|
||||
return {
|
||||
'video': {
|
||||
'thumb': urls.shift(),
|
||||
'urls': urls,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {deObfuscate, matchCustomDownload} from './util';
|
||||
|
||||
/**
|
||||
* @class TikmateProvider
|
||||
*/
|
||||
export class TikmateProvider extends BaseProvider {
|
||||
public client = getFetch('https://tikmate.online');
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'tikmate';
|
||||
}
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// we need to get the token
|
||||
|
||||
const response = await this.client('./');
|
||||
const matchs = (
|
||||
response.body.match(/id="token" value="(.*)?"/) as string[]);
|
||||
|
||||
const cookies = response.headers['cookie'];
|
||||
|
||||
const abcResponse = await this.client.post('./abc.php', {
|
||||
form: matchs ? {
|
||||
'url': url,
|
||||
'token': matchs[1],
|
||||
} : {
|
||||
'url': url,
|
||||
},
|
||||
headers: {
|
||||
'Origin': this.client.defaults.options.prefixUrl,
|
||||
'Referer': this.client.defaults.options.prefixUrl + '/',
|
||||
'Cookie': cookies,
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(abcResponse.body);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract information from raw html
|
||||
* @param {string} html - Raw HTML
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const matchs = matchCustomDownload('tikmate', deObfuscate(html));
|
||||
return {
|
||||
'video': {
|
||||
'thumb': matchs.shift(),
|
||||
'urls': matchs,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {getFetch} from '../fetch';
|
||||
|
||||
/**
|
||||
* @class TokupProvider
|
||||
*/
|
||||
export class TokupProvider extends BaseProvider {
|
||||
/**
|
||||
* Get provider name
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'tokup';
|
||||
}
|
||||
|
||||
public client = getFetch('https://tokup.app');
|
||||
|
||||
public maintenance = {
|
||||
reason: 'Tokup site returned \'Oops! Something went wrong!\'',
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch tokup
|
||||
* @param {string} url - TikTok Video URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(
|
||||
url: string,
|
||||
): Promise<ExtractedInfo> {
|
||||
const response = await this.client.post(
|
||||
'./', {
|
||||
'form': {
|
||||
'url': url,
|
||||
},
|
||||
'headers': {
|
||||
'Origin': this.client.defaults.options.prefixUrl,
|
||||
'Referer': this.client.defaults.options.prefixUrl,
|
||||
},
|
||||
'timeout': 3000,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode !== 200 ||
|
||||
/video not found\b/gi.test(
|
||||
response.body,
|
||||
)) {
|
||||
return {
|
||||
'error': 'Video Not Found',
|
||||
};
|
||||
} else if (/oops/gi.test(response.body)) {
|
||||
return {
|
||||
'error': 'Tokup Error',
|
||||
};
|
||||
} else {
|
||||
return this.extract(
|
||||
response.body,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract tokup html elements
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
console.log(html);
|
||||
const authorProfile = (/http(s)?(:\/\/(.*)\.tiktokcdn\.com\/(.*))/gi.exec(
|
||||
html,
|
||||
) as string[])[0];
|
||||
const nums = (html.match(/<td>[0-9]+<\/td>/g) as string[]).map(
|
||||
(n) => n.replace(/<(\/)?[a-zA-Z0-9]+>/gi, ''));
|
||||
const url = [...new Set(
|
||||
(html.match(
|
||||
// eslint-disable-next-line max-len
|
||||
/http(s)?(:\/\/tikmate\.app\/download\/[A-Za-z0-9\-\_]+\/[0-9]+\.mp4+)/gi) as string[]),
|
||||
)][0];
|
||||
|
||||
return {
|
||||
'video': {
|
||||
'urls': [url, url + '?hd=1'],
|
||||
},
|
||||
'author': {
|
||||
'username': (/target="_blank"\>(.*)\</.exec(
|
||||
html,
|
||||
) as string[])[1],
|
||||
'thumb': authorProfile
|
||||
.substring(0, authorProfile.length-1),
|
||||
},
|
||||
'uploadedAt': (html.match(
|
||||
/<p>(.+)<\/p>/,
|
||||
) as string[])[1],
|
||||
'likesCount': nums[0] as unknown as number,
|
||||
'commentsCount': nums[1] as unknown as number,
|
||||
'sharesCount': nums[2] as unknown as number,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {matchLink} from './util';
|
||||
|
||||
/**
|
||||
* @class TTDownloader
|
||||
*/
|
||||
export class TTDownloader extends BaseProvider {
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'ttdownloader';
|
||||
}
|
||||
|
||||
public client = getFetch('https://ttdownloader.com');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// getting token and cookies
|
||||
const firstResponse = await this.client('./');
|
||||
const token = (firstResponse.body
|
||||
.match(/name="token" value="(.*)?"/) as string[])[1];
|
||||
const videoResponse = await this.client.post('./req', {
|
||||
form: {
|
||||
'token': token,
|
||||
'format': '',
|
||||
'url': url,
|
||||
},
|
||||
headers: {
|
||||
'Origin': 'https://ttdownloader.com',
|
||||
'Referer': 'https://ttdownloader.com',
|
||||
'Cookie': firstResponse.headers['set-cookie']?.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(videoResponse.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - HTML Raw
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const urls = matchLink(html);
|
||||
urls?.pop(); // remove 'https://snaptik.fans'
|
||||
return {
|
||||
'video': {
|
||||
'urls': urls as string[],
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
import {getFetch} from '..';
|
||||
import {BaseProvider, ExtractedInfo} from './baseProvider';
|
||||
import {keyGeneratorTTSave, matchLink} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* @class TTSave
|
||||
*/
|
||||
export class TTSave extends BaseProvider {
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'ttsave';
|
||||
}
|
||||
|
||||
public client = getFetch('https://ttsave.app');
|
||||
|
||||
public maintenance = {
|
||||
reason: 'TTSave doesn\'t returned cookie to manipulate the session',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - TikTok Video URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// getting token
|
||||
const response = await this.client('./');
|
||||
|
||||
const token = (
|
||||
response.body.match(/(m|doDownload)?\(e,"(.*)"\)}/) as string[]
|
||||
).filter((x) => x.length).pop() as string;
|
||||
const key = await keyGeneratorTTSave(token);
|
||||
|
||||
const dlResponse = await this.client.post('./download.php', {
|
||||
'json': {
|
||||
'id': url,
|
||||
'token': token,
|
||||
'key': key,
|
||||
},
|
||||
'headers': {
|
||||
'Origin': this.client.defaults.options.prefixUrl,
|
||||
'Referer': this.client.defaults.options.prefixUrl,
|
||||
'Cookie': response.headers['set-cookie']?.toString(), // no cookies :(
|
||||
...response.headers,
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(dlResponse.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - HTML Raw
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const tiktokCDNs = (matchLink(html) as string[]).filter(
|
||||
(x) => /http(s)?:\/\/(.*)?.tiktokcdn.com/gi.test(x),
|
||||
);
|
||||
const videoCDNs = tiktokCDNs.filter((x) => !/jpeg/gi.test(x));
|
||||
|
||||
return {
|
||||
'video': {
|
||||
'thumb': tiktokCDNs.find((x) => /jpeg/gi.test(x)),
|
||||
'urls': videoCDNs.filter((x) => !/music/gi.test(x)),
|
||||
},
|
||||
'music': {
|
||||
'url': videoCDNs.find((x) => /music/gi.test(x)) as string,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
import {getProvider} from '..';
|
||||
import type {BaseProvider} from '../baseProvider';
|
||||
|
||||
import {NodeVM} from 'vm2';
|
||||
|
||||
export const deObfuscate = (html: string): string => {
|
||||
if (/error/gi.test(html)) {
|
||||
throw new Error(html.split('\'')
|
||||
.find((x) => /(((url)? error)|could)/gi.test(x)),
|
||||
);
|
||||
} else {
|
||||
// only match script tag
|
||||
const obfuscatedScripts = html
|
||||
.match(/<script[\s\S]*?>[\s\S]*?<\/script>/gi);
|
||||
if (!obfuscatedScripts?.length) {
|
||||
throw new Error(
|
||||
'Cannot download the video!',
|
||||
);
|
||||
} else {
|
||||
const transformed = obfuscatedScripts[0]
|
||||
.replace(/<(\/)?script( type=".+")?>/g, '').trim().replace('eval', '')
|
||||
.replace(/\(function(.)?\(h/gi, 'module.exports = (function (h');
|
||||
const deObfuscated = new NodeVM({
|
||||
'compiler': 'javascript',
|
||||
'console': 'inherit',
|
||||
'require': {
|
||||
'external': true,
|
||||
'root': './',
|
||||
},
|
||||
}).run(transformed, 'deobfuscate.js');
|
||||
return deObfuscated;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const matchLink = (raw: string): string[] | null => {
|
||||
// eslint-disable-next-line max-len
|
||||
return raw.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi);
|
||||
};
|
||||
|
||||
export const matchCustomDownload =
|
||||
(provider: string, raw: string): string[] => {
|
||||
const links = matchLink(raw) as string[];
|
||||
const urls = raw.match(/\/download.php\?token=(.*?)"/gi)
|
||||
?.map((url) => (getProvider(provider) as BaseProvider).client.
|
||||
defaults.options.prefixUrl.slice(0, -1)+
|
||||
url.slice(0, -3));
|
||||
|
||||
return [links[0]].concat(urls as string[]);
|
||||
};
|
||||
|
||||
export const deObfuscateSaveFromScript = (scriptContent: string): string => {
|
||||
const safeScript = 'let result;' +
|
||||
scriptContent.replace(/\/\*js\-response\*\//gi, '')
|
||||
.replace(/eval\(a\)/gi, 'return a')
|
||||
.replace(/\[\]\["filter"\]\["constructor"\]\(b\)\.call\(a\);/gi, `
|
||||
if (b.includes('showResult')) {
|
||||
result = b;
|
||||
return;
|
||||
} else []['filter']['constructor'](b).call(a);`) +
|
||||
'module.exports = result;';
|
||||
const vm = new NodeVM({
|
||||
'compiler': 'javascript',
|
||||
'console': 'inherit',
|
||||
'require': {
|
||||
'external': true,
|
||||
'root': './',
|
||||
},
|
||||
});
|
||||
const result = vm.run(safeScript, 'savefrom.js');
|
||||
return result;
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Generate key for ttsave.app
|
||||
*
|
||||
* @param {string} token - Generated token by TTSave
|
||||
* @return {string | undefined}
|
||||
*/
|
||||
export const keyGeneratorTTSave = async (token: string): Promise<string> => {
|
||||
const expectedLen = (token.length / 3);
|
||||
|
||||
return token.split('').reverse().join('')
|
||||
.slice(-(expectedLen));
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
export const client = new Redis(process.env.REDIS_URL);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import {getRandomProvider} from '.';
|
||||
import {providerCache} from '../config';
|
||||
import {BaseProvider, ExtractedInfo} from './providers/baseProvider';
|
||||
import {client as redisClient} from './redis';
|
||||
|
||||
/**
|
||||
* Rotate provider.
|
||||
* @param {BaseProvider} provider Provider instance
|
||||
* @param {string} url Video TikTok URL
|
||||
* @param {boolean?} noCache NoCache option
|
||||
* @param {boolean?} skipOnError Rotate when error
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
export const rotateProvider = async (
|
||||
provider: BaseProvider, url: string,
|
||||
noCache: boolean = false, skipOnError: boolean = true):
|
||||
Promise<ExtractedInfo & { provider: string; }> => {
|
||||
// await redisClient.del(url);
|
||||
// console.log(provider.resourceName());
|
||||
if (provider.maintenance) {
|
||||
return await rotateProvider(getRandomProvider(), url, noCache, skipOnError);
|
||||
}
|
||||
const cachedData = await redisClient.get(url);
|
||||
if (!cachedData) {
|
||||
try {
|
||||
const data = await provider.fetch(url);
|
||||
if (data.error) {
|
||||
// switching to other provider
|
||||
return await rotateProvider(getRandomProvider(), url);
|
||||
} else if (data.video && !data.video.urls.length) {
|
||||
return await rotateProvider(getRandomProvider(), url);
|
||||
} else {
|
||||
if (!noCache) {
|
||||
redisClient.set(url,
|
||||
JSON.stringify(
|
||||
{...data, provider: provider.resourceName()}), 'ex',
|
||||
providerCache);
|
||||
}
|
||||
return {...data, provider: provider.resourceName()};
|
||||
}
|
||||
} catch (e) {
|
||||
if (skipOnError) {
|
||||
return await rotateProvider(getRandomProvider(), url);
|
||||
} else {
|
||||
return {
|
||||
error: (e as Error).message,
|
||||
provider: provider.resourceName(),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import {VercelRequest} from '@vercel/node';
|
||||
import {rateLimitConfig} from '../config';
|
||||
import {client} from '../lib/redis';
|
||||
|
||||
export const ratelimitMiddleware = async (req: VercelRequest) => {
|
||||
const ip = req.headers['x-real-ip'];
|
||||
return await new Promise((resolve, reject) => {
|
||||
if (!rateLimitConfig.enable) return resolve(undefined);
|
||||
else if (!ip) {
|
||||
return reject(
|
||||
new Error('Can\'t find your real ip address!'));
|
||||
}
|
||||
client.get('rate-' + ip, (_, result) => {
|
||||
if (result) {
|
||||
if (parseInt(result) > rateLimitConfig.maxRatelimitPerXSeconds) {
|
||||
return reject(
|
||||
new Error('Please try again, you are getting ratelimit!'));
|
||||
}
|
||||
client.incr('rate-' + ip);
|
||||
return resolve(undefined);
|
||||
} else {
|
||||
client.set('rate-' + ip, '1', 'ex', rateLimitConfig.ratelimitTime);
|
||||
return resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
39
package.json
39
package.json
@@ -1,27 +1,28 @@
|
||||
{
|
||||
"name": "tktk",
|
||||
"version": "1.0.3",
|
||||
"version": "2.0.0",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"got": "^11.8.2",
|
||||
"ioredis": "^4.28.0",
|
||||
"ow": "^0.28.1",
|
||||
"vm2": "^3.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^4.28.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||
"@typescript-eslint/parser": "^5.3.0",
|
||||
"@vercel/node": "^1.12.1",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"husky": "^7.0.4",
|
||||
"typescript": "^4.4.4"
|
||||
"husky": "8.0.3",
|
||||
"turbo": "1.13.4"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"apps/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "echo build",
|
||||
"lint": "eslint \"+(lib|api|middleware)/**/*.ts\" --fix",
|
||||
"prepare": "husky install"
|
||||
}
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev --parallel",
|
||||
"lint": "turbo run lint --parallel",
|
||||
"format": "turbo run format --parallel",
|
||||
"prepare": "husky install",
|
||||
"start": "turbo run start"
|
||||
},
|
||||
"packageManager": "yarn@4.9.3",
|
||||
"engines": {
|
||||
"node": ">=14.x"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
12
packages/config/eslint.js
Normal file
12
packages/config/eslint.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
extends: ['google', 'prettier'],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: [
|
||||
'apps/web/',
|
||||
'packages/core/',
|
||||
'packages/config/'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
4
packages/config/eslint.typescript.js
Normal file
4
packages/config/eslint.typescript.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
...require('./eslint.js'),
|
||||
parser: '@typescript-eslint/parser',
|
||||
};
|
||||
22
packages/config/package.json
Normal file
22
packages/config/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "tiktok-dl-config",
|
||||
"version": "1.4.1",
|
||||
"description": "TikTok-DL Project Configuration",
|
||||
"files": [
|
||||
"eslint.js",
|
||||
"eslint.typescript.js",
|
||||
"useragents.js",
|
||||
"useragents.d.ts",
|
||||
"prettier.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"eslint-config-google": "0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "5.33.0",
|
||||
"@typescript-eslint/parser": "5.33.0",
|
||||
"eslint": "8.21.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"prettier": "2.7.1"
|
||||
}
|
||||
}
|
||||
8
packages/config/prettier.js
Normal file
8
packages/config/prettier.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
tabWidth: 4,
|
||||
trailingComma: 'all',
|
||||
bracketSpacing: false,
|
||||
// useTabs: true,
|
||||
};
|
||||
5
packages/config/useragents.d.ts
vendored
Normal file
5
packages/config/useragents.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare const _default: {
|
||||
random: () => string;
|
||||
}
|
||||
|
||||
export = _default;
|
||||
513
packages/config/useragents.js
Normal file
513
packages/config/useragents.js
Normal file
@@ -0,0 +1,513 @@
|
||||
const uas = [
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 (+https://whatis.contentkingapp.com)",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; LG-H631 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/38.0.2125.102 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||
"Mozilla/5.0 (en-us) AppleWebKit/534.14 (KHTML, like Gecko; Google Wireless Transcoder) Chrome/9.0.597 Safari/534.14",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/58.0.3029.83 Mobile/14A346 Safari/E7FBAF",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/79.0.3945.73 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; CrOS x86_64 13597.94.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.24 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/80.0.3987.95 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36 HubSpot Webcrawler - web-crawlers@hubspot.com",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/91.0.4472.80 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/92.0.4515.90 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (X11; CrOS x86_64 13597.105.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.208 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.163 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/E7FBAF",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 5.1.1; Lenovo-A6020l36 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 11; SM-A102U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1.2; vivo y35 Build/N2G48B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.158 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; Lenovo-A6020l36 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 4.3; MediaPad 7 Youth 2 Build/HuaweiMediaPad) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.79 Safari/E7FBAF",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; Google Web Preview) Chrome/27.0.1453 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 9; SM-A102U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Get random UA.
|
||||
* @return {string}
|
||||
*/
|
||||
random: () => {
|
||||
return uas[Math.floor(Math.random() * uas.length)];
|
||||
},
|
||||
uas,
|
||||
}
|
||||
1
packages/core/.eslintrc.cjs
Normal file
1
packages/core/.eslintrc.cjs
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('tiktok-dl-config/eslint.typescript');
|
||||
1
packages/core/.prettierignore
Normal file
1
packages/core/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
*.json
|
||||
1
packages/core/.prettierrc.cjs
Normal file
1
packages/core/.prettierrc.cjs
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('tiktok-dl-config/prettier');
|
||||
8
packages/core/fetch.ts
Normal file
8
packages/core/fetch.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import got, {ExtendOptions} from 'got';
|
||||
|
||||
export const getFetch = (baseUrl: string, options?: ExtendOptions) =>
|
||||
got.extend({
|
||||
prefixUrl: baseUrl,
|
||||
dnsCache: true,
|
||||
...options,
|
||||
});
|
||||
3
packages/core/index.ts
Normal file
3
packages/core/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './src';
|
||||
export * from './fetch';
|
||||
export * from './src/utils';
|
||||
21
packages/core/package.json
Normal file
21
packages/core/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "tiktok-dl-core",
|
||||
"version": "2.0.0",
|
||||
"main": "./index.ts",
|
||||
"license": "MIT",
|
||||
"description": "TikTok-DL Project Core",
|
||||
"dependencies": {
|
||||
"got": "^12.6.1",
|
||||
"ow": "0.28.1",
|
||||
"vm2": "^3.9.19"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"+(src)/**/*.ts\" --fix",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "5.33.0",
|
||||
"eslint": "8.21.0",
|
||||
"prettier": "2.7.1"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Got} from 'got';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
export interface ExtractedInfo {
|
||||
error?: string;
|
||||
@@ -15,11 +16,14 @@ export interface ExtractedInfo {
|
||||
author?: string;
|
||||
id?: string;
|
||||
cover?: string;
|
||||
album?: string;
|
||||
duration?: number;
|
||||
};
|
||||
author?: {
|
||||
username?: string;
|
||||
thumb?: string;
|
||||
id?: string;
|
||||
nick?: string;
|
||||
};
|
||||
caption?: string;
|
||||
playsCount?: number;
|
||||
@@ -28,19 +32,23 @@ export interface ExtractedInfo {
|
||||
likesCount?: number;
|
||||
uploadedAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MaintenanceProvider {
|
||||
reason: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @class BaseProvider
|
||||
*/
|
||||
export abstract class BaseProvider {
|
||||
abstract client: Got;
|
||||
abstract client?: Got;
|
||||
abstract getParams(): Shape | undefined;
|
||||
abstract maintenance?: MaintenanceProvider;
|
||||
abstract resourceName(): string;
|
||||
abstract fetch(url: string): Promise<ExtractedInfo>;
|
||||
abstract fetch(
|
||||
url: string,
|
||||
params?: Record<string, string>,
|
||||
): Promise<ExtractedInfo>;
|
||||
abstract extract(html: string): ExtractedInfo;
|
||||
};
|
||||
}
|
||||
108
packages/core/src/downTikProvider.ts
Normal file
108
packages/core/src/downTikProvider.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import {getFetch} from '../fetch';
|
||||
import {matchCustomDownload, matchLink, runObfuscatedScript} from './utils';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class DownTikProvider
|
||||
*/
|
||||
export class DownTikProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'downtik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://downtik.io');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client('./', {
|
||||
searchParams: new URLSearchParams({
|
||||
lang: 'en',
|
||||
}),
|
||||
});
|
||||
|
||||
const token = (
|
||||
response.body.match(/id="token" value="([^""]+)"/) as string[]
|
||||
)[1];
|
||||
|
||||
const responseAction = await this.client.post('./action.php', {
|
||||
form: {
|
||||
url: url,
|
||||
token: token,
|
||||
},
|
||||
searchParams: new URLSearchParams({
|
||||
lang: 'en',
|
||||
}),
|
||||
headers: {
|
||||
cookie: response.headers['set-cookie']?.toString(),
|
||||
Referer: 'https://downtik.io/',
|
||||
Origin: 'https://downtik.io',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
if (JSON.parse(responseAction.body).error) {
|
||||
return {
|
||||
error: JSON.parse(responseAction.body).message,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// if JSON.parse fail
|
||||
return this.extract(responseAction.body);
|
||||
}
|
||||
|
||||
return this.extract(responseAction.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
let urls = matchCustomDownload('downtik', html);
|
||||
|
||||
if (!urls?.length) {
|
||||
urls = matchLink(runObfuscatedScript(html)) as string[];
|
||||
if (!urls?.length)
|
||||
return {
|
||||
error: "Couldn't match any links!",
|
||||
};
|
||||
|
||||
return {
|
||||
video: {
|
||||
thumb: urls?.shift(),
|
||||
urls: urls as string[],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
music: {
|
||||
url: urls.pop() as string,
|
||||
},
|
||||
video: {
|
||||
thumb: urls?.shift(),
|
||||
urls: urls as string[],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
41
packages/core/src/index.ts
Normal file
41
packages/core/src/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type {BaseProvider, ExtractedInfo} from './base';
|
||||
|
||||
import {MusicalyDown} from './musicalyDownProvider';
|
||||
import {SnaptikProvider} from './snaptikProvider';
|
||||
import {TikmateProvider} from './tikmateProvider';
|
||||
import {TTDownloader} from './ttDownloaderProvider';
|
||||
import {SaveFromProvider} from './saveFromProvider';
|
||||
import {SaveTikProvider} from './saveTikProvider';
|
||||
import {TikDownProvider} from './tikDownProvider';
|
||||
import {DownTikProvider} from './downTikProvider';
|
||||
// import {LoveTikProvider} from './loveTikProvider';
|
||||
// import {DDDTikProvider} from './dddTikProvider';
|
||||
// import {DownloadOne} from './downloaderOneProvider';
|
||||
import {NativeProvider} from './nativeProvider';
|
||||
// import {GetVidTikProvider} from './getVidTikProvider';
|
||||
|
||||
export const Providers: BaseProvider[] = [
|
||||
new SnaptikProvider(),
|
||||
new TikmateProvider(),
|
||||
new MusicalyDown(),
|
||||
new TTDownloader(),
|
||||
new SaveFromProvider(),
|
||||
new SaveTikProvider(),
|
||||
new TikDownProvider(),
|
||||
new DownTikProvider(), // SaveTik Mirror
|
||||
// new LoveTikProvider(),
|
||||
// new DDDTikProvider(),
|
||||
// new DownloadOne(),
|
||||
new NativeProvider(),
|
||||
// new GetVidTikProvider(),
|
||||
];
|
||||
|
||||
export const getRandomProvider = () =>
|
||||
Providers[Math.floor(Math.random() * Providers.length)];
|
||||
|
||||
export const getProvider = (name: string) =>
|
||||
name.toLowerCase() !== 'random'
|
||||
? Providers.find((p) => p.resourceName() === name.toLowerCase())
|
||||
: getRandomProvider();
|
||||
|
||||
export {BaseProvider, ExtractedInfo};
|
||||
83
packages/core/src/musicalyDownProvider.ts
Normal file
83
packages/core/src/musicalyDownProvider.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import {getFetch} from '../fetch';
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import type {Shape} from 'ow';
|
||||
import { matchLink } from './utils';
|
||||
|
||||
/**
|
||||
* @class MusicalyDown
|
||||
*/
|
||||
export class MusicalyDown extends BaseProvider {
|
||||
public client = getFetch('https://musicaldown.com/id');
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'musicalydown';
|
||||
}
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video Tiktok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const res = await this.client('./', {
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
Referer: this.client.defaults.options.prefixUrl.toString(),
|
||||
Origin: this.client.defaults.options.prefixUrl.toString(),
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
|
||||
},
|
||||
});
|
||||
|
||||
const tokens = res.body.match(
|
||||
/input name="([^""]+)" type="hidden" value="([^""]+)"/,
|
||||
) as string[];
|
||||
|
||||
const response = await this.client.post('./download', {
|
||||
form: {
|
||||
[(res.body.match(/input name="([^"]+)/) as string[])[1]]: url,
|
||||
[tokens[1]]: tokens[2],
|
||||
verify: 1,
|
||||
},
|
||||
headers: {
|
||||
Cookie: res.headers['set-cookie']?.toString(),
|
||||
Accept: '*/*',
|
||||
Referer: this.client.defaults.options.prefixUrl.toString(),
|
||||
Origin: this.client.defaults.options.prefixUrl.toString(),
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - Raw HTML
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
public extract(html: string): ExtractedInfo {
|
||||
const urls = matchLink(html);
|
||||
|
||||
const matchedUrls = urls?.filter(url => /muscdn/gi.test(url)) ?? [];
|
||||
|
||||
return {
|
||||
video: {
|
||||
urls: matchedUrls.filter(murl => !murl.includes('images')),
|
||||
thumb: matchedUrls.find(murl => murl.includes('images')),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
106
packages/core/src/nativeProvider.ts
Normal file
106
packages/core/src/nativeProvider.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import {getFetch} from '../fetch';
|
||||
import {matchTikTokData} from './utils';
|
||||
import ow, {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class NativeProvider
|
||||
*/
|
||||
export class NativeProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name.
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'native';
|
||||
}
|
||||
|
||||
public maintenance = undefined;
|
||||
public client = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url Tiktok video url
|
||||
* @param {Record<string, string>} params Advanced options.
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(
|
||||
url: string,
|
||||
params: Record<string, string>,
|
||||
): Promise<ExtractedInfo> {
|
||||
const urlInstance = new URL(url);
|
||||
const response = await getFetch(urlInstance.origin).get(
|
||||
`.${urlInstance.pathname}`,
|
||||
{
|
||||
headers: {
|
||||
Referer: urlInstance.href,
|
||||
Origin: urlInstance.origin,
|
||||
'User-Agent': params['user-agent'],
|
||||
},
|
||||
timeout: {
|
||||
socket: 10000,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html Raw HTML Data
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const matches = matchTikTokData(html);
|
||||
if (matches.length) {
|
||||
const json = Object.values(
|
||||
JSON.parse(matches).ItemModule,
|
||||
)[0] as any;
|
||||
|
||||
return {
|
||||
video: {
|
||||
id: json.id,
|
||||
urls: [json.video.playAddr, json.video.downloadAddr],
|
||||
thumb: json.video.cover,
|
||||
duration: json.video.duration,
|
||||
},
|
||||
music: {
|
||||
url: json.music.playUrl,
|
||||
title: json.music.title,
|
||||
author: json.music.authorName,
|
||||
id: json.music.id,
|
||||
cover: json.music.coverLarge,
|
||||
album: json.music.album,
|
||||
duration: json.music.duration,
|
||||
},
|
||||
author: {
|
||||
username: json.author,
|
||||
id: json.authorId,
|
||||
thumb: json.avatarThumb,
|
||||
nick: json.nickname,
|
||||
},
|
||||
caption: json.desc,
|
||||
playsCount: json.stats.playCount,
|
||||
sharesCount: json.stats.shareCount,
|
||||
commentsCount: json.stats.commentCount,
|
||||
likesCount: json.stats.diggCount,
|
||||
uploadedAt: json.createTime,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
error: 'Something was wrong!',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return {
|
||||
'user-agent': ow.string.not.empty
|
||||
.minLength(5)
|
||||
.message('Invalid user-agent'),
|
||||
};
|
||||
}
|
||||
}
|
||||
85
packages/core/src/saveFromProvider.ts
Normal file
85
packages/core/src/saveFromProvider.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import {getFetch} from '../fetch';
|
||||
import {BaseProvider, ExtractedInfo, MaintenanceProvider} from './base';
|
||||
import {deObfuscateSaveFromScript} from './utils';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class saveFromProvider
|
||||
*/
|
||||
export class SaveFromProvider extends BaseProvider {
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'savefrom';
|
||||
}
|
||||
|
||||
public client = getFetch('https://worker.savefrom.net');
|
||||
|
||||
public maintenance: MaintenanceProvider = {
|
||||
reason: 'Need advance investigate to Reverse Engineering the response scripts.'
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client.post('./savefrom.php', {
|
||||
form: {
|
||||
sf_url: url,
|
||||
sf_submit: '',
|
||||
new: '2',
|
||||
lang: 'id',
|
||||
country: 'id',
|
||||
os: 'Ubuntu',
|
||||
browser: 'Firefox',
|
||||
channel: 'Downloader',
|
||||
'sf-nomad': '1',
|
||||
url,
|
||||
ts: Date.now(),
|
||||
},
|
||||
headers: {
|
||||
Origin: 'https://id.savefrom.net',
|
||||
Referer: 'https://id.savefrom.net',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - HTML Raw
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const deobfuscated = deObfuscateSaveFromScript(html);
|
||||
const json = JSON.parse(
|
||||
(deobfuscated.match(/\({(.*)}\)/) as string[])[0].replace(
|
||||
/(\(|\))/g,
|
||||
'',
|
||||
),
|
||||
);
|
||||
return {
|
||||
video: {
|
||||
thumb: json.thumb,
|
||||
id: json.id,
|
||||
urls: json.url.map((x: {url: string}) => x.url),
|
||||
duration: json.meta.duration,
|
||||
title: json.meta.title,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
106
packages/core/src/saveTikProvider.ts
Normal file
106
packages/core/src/saveTikProvider.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import {getFetch} from '../fetch';
|
||||
// import {matchLink, runObfuscatedReplaceEvalScript} from './utils';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class SaveTikProvider
|
||||
*/
|
||||
export class SaveTikProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'savetik';
|
||||
}
|
||||
|
||||
public client = getFetch('https://savetik.net');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client.get('./api/action', {
|
||||
searchParams: new URLSearchParams({
|
||||
url,
|
||||
}),
|
||||
throwHttpErrors: false,
|
||||
});
|
||||
|
||||
if (response.statusCode === 400)
|
||||
{
|
||||
return {
|
||||
error: 'Video not found',
|
||||
};
|
||||
}
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
// const results = runObfuscatedReplaceEvalScript(html);
|
||||
// const matchUrls = matchLink(results) ?? [];
|
||||
|
||||
// const urls = matchUrls.filter(url => /(tiktokcdn|rapidcdn)/gi.test(url));
|
||||
|
||||
// return {
|
||||
// music: {
|
||||
// url: urls?.pop() as string,
|
||||
// },
|
||||
// video: {
|
||||
// thumb: urls?.shift(),
|
||||
// urls: urls as string[],
|
||||
// },
|
||||
// };
|
||||
|
||||
try {
|
||||
const json = JSON.parse(html);
|
||||
if (json.error) {
|
||||
return {
|
||||
error: json.error,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
video: {
|
||||
urls: [
|
||||
json.hdDownloadUrl,
|
||||
json.downloadUrl,
|
||||
],
|
||||
title: json.postinfo.media_title,
|
||||
duration: json.duration.toString(),
|
||||
},
|
||||
author: {
|
||||
username: json.postinfo.unique_id,
|
||||
id: json.postinfo.uid,
|
||||
nick: json.postinfo.username,
|
||||
thumb: json.postinfo.avatar_url,
|
||||
},
|
||||
sharesCount: json.stats.shareCount,
|
||||
playsCount: json.stats.playCount,
|
||||
commentsCount: json.stats.commentCount,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
error: 'Video not found',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
75
packages/core/src/snaptikProvider.ts
Normal file
75
packages/core/src/snaptikProvider.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import {getFetch} from '../fetch';
|
||||
import {BaseProvider, ExtractedInfo, MaintenanceProvider} from './base';
|
||||
import {matchLink, runObfuscatedReplaceEvalScript} from './utils';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class SnaptikProvider
|
||||
*/
|
||||
export class SnaptikProvider extends BaseProvider {
|
||||
public client = getFetch('https://snaptik.app/en');
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'snaptik';
|
||||
}
|
||||
|
||||
public maintenance?: MaintenanceProvider | undefined = undefined
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - TikTok Video URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// get token
|
||||
const responseToken = await this.client('./', {
|
||||
headers: {},
|
||||
});
|
||||
const token = (
|
||||
responseToken.body.match(
|
||||
/name="token" value="([^""]+)"/,
|
||||
) as string[]
|
||||
)[1];
|
||||
|
||||
const response = await this.client('./abc2.php', {
|
||||
searchParams: {
|
||||
url: url,
|
||||
token,
|
||||
lang: 'ID2',
|
||||
},
|
||||
headers: {
|
||||
Cookie: responseToken.headers['set-cookie']?.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information from raw html
|
||||
* @param {string} html - Raw HTML
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const scriptsResult = runObfuscatedReplaceEvalScript(html);
|
||||
const results = matchLink(scriptsResult);
|
||||
if (!results || !results.length) throw new Error('Broken');
|
||||
return {
|
||||
video: {
|
||||
thumb: results?.shift(),
|
||||
urls: [...new Set(results.slice(0, results.length - 1))],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
78
packages/core/src/tikDownProvider.ts
Normal file
78
packages/core/src/tikDownProvider.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import {getFetch} from '../fetch';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class TikDownProvider
|
||||
*/
|
||||
export class TikDownProvider extends BaseProvider {
|
||||
/**
|
||||
* Get resource name.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'tikdown';
|
||||
}
|
||||
|
||||
public client = getFetch('https://tikdown.org');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
*
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
async fetch(url: string): Promise<ExtractedInfo> {
|
||||
const response = await this.client.post('./', {
|
||||
form: {
|
||||
'tiktok-url': url,
|
||||
},
|
||||
});
|
||||
|
||||
const body = response.body;
|
||||
if (/please double/gi.test(body))
|
||||
{
|
||||
return {
|
||||
error: 'Video not found',
|
||||
};
|
||||
}
|
||||
|
||||
const indexLink = body.match(/\.\/index\.php\?url=[^'"]+/gi)?.at(0);
|
||||
if (!indexLink) {
|
||||
return {
|
||||
error: 'Couldnt find URL',
|
||||
};
|
||||
}
|
||||
|
||||
const responseVideo = await this.client.get(indexLink);
|
||||
if (!responseVideo.body.length) {
|
||||
return {
|
||||
error: 'Couldnt find downloaded URL',
|
||||
}
|
||||
}
|
||||
|
||||
return this.extract(responseVideo.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
return {
|
||||
video: {
|
||||
urls: [new URL(`./${html}`, this.client.defaults.options.prefixUrl.toString()).href],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
90
packages/core/src/tikmateProvider.ts
Normal file
90
packages/core/src/tikmateProvider.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import {getFetch} from '../fetch';
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import {deObfuscate, matchLink} from './utils';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class TikmateProvider
|
||||
*/
|
||||
export class TikmateProvider extends BaseProvider {
|
||||
public client = getFetch('https://tikmate.io');
|
||||
/**
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'tikmate';
|
||||
}
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// we need to get the token
|
||||
|
||||
const response = await this.client('./', {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
|
||||
}
|
||||
});
|
||||
|
||||
const matchs = response.body.match(
|
||||
/(name|id)="(\_)?token" value="([^""]+)"/,
|
||||
) as string[];
|
||||
|
||||
const cookies =
|
||||
response.headers['cookie'] ||
|
||||
response.headers['set-cookie']?.toString();
|
||||
|
||||
const abcResponse = await this.client.post('./abc.php', {
|
||||
form: matchs.at(-1)
|
||||
? {
|
||||
url: url,
|
||||
token: matchs.at(-1),
|
||||
}
|
||||
: {
|
||||
url: url,
|
||||
},
|
||||
headers: {
|
||||
Origin: this.client.defaults.options.prefixUrl.toString(),
|
||||
Referer: this.client.defaults.options.prefixUrl.toString(),
|
||||
Cookie: cookies,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(abcResponse.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information from raw html
|
||||
* @param {string} html - Raw HTML
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const matchs = matchLink(deObfuscate(html));
|
||||
if (!matchs)
|
||||
return {
|
||||
error: "Couldn't match any links!",
|
||||
};
|
||||
|
||||
return {
|
||||
video: {
|
||||
thumb: matchs.shift(),
|
||||
urls: matchs,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
70
packages/core/src/ttDownloaderProvider.ts
Normal file
70
packages/core/src/ttDownloaderProvider.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {getFetch} from '../fetch';
|
||||
import {BaseProvider, ExtractedInfo} from './base';
|
||||
import {matchLink} from './utils';
|
||||
import type {Shape} from 'ow';
|
||||
|
||||
/**
|
||||
* @class TTDownloader
|
||||
*/
|
||||
export class TTDownloader extends BaseProvider {
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
public resourceName(): string {
|
||||
return 'ttdownloader';
|
||||
}
|
||||
|
||||
public client = getFetch('https://ttdownloader.com');
|
||||
|
||||
public maintenance = undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url - Video TikTok URL
|
||||
* @return {Promise<ExtractedInfo>}
|
||||
*/
|
||||
public async fetch(url: string): Promise<ExtractedInfo> {
|
||||
// getting token and cookies
|
||||
const firstResponse = await this.client('./');
|
||||
const token = (
|
||||
firstResponse.body.match(/name="token" value="(.*)?"/) as string[]
|
||||
)[1];
|
||||
const videoResponse = await this.client.post('./search', {
|
||||
form: {
|
||||
token: token,
|
||||
format: '',
|
||||
url: url,
|
||||
},
|
||||
headers: {
|
||||
Origin: 'https://ttdownloader.com',
|
||||
Referer: 'https://ttdownloader.com',
|
||||
Cookie: firstResponse.headers['set-cookie']?.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
return this.extract(videoResponse.body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} html - HTML Raw
|
||||
* @return {ExtractedInfo}
|
||||
*/
|
||||
extract(html: string): ExtractedInfo {
|
||||
const urls = matchLink(html);
|
||||
urls?.pop(); // remove 'https://snaptik.fans'
|
||||
return {
|
||||
video: {
|
||||
urls: (urls as string[]) ?? [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ow.Shape params.
|
||||
* @return {Shape | undefined}
|
||||
*/
|
||||
public getParams(): Shape | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
103
packages/core/src/utils/extractor.ts
Normal file
103
packages/core/src/utils/extractor.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {getProvider} from '..';
|
||||
import type {BaseProvider} from '../base';
|
||||
|
||||
import {NodeVM} from 'vm2';
|
||||
|
||||
export const matchTikTokData = (html: string): string => {
|
||||
const data = html.match(
|
||||
// eslint-disable-next-line max-len
|
||||
/window\['SIGI_STATE'\]=(.*)<\/script><script id="__LOADABLE_REQUIRED_CHUNKS__" type="application\/json">/,
|
||||
);
|
||||
|
||||
if (data) {
|
||||
return data[1].replace(/;window.+/gi, '');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const runObfuscatedReplaceEvalScript = (jsCode: string): string => {
|
||||
return runObfuscatedScript(jsCode.replace('eval', 'module.exports = '));
|
||||
}
|
||||
|
||||
export const runObfuscatedScript = (jsCode: string): string => {
|
||||
const transformed = jsCode
|
||||
.trim()
|
||||
.replace('eval', '')
|
||||
.replace(/\(function(.)?\(h/gi, 'module.exports = (function (h');
|
||||
const deObfuscated = new NodeVM({
|
||||
compiler: 'javascript',
|
||||
console: 'inherit',
|
||||
require: {
|
||||
external: true,
|
||||
root: './',
|
||||
},
|
||||
}).run(transformed, 'deobfuscate.js');
|
||||
|
||||
return deObfuscated;
|
||||
};
|
||||
|
||||
export const deObfuscate = (html: string): string => {
|
||||
if (/error/gi.test(html)) {
|
||||
throw new Error(
|
||||
html.split("'").find((x) => /(((url)? error)|could)/gi.test(x)),
|
||||
);
|
||||
} else {
|
||||
// only match script tag.
|
||||
const obfuscatedScripts = html.match(
|
||||
/<script[\s\S]*?>[\s\S]*?<\/script>/gi,
|
||||
);
|
||||
if (!obfuscatedScripts?.length) {
|
||||
throw new Error('Cannot download the video!');
|
||||
} else {
|
||||
return runObfuscatedScript(
|
||||
obfuscatedScripts
|
||||
.find((x) => x.length)!
|
||||
.replace(/<(\/)?script( type=".+")?>/g, '')
|
||||
.trim(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const matchLink = (raw: string): string[] | null => {
|
||||
return raw.match(
|
||||
// eslint-disable-next-line max-len
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi,
|
||||
);
|
||||
};
|
||||
|
||||
export const matchCustomDownload = (
|
||||
provider: string,
|
||||
raw: string,
|
||||
): string[] => {
|
||||
const links = matchLink(raw) as string[];
|
||||
const urls = raw
|
||||
.match(/\/download.php\?token=(.*?)"/gi)
|
||||
?.map(
|
||||
(url) =>
|
||||
(getProvider(provider) as BaseProvider)
|
||||
.client!.defaults.options.prefixUrl.toString()
|
||||
.slice(0, -1) + url.slice(0, -3),
|
||||
);
|
||||
|
||||
if (!urls?.length) return [];
|
||||
return [links[0]].concat(urls as string[]);
|
||||
};
|
||||
|
||||
export const deObfuscateSaveFromScript = (scriptContent: string): string => {
|
||||
const safeScript =
|
||||
'let result;' +
|
||||
scriptContent
|
||||
.replace(/\/\*js\-response\*\//gi, '');
|
||||
const vm = new NodeVM({
|
||||
compiler: 'javascript',
|
||||
console: 'inherit',
|
||||
require: {
|
||||
external: true,
|
||||
root: './',
|
||||
},
|
||||
});
|
||||
const result = vm.run(safeScript, 'savefrom.js');
|
||||
return result;
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export * from './extractor';
|
||||
export * from './generator';
|
||||
@@ -7,14 +7,14 @@
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "dist",
|
||||
"target": "ES2016",
|
||||
"module": "CommonJS",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TikTok Download</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre>Go to:</pre>
|
||||
<a href="/api">/api</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
hello there!
|
||||
10
renovate.json
Normal file
10
renovate.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"assignees": ["hansputera"],
|
||||
"username": "hansputera",
|
||||
"labels": ["dependencies"],
|
||||
"reviewers": ["hansputera"]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user