♻️ refactor: 1. Refactor the account creation code 2. Add the function of parsing the proxy from the clipboard
This commit is contained in:
parent
e1c2105501
commit
1039099d04
8 changed files with 471 additions and 283 deletions
|
|
@ -553,7 +553,13 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
return edit ? this.execute(new TdApi.EditProxy(tdProxy.id, proxy.server, proxy.port, true, proxyType))
|
||||
: this.execute(new TdApi.AddProxy(proxy.server, proxy.port, true, proxyType));
|
||||
})
|
||||
.compose(r -> DataVerticle.telegramRepository.update(this.telegramRecord.withProxy(proxyName)).map(r))
|
||||
.compose(r -> {
|
||||
if (this.telegramRecord != null) {
|
||||
return DataVerticle.telegramRepository.update(this.telegramRecord.withProxy(proxyName)).map(r);
|
||||
} else {
|
||||
return Future.succeededFuture(r);
|
||||
}
|
||||
})
|
||||
.compose(r -> {
|
||||
this.proxyName = proxyName;
|
||||
return Future.succeededFuture(r);
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import { EmptyState } from "@/components/empty-state";
|
|||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||
|
||||
export default function Home() {
|
||||
const { getAccounts, handleAccountChange } = useTelegramAccount();
|
||||
const { getAccounts, handleAccountChange, isLoading } = useTelegramAccount();
|
||||
const accounts = getAccounts();
|
||||
return (
|
||||
<EmptyState
|
||||
isLoadingAccount={isLoading}
|
||||
hasAccounts={(accounts ?? []).length > 0}
|
||||
accounts={accounts}
|
||||
onSelectAccount={handleAccountChange}
|
||||
|
|
|
|||
287
web/src/components/account-creator.tsx
Normal file
287
web/src/components/account-creator.tsx
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
import { useTelegramMethod } from "@/hooks/use-telegram-method";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { request } from "@/lib/api";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useSWRConfig } from "swr";
|
||||
import { type FormEvent, useCallback, useEffect, useState } from "react";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { Ellipsis, LoaderCircle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
TelegramConstructor,
|
||||
type TelegramObject,
|
||||
WebSocketMessageType,
|
||||
} from "@/lib/websocket-types";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { useWebsocket } from "@/hooks/use-websocket";
|
||||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||
|
||||
interface AccountCreatorProps {
|
||||
isAdd?: boolean;
|
||||
proxyName: string | undefined;
|
||||
onCreated?: (id: string) => void;
|
||||
onLoginSuccess?: () => void;
|
||||
}
|
||||
|
||||
export default function AccountCreator({
|
||||
isAdd,
|
||||
proxyName,
|
||||
onCreated,
|
||||
onLoginSuccess,
|
||||
}: AccountCreatorProps) {
|
||||
const { triggerMethod, isMethodExecuting } = useTelegramMethod();
|
||||
const { toast } = useToast();
|
||||
const { mutate } = useSWRConfig();
|
||||
const { lastJsonMessage } = useWebsocket();
|
||||
const { account, resetAccount } = useTelegramAccount();
|
||||
const [initSuccessfully, setInitSuccessfully] = useState(false);
|
||||
const [authState, setAuthState] = useState<number | undefined>(undefined);
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const {
|
||||
trigger: triggerCreate,
|
||||
isMutating: isCreateMutating,
|
||||
error: createError,
|
||||
} = useSWRMutation<{ id: string }, Error>(
|
||||
"/telegram/create",
|
||||
async (key: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return await request(key, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
proxyName: proxyName,
|
||||
}),
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
onCreated?.(data.id);
|
||||
},
|
||||
},
|
||||
);
|
||||
const [debounceIsCreateMutating] = useDebounce(isCreateMutating, 1000, {
|
||||
leading: true,
|
||||
});
|
||||
|
||||
const handleAuthState = useCallback(
|
||||
(state: TelegramObject) => {
|
||||
switch (state.constructor) {
|
||||
case TelegramConstructor.WAIT_PHONE_NUMBER:
|
||||
case TelegramConstructor.WAIT_CODE:
|
||||
case TelegramConstructor.WAIT_PASSWORD:
|
||||
setAuthState(state.constructor);
|
||||
break;
|
||||
case TelegramConstructor.STATE_READY:
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Account added successfully",
|
||||
});
|
||||
setTimeout(() => {
|
||||
void mutate("/telegrams");
|
||||
onLoginSuccess?.();
|
||||
setPhoneNumber("");
|
||||
setCode("");
|
||||
setPassword("");
|
||||
}, 1000);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown telegram constructor:", state.constructor);
|
||||
}
|
||||
},
|
||||
[mutate, onLoginSuccess, toast],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
if (
|
||||
!isAdd &&
|
||||
account.status === "inactive" &&
|
||||
account.lastAuthorizationState
|
||||
) {
|
||||
setInitSuccessfully(true);
|
||||
handleAuthState(account.lastAuthorizationState);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdd && !initSuccessfully) {
|
||||
resetAccount();
|
||||
}
|
||||
}, [account, handleAuthState, initSuccessfully, isAdd, resetAccount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastJsonMessage) return;
|
||||
|
||||
if (lastJsonMessage.type === WebSocketMessageType.AUTHORIZATION) {
|
||||
handleAuthState(lastJsonMessage.data as TelegramObject);
|
||||
}
|
||||
}, [handleAuthState, lastJsonMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (phoneNumber) {
|
||||
setPhoneNumber((prev) => prev.replaceAll(/\D/g, ""));
|
||||
}
|
||||
}, [phoneNumber]);
|
||||
|
||||
if (debounceIsCreateMutating) {
|
||||
return (
|
||||
<div className="flex items-center justify-center space-x-2 text-xl">
|
||||
<span>Initializing account, please wait</span>
|
||||
<Ellipsis className="h-4 w-4 animate-pulse" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (createError) {
|
||||
return (
|
||||
<div className="text-center text-xl">
|
||||
<span className="mr-3 text-3xl">😲</span>
|
||||
Initializing account failed, please try again later.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!initSuccessfully) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center space-y-4">
|
||||
<Button
|
||||
className={cn("w-full", debounceIsCreateMutating ? "opacity-50" : "")}
|
||||
disabled={debounceIsCreateMutating}
|
||||
onClick={async () => {
|
||||
await triggerCreate().then(() => {
|
||||
void mutate("/telegrams");
|
||||
setInitSuccessfully(true);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Start Initialization
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!authState && !isMethodExecuting) {
|
||||
return (
|
||||
<div className="flex items-center justify-center space-x-2 text-xl">
|
||||
<p>Waiting for the telegram account to be initialized, please wait</p>
|
||||
<p>If it takes too long, please refresh the page or try again later.</p>
|
||||
<Ellipsis className="h-4 w-4 animate-pulse" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const authStateFormFields = {
|
||||
[TelegramConstructor.WAIT_PHONE_NUMBER]: (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">Phone Number</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
Should with country code like: 8613712345678
|
||||
</p>
|
||||
<Input
|
||||
id="phone"
|
||||
value={phoneNumber}
|
||||
onChange={(e) => setPhoneNumber(e.target.value)}
|
||||
disabled={isMethodExecuting}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[TelegramConstructor.WAIT_CODE]: (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="code">Authentication Code</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
Please enter the code sent to your telegram account.
|
||||
</p>
|
||||
<InputOTP
|
||||
id="code"
|
||||
maxLength={5}
|
||||
value={code}
|
||||
disabled={isMethodExecuting}
|
||||
required
|
||||
onChange={(value) => setCode(value)}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
),
|
||||
[TelegramConstructor.WAIT_PASSWORD]: (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
You have enabled two-step verification, please enter your password.
|
||||
</p>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={isMethodExecuting}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (authState === TelegramConstructor.WAIT_PHONE_NUMBER) {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
phoneNumber: phoneNumber,
|
||||
settings: null,
|
||||
},
|
||||
method: "SetAuthenticationPhoneNumber",
|
||||
});
|
||||
} else if (authState === TelegramConstructor.WAIT_CODE) {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
code: code,
|
||||
},
|
||||
method: "CheckAuthenticationCode",
|
||||
});
|
||||
} else if (authState === TelegramConstructor.WAIT_PASSWORD) {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
password: password,
|
||||
},
|
||||
method: "CheckAuthenticationPassword",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{authState && (
|
||||
<>
|
||||
{authStateFormFields[authState]}
|
||||
<Button
|
||||
type="submit"
|
||||
className={cn("w-full", isMethodExecuting ? "opacity-50" : "")}
|
||||
disabled={isMethodExecuting}
|
||||
>
|
||||
{isMethodExecuting ? (
|
||||
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
"🚀 Submit"
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -8,180 +8,33 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Ellipsis, LoaderCircle } from "lucide-react";
|
||||
import { useWebsocket } from "@/hooks/use-websocket";
|
||||
import {
|
||||
TelegramConstructor,
|
||||
type TelegramObject,
|
||||
WebSocketMessageType,
|
||||
} from "@/lib/websocket-types";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { request, telegramApi, type TelegramApiArg } from "@/lib/api";
|
||||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type TelegramApiResult } from "@/lib/types";
|
||||
import { useSWRConfig } from "swr";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { InputOTP, InputOTPGroup, InputOTPSlot } from "./ui/input-otp";
|
||||
import ProxysDialog from "@/components/proxys-dialog";
|
||||
import AccountCreator from "@/components/account-creator";
|
||||
|
||||
export function AccountDialog({
|
||||
children,
|
||||
isAdd,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
isAdd?: boolean;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const { mutate } = useSWRConfig();
|
||||
const [proxyName, setProxyName] = useState<string | undefined>();
|
||||
const {
|
||||
data: createData,
|
||||
trigger: triggerCreate,
|
||||
isMutating: isCreateMutating,
|
||||
error: createError,
|
||||
} = useSWRMutation<{ id: string }, Error>(
|
||||
"/telegram/create",
|
||||
async (key: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return await request(key, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
proxyName: proxyName,
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
const { trigger: triggerMethod, isMutating: isMethodMutating } =
|
||||
useSWRMutation<TelegramApiResult, Error, string, TelegramApiArg>(
|
||||
"/telegram/api",
|
||||
telegramApi,
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setMethodCodes((prev) => [...prev, data.code]);
|
||||
},
|
||||
},
|
||||
);
|
||||
const { account, resetAccount } = useTelegramAccount();
|
||||
const [initSuccessfully, setInitSuccessfully] = useState(false);
|
||||
const { account } = useTelegramAccount();
|
||||
const [newAccountId, setNewAccountId] = useState<string | undefined>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [authState, setAuthState] = useState<number | undefined>(undefined);
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const { lastJsonMessage } = useWebsocket();
|
||||
|
||||
const [methodCodes, setMethodCodes] = useState<string[]>([]);
|
||||
const [methodCompleteCodes, setMethodCompleteCodes] = useState<string[]>([]);
|
||||
|
||||
const [debounceIsCreateMutating] = useDebounce(isCreateMutating, 1000, {
|
||||
leading: true,
|
||||
});
|
||||
|
||||
const handleAuthState = useCallback(
|
||||
(state: TelegramObject) => {
|
||||
switch (state.constructor) {
|
||||
case TelegramConstructor.WAIT_PHONE_NUMBER:
|
||||
case TelegramConstructor.WAIT_CODE:
|
||||
case TelegramConstructor.WAIT_PASSWORD:
|
||||
setAuthState(state.constructor);
|
||||
break;
|
||||
case TelegramConstructor.STATE_READY:
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Account added successfully",
|
||||
});
|
||||
setTimeout(() => {
|
||||
void mutate("/telegrams");
|
||||
setOpen(false);
|
||||
setPhoneNumber("");
|
||||
setCode("");
|
||||
setPassword("");
|
||||
}, 1000);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown telegram constructor:", state.constructor);
|
||||
}
|
||||
},
|
||||
[mutate, toast],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
if (
|
||||
!isAdd &&
|
||||
account.status === "inactive" &&
|
||||
account.lastAuthorizationState
|
||||
) {
|
||||
setInitSuccessfully(true);
|
||||
handleAuthState(account.lastAuthorizationState);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (open && isAdd && !initSuccessfully) {
|
||||
resetAccount();
|
||||
}
|
||||
}, [account, handleAuthState, initSuccessfully, isAdd, open, resetAccount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (phoneNumber) {
|
||||
setPhoneNumber((prev) => prev.replaceAll(/\D/g, ""));
|
||||
}
|
||||
}, [phoneNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastJsonMessage) return;
|
||||
if (lastJsonMessage.code) {
|
||||
setMethodCompleteCodes((prev) => [...prev, lastJsonMessage.code]);
|
||||
}
|
||||
|
||||
if (lastJsonMessage.type === WebSocketMessageType.AUTHORIZATION) {
|
||||
handleAuthState(lastJsonMessage.data as TelegramObject);
|
||||
}
|
||||
}, [handleAuthState, lastJsonMessage, toast]);
|
||||
|
||||
const isMethodExecuting = useMemo(() => {
|
||||
if (isMethodMutating) return true;
|
||||
const lastCode = methodCodes[methodCodes.length - 1];
|
||||
if (!lastCode) return false;
|
||||
return !methodCompleteCodes.includes(lastCode);
|
||||
}, [isMethodMutating, methodCodes, methodCompleteCodes]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (authState === TelegramConstructor.WAIT_PHONE_NUMBER) {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
phoneNumber: phoneNumber,
|
||||
settings: null,
|
||||
},
|
||||
method: "SetAuthenticationPhoneNumber",
|
||||
});
|
||||
} else if (authState === TelegramConstructor.WAIT_CODE) {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
code: code,
|
||||
},
|
||||
method: "CheckAuthenticationCode",
|
||||
});
|
||||
} else if (authState === TelegramConstructor.WAIT_PASSWORD) {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
password: password,
|
||||
},
|
||||
method: "CheckAuthenticationPassword",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
if (isAdd) {
|
||||
setNewAccountId(undefined);
|
||||
setProxyName(undefined);
|
||||
}
|
||||
setOpen(open);
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent
|
||||
aria-describedby={undefined}
|
||||
|
|
@ -190,125 +43,22 @@ export function AccountDialog({
|
|||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Add Telegram Account
|
||||
<p className="rounded-md bg-gray-100 p-1 text-xs text-muted-foreground">
|
||||
{account ? account.id : createData?.id}
|
||||
</p>
|
||||
{(account ?? newAccountId) && (
|
||||
<p className="rounded-md bg-gray-100 p-1 text-xs text-muted-foreground">
|
||||
{account ? account.id : newAccountId}
|
||||
</p>
|
||||
)}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="relative h-full w-full">
|
||||
{!debounceIsCreateMutating && !initSuccessfully && (
|
||||
<div className="flex flex-col items-center justify-center space-y-4">
|
||||
<Button
|
||||
className={cn(
|
||||
"w-full",
|
||||
debounceIsCreateMutating ? "opacity-50" : "",
|
||||
)}
|
||||
disabled={debounceIsCreateMutating}
|
||||
onClick={async () => {
|
||||
await triggerCreate().then(() => {
|
||||
void mutate("/telegrams");
|
||||
setInitSuccessfully(true);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{debounceIsCreateMutating ? (
|
||||
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
"Start Initialization"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{debounceIsCreateMutating && (
|
||||
<div className="flex items-center justify-center space-x-2 text-xl">
|
||||
<span>Initializing account, please wait</span>
|
||||
<Ellipsis className="h-4 w-4 animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
{!debounceIsCreateMutating && createError && (
|
||||
<div className="text-center text-xl">
|
||||
<span className="mr-3 text-3xl">😲</span>
|
||||
Initializing account failed, please try again later.
|
||||
</div>
|
||||
)}
|
||||
{!debounceIsCreateMutating && initSuccessfully && (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{authState === TelegramConstructor.WAIT_PHONE_NUMBER && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">Phone Number</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
Should with country code like: 8613712345678
|
||||
</p>
|
||||
<Input
|
||||
id="phone"
|
||||
value={phoneNumber}
|
||||
onChange={(e) => setPhoneNumber(e.target.value)}
|
||||
disabled={isMethodExecuting}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{authState === TelegramConstructor.WAIT_CODE && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="code">Authentication Code</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
Please enter the code sent to your telegram account.
|
||||
</p>
|
||||
<InputOTP
|
||||
id="code"
|
||||
maxLength={5}
|
||||
value={code}
|
||||
disabled={isMethodExecuting}
|
||||
required
|
||||
onChange={(value) => setCode(value)}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
)}
|
||||
{authState === TelegramConstructor.WAIT_PASSWORD && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
You have enabled two-step verification, please enter your
|
||||
password.
|
||||
</p>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={isMethodExecuting}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{authState && (
|
||||
<Button
|
||||
type="submit"
|
||||
className={cn(
|
||||
"w-full",
|
||||
isMethodExecuting ? "opacity-50" : "",
|
||||
)}
|
||||
disabled={isMethodExecuting}
|
||||
>
|
||||
{isMethodExecuting ? (
|
||||
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
"🚀 Submit"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
<AccountCreator
|
||||
isAdd={isAdd}
|
||||
proxyName={proxyName}
|
||||
onCreated={setNewAccountId}
|
||||
onLoginSuccess={() => setOpen(false)}
|
||||
/>
|
||||
<ProxysDialog
|
||||
telegramId={createData?.id}
|
||||
telegramId={newAccountId}
|
||||
proxyName={proxyName}
|
||||
onProxyNameChange={setProxyName}
|
||||
enableSelect={true}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MessageSquare, UserPlus } from "lucide-react";
|
||||
import { LoaderPinwheel, MessageSquare, UserPlus } from "lucide-react";
|
||||
import { AccountList } from "./account-list";
|
||||
import { type TelegramAccount } from "@/lib/types";
|
||||
import TelegramIcon from "@/components/telegram-icon";
|
||||
|
|
@ -9,6 +9,7 @@ import { BorderBeam } from "@/components/ui/border-beam";
|
|||
import ProxysDialog from "@/components/proxys-dialog";
|
||||
|
||||
interface EmptyStateProps {
|
||||
isLoadingAccount?: boolean;
|
||||
hasAccounts: boolean;
|
||||
accounts?: TelegramAccount[];
|
||||
message?: string;
|
||||
|
|
@ -16,6 +17,7 @@ interface EmptyStateProps {
|
|||
}
|
||||
|
||||
export function EmptyState({
|
||||
isLoadingAccount,
|
||||
hasAccounts,
|
||||
accounts = [],
|
||||
message,
|
||||
|
|
@ -71,6 +73,15 @@ export function EmptyState({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{isLoadingAccount && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<LoaderPinwheel
|
||||
className="h-8 w-8 animate-spin"
|
||||
style={{ strokeWidth: "0.8px" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasAccounts && accounts.length > 0 && onSelectAccount && (
|
||||
<AccountList accounts={accounts} onSelectAccount={onSelectAccount} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,21 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Edit2, Plus, Trash2 } from "lucide-react";
|
||||
import { Copy, Edit2, Plus, Trash2 } from "lucide-react";
|
||||
import { type Proxy } from "@/lib/types";
|
||||
import { BorderBeam } from "@/components/ui/border-beam";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn, parseProxyString } from "@/lib/utils";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { request } from "@/lib/api";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import ProxyPing from "@/components/proxy-ping";
|
||||
import { Label } from "./ui/label";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export interface ProxysProps {
|
||||
enableSelect?: boolean;
|
||||
|
|
@ -160,6 +166,11 @@ export default function Proxys({
|
|||
<Plus className="mr-2 h-5 w-5" /> Add Proxy
|
||||
</Button>
|
||||
</div>
|
||||
{proxys.length === 0 && (
|
||||
<div className="flex h-32 items-center justify-center">
|
||||
<p className="text-center text-gray-500">No proxys added yet</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-3 2xl:grid-cols-4">
|
||||
{proxys.map((proxy) => (
|
||||
<Card
|
||||
|
|
@ -226,7 +237,10 @@ export default function Proxys({
|
|||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{editingProxy ? "Edit Proxy" : "Add Proxy"}
|
||||
<span className="mr-2">
|
||||
{editingProxy ? "Edit Proxy" : "Add Proxy"}
|
||||
</span>
|
||||
<ProxyParser onParsed={(proxy) => setFormState(proxy)} />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
|
|
@ -347,3 +361,51 @@ export default function Proxys({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProxyParser({ onParsed }: { onParsed: (proxys: Proxy) => void }) {
|
||||
const handleParseProxy = async () => {
|
||||
const clipboardText = await navigator.clipboard.readText();
|
||||
if (!clipboardText || clipboardText.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
const proxy = parseProxyString(clipboardText);
|
||||
if (proxy) {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Proxy string is parsed successfully",
|
||||
});
|
||||
onParsed(proxy);
|
||||
} else {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Invalid proxy string format",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="xs" variant="ghost" onClick={() => handleParseProxy()}>
|
||||
<Copy className="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex flex-col space-y-2 p-2">
|
||||
<p className="text-sm font-semibold">
|
||||
Parse the proxy string from the clipboard and add them to the list
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
The proxy should be in the following format:
|
||||
<br />
|
||||
<code>http://username:password@server:port</code>
|
||||
<br />
|
||||
<code>socks://username:password@server:port</code>
|
||||
</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
41
web/src/hooks/use-telegram-method.tsx
Normal file
41
web/src/hooks/use-telegram-method.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import useSWRMutation from "swr/mutation";
|
||||
import type { TelegramApiResult } from "@/lib/types";
|
||||
import { telegramApi, type TelegramApiArg } from "@/lib/api";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useWebsocket } from "@/hooks/use-websocket";
|
||||
|
||||
export function useTelegramMethod() {
|
||||
const [methodCodes, setMethodCodes] = useState<string[]>([]);
|
||||
const [methodCompleteCodes, setMethodCompleteCodes] = useState<string[]>([]);
|
||||
const { lastJsonMessage } = useWebsocket();
|
||||
|
||||
const { trigger: triggerMethod, isMutating: isMethodMutating } =
|
||||
useSWRMutation<TelegramApiResult, Error, string, TelegramApiArg>(
|
||||
"/telegram/api",
|
||||
telegramApi,
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setMethodCodes((prev) => [...prev, data.code]);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastJsonMessage) return;
|
||||
if (lastJsonMessage.code) {
|
||||
setMethodCompleteCodes((prev) => [...prev, lastJsonMessage.code]);
|
||||
}
|
||||
}, [lastJsonMessage]);
|
||||
|
||||
const isMethodExecuting = useMemo(() => {
|
||||
if (isMethodMutating) return true;
|
||||
const lastCode = methodCodes[methodCodes.length - 1];
|
||||
if (!lastCode) return false;
|
||||
return !methodCompleteCodes.includes(lastCode);
|
||||
}, [isMethodMutating, methodCodes, methodCompleteCodes]);
|
||||
|
||||
return {
|
||||
triggerMethod,
|
||||
isMethodExecuting,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,36 @@
|
|||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { type Proxy } from "@/lib/types";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function parseProxyString(proxyString: string): Proxy | null {
|
||||
const proxyRegex = /^(http|socks|socks5):\/\/(([^:]+):([^@]+)@)?([^:]+):(\d+)$/i;
|
||||
const match = proxyRegex.exec(proxyString);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let type = match[1];
|
||||
if (type === "http") {
|
||||
type = "http";
|
||||
} else {
|
||||
type = "socks5";
|
||||
}
|
||||
const username = match[3] ?? "";
|
||||
const password = match[4] ?? "";
|
||||
const server = match[5] ?? "";
|
||||
const port = parseInt(match[6] ?? "0", 10);
|
||||
|
||||
return {
|
||||
name: `${type} proxy`,
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
type: type as "http" | "socks5",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue