Files
ihaleist/server/telegram.mjs
T

79 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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/<ad>) — 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.date<Math.floor(issuedAt/1000)||m.date>Math.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) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));