/** * Telegram bildirim kanalı — Bot API, bağımlılık yok. * * KURULUM (kullanıcı/yönetici): BotFather'da bot oluşturulur, tokenı * TELEGRAM_BOT_TOKEN env'ine girilir. Her kullanıcı uygulamadan aldığı süreli * kodu bota yazar; sunucu getUpdates ile kodu görüp chat id'yi kullanıcıya * bağlar. Token bizde tutulmaz, sohbet kimliği kullanıcı verisinde durur. */ const API = () => `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}`; export function telegramConfigured() { const token = String(process.env.TELEGRAM_BOT_TOKEN || '').trim(); if (!token) return false; // Üretimde placeholder veya hatalı token ile kanalın aktif görünmesini engelle. if (process.env.NODE_ENV === 'production') return /^\d{6,}:[A-Za-z0-9_-]{20,}$/.test(token); return true; } let botOnbellek = { ad: null, zaman: 0 }; /** Bot kullanıcı adı (t.me/) — arayüzde bağlantı vermek için. */ export async function botAdi() { if (!telegramConfigured()) return null; if (botOnbellek.ad && Date.now() - botOnbellek.zaman < 3600e3) return botOnbellek.ad; try { const r = await fetch(`${API()}/getMe`, { signal: AbortSignal.timeout(10000) }); const d = await r.json(); if (d.ok) { botOnbellek = { ad: d.result.username, zaman: Date.now() }; return d.result.username; } } catch { /* aşağıda null */ } return null; } export async function sendTelegram(chatId, text, { parseMode = 'HTML', returnReceipt = false } = {}) { if (!telegramConfigured() || !chatId) return false; const r = await fetch(`${API()}/sendMessage`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chat_id: chatId, text: String(text).slice(0, 4000), ...(parseMode === null ? {} : { parse_mode:parseMode }), disable_web_page_preview: true }), signal: AbortSignal.timeout(15000), }); const d = await r.json().catch(() => ({})); if (!r.ok || d.ok !== true) throw new Error(d.description || `Telegram HTTP ${r.status}`); if (returnReceipt) { const messageId=d.result?.message_id,returnedChat=d.result?.chat?.id; if (!Number.isSafeInteger(messageId) || messageId <= 0 || !Number.isSafeInteger(returnedChat) || returnedChat === 0) throw new Error('Telegram mesaj kimliği doğrulanamadı; gönderim sonucu belirsiz.'); if (/^-?\d+$/.test(String(chatId)) && String(returnedChat) !== String(chatId)) throw new Error('Telegram kabulü hedef sohbetle eşleşmiyor; gönderim sonucu belirsiz.'); return { id:`${returnedChat}:${messageId}`, chatId:String(returnedChat), messageId }; } return true; } /** * Kullanıcının bota yazdığı süreli bağlama kodunu son güncellemelerde arar. * Bulursa { chatId, ad } döner. Long-polling yok; kullanıcı "doğrula" dediğinde * tek getUpdates çağrısı yeterli (Telegram son 24 saati tutar). */ export async function kodIleBagla(kod, {issuedAt,expiresAt,now=Date.now()} = {}) { if (!telegramConfigured()) throw new Error('Sunucuda TELEGRAM_BOT_TOKEN tanımlı değil'); if(!/^[a-f0-9]{48}$/.test(String(kod))||!Number.isFinite(issuedAt)||!Number.isFinite(expiresAt)||now>=expiresAt)return null; const r = await fetch(`${API()}/getUpdates?limit=100`, { signal: AbortSignal.timeout(15000) }); const d = await r.json().catch(() => ({})); if (!r.ok || d.ok!==true || !Array.isArray(d.result)) throw new Error(d.description || 'Telegram güncelleme yanıtı doğrulanamadı. Yeniden deneyin.'); const aranan = String(kod); for (const u of [...(d.result || [])].reverse()) { const m = u.message; if(typeof m?.text!=='string'||m.chat?.type!=='private'||m.from?.is_bot!==false|| !Number.isSafeInteger(m.chat.id)||m.chat.id<=0||m.from.id!==m.chat.id|| !Number.isFinite(m.date)||m.dateMath.floor(now/1000))continue; if(m.text.trim()===aranan || m.text.trim()===`/start ${aranan}`) return {chatId:String(m.chat.id),ad:[m.chat.first_name,m.chat.last_name].filter(Boolean).join(' ')||m.chat.username||null}; } return null; } /** HTML kaçışı — ihale adları '<' '&' içerebiliyor. */ export const tgKacir = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]));