feat: started working with api
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap");
|
||||
@import "tailwindcss";
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--tg-theme-secondary-bg-color, white);
|
||||
font-family: "Montserrat", sans-serif;
|
||||
height: 100%;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
56
src/app/_dto/inbounds.ts
Normal file
56
src/app/_dto/inbounds.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export interface InboundResponse {
|
||||
success: boolean;
|
||||
msg: string;
|
||||
obj: Obj[];
|
||||
}
|
||||
|
||||
export interface Obj {
|
||||
id: number;
|
||||
up: number;
|
||||
down: number;
|
||||
total: number;
|
||||
remark: string;
|
||||
enable: boolean;
|
||||
expiryTime: number;
|
||||
clientStats: ClientStat[];
|
||||
listen: string;
|
||||
port: number;
|
||||
protocol: string;
|
||||
settings: string;
|
||||
streamSettings: string;
|
||||
tag: string;
|
||||
sniffing: string;
|
||||
allocate: string;
|
||||
}
|
||||
|
||||
export interface ClientStat {
|
||||
id: number;
|
||||
inboundId: number;
|
||||
enable: boolean;
|
||||
email: string;
|
||||
up: number;
|
||||
down: number;
|
||||
expiryTime: number;
|
||||
total: number;
|
||||
reset: number;
|
||||
}
|
||||
|
||||
export interface ClientSettings {
|
||||
clients: Client[];
|
||||
decryption: string;
|
||||
fallbacks: object[];
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
email: string;
|
||||
enable: boolean;
|
||||
expiryTime: number;
|
||||
flow: string;
|
||||
id: string;
|
||||
limitIp: number;
|
||||
reset: number;
|
||||
subId: string;
|
||||
tgId: string;
|
||||
totalGB: number;
|
||||
comment?: string;
|
||||
}
|
||||
42
src/app/actions.ts
Normal file
42
src/app/actions.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
"use server";
|
||||
import { API_LINK, XUIApiLinks } from "@/lib/enum";
|
||||
import { authFetch } from "@/lib/login";
|
||||
import { prisma } from "@/utils/prisma";
|
||||
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) {
|
||||
const res = await authFetch(API_LINK + XUIApiLinks.GET_INBOUNDS);
|
||||
const data: InboundResponse = await res.json();
|
||||
const inbound = data.obj.find((inbound) => inbound.remark === "WS");
|
||||
if (!inbound) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Inbound not found",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
const users: ClientSettings = JSON.parse(inbound.settings);
|
||||
const user = users.clients.find((user) => user.email === email);
|
||||
return user;
|
||||
}
|
||||
export async function getUrl(initData: string = "") {
|
||||
try {
|
||||
if (process.env.NODE_ENV === "production")
|
||||
validate(initData, process.env.BOT_TOKEN || "", {
|
||||
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 || "");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import { authFetch } from "./login";
|
||||
export { authFetch };
|
||||
21
src/app/api/get-url/route.ts
Normal file
21
src/app/api/get-url/route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ClientSettings, InboundResponse } from "@/app/_dto/inbounds";
|
||||
import { API_LINK, XUIApiLinks } from "@/lib/enum";
|
||||
import { authFetch } from "@/lib/login";
|
||||
|
||||
//TODO: its just for testing. Make it normal
|
||||
export async function GET() {
|
||||
const res = await authFetch(API_LINK + XUIApiLinks.GET_INBOUNDS);
|
||||
const data: InboundResponse = await res.json();
|
||||
const inbound = data.obj.find((inbound) => inbound.remark === "WS");
|
||||
if (!inbound) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Inbound not found",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
const users: ClientSettings = JSON.parse(inbound.settings);
|
||||
return Response.json(users);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { authFetch } from "../_lib/login";
|
||||
|
||||
//TODO: its just for testing. Make it normal
|
||||
export async function GET() {
|
||||
const res = await authFetch(process.env.XUI_HOST + "/panel/api/inbounds/list");
|
||||
const data = await res.json();
|
||||
return Response.json(data);
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
"use client";
|
||||
import { Block } from "@/components/Block";
|
||||
import { Page } from "@/components/Page";
|
||||
import { useState } from "react";
|
||||
import { useRawInitData } from "@telegram-apps/sdk-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getUrl } from "./actions";
|
||||
|
||||
export default function Home() {
|
||||
const initData = useRawInitData();
|
||||
const onCopyClick = async () => {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
setIsCopied(true);
|
||||
@@ -12,12 +15,28 @@ export default function Home() {
|
||||
}, 2000);
|
||||
};
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const data = await getUrl(initData);
|
||||
console.log(data);
|
||||
};
|
||||
fetchData();
|
||||
}, [initData]);
|
||||
return (
|
||||
<Page back={false}>
|
||||
<header className="flex h-12 w-full flex-col items-center justify-center bg-[var(--tg-theme-header-bg-color)]">
|
||||
<span className="text-xl font-semibold text-[var(--tg-theme-text-color)]">Nwaifu Proxy</span>
|
||||
</header>
|
||||
<main className="flex h-full w-full flex-col items-center gap-3 px-2.5 py-3.5 text-[var(--tg-theme-text-color)]">
|
||||
<main className="absolute bottom-0 left-0 flex w-full flex-col items-center gap-3 px-2.5 py-3.5 text-[var(--tg-theme-text-color)]">
|
||||
<Block name="Ссылка">
|
||||
<div className="size-44 rounded-md bg-white"></div>
|
||||
<button
|
||||
className="h-8 w-48 rounded-md bg-[var(--tg-theme-button-color)] text-[var(--tg-theme-button-text-color)] transition-[scale] hover:scale-[110%] active:scale-[115%]"
|
||||
onClick={onCopyClick}
|
||||
>
|
||||
{isCopied ? "Скопировано!" : "Копировать"}
|
||||
</button>
|
||||
</Block>
|
||||
<Block name="Подписка">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span>Статус:</span>
|
||||
@@ -28,15 +47,6 @@ export default function Home() {
|
||||
<span>01.01.2023</span>
|
||||
</div>
|
||||
</Block>
|
||||
<Block name="Ссылка">
|
||||
<div className="size-44 rounded-md bg-white"></div>
|
||||
<button
|
||||
className="mt-4 h-8 w-48 rounded-md bg-[var(--tg-theme-button-color)] text-[var(--tg-theme-button-text-color)] transition-[scale] hover:scale-[110%] active:scale-[115%]"
|
||||
onClick={onCopyClick}
|
||||
>
|
||||
{isCopied ? "Скопировано!" : "Копировать"}
|
||||
</button>
|
||||
</Block>
|
||||
</main>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ const RootInner: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<AppRoot
|
||||
appearance={isDark ? "dark" : "light"}
|
||||
className="relative h-full"
|
||||
platform={["macos", "ios"].includes(lp.tgWebAppPlatform) ? "ios" : "base"}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import clsx, { ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
export default function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(...inputs));
|
||||
}
|
||||
5
src/lib/enum.ts
Normal file
5
src/lib/enum.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const API_LINK = process.env.XUI_HOST || "";
|
||||
export enum XUIApiLinks {
|
||||
GET_INBOUNDS = "/panel/api/inbounds/list",
|
||||
LOGIN = "/login",
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { API_LINK, XUIApiLinks } from "./enum";
|
||||
|
||||
const cookieJar: Map<string, string> = new Map();
|
||||
|
||||
let loginInProgress: Promise<void> | null = null;
|
||||
@@ -32,7 +34,7 @@ const login = async (): Promise<void> => {
|
||||
password: password,
|
||||
};
|
||||
const encodedData = new URLSearchParams(details).toString();
|
||||
const response = await fetch(process.env.XUI_HOST + "/login", {
|
||||
const response = await fetch(API_LINK + XUIApiLinks.LOGIN, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
11
src/utils/prisma.ts
Normal file
11
src/utils/prisma.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = global as unknown as { prisma: PrismaClient };
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ||
|
||||
new PrismaClient({
|
||||
log: ["query", "info", "warn", "error"],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
Reference in New Issue
Block a user