112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import nodemailer from 'nodemailer'
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = await request.json()
|
|
const { channelName, category, url, notes, email } = body
|
|
|
|
// Validate required fields
|
|
if (!channelName || !email) {
|
|
return NextResponse.json(
|
|
{ error: 'Channel name and email are required' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
// Email validation
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
if (!emailRegex.test(email)) {
|
|
return NextResponse.json(
|
|
{ error: 'Invalid email format' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
const smtpHost = process.env.SMTP_HOST
|
|
const smtpUser = process.env.SMTP_USER
|
|
const smtpPass = process.env.SMTP_PASS
|
|
if (!smtpHost || !smtpUser || !smtpPass) {
|
|
console.error('SMTP credentials not configured')
|
|
return NextResponse.json(
|
|
{ error: 'Email service not configured' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
|
|
const adminEmail = process.env.ADMIN_EMAIL || 'jeff@jeffemmett.com'
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
host: smtpHost,
|
|
port: Number(process.env.SMTP_PORT) || 587,
|
|
secure: false,
|
|
auth: { user: smtpUser, pass: smtpPass },
|
|
tls: { rejectUnauthorized: false },
|
|
})
|
|
|
|
await transporter.sendMail({
|
|
from: `Jefflix <${smtpUser}>`,
|
|
to: adminEmail,
|
|
subject: `[Jefflix] Channel Request: ${escapeHtml(channelName)}`,
|
|
html: `
|
|
<h2>New Channel Request</h2>
|
|
<p>Someone has requested a new Live TV channel:</p>
|
|
<table style="border-collapse: collapse; margin: 20px 0;">
|
|
<tr>
|
|
<td style="padding: 8px; font-weight: bold; border: 1px solid #ddd;">Channel:</td>
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${escapeHtml(channelName)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px; font-weight: bold; border: 1px solid #ddd;">Category:</td>
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${escapeHtml(category || 'Not specified')}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px; font-weight: bold; border: 1px solid #ddd;">URL/Link:</td>
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${url ? `<a href="${escapeHtml(url)}">${escapeHtml(url)}</a>` : 'Not provided'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px; font-weight: bold; border: 1px solid #ddd;">Notes:</td>
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${escapeHtml(notes || 'None')}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px; font-weight: bold; border: 1px solid #ddd;">Email:</td>
|
|
<td style="padding: 8px; border: 1px solid #ddd;"><a href="mailto:${escapeHtml(email)}">${escapeHtml(email)}</a></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px; font-weight: bold; border: 1px solid #ddd;">Requested:</td>
|
|
<td style="padding: 8px; border: 1px solid #ddd;">${new Date().toLocaleString()}</td>
|
|
</tr>
|
|
</table>
|
|
<p><strong>To add this channel:</strong></p>
|
|
<ol>
|
|
<li>Check <a href="https://iptv-org.github.io">iptv-org</a> for an existing stream</li>
|
|
<li>If found, add to Threadfin channel list and map the stream</li>
|
|
<li>If not in iptv-org, add as a custom M3U source in Threadfin</li>
|
|
<li>Reply to ${escapeHtml(email)} to let them know it's been added</li>
|
|
</ol>
|
|
<hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;" />
|
|
<p style="color: #666; font-size: 12px;">This is an automated message from Jefflix.</p>
|
|
`,
|
|
})
|
|
|
|
return NextResponse.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Channel request error:', error)
|
|
return NextResponse.json(
|
|
{ error: 'Internal server error' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
function escapeHtml(text: string): string {
|
|
const map: Record<string, string> = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": ''',
|
|
}
|
|
return text.replace(/[&<>"']/g, (char) => map[char])
|
|
}
|