diff --git a/bun.lock b/bun.lock index 986e7c2..ddf72ee 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@telegram-apps/init-data-node": "^2.0.7", "@telegram-apps/sdk-react": "^3.3.0", "@telegram-apps/telegram-ui": "^2.1.8", + "ioredis": "^5.6.1", "next": "15.3.3", "qrcode": "^1.5.4", "qrcode.react": "^4.2.0", @@ -130,6 +131,8 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw=="], + "@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], @@ -434,6 +437,8 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -470,6 +475,8 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], @@ -636,6 +643,8 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "ioredis": ["ioredis@5.6.1", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA=="], + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], @@ -750,6 +759,10 @@ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], @@ -872,6 +885,10 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], @@ -970,6 +987,8 @@ "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], diff --git a/docker-compose.yml b/docker-compose.yml index 8d23425..3057411 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,12 @@ services: + redis: + image: redis:8-alpine + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis-data:/data + command: ["redis-server", "--appendonly", "yes"] db: image: postgres:17-alpine restart: unless-stopped @@ -15,4 +23,5 @@ services: ports: - "3900:3000" volumes: - db_data: \ No newline at end of file + db_data: + redis-data: \ No newline at end of file diff --git a/package.json b/package.json index f068a96..1158509 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@telegram-apps/init-data-node": "^2.0.7", "@telegram-apps/sdk-react": "^3.3.0", "@telegram-apps/telegram-ui": "^2.1.8", + "ioredis": "^5.6.1", "next": "15.3.3", "qrcode": "^1.5.4", "qrcode.react": "^4.2.0", diff --git a/src/app/actions.ts b/src/app/actions.ts index 56b2682..dbc5f4a 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -1,27 +1,32 @@ "use server"; import { API_LINK, XUIApiLinks } from "@/lib/enum"; import { authFetch } from "@/lib/login"; +import { getValidUrl } from "@/lib/url"; import { prisma } from "@/utils/prisma"; +import { redis } from "@/utils/redis"; import { parse, validate } from "@telegram-apps/init-data-node"; import { ClientSettings, InboundResponse } from "./_dto/inbounds"; -//TODO: Make it valid proxy url -async function getUrlApi(email: string) { +async function getInboundApi() { const res = await authFetch(API_LINK + XUIApiLinks.GET_INBOUNDS); const data: InboundResponse = await res.json(); const inbound = data.obj.find((inbound) => inbound.remark === "WS"); + return inbound; +} + +async function getUrlApi(email: string) { + const cachedInbound = await redis.get("inbound"); + const inbound = cachedInbound ? JSON.parse(cachedInbound) : await getInboundApi(); if (!inbound) { - return Response.json( - { - success: false, - error: "Inbound not found", - }, - { status: 404 } - ); + throw new Error("Inbound not found"); } + await redis.set("inbound", JSON.stringify(inbound), "EX", 3600); const users: ClientSettings = JSON.parse(inbound.settings); const user = users.clients.find((user) => user.email === email); - return user; + if (!user) { + throw new Error("User not found"); + } + return getValidUrl({ email, id: user.id }); } export async function getUrl(initData: string = "") { try { @@ -30,13 +35,23 @@ export async function getUrl(initData: string = "") { expiresIn: 3600, }); const initDataParsed = parse(initData); - const user = await prisma.user.findFirst({ - where: { - tgId: initDataParsed.user ? initDataParsed.user.id.toString() : "0", - }, - }); - return await getUrlApi(user?.email || ""); + if (!initDataParsed.user) { + throw new Error("User not found"); + } + const cachedUser = await redis.get(`user:${initDataParsed.user.id}`); + const user = cachedUser + ? JSON.parse(cachedUser) + : await prisma.user.findFirst({ + where: { + tgId: initDataParsed.user.id.toString(), + }, + }); + if (!user) { + throw new Error("User not found"); + } + await redis.set(`user:${initDataParsed.user.id}`, JSON.stringify(user), "EX", 60); + return await getUrlApi(user.email || ""); } catch (e) { - console.log(e); + throw e; } } diff --git a/src/app/page.tsx b/src/app/page.tsx index 686af0e..36fd4b2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,9 +6,11 @@ import { useEffect, useState } from "react"; import { getUrl } from "./actions"; export default function Home() { + const [url, setUrl] = useState(""); const initData = useRawInitData(); const onCopyClick = async () => { - await navigator.clipboard.writeText(window.location.href); + if (!url.length) return; + await navigator.clipboard.writeText(url); setIsCopied(true); setTimeout(() => { setIsCopied(false); @@ -17,8 +19,12 @@ export default function Home() { const [isCopied, setIsCopied] = useState(false); useEffect(() => { const fetchData = async () => { - const data = await getUrl(initData); - console.log(data); + try { + const data = await getUrl(initData); + setUrl(data); + } catch (e) { + console.error(e); + } }; fetchData(); }, [initData]); diff --git a/src/lib/url.ts b/src/lib/url.ts new file mode 100644 index 0000000..d637629 --- /dev/null +++ b/src/lib/url.ts @@ -0,0 +1,13 @@ +export function getValidUrl({ email, id }: { email: string; id: string }) { + const obj = { + fp: "chrome", + alpn: "h2,h3", + packetEncoding: "xudp", + security: "tls", + type: "ws", + path: "/myverysecretpath", + }; + const params = new URLSearchParams(obj).toString(); + const url = `vless://${id}@nwaifu.su:443?${params}#WS-${email}`; + return url; +} diff --git a/src/utils/redis.ts b/src/utils/redis.ts new file mode 100644 index 0000000..742cfe4 --- /dev/null +++ b/src/utils/redis.ts @@ -0,0 +1,3 @@ +import Redis from "ioredis"; + +export const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379");