mirror of
https://github.com/hansputera/tiktok-dl.git
synced 2026-04-05 19:51:57 +02:00
feat(web): reconstruct
Signed-off-by: Hanif Dwy Putra S <hanifdwyputrasembiring@gmail.com>
This commit is contained in:
@@ -23,6 +23,13 @@ export type ExtractedInfoWithProvider = ExtractedInfo & {
|
||||
_url: string;
|
||||
};
|
||||
|
||||
interface StateData {
|
||||
submitted: boolean;
|
||||
error?: string | Error;
|
||||
url: string;
|
||||
wasSubmit: boolean;
|
||||
}
|
||||
|
||||
const fetcher: Fetcher<ExtractedInfoWithProvider, string> = (...args) =>
|
||||
fetch(...args).then((r) => r.json());
|
||||
|
||||
@@ -31,18 +38,22 @@ const fetcher: Fetcher<ExtractedInfoWithProvider, string> = (...args) =>
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export const FormInputComponent = (): JSX.Element => {
|
||||
const [url, setUrl] = React.useState('');
|
||||
const [error, setError] = React.useState<string | Error>();
|
||||
const [submitted, setSubmit] = React.useState<boolean>(false);
|
||||
const [state, setState] = React.useState<StateData>({
|
||||
submitted: false,
|
||||
error: undefined,
|
||||
url: '',
|
||||
wasSubmit: false,
|
||||
});
|
||||
|
||||
const {data, mutate} = useSWR(
|
||||
submitted &&
|
||||
(!error || !(error as string).length) &&
|
||||
/^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.+)$/gi.test(url)
|
||||
(state.submitted || state.wasSubmit) &&
|
||||
(!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}),
|
||||
body: JSON.stringify({url: state.url}),
|
||||
},
|
||||
]
|
||||
: null,
|
||||
@@ -50,39 +61,59 @@ export const FormInputComponent = (): JSX.Element => {
|
||||
{
|
||||
loadingTimeout: 10_000,
|
||||
refreshInterval: 30_000,
|
||||
revalidateIfStale: true,
|
||||
revalidateOnMount: false,
|
||||
onSuccess: () =>
|
||||
setState({
|
||||
...state,
|
||||
submitted: false,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
!/^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.+)$/gi.test(url) &&
|
||||
url.length
|
||||
!/^http(s?)(:\/\/)([a-z]+\.)*tiktok\.com\/(.+)$/gi.test(
|
||||
state.url,
|
||||
) &&
|
||||
state.url.length
|
||||
) {
|
||||
setError(new InvalidUrlError('Invalid TikTok Video URL'));
|
||||
setState({
|
||||
...state,
|
||||
error: new InvalidUrlError('Invalid TikTok URL'),
|
||||
});
|
||||
} else {
|
||||
// submit event trigger.
|
||||
if (submitted && !error) {
|
||||
if (state.submitted && !state.error) {
|
||||
mutate();
|
||||
}
|
||||
|
||||
try {
|
||||
const u = getTikTokURL(url);
|
||||
const u = getTikTokURL(state.url);
|
||||
if (!u) {
|
||||
setError(new InvalidUrlError('Invalid TikTok URL'));
|
||||
setState({
|
||||
...state,
|
||||
error: new InvalidUrlError('Invalid TikTok URL'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(u);
|
||||
setUrl(u);
|
||||
setState({
|
||||
...state,
|
||||
url: u,
|
||||
});
|
||||
} catch {
|
||||
setError(new InvalidUrlError('Invalid TikTok Video URL'));
|
||||
setState({
|
||||
...state,
|
||||
error: new InvalidUrlError('Invalid TikTok URL'),
|
||||
});
|
||||
}
|
||||
|
||||
setError(undefined);
|
||||
setState({
|
||||
...state,
|
||||
error: undefined,
|
||||
});
|
||||
}
|
||||
}, [url, submitted]);
|
||||
}, [state.submitted, state.url]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -91,28 +122,42 @@ export const FormInputComponent = (): JSX.Element => {
|
||||
Fill TikTok's Video URL below:
|
||||
</h1>
|
||||
<p className="text-red-400 font-sans font-semibold">
|
||||
{error instanceof Error
|
||||
? error.name.concat(': '.concat(error.message))
|
||||
: error
|
||||
? error
|
||||
{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 (!url.length) {
|
||||
setError('Please fill the URL!');
|
||||
if (!state.url.length) {
|
||||
setState({
|
||||
...state,
|
||||
error: 'Please fill the URL!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
!error && setSubmit(true);
|
||||
!state.error &&
|
||||
setState({
|
||||
...state,
|
||||
submitted: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
type="url"
|
||||
onChange={(event) => setUrl(event.target.value)}
|
||||
value={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"
|
||||
/>
|
||||
@@ -121,7 +166,7 @@ export const FormInputComponent = (): JSX.Element => {
|
||||
<div>
|
||||
<button
|
||||
className="p-3 lg:ml-2 mt-1 bg-sky-400 uppercase text-white shadow-sm"
|
||||
disabled={submitted}
|
||||
disabled={state.submitted}
|
||||
>
|
||||
download
|
||||
</button>
|
||||
@@ -129,14 +174,13 @@ export const FormInputComponent = (): JSX.Element => {
|
||||
</form>
|
||||
|
||||
<section className="mt-3 mb-3">
|
||||
{submitted && !data ? (
|
||||
{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} />
|
||||
)}
|
||||
{data && data && data.video && data.video.urls.length && (
|
||||
<VideoComponent data={data} />
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -2,64 +2,37 @@ 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>
|
||||
<h1 className="text-2xl text-center">Your Video Is Ready!</h1>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<video
|
||||
autoPlay={false}
|
||||
controls
|
||||
className="h-64 w-80 rounded-md md:ml-2"
|
||||
>
|
||||
<source src={data.video?.urls[0]} />
|
||||
</video>
|
||||
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 className="bg-teal-200 rounded-sm text-center">
|
||||
<ul className="grid grid-cols-1 gap-1">
|
||||
<li key="download-url">
|
||||
<p className="font-semibold">Download URLs:</p>
|
||||
</li>
|
||||
{data.video!.urls.map((url, index) => (
|
||||
<li key={index}>
|
||||
<a href={url}>Click to Download #{index + 1}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{data.music && (
|
||||
<div className="bg-light-500 rounded-sm text-center">
|
||||
<ul className="grid grid-cols-1 gap-1">
|
||||
<li key="music-head">
|
||||
<p className="font-semibold">Music:</p>
|
||||
</li>
|
||||
<li key="music-url">
|
||||
Music URL:{' '}
|
||||
<a
|
||||
href={data.music.url}
|
||||
className="text-blue-500 uppercase"
|
||||
target="_blank"
|
||||
>
|
||||
Click here
|
||||
</a>
|
||||
</li>
|
||||
{data.music.author && (
|
||||
<li key="music-author">
|
||||
Music Author: {data.music.author}
|
||||
</li>
|
||||
)}
|
||||
{data.music.title && (
|
||||
<li key="music-title">
|
||||
Music Title: {data.music.title}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="font-sans text-base mt-2">
|
||||
© Source: {data.provider}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ const FormInputComponentDynamic = dynamic(
|
||||
export default () => {
|
||||
return (
|
||||
<section className="p-5">
|
||||
<h1 className="align-middle text-4xl font-sans font-medium tracking-wide leading-relaxed">
|
||||
<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.
|
||||
|
||||
Reference in New Issue
Block a user