mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
feat(notifications): update Telegram bot support and update settings management
This commit is contained in:
@@ -44,6 +44,10 @@ R2_BUCKET_NAME="openframe"
|
||||
# EMAIL & NOTIFICATIONS
|
||||
# ============================================================================
|
||||
|
||||
# Telegram bot token from @BotFather — shared across all users
|
||||
# Users only need to provide their own Chat ID in Settings
|
||||
TELEGRAM_BOT_TOKEN="your-telegram-bot-token"
|
||||
|
||||
SMTP_HOST="smtp.gmail.com"
|
||||
SMTP_PORT="587"
|
||||
SMTP_USER="[email protected]"
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface NotificationSettings {
|
||||
telegramBotToken: string | null;
|
||||
telegramChatId: string | null;
|
||||
telegramEnabled: boolean;
|
||||
emailEnabled: boolean;
|
||||
@@ -80,7 +79,6 @@ function ToggleButton({
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [settings, setSettings] = useState<NotificationSettings>({
|
||||
telegramBotToken: null,
|
||||
telegramChatId: null,
|
||||
telegramEnabled: false,
|
||||
emailEnabled: false,
|
||||
@@ -96,8 +94,7 @@ export default function SettingsPage() {
|
||||
const [testing, setTesting] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||
|
||||
// Form state for Telegram fields (separate from saved settings for editing)
|
||||
const [telegramToken, setTelegramToken] = useState('');
|
||||
// Form state for Telegram chat ID (separate from saved settings for editing)
|
||||
const [telegramChatId, setTelegramChatId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -107,7 +104,6 @@ export default function SettingsPage() {
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setSettings(data.data);
|
||||
setTelegramToken(data.data.telegramBotToken || '');
|
||||
setTelegramChatId(data.data.telegramChatId || '');
|
||||
}
|
||||
} catch {
|
||||
@@ -132,7 +128,6 @@ export default function SettingsPage() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...settings,
|
||||
telegramBotToken: telegramToken || null,
|
||||
telegramChatId: telegramChatId || null,
|
||||
}),
|
||||
});
|
||||
@@ -150,7 +145,7 @@ export default function SettingsPage() {
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [settings, telegramToken, telegramChatId, showMessage]);
|
||||
}, [settings, telegramChatId, showMessage]);
|
||||
|
||||
const handleTest = useCallback(
|
||||
async (channel: 'telegram' | 'email') => {
|
||||
@@ -161,7 +156,6 @@ export default function SettingsPage() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
channel,
|
||||
telegramBotToken: telegramToken,
|
||||
telegramChatId,
|
||||
}),
|
||||
});
|
||||
@@ -177,7 +171,7 @@ export default function SettingsPage() {
|
||||
setTesting(null);
|
||||
}
|
||||
},
|
||||
[telegramToken, telegramChatId, showMessage]
|
||||
[telegramChatId, showMessage]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
@@ -309,49 +303,65 @@ export default function SettingsPage() {
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Get instant notifications via a Telegram bot
|
||||
Get instant notifications via Telegram
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="telegram-token">Bot Token</Label>
|
||||
<Input
|
||||
id="telegram-token"
|
||||
type="password"
|
||||
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||
value={telegramToken}
|
||||
onChange={(e) => setTelegramToken(e.target.value)}
|
||||
className="mt-1 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="telegram-chat-id">Chat ID</Label>
|
||||
<Input
|
||||
id="telegram-chat-id"
|
||||
placeholder="-1001234567890"
|
||||
value={telegramChatId}
|
||||
onChange={(e) => setTelegramChatId(e.target.value)}
|
||||
className="mt-1 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-md border bg-muted/40 p-3 space-y-2 text-sm text-muted-foreground">
|
||||
<p className="font-medium text-foreground">Setup instructions</p>
|
||||
<ol className="space-y-1.5 list-decimal list-inside">
|
||||
<li>
|
||||
Message{' '}
|
||||
<a
|
||||
href="https://t.me/UserInfeBot"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline underline-offset-2"
|
||||
>
|
||||
@UserInfeBot
|
||||
</a>
|
||||
{' '}on Telegram and send <code className="bg-muted px-1 rounded text-xs">/start</code> to get your Chat ID
|
||||
</li>
|
||||
<li>
|
||||
Start{' '}
|
||||
<a
|
||||
href="https://t.me/openframe_bot"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline underline-offset-2"
|
||||
>
|
||||
@openframe_bot
|
||||
</a>
|
||||
{' '}and send <code className="bg-muted px-1 rounded text-xs">/start</code> so it can message you
|
||||
</li>
|
||||
<li>Paste your Chat ID below and enable notifications</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<ToggleButton
|
||||
enabled={settings.telegramEnabled}
|
||||
onToggle={() =>
|
||||
setSettings((s) => ({ ...s, telegramEnabled: !s.telegramEnabled }))
|
||||
}
|
||||
label="Enable Telegram notifications"
|
||||
<div>
|
||||
<Label htmlFor="telegram-chat-id">Your Chat ID</Label>
|
||||
<Input
|
||||
id="telegram-chat-id"
|
||||
placeholder="123456789"
|
||||
value={telegramChatId}
|
||||
onChange={(e) => setTelegramChatId(e.target.value)}
|
||||
className="mt-1 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ToggleButton
|
||||
enabled={settings.telegramEnabled}
|
||||
onToggle={() =>
|
||||
setSettings((s) => ({ ...s, telegramEnabled: !s.telegramEnabled }))
|
||||
}
|
||||
label="Enable Telegram notifications"
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleTest('telegram')}
|
||||
disabled={!telegramToken || !telegramChatId || testing === 'telegram'}
|
||||
disabled={!telegramChatId || testing === 'telegram'}
|
||||
>
|
||||
{testing === 'telegram' ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
|
||||
@@ -21,7 +21,6 @@ export async function GET() {
|
||||
// Return defaults if no settings exist yet
|
||||
const response = successResponse(
|
||||
settings ?? {
|
||||
telegramBotToken: null,
|
||||
telegramChatId: null,
|
||||
telegramEnabled: false,
|
||||
emailEnabled: false,
|
||||
@@ -54,7 +53,6 @@ export async function PUT(request: NextRequest) {
|
||||
|
||||
const body = await request.json();
|
||||
const {
|
||||
telegramBotToken,
|
||||
telegramChatId,
|
||||
telegramEnabled,
|
||||
emailEnabled,
|
||||
@@ -66,16 +64,18 @@ export async function PUT(request: NextRequest) {
|
||||
timezone,
|
||||
} = body;
|
||||
|
||||
// Validate: if enabling Telegram, both token and chatId are required
|
||||
if (telegramEnabled && (!telegramBotToken || !telegramChatId)) {
|
||||
return apiErrors.badRequest('Telegram Bot Token and Chat ID are required to enable Telegram notifications');
|
||||
// Validate: if enabling Telegram, chatId is required and must be a valid Telegram ID
|
||||
if (telegramChatId && !/^-?\d{1,20}$/.test(telegramChatId)) {
|
||||
return apiErrors.badRequest('Invalid Chat ID format');
|
||||
}
|
||||
if (telegramEnabled && !telegramChatId) {
|
||||
return apiErrors.badRequest('Chat ID is required to enable Telegram notifications');
|
||||
}
|
||||
|
||||
const settings = await db.notificationSetting.upsert({
|
||||
where: { userId: session.user.id },
|
||||
create: {
|
||||
userId: session.user.id,
|
||||
telegramBotToken: telegramBotToken || null,
|
||||
telegramChatId: telegramChatId || null,
|
||||
telegramEnabled: !!telegramEnabled,
|
||||
emailEnabled: !!emailEnabled,
|
||||
@@ -87,7 +87,6 @@ export async function PUT(request: NextRequest) {
|
||||
timezone: timezone || 'UTC',
|
||||
},
|
||||
update: {
|
||||
telegramBotToken: telegramBotToken || null,
|
||||
telegramChatId: telegramChatId || null,
|
||||
telegramEnabled: !!telegramEnabled,
|
||||
emailEnabled: !!emailEnabled,
|
||||
@@ -120,11 +119,18 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { channel, telegramBotToken, telegramChatId } = body;
|
||||
const { channel, telegramChatId } = body;
|
||||
|
||||
if (channel === 'telegram') {
|
||||
if (!telegramBotToken || !telegramChatId) {
|
||||
return apiErrors.badRequest('Bot Token and Chat ID are required');
|
||||
const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||
if (!telegramBotToken) {
|
||||
return apiErrors.internalError('Telegram bot not configured (TELEGRAM_BOT_TOKEN missing)');
|
||||
}
|
||||
if (!telegramChatId) {
|
||||
return apiErrors.badRequest('Chat ID is required');
|
||||
}
|
||||
if (!/^-?\d{1,20}$/.test(telegramChatId)) {
|
||||
return apiErrors.badRequest('Invalid Chat ID format');
|
||||
}
|
||||
|
||||
const settingsUrl = `${process.env.NEXTAUTH_URL || ''}/settings`;
|
||||
|
||||
@@ -456,10 +456,11 @@ export async function notifyUsers(userIds: string[], event: NotificationEvent):
|
||||
const promises: Promise<boolean>[] = [];
|
||||
const tz = settings.timezone || 'UTC';
|
||||
|
||||
if (settings.telegramEnabled && settings.telegramBotToken && settings.telegramChatId) {
|
||||
const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||
if (settings.telegramEnabled && telegramBotToken && settings.telegramChatId) {
|
||||
const msg = formatTelegramMessage(event, tz);
|
||||
promises.push(sendTelegram(
|
||||
settings.telegramBotToken,
|
||||
telegramBotToken,
|
||||
settings.telegramChatId,
|
||||
msg.text,
|
||||
msg.buttonLabel,
|
||||
|
||||
@@ -610,8 +610,7 @@ model NotificationSetting {
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Telegram webhook
|
||||
telegramBotToken String? // Bot token from @BotFather
|
||||
telegramChatId String? // Chat/group ID to send messages to
|
||||
telegramChatId String? // Chat ID to send messages to (bot token is in TELEGRAM_BOT_TOKEN env var)
|
||||
telegramEnabled Boolean @default(false)
|
||||
|
||||
// Email notifications (uses account email by default)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Reference in New Issue
Block a user