✨ 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);
|
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));
|
transferStatusUpdated.accept(new TransferStatusUpdated(fileRecord, FileRecord.TransferStatus.completed, transferPath));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -172,3 +172,12 @@ Cookie: {{tf}}
|
||||||
"@type": 1352130963
|
"@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": "^15.0.1",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
|
"qr-code-styling": "^1.9.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.2",
|
"react-hook-form": "^7.53.2",
|
||||||
|
|
@ -6923,6 +6924,24 @@
|
||||||
"node": ">=6"
|
"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": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
|
@ -12608,6 +12627,19 @@
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
"dev": true
|
"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": {
|
"queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
"next": "^15.0.1",
|
"next": "^15.0.1",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
|
"qr-code-styling": "^1.9.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.2",
|
"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 { request } from "@/lib/api";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useSWRConfig } from "swr";
|
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 { useDebounce } from "use-debounce";
|
||||||
import { Ellipsis, LoaderCircle } from "lucide-react";
|
import { Ellipsis, LoaderCircle } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -23,7 +29,9 @@ import {
|
||||||
import { useWebsocket } from "@/hooks/use-websocket";
|
import { useWebsocket } from "@/hooks/use-websocket";
|
||||||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||||
import TGDuck16Plane from "@/components/animations/tg-duck16_plane.json";
|
import TGDuck16Plane from "@/components/animations/tg-duck16_plane.json";
|
||||||
|
import TGQRPlane from "@/components/animations/tg-qr-plane.json";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import QRCodeStyling, { Options } from "qr-code-styling";
|
||||||
|
|
||||||
interface AccountCreatorProps {
|
interface AccountCreatorProps {
|
||||||
isAdd?: boolean;
|
isAdd?: boolean;
|
||||||
|
|
@ -47,6 +55,7 @@ export default function AccountCreator({
|
||||||
const { account, resetAccount } = useTelegramAccount();
|
const { account, resetAccount } = useTelegramAccount();
|
||||||
const [initSuccessfully, setInitSuccessfully] = useState(false);
|
const [initSuccessfully, setInitSuccessfully] = useState(false);
|
||||||
const [authState, setAuthState] = useState<number | undefined>(undefined);
|
const [authState, setAuthState] = useState<number | undefined>(undefined);
|
||||||
|
const [qrCodeLink, setQrCodeLink] = useState<string | undefined>(undefined);
|
||||||
const [phoneNumber, setPhoneNumber] = useState("");
|
const [phoneNumber, setPhoneNumber] = useState("");
|
||||||
const [code, setCode] = useState("");
|
const [code, setCode] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
@ -84,6 +93,10 @@ export default function AccountCreator({
|
||||||
case TelegramConstructor.WAIT_PASSWORD:
|
case TelegramConstructor.WAIT_PASSWORD:
|
||||||
setAuthState(state.constructor);
|
setAuthState(state.constructor);
|
||||||
break;
|
break;
|
||||||
|
case TelegramConstructor.WAIT_OTHER_DEVICE_CONFIRMATION:
|
||||||
|
setAuthState(state.constructor);
|
||||||
|
setQrCodeLink(state.link as string);
|
||||||
|
break;
|
||||||
case TelegramConstructor.STATE_READY:
|
case TelegramConstructor.STATE_READY:
|
||||||
toast({
|
toast({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
|
|
@ -204,6 +217,9 @@ export default function AccountCreator({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
[TelegramConstructor.WAIT_OTHER_DEVICE_CONFIRMATION]: (
|
||||||
|
<QRCode link={qrCodeLink} />
|
||||||
|
),
|
||||||
[TelegramConstructor.WAIT_CODE]: (
|
[TelegramConstructor.WAIT_CODE]: (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="code">Authentication Code</Label>
|
<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 (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
{authState && (
|
{authState && (
|
||||||
<>
|
<>
|
||||||
{authStateFormFields[authState]}
|
{authStateFormFields[authState]}
|
||||||
<Button
|
{authState !== TelegramConstructor.WAIT_OTHER_DEVICE_CONFIRMATION && (
|
||||||
type="submit"
|
<Button
|
||||||
className={cn("w-full", isMethodExecuting ? "opacity-50" : "")}
|
type="submit"
|
||||||
disabled={isMethodExecuting}
|
className={cn("w-full", isMethodExecuting ? "opacity-50" : "")}
|
||||||
>
|
disabled={isMethodExecuting}
|
||||||
{isMethodExecuting ? (
|
>
|
||||||
<LoaderCircle className="h-4 w-4 animate-spin" />
|
{isMethodExecuting ? (
|
||||||
) : (
|
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||||
"🚀 Submit"
|
) : (
|
||||||
)}
|
"🚀 Submit"
|
||||||
</Button>
|
)}
|
||||||
|
</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>
|
</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: {
|
OVERWRITE: {
|
||||||
title: "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: {
|
SKIP: {
|
||||||
title: "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: {
|
RENAME: {
|
||||||
title: "Rename",
|
title: "Rename",
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,5 @@ export const TelegramConstructor = {
|
||||||
WAIT_PHONE_NUMBER: 306402531,
|
WAIT_PHONE_NUMBER: 306402531,
|
||||||
WAIT_CODE: 52643073,
|
WAIT_CODE: 52643073,
|
||||||
WAIT_PASSWORD: 112238030,
|
WAIT_PASSWORD: 112238030,
|
||||||
|
WAIT_OTHER_DEVICE_CONFIRMATION: 860166378,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue