feat: Support the use of qrcode scanning to log in Telegram.

This commit is contained in:
jarvis2f 2025-01-24 14:04:30 +08:00
parent fb511b4d1a
commit 06b18a7e06
9 changed files with 174 additions and 14 deletions

View file

@ -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) {

View file

@ -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
View file

@ -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",

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View file

@ -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>
);
}

File diff suppressed because one or more lines are too long

View file

@ -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",

View file

@ -28,4 +28,5 @@ export const TelegramConstructor = {
WAIT_PHONE_NUMBER: 306402531,
WAIT_CODE: 52643073,
WAIT_PASSWORD: 112238030,
WAIT_OTHER_DEVICE_CONFIRMATION: 860166378,
};