接入tgbot
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import type { AdminConfig } from '@/lib/admin.types';
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import { getTelegramConfig, sendTelegramMessage, setTelegramWebhook, TelegramApiError } from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
|
||||
async function assertAdmin(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo?.username) return { error: 'Unauthorized', status: 401 } as const;
|
||||
|
||||
const storage = getStorage();
|
||||
const userInfo = await storage.getUserInfoV2?.(authInfo.username);
|
||||
if (!userInfo || (userInfo.role !== 'admin' && userInfo.role !== 'owner')) {
|
||||
return { error: 'Forbidden', status: 403 } as const;
|
||||
}
|
||||
|
||||
return { storage } as const;
|
||||
}
|
||||
|
||||
function maskTelegramConfig(config: AdminConfig['TelegramConfig']) {
|
||||
return {
|
||||
enabled: config?.enabled || false,
|
||||
botToken: config?.botToken ? '******' : '',
|
||||
botUsername: config?.botUsername || '',
|
||||
webhookSecret: config?.webhookSecret ? '******' : '',
|
||||
apiProxy: config?.apiProxy || '',
|
||||
apiBaseUrl: config?.apiBaseUrl || '',
|
||||
loginEnabled: config?.loginEnabled !== false,
|
||||
bindingEnabled: config?.bindingEnabled !== false,
|
||||
notificationsEnabled: config?.notificationsEnabled !== false,
|
||||
defaultNotifications: config?.defaultNotifications !== false,
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const auth = await assertAdmin(request);
|
||||
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
||||
|
||||
const adminConfig = await getConfig();
|
||||
return NextResponse.json(maskTelegramConfig(adminConfig.TelegramConfig));
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await assertAdmin(request);
|
||||
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
||||
|
||||
const body = await request.json();
|
||||
const { action, config, testChatId, webhookUrl, origin } = body;
|
||||
|
||||
if (action === 'set_webhook') {
|
||||
const overrideConfig = config as AdminConfig['TelegramConfig'] | undefined;
|
||||
const savedConfig = await getTelegramConfig();
|
||||
const botToken =
|
||||
overrideConfig?.botToken && overrideConfig.botToken !== '******'
|
||||
? overrideConfig.botToken
|
||||
: savedConfig.botToken;
|
||||
const secret =
|
||||
overrideConfig?.webhookSecret && overrideConfig.webhookSecret !== '******'
|
||||
? overrideConfig.webhookSecret
|
||||
: savedConfig.webhookSecret;
|
||||
const baseOrigin = String(origin || '').trim().replace(/\/$/, '');
|
||||
const resolvedWebhookUrl = String(webhookUrl || '').trim() ||
|
||||
(baseOrigin && secret ? `${baseOrigin}/api/telegram/webhook/${secret}` : '');
|
||||
|
||||
if (!botToken || !secret || !resolvedWebhookUrl) {
|
||||
return NextResponse.json({ error: '缺少 Bot Token、Webhook Secret 或站点地址' }, { status: 400 });
|
||||
}
|
||||
|
||||
const apiProxy = overrideConfig?.apiProxy || savedConfig.apiProxy;
|
||||
const apiBaseUrl = overrideConfig?.apiBaseUrl || savedConfig.apiBaseUrl;
|
||||
|
||||
try {
|
||||
const result = await setTelegramWebhook(botToken, resolvedWebhookUrl, secret, {
|
||||
apiProxy,
|
||||
apiBaseUrl,
|
||||
});
|
||||
return NextResponse.json({ success: true, message: 'Webhook 设置成功', result });
|
||||
} catch (error) {
|
||||
if (error instanceof TelegramApiError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error.message,
|
||||
telegram: {
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
data: error.data,
|
||||
apiBaseUrl: apiBaseUrl || 'https://api.telegram.org',
|
||||
apiProxyEnabled: Boolean(apiProxy),
|
||||
},
|
||||
},
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Webhook 设置失败' },
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'test') {
|
||||
if (!testChatId) {
|
||||
return NextResponse.json({ error: '请填写测试 Chat ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const overrideConfig = config as AdminConfig['TelegramConfig'] | undefined;
|
||||
const savedConfig = await getTelegramConfig();
|
||||
const botToken =
|
||||
overrideConfig?.botToken && overrideConfig.botToken !== '******'
|
||||
? overrideConfig.botToken
|
||||
: savedConfig.botToken;
|
||||
|
||||
await sendTelegramMessage(
|
||||
String(testChatId),
|
||||
'MoonTVPlus Telegram Bot 测试消息发送成功。',
|
||||
undefined,
|
||||
{
|
||||
enabled: true,
|
||||
botToken,
|
||||
botUsername: overrideConfig?.botUsername || savedConfig.botUsername,
|
||||
apiProxy: overrideConfig?.apiProxy || savedConfig.apiProxy,
|
||||
apiBaseUrl: overrideConfig?.apiBaseUrl || savedConfig.apiBaseUrl,
|
||||
}
|
||||
);
|
||||
return NextResponse.json({ success: true, message: '测试消息发送成功' });
|
||||
}
|
||||
|
||||
if (action !== 'save') {
|
||||
return NextResponse.json({ error: '无效的操作' }, { status: 400 });
|
||||
}
|
||||
|
||||
const telegramConfig = config as AdminConfig['TelegramConfig'];
|
||||
if (!telegramConfig) {
|
||||
return NextResponse.json({ error: 'Telegram 配置不能为空' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (telegramConfig.enabled && (!telegramConfig.botToken || !telegramConfig.botUsername)) {
|
||||
return NextResponse.json({ error: '启用 Telegram 时必须填写 Bot Token 和 Bot 用户名' }, { status: 400 });
|
||||
}
|
||||
|
||||
const adminConfig = await getConfig();
|
||||
const oldConfig = adminConfig.TelegramConfig;
|
||||
if (telegramConfig.botToken === '******') telegramConfig.botToken = oldConfig?.botToken || '';
|
||||
if (telegramConfig.webhookSecret === '******') telegramConfig.webhookSecret = oldConfig?.webhookSecret || '';
|
||||
|
||||
adminConfig.TelegramConfig = telegramConfig;
|
||||
await auth.storage.setAdminConfig(adminConfig);
|
||||
return NextResponse.json({ success: true, message: 'Telegram 配置保存成功' });
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { db } from '@/lib/db';
|
||||
import { lockManager } from '@/lib/lock';
|
||||
import {
|
||||
createTelegramBindSession,
|
||||
getTelegramConfig,
|
||||
getTelegramDeepLink,
|
||||
} from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
@@ -171,6 +176,23 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
await db.createUserV2(username, password, 'user', defaultTags);
|
||||
|
||||
const telegramConfig = await getTelegramConfig();
|
||||
if (telegramConfig.enabled && telegramConfig.bindingEnabled && telegramConfig.botToken) {
|
||||
const bindSession = await createTelegramBindSession(username);
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
message: '注册成功',
|
||||
telegramBind: {
|
||||
code: bindSession.code,
|
||||
expiresAt: bindSession.expiresAt,
|
||||
botUsername: telegramConfig.botUsername,
|
||||
deepLink: telegramConfig.botUsername
|
||||
? getTelegramDeepLink(telegramConfig.botUsername, `bind_${bindSession.code}`)
|
||||
: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 注册成功
|
||||
return NextResponse.json({ ok: true, message: '注册成功' });
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -40,6 +40,8 @@ export async function GET(request: NextRequest) {
|
||||
WatchRoom: watchRoomConfig,
|
||||
EnableOfflineDownload: process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true',
|
||||
DanmakuAutoLoadDefault: true,
|
||||
EnableTelegramLogin: Boolean(process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_BOT_USERNAME && process.env.TELEGRAM_LOGIN_ENABLED !== 'false'),
|
||||
TelegramBotUsername: process.env.TELEGRAM_BOT_USERNAME || '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,6 +62,13 @@ export async function GET(request: NextRequest) {
|
||||
EnableOIDCLogin: config.SiteConfig.EnableOIDCLogin || false,
|
||||
EnableOIDCRegistration: config.SiteConfig.EnableOIDCRegistration || false,
|
||||
OIDCButtonText: config.SiteConfig.OIDCButtonText || '',
|
||||
EnableTelegramLogin: Boolean(
|
||||
config.TelegramConfig?.enabled &&
|
||||
config.TelegramConfig?.loginEnabled &&
|
||||
(config.TelegramConfig?.botToken || process.env.TELEGRAM_BOT_TOKEN) &&
|
||||
(config.TelegramConfig?.botUsername || process.env.TELEGRAM_BOT_USERNAME)
|
||||
),
|
||||
TelegramBotUsername: config.TelegramConfig?.botUsername || process.env.TELEGRAM_BOT_USERNAME || '',
|
||||
DanmakuAutoLoadDefault: config.SiteConfig.DanmakuAutoLoadDefault !== false,
|
||||
loginBackgroundImage: config.ThemeConfig?.loginBackgroundImage || '',
|
||||
registerBackgroundImage: config.ThemeConfig?.registerBackgroundImage || '',
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthInfoFromCookie } from '@/lib/auth';
|
||||
import {
|
||||
createTelegramBindSession,
|
||||
getTelegramBinding,
|
||||
getTelegramConfig,
|
||||
getTelegramDeepLink,
|
||||
} from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo?.username) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const config = await getTelegramConfig();
|
||||
const binding = await getTelegramBinding(authInfo.username);
|
||||
return NextResponse.json({
|
||||
enabled: config.enabled && config.bindingEnabled && Boolean(config.botToken),
|
||||
botUsername: config.botUsername,
|
||||
binding,
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authInfo = getAuthInfoFromCookie(request);
|
||||
if (!authInfo?.username) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const config = await getTelegramConfig();
|
||||
if (!config.enabled || !config.bindingEnabled || !config.botToken) {
|
||||
return NextResponse.json({ error: 'Telegram Bot 未启用' }, { status: 400 });
|
||||
}
|
||||
|
||||
const session = await createTelegramBindSession(authInfo.username);
|
||||
return NextResponse.json({
|
||||
code: session.code,
|
||||
expiresAt: session.expiresAt,
|
||||
botUsername: config.botUsername,
|
||||
deepLink: config.botUsername ? getTelegramDeepLink(config.botUsername, `bind_${session.code}`) : '',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { getTelegramConfig } from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function GET() {
|
||||
const config = await getTelegramConfig();
|
||||
return NextResponse.json({
|
||||
enabled: config.enabled && Boolean(config.botToken),
|
||||
botUsername: config.botUsername,
|
||||
loginEnabled: config.loginEnabled,
|
||||
bindingEnabled: config.bindingEnabled,
|
||||
notificationsEnabled: config.notificationsEnabled,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import {
|
||||
createTelegramLoginSession,
|
||||
getTelegramConfig,
|
||||
getTelegramDeepLink,
|
||||
} from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function POST() {
|
||||
const config = await getTelegramConfig();
|
||||
if (!config.enabled || !config.loginEnabled || !config.botToken || !config.botUsername) {
|
||||
return NextResponse.json({ error: 'Telegram 登录未启用' }, { status: 400 });
|
||||
}
|
||||
|
||||
const session = await createTelegramLoginSession();
|
||||
return NextResponse.json({
|
||||
token: session.token,
|
||||
expiresAt: session.expiresAt,
|
||||
botUsername: config.botUsername,
|
||||
deepLink: getTelegramDeepLink(config.botUsername, `login_${session.token}`),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import {
|
||||
consumeConfirmedTelegramLogin,
|
||||
getTelegramLoginSession,
|
||||
} from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const token = new URL(request.url).searchParams.get('token');
|
||||
const session = await getTelegramLoginSession(token);
|
||||
if (!session) return NextResponse.json({ status: 'expired' });
|
||||
|
||||
if (session.status === 'confirmed' && session.authToken) {
|
||||
const consumed = await consumeConfirmedTelegramLogin(session.token);
|
||||
const response = NextResponse.json({ status: 'confirmed', username: consumed?.username });
|
||||
const expires = new Date();
|
||||
expires.setDate(expires.getDate() + 60);
|
||||
response.cookies.set('auth', session.authToken, {
|
||||
path: '/',
|
||||
expires,
|
||||
sameSite: 'lax',
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
status: session.status,
|
||||
expiresAt: session.expiresAt,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import {
|
||||
handleTelegramWebhookUpdate,
|
||||
validateTelegramWebhookRequest,
|
||||
} from '@/lib/telegram';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { secret: string } }
|
||||
) {
|
||||
if (!(await validateTelegramWebhookRequest(request, params.secret))) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const update = await request.json();
|
||||
await handleTelegramWebhookUpdate(update);
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
Reference in New Issue
Block a user