fix: bildirim spam'i - kanal başına 10dk soğuma süresi
Çöküp yeniden başlayan bir kanal (bkz. streamIngest'teki crash-loop notu) dakikalar içinde art arda "başladı" + "hata" + "düzeldi" mesajı atıyordu — birkaç kanal aynı anda flap edince gerçek bir spam'e dönüştü (kullanıcı raporu: "sürekli bildirim geliyor"). Üç bildirim türü de artık kanal başına ortak 10dk'lık bir soğuma süresini paylaşıyor — bir kanal ilk olayını bildirir, sonra durum tekrar tekrar sıçrasa bile bir süre sessiz kalır. Not: bu sadece bildirim gürültüsünü kesiyor, alttaki crash-loop'un kendisi (muhtemelen kaynak/proxy kapasitesi) hâlâ duruyor — o konu ayrı, kullanıcı ile "önce test edelim" üzerinde anlaşıldı. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,12 +43,29 @@ const BENIGN_POLL_MESSAGE = /is not currently live|will begin in/;
|
|||||||
* solver) is included too — some channels return "The page needs to be
|
* solver) is included too — some channels return "The page needs to be
|
||||||
* reloaded" on the plain metadata fetch too, not just format resolution.
|
* reloaded" on the plain metadata fetch too, not just format resolution.
|
||||||
*/
|
*/
|
||||||
async function notifyIfEnabled(key: string, text: string): Promise<void> {
|
/**
|
||||||
|
* A channel whose capture keeps crashing and restarting (see streamIngest's
|
||||||
|
* crash-loop notes) fires "started" + "poll failing" + "poll recovered" over
|
||||||
|
* and over within minutes — observed directly once several channels were
|
||||||
|
* live at once. One shared per-channel cooldown across all three message
|
||||||
|
* types means a flapping channel says its piece once, then goes quiet for a
|
||||||
|
* while instead of narrating every blip.
|
||||||
|
*/
|
||||||
|
const NOTIFY_COOLDOWN_MS = 10 * 60 * 1000;
|
||||||
|
const lastNotifyAt = new Map<string, number>();
|
||||||
|
|
||||||
|
async function notifyIfEnabled(key: string, channelId: string, text: string): Promise<void> {
|
||||||
|
const last = lastNotifyAt.get(channelId) ?? 0;
|
||||||
|
if (Date.now() - last < NOTIFY_COOLDOWN_MS) return;
|
||||||
|
|
||||||
const setting = await prisma.appSetting.findUnique({ where: { key } });
|
const setting = await prisma.appSetting.findUnique({ where: { key } });
|
||||||
if (setting?.value !== "false") await sendTelegramMessage(text);
|
if (setting?.value === "false") return;
|
||||||
|
|
||||||
|
lastNotifyAt.set(channelId, Date.now());
|
||||||
|
await sendTelegramMessage(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findLiveVideoId(channelId: string, channelName: string): Promise<string | null> {
|
async function findLiveVideoId(channelId: string, channelName: string, channelDbId: string): Promise<string | null> {
|
||||||
const liveUrl = `https://www.youtube.com/channel/${channelId}/live`;
|
const liveUrl = `https://www.youtube.com/channel/${channelId}/live`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -70,7 +87,7 @@ async function findLiveVideoId(channelId: string, channelName: string): Promise<
|
|||||||
// this runs on every successful poll, most of which were never broken.
|
// this runs on every successful poll, most of which were never broken.
|
||||||
if (lastPollErrors.has(channelId)) {
|
if (lastPollErrors.has(channelId)) {
|
||||||
lastPollErrors.delete(channelId);
|
lastPollErrors.delete(channelId);
|
||||||
await notifyIfEnabled("notify_poll_error", `✅ *${channelName}* canlı-tespiti tekrar çalışıyor.`);
|
await notifyIfEnabled("notify_poll_error", channelDbId, `✅ *${channelName}* canlı-tespiti tekrar çalışıyor.`);
|
||||||
}
|
}
|
||||||
const videoId = stdout.trim().split("\n")[0];
|
const videoId = stdout.trim().split("\n")[0];
|
||||||
return videoId || null;
|
return videoId || null;
|
||||||
@@ -86,6 +103,7 @@ async function findLiveVideoId(channelId: string, channelName: string): Promise<
|
|||||||
if (!wasAlreadyFailing) {
|
if (!wasAlreadyFailing) {
|
||||||
await notifyIfEnabled(
|
await notifyIfEnabled(
|
||||||
"notify_poll_error",
|
"notify_poll_error",
|
||||||
|
channelDbId,
|
||||||
`⚠️ *${channelName}* canlı-tespiti başarısız oluyor:\n\`${message.slice(0, 300)}\``,
|
`⚠️ *${channelName}* canlı-tespiti başarısız oluyor:\n\`${message.slice(0, 300)}\``,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -124,7 +142,7 @@ export async function startCaptureSession(
|
|||||||
segmentTimeSec: channel.segmentTimeSec ?? undefined,
|
segmentTimeSec: channel.segmentTimeSec ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await notifyIfEnabled("notify_stream_start", `🔴 *${channel.name}* canlıya geçti, kayıt başlatılıyor.`);
|
await notifyIfEnabled("notify_stream_start", channel.id, `🔴 *${channel.name}* canlıya geçti, kayıt başlatılıyor.`);
|
||||||
console.log(`[youtube-polling] started session ${session.id} for channel ${channel.name}`);
|
console.log(`[youtube-polling] started session ${session.id} for channel ${channel.name}`);
|
||||||
return { started: true };
|
return { started: true };
|
||||||
}
|
}
|
||||||
@@ -139,7 +157,7 @@ export async function checkChannel(channel: {
|
|||||||
liveVideoId: string | null;
|
liveVideoId: string | null;
|
||||||
error: PollError | null;
|
error: PollError | null;
|
||||||
}> {
|
}> {
|
||||||
const liveVideoId = await findLiveVideoId(channel.channelId, channel.name);
|
const liveVideoId = await findLiveVideoId(channel.channelId, channel.name, channel.id);
|
||||||
await prisma.channel.update({
|
await prisma.channel.update({
|
||||||
where: { id: channel.id },
|
where: { id: channel.id },
|
||||||
data: { lastCheckedAt: new Date() },
|
data: { lastCheckedAt: new Date() },
|
||||||
|
|||||||
Reference in New Issue
Block a user