Files
YouTubeOutlierBadge/test/check.js
T
yusufipek 0d5a232f08 Outlier score badge on YouTube thumbnails
Scores every card against the median views of its own channel's last N
videos, badging the thumbnail above the duration stamp. Shorts and regular
videos are scored in separate pools, live streams in none. Baselines come
from YouTube's InnerTube endpoint, one request per channel, cached a day.
Interface in English and Turkish.
2026-08-15 10:32:29 +03:00

131 lines
5.9 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.
/* Eklenti mantığının Node altında doğrulanması: ayrıştırma ve skor birim
* testleriyle, InnerTube tarafı gerçek bir kanal çağrısıyla. */
const fs = require("fs");
const vm = require("vm");
const path = require("path");
const dir = path.join(__dirname, "..", "src");
const sandbox = {
console,
fetch,
navigator: { language: "tr-TR" },
document: { querySelectorAll: () => [], documentElement: { lang: "en" } },
setTimeout,
Promise,
Date,
Math,
JSON,
Object,
Array,
parseInt,
parseFloat,
isNaN,
String
};
vm.createContext(sandbox);
for (const f of ["i18n.js", "parse.js", "innertube.js", "score.js"]) {
vm.runInContext(fs.readFileSync(path.join(dir, f), "utf8"), sandbox, { filename: f });
}
const { OBParse, OBTube, OBI18n } = sandbox;
OBI18n.init("tr");
let fails = 0;
function eq(actual, expected, label) {
const ok = typeof actual === "number" && typeof expected === "number"
? Math.abs(actual - expected) < 1e-9
: actual === expected;
if (!ok) fails++;
console.log((ok ? "ok " : "FAIL ") + label + " -> " + actual + (ok ? "" : " (beklenen " + expected + ")"));
}
console.log("--- sayı ayrıştırma (InnerTube, hep İngilizce) ---");
eq(OBParse.parseCountEn("1.2M views"), 1200000, "1.2M views");
eq(OBParse.parseCountEn("341,331 views"), 341331, "341,331 views");
eq(OBParse.parseCountEn("24K views"), 24000, "24K views");
eq(OBParse.parseCountEn("1.4B views"), 1400000000, "1.4B views");
eq(OBParse.parseCountEn("No views"), null, "No views");
eq(OBParse.parseCountEn("2 months ago"), 2, "yaş metni sayı vermemeli (2 döner, filtre dışarıda)");
console.log("--- sayı ayrıştırma (sayfa DOM'u, arayüz dili) ---");
eq(OBParse.parseCountUI("24 B görüntüleme", "tr"), 24000, "tr: 24 B = 24 bin");
eq(OBParse.parseCountUI("1,3 Mn görüntüleme", "tr"), 1300000, "tr: 1,3 Mn");
eq(OBParse.parseCountUI("2,1 Mr görüntüleme", "tr"), 2100000000, "tr: 2,1 Mr");
eq(OBParse.parseCountUI("1.234.567 görüntüleme", "tr"), 1234567, "tr: tam sayı");
eq(OBParse.parseCountUI("1.2M views", "en"), 1200000, "en: 1.2M");
eq(OBParse.parseCountUI("24B views", "en"), 24000000000, "en: B = milyar");
console.log("--- yaş ---");
eq(Math.round(OBParse.parseAgeDays("3 months ago")), 91, "3 months ago");
eq(OBParse.parseAgeDays("2 days ago"), 2, "2 days ago");
eq(OBParse.parseAgeDays("3 ay önce"), 91.32, "3 ay önce");
eq(OBParse.parseAgeDays("5 saat önce"), 5 / 24, "5 saat önce");
eq(OBParse.parseAgeDays("Streamed 2 weeks ago"), 14, "Streamed 2 weeks ago");
console.log("--- medyan ve gösterim ---");
eq(OBParse.median([3, 1, 2]), 2, "medyan tek sayıda");
eq(OBParse.median([4, 1, 2, 3]), 2.5, "medyan çift sayıda");
eq(OBParse.humanCount(1234567), "1,2 Mn", "humanCount milyon (tr)");
eq(OBParse.humanCount(24000), "24 B", "humanCount bin (tr)");
eq(OBParse.humanCount(965400), "965 B", "humanCount yüz binler (tr)");
console.log("--- dil ---");
OBI18n.init("en");
eq(OBParse.humanCount(1234567), "1.2M", "humanCount milyon (en)");
eq(OBParse.humanCount(24000), "24K", "humanCount bin (en)");
eq(OBI18n.num(2.4, 1), "2.4", "en ondalık nokta");
eq(OBI18n.t("badgeViews", "1.2M"), "Views: 1.2M", "en metin");
OBI18n.init("tr");
eq(OBI18n.num(2.4, 1), "2,4", "tr ondalık virgül");
eq(OBI18n.t("badgeViews", "1,2 Mn"), "İzlenme: 1,2 Mn", "tr metin");
eq(OBI18n.t("badgeMedian", "45 B", 27, "video"),
"Kanal medyanı: 45 B (son 27 video)", "tr yer tutuculu metin");
console.log("--- skor ---");
const { OBScore } = sandbox;
const pool = { video: [], short: null };
[100, 200, 300, 400, 500].forEach((v, i) => pool.video.push({ id: "v" + i, views: v * 1000, age: 10 }));
pool.video.push({ id: "hit", views: 1500000, age: 3 });
const r = OBScore.score("hit", 1500000, false, pool);
eq(r.median, 300000, "medyan kendisi hariç (100..500 bin)");
eq(r.score, 5, "skor 1.5M / 300K");
eq(r.sample, 5, "örnek sayısı");
eq(r.percentile, 1, "hepsinden yüksek");
const low = OBScore.score("v0", 100000, false, pool);
/* v0 çıkınca kalan [200,300,400,500,1500] bin -> medyan 400 bin */
eq(low.median, 400000, "kendisi çıkınca medyan kayar");
const tiny = OBScore.score("x", 5000, false, { video: [{ id: "a", views: 1 }, { id: "b", views: 2 }] });
eq(tiny.score, null, "3'ten az örnek: skor yok");
console.log("\n--- gerçek InnerTube çağrısı ---");
const CHANNEL = process.argv[2] || "UCXuqSBlHAE6Xw-yeJA0Tunw"; /* Linus Tech Tips */
OBTube.channelTab(CHANNEL, "videos", 30).then((items) => {
console.log("videos sekmesi: " + items.length + " video");
const withViews = items.filter((v) => v.views);
console.log("izlenmesi çözülen: " + withViews.length);
console.log("canlı yayın işaretlenen: " + items.filter((v) => v.wasLive).length);
console.log("üyelere özel: " + items.filter((v) => v.membersOnly).length);
for (const v of items.slice(0, 3)) {
console.log(" " + v.videoId + " | " + OBParse.humanCount(v.views) + " | " +
(v.ageDays == null ? "?" : v.ageDays.toFixed(1) + " gün") + " | " + v.title.slice(0, 50));
}
const views = withViews.map((v) => v.views);
console.log("medyan: " + OBParse.humanCount(OBParse.median(views)));
if (withViews.length < 10) { fails++; console.log("FAIL izlenmesi çözülen video sayısı çok düşük"); }
return OBTube.channelTab(CHANNEL, "shorts", 30);
}).then((shorts) => {
console.log("shorts sekmesi: " + shorts.length + " video, izlenmesi çözülen: " +
shorts.filter((v) => v.views).length);
return OBTube.resolveChannel("@LinusTechTips");
}).then((id) => {
eq(id, CHANNEL, "handle -> kanal kimliği");
return OBTube.videoDetails("dQw4w9WgXcQ");
}).then((d) => {
console.log("player ucu: kanal " + d.channelId + ", izlenme " + OBParse.humanCount(d.views));
if (!d.channelId) { fails++; console.log("FAIL player ucu kanal kimliği vermedi"); }
console.log(fails ? "\n" + fails + " test başarısız" : "\nhepsi geçti");
process.exit(fails ? 1 : 0);
}).catch((e) => {
console.log("HATA: " + e.message);
process.exit(1);
});