♻️ 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))
|
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));
|
: 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 -> {
|
.compose(r -> {
|
||||||
this.proxyName = proxyName;
|
this.proxyName = proxyName;
|
||||||
return Future.succeededFuture(r);
|
return Future.succeededFuture(r);
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import { EmptyState } from "@/components/empty-state";
|
||||||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { getAccounts, handleAccountChange } = useTelegramAccount();
|
const { getAccounts, handleAccountChange, isLoading } = useTelegramAccount();
|
||||||
const accounts = getAccounts();
|
const accounts = getAccounts();
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
isLoadingAccount={isLoading}
|
||||||
hasAccounts={(accounts ?? []).length > 0}
|
hasAccounts={(accounts ?? []).length > 0}
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
onSelectAccount={handleAccountChange}
|
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";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { type ReactNode, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
|
@ -8,180 +8,33 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} 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 { 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 ProxysDialog from "@/components/proxys-dialog";
|
||||||
|
import AccountCreator from "@/components/account-creator";
|
||||||
|
|
||||||
export function AccountDialog({
|
export function AccountDialog({
|
||||||
children,
|
children,
|
||||||
isAdd,
|
isAdd,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
isAdd?: boolean;
|
isAdd?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { toast } = useToast();
|
|
||||||
const { mutate } = useSWRConfig();
|
|
||||||
const [proxyName, setProxyName] = useState<string | undefined>();
|
const [proxyName, setProxyName] = useState<string | undefined>();
|
||||||
const {
|
const { account } = useTelegramAccount();
|
||||||
data: createData,
|
const [newAccountId, setNewAccountId] = useState<string | undefined>();
|
||||||
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 [open, setOpen] = useState(false);
|
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 (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (isAdd) {
|
||||||
|
setNewAccountId(undefined);
|
||||||
|
setProxyName(undefined);
|
||||||
|
}
|
||||||
|
setOpen(open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
aria-describedby={undefined}
|
aria-describedby={undefined}
|
||||||
|
|
@ -190,125 +43,22 @@ export function AccountDialog({
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
Add Telegram Account
|
Add Telegram Account
|
||||||
<p className="rounded-md bg-gray-100 p-1 text-xs text-muted-foreground">
|
{(account ?? newAccountId) && (
|
||||||
{account ? account.id : createData?.id}
|
<p className="rounded-md bg-gray-100 p-1 text-xs text-muted-foreground">
|
||||||
</p>
|
{account ? account.id : newAccountId}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="relative h-full w-full">
|
<div className="relative h-full w-full">
|
||||||
{!debounceIsCreateMutating && !initSuccessfully && (
|
<AccountCreator
|
||||||
<div className="flex flex-col items-center justify-center space-y-4">
|
isAdd={isAdd}
|
||||||
<Button
|
proxyName={proxyName}
|
||||||
className={cn(
|
onCreated={setNewAccountId}
|
||||||
"w-full",
|
onLoginSuccess={() => setOpen(false)}
|
||||||
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>
|
|
||||||
)}
|
|
||||||
<ProxysDialog
|
<ProxysDialog
|
||||||
telegramId={createData?.id}
|
telegramId={newAccountId}
|
||||||
proxyName={proxyName}
|
proxyName={proxyName}
|
||||||
onProxyNameChange={setProxyName}
|
onProxyNameChange={setProxyName}
|
||||||
enableSelect={true}
|
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 { AccountList } from "./account-list";
|
||||||
import { type TelegramAccount } from "@/lib/types";
|
import { type TelegramAccount } from "@/lib/types";
|
||||||
import TelegramIcon from "@/components/telegram-icon";
|
import TelegramIcon from "@/components/telegram-icon";
|
||||||
|
|
@ -9,6 +9,7 @@ import { BorderBeam } from "@/components/ui/border-beam";
|
||||||
import ProxysDialog from "@/components/proxys-dialog";
|
import ProxysDialog from "@/components/proxys-dialog";
|
||||||
|
|
||||||
interface EmptyStateProps {
|
interface EmptyStateProps {
|
||||||
|
isLoadingAccount?: boolean;
|
||||||
hasAccounts: boolean;
|
hasAccounts: boolean;
|
||||||
accounts?: TelegramAccount[];
|
accounts?: TelegramAccount[];
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
@ -16,6 +17,7 @@ interface EmptyStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EmptyState({
|
export function EmptyState({
|
||||||
|
isLoadingAccount,
|
||||||
hasAccounts,
|
hasAccounts,
|
||||||
accounts = [],
|
accounts = [],
|
||||||
message,
|
message,
|
||||||
|
|
@ -71,6 +73,15 @@ export function EmptyState({
|
||||||
</div>
|
</div>
|
||||||
</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 && (
|
{hasAccounts && accounts.length > 0 && onSelectAccount && (
|
||||||
<AccountList accounts={accounts} onSelectAccount={onSelectAccount} />
|
<AccountList accounts={accounts} onSelectAccount={onSelectAccount} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,21 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} 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 { type Proxy } from "@/lib/types";
|
||||||
import { BorderBeam } from "@/components/ui/border-beam";
|
import { BorderBeam } from "@/components/ui/border-beam";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn, parseProxyString } from "@/lib/utils";
|
||||||
import useSWRMutation from "swr/mutation";
|
import useSWRMutation from "swr/mutation";
|
||||||
import { request } from "@/lib/api";
|
import { request } from "@/lib/api";
|
||||||
import { toast } from "@/hooks/use-toast";
|
import { toast } from "@/hooks/use-toast";
|
||||||
import ProxyPing from "@/components/proxy-ping";
|
import ProxyPing from "@/components/proxy-ping";
|
||||||
import { Label } from "./ui/label";
|
import { Label } from "./ui/label";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
|
||||||
export interface ProxysProps {
|
export interface ProxysProps {
|
||||||
enableSelect?: boolean;
|
enableSelect?: boolean;
|
||||||
|
|
@ -160,6 +166,11 @@ export default function Proxys({
|
||||||
<Plus className="mr-2 h-5 w-5" /> Add Proxy
|
<Plus className="mr-2 h-5 w-5" /> Add Proxy
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-3 2xl:grid-cols-4">
|
||||||
{proxys.map((proxy) => (
|
{proxys.map((proxy) => (
|
||||||
<Card
|
<Card
|
||||||
|
|
@ -226,7 +237,10 @@ export default function Proxys({
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{editingProxy ? "Edit Proxy" : "Add Proxy"}
|
<span className="mr-2">
|
||||||
|
{editingProxy ? "Edit Proxy" : "Add Proxy"}
|
||||||
|
</span>
|
||||||
|
<ProxyParser onParsed={(proxy) => setFormState(proxy)} />
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
@ -347,3 +361,51 @@ export default function Proxys({
|
||||||
</div>
|
</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 { type ClassValue, clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import { type Proxy } from "@/lib/types";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
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