✨ feat: Support the use of qrcode scanning to log in Telegram.
This commit is contained in:
parent
fb511b4d1a
commit
06b18a7e06
9 changed files with 174 additions and 14 deletions
|
|
@ -68,6 +68,7 @@ public abstract class Transfer {
|
|||
}
|
||||
|
||||
FileUtil.move(Path.of(fileRecord.localPath()), Path.of(transferPath), isOverwrite);
|
||||
log.info("Transfer file {} to {}, duplication policy: {} overwrite: {}", fileRecord.id(), transferPath, duplicationPolicy, isOverwrite);
|
||||
|
||||
transferStatusUpdated.accept(new TransferStatusUpdated(fileRecord, FileRecord.TransferStatus.completed, transferPath));
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -172,3 +172,12 @@ Cookie: {{tf}}
|
|||
"@type": 1352130963
|
||||
}
|
||||
}
|
||||
|
||||
### Telegram RequestQrCodeAuthentication
|
||||
POST http://{{server}}/telegram/api/RequestQrCodeAuthentication
|
||||
Content-Type: application/json
|
||||
Cookie: {{tf}}
|
||||
|
||||
{
|
||||
"otherUserIds": null
|
||||
}
|
||||
|
|
|
|||
32
web/package-lock.json
generated
32
web/package-lock.json
generated
|
|
@ -43,6 +43,7 @@
|
|||
"next": "^15.0.1",
|
||||
"next-themes": "^0.4.4",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"qr-code-styling": "^1.9.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.2",
|
||||
|
|
@ -6923,6 +6924,24 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qr-code-styling": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.9.1.tgz",
|
||||
"integrity": "sha512-T/VxQchuZkQwYhIcyyMUmtXHPeDT6lJBYHfqGD5CBDyIjswxS7JZKf443q+SXO1K/9SUswi6JpXEUQ5AoMCpyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"qrcode-generator": "^1.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode-generator": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz",
|
||||
"integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
@ -12608,6 +12627,19 @@
|
|||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"qr-code-styling": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.9.1.tgz",
|
||||
"integrity": "sha512-T/VxQchuZkQwYhIcyyMUmtXHPeDT6lJBYHfqGD5CBDyIjswxS7JZKf443q+SXO1K/9SUswi6JpXEUQ5AoMCpyg==",
|
||||
"requires": {
|
||||
"qrcode-generator": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"qrcode-generator": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz",
|
||||
"integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw=="
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
"next": "^15.0.1",
|
||||
"next-themes": "^0.4.4",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"qr-code-styling": "^1.9.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.2",
|
||||
|
|
|
|||
BIN
web/public/blank.png
Normal file
BIN
web/public/blank.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
|
|
@ -3,7 +3,13 @@ import useSWRMutation from "swr/mutation";
|
|||
import { request } from "@/lib/api";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useSWRConfig } from "swr";
|
||||
import React, { type FormEvent, useCallback, useEffect, useState } from "react";
|
||||
import React, {
|
||||
type FormEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { Ellipsis, LoaderCircle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -23,7 +29,9 @@ import {
|
|||
import { useWebsocket } from "@/hooks/use-websocket";
|
||||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||
import TGDuck16Plane from "@/components/animations/tg-duck16_plane.json";
|
||||
import TGQRPlane from "@/components/animations/tg-qr-plane.json";
|
||||
import dynamic from "next/dynamic";
|
||||
import QRCodeStyling, { Options } from "qr-code-styling";
|
||||
|
||||
interface AccountCreatorProps {
|
||||
isAdd?: boolean;
|
||||
|
|
@ -47,6 +55,7 @@ export default function AccountCreator({
|
|||
const { account, resetAccount } = useTelegramAccount();
|
||||
const [initSuccessfully, setInitSuccessfully] = useState(false);
|
||||
const [authState, setAuthState] = useState<number | undefined>(undefined);
|
||||
const [qrCodeLink, setQrCodeLink] = useState<string | undefined>(undefined);
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
|
@ -84,6 +93,10 @@ export default function AccountCreator({
|
|||
case TelegramConstructor.WAIT_PASSWORD:
|
||||
setAuthState(state.constructor);
|
||||
break;
|
||||
case TelegramConstructor.WAIT_OTHER_DEVICE_CONFIRMATION:
|
||||
setAuthState(state.constructor);
|
||||
setQrCodeLink(state.link as string);
|
||||
break;
|
||||
case TelegramConstructor.STATE_READY:
|
||||
toast({
|
||||
title: "Success",
|
||||
|
|
@ -204,6 +217,9 @@ export default function AccountCreator({
|
|||
/>
|
||||
</div>
|
||||
),
|
||||
[TelegramConstructor.WAIT_OTHER_DEVICE_CONFIRMATION]: (
|
||||
<QRCode link={qrCodeLink} />
|
||||
),
|
||||
[TelegramConstructor.WAIT_CODE]: (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="code">Authentication Code</Label>
|
||||
|
|
@ -273,24 +289,123 @@ export default function AccountCreator({
|
|||
}
|
||||
};
|
||||
|
||||
const handleRequestQrCodeAuthentication = async () => {
|
||||
await triggerMethod({
|
||||
data: {
|
||||
otherUserIds: null,
|
||||
},
|
||||
method: "RequestQrCodeAuthentication",
|
||||
});
|
||||
};
|
||||
|
||||
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>
|
||||
{authState !== TelegramConstructor.WAIT_OTHER_DEVICE_CONFIRMATION && (
|
||||
<Button
|
||||
type="submit"
|
||||
className={cn("w-full", isMethodExecuting ? "opacity-50" : "")}
|
||||
disabled={isMethodExecuting}
|
||||
>
|
||||
{isMethodExecuting ? (
|
||||
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
"🚀 Submit"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{authState === TelegramConstructor.WAIT_PHONE_NUMBER && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn("w-full", isMethodExecuting ? "opacity-50" : "")}
|
||||
disabled={isMethodExecuting}
|
||||
onClick={handleRequestQrCodeAuthentication}
|
||||
>
|
||||
{isMethodExecuting ? (
|
||||
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
"LOG IN BY QR CODE"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const options: Options = {
|
||||
width: 280,
|
||||
height: 280,
|
||||
type: "svg",
|
||||
image: "blank.png",
|
||||
margin: 10,
|
||||
qrOptions: {
|
||||
errorCorrectionLevel: "M",
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: "extra-rounded",
|
||||
},
|
||||
imageOptions: {
|
||||
imageSize: 0.4,
|
||||
margin: 8,
|
||||
},
|
||||
dotsOptions: {
|
||||
type: "rounded",
|
||||
},
|
||||
};
|
||||
|
||||
function QRCode({ link }: { link?: string }) {
|
||||
const [qrCode, setQrCode] = useState<QRCodeStyling>();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
qrCode?.append(ref.current);
|
||||
}
|
||||
}, [qrCode, ref]);
|
||||
|
||||
useEffect(() => {
|
||||
if (link) {
|
||||
if (!qrCode) {
|
||||
const qrCode = new QRCodeStyling({
|
||||
...options,
|
||||
data: link,
|
||||
});
|
||||
setQrCode(qrCode);
|
||||
} else {
|
||||
qrCode.update({
|
||||
data: link,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [link, qrCode]);
|
||||
|
||||
if (!link) {
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<LoaderCircle className="h-14 w-14 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
<div className="relative flex items-center justify-center">
|
||||
<div className="overflow-hidden rounded-3xl bg-white" ref={ref} />
|
||||
<Lottie
|
||||
className="absolute left-1/2 top-1/2 z-10 h-14 w-14 -translate-x-1/2 -translate-y-1/2 transform rounded-full bg-gray-800"
|
||||
animationData={TGQRPlane}
|
||||
loop={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-lg bg-white bg-opacity-80 p-1 dark:bg-gray-800">
|
||||
<p className="text-center text-xs text-gray-500 dark:text-gray-400">
|
||||
Scan the QR code with your telegram app to log in.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
1
web/src/components/animations/tg-qr-plane.json
Normal file
1
web/src/components/animations/tg-qr-plane.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -546,11 +546,11 @@ const PolicyLegends: Record<
|
|||
},
|
||||
OVERWRITE: {
|
||||
title: "Overwrite",
|
||||
description: "This will overwrite the existing file if it already exists.",
|
||||
description: "If destination exists same name file, move and overwrite the file.",
|
||||
},
|
||||
SKIP: {
|
||||
title: "Skip",
|
||||
description: "This will skip the file if it already exists.",
|
||||
description: "If destination exists same name file, skip the file, nothing to do.",
|
||||
},
|
||||
RENAME: {
|
||||
title: "Rename",
|
||||
|
|
|
|||
|
|
@ -28,4 +28,5 @@ export const TelegramConstructor = {
|
|||
WAIT_PHONE_NUMBER: 306402531,
|
||||
WAIT_CODE: 52643073,
|
||||
WAIT_PASSWORD: 112238030,
|
||||
WAIT_OTHER_DEVICE_CONFIRMATION: 860166378,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue