feat: takip izleme (zeyilname + sonuc bildirimi), hazirlik gorevi, belge sure uyarisi, Telegram, OCR, yasakli sorgu
28 depo taramasindan alinan alti ozellik. Kod kopyalanmadi (depolar lisanssiz), fikirler uygulandi. 1) Zeyilname / duzeltme ilani takibi (server/zeyilname.mjs): takipteki IKN'ler sunucuya yazilir; 3 saatte bir EKAP detayindan ilan sayisi ve ihale tarihi karsilastirilir, degisince "ne degisti" bildirimi (uygulama ici + e-posta + Telegram). Test: anlik kaydi eskitilince tur "yeni ilan yayimlandi; ihale tarihi 01.01.2026 → 08.10.2026" bildirimi uretti. 2) Sonuc bildirimi: takipteki ihale sonuclaninca "X firmasi Y'ye aldi, %Z kirim". Yalniz takiptekiler; kismi ihalede oran yazilmaz. 3) Ihaleden goreve (MOBIT-DURT): detayda "Hazirlik Gorevi Ac" → teslim tarihi ihale saati, pano "Teklif Hazirlaniyor", derin analizden gelen belge kontrol listesi gorevde; Kanban kartinda geri sayim rozeti. 4) Sirket belgesi sure takibi: Belge Kasasi'nda son kullanma tarihi girisi, dolmus / 30 gun icinde dolacak uyarisi; girise gunde bir bildirim. PATCH /api/me/documents/meta. 5) Telegram (server/telegram.mjs): TELEGRAM_BOT_TOKEN sunucuda; kullanici 6 haneli kodu bota yazar, "Dogrula" → getUpdates ile chat id baglanir. Arayuzde adim adim kurulum rehberi (BotFather → env → Redeploy → kod). Ihale ozeti, zeyilname ve sonuc bildirimleri Telegram'a da gider. 6) OCR (tesseract.js, tur): PDF metin katmani bossa (taranmis) ilk 8 sayfa OCR. Olcum: sentetik cetvelde guven 87, ~0,3 sn/sayfa; IKN ve kalemler dogru okundu. Kalem havuzu ve derin analiz artik taranmis PDF'ten de besleniyor. 7) Rakip Radari'nda KIK yasakli sorgusu baglantisi (firma adi panoya kopyalanir; EKAP sorguyu API'den vermiyor). Sinir deger hesaplayici KURULMADI: Kamu Ihale Genel Tebligi'ne ulasilamadi (mevzuat.gov.tr / ihale.gov.tr bu makineden erisilemiyor); formul kaynaksiz yazilmadi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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ığı 6 haneli
|
||||
* 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() {
|
||||
return Boolean(process.env.TELEGRAM_BOT_TOKEN);
|
||||
}
|
||||
|
||||
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) {
|
||||
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), parse_mode: 'HTML', disable_web_page_preview: true }),
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
const d = await r.json().catch(() => ({}));
|
||||
if (!d.ok) throw new Error(d.description || `Telegram HTTP ${r.status}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kullanıcının bota yazdığı 6 haneli kodu 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) {
|
||||
if (!telegramConfigured()) throw new Error('Sunucuda TELEGRAM_BOT_TOKEN tanımlı değil');
|
||||
const r = await fetch(`${API()}/getUpdates?limit=100`, { signal: AbortSignal.timeout(15000) });
|
||||
const d = await r.json().catch(() => ({}));
|
||||
if (!d.ok) throw new Error(d.description || 'getUpdates başarısız');
|
||||
const aranan = String(kod).trim();
|
||||
for (const u of [...(d.result || [])].reverse()) {
|
||||
const m = u.message || u.edited_message;
|
||||
if (!m?.text) continue;
|
||||
if (m.text.replace(/\D/g, '').includes(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]));
|
||||
Reference in New Issue
Block a user