diff --git a/src/content.js b/src/content.js index 0b14998..78732d5 100644 --- a/src/content.js +++ b/src/content.js @@ -91,11 +91,26 @@ /* --- Kanal kimliği --------------------------------------------------- */ + /* Kanal sayfasındaki kartlarda kanal bağlantısı hiç bulunmuyor: zaten o + * kanaldasın, YouTube adı kartta tekrar etmiyor. O kartların kanalı sayfanın + * kendi adresidir, video başına ayrı bir istek atmaya gerek yok. */ + function pageChannel() { + var path = location.pathname; + var m = /^\/channel\/(UC[\w-]{22})(?:\/|$)/.exec(path); + if (m) return { channelId: m[1] }; + m = /^\/(@[\w.\-]+)(?:\/|$)/.exec(path); + if (m) return { handle: m[1] }; + /* Eski /c/ ve /user/ adresleri: çözüm için tam adres gerekiyor. */ + m = /^\/((?:c|user)\/[^/]+)(?:\/|$)/.exec(path); + if (m) return { handle: location.origin + "/" + m[1] }; + return null; + } + /* Handle'dan kanal kimliğine çözüm kalıcıdır (değişmez), o yüzden TTL'siz * saklanır. İzleme sayfasının yan listesinde kartta kanal bağlantısı hiç * olmayabiliyor; orada son çare videonun kendi ucundan sorulur. */ function channelIdOf(card, videoId) { - var found = handleOf(card); + var found = handleOf(card) || pageChannel(); if (found && found.channelId) return Promise.resolve(found.channelId); if (found && found.handle) { return OBStore.readChannelId(found.handle).then(function (cached) { @@ -166,6 +181,16 @@ return m ? m[1] : null; } + /* Videonun süresi InnerTube'un next yanıtında yok, sayfanın oynatıcısından + * okunuyor. Okunamazsa normal video sayılır: /watch adresinde açılan bir + * Short nadir, yanlış havuzda puanlamaktansa gecikmeli doğruyu beklemek + * anlamsız olurdu. */ + function watchIsShort() { + if (location.pathname.indexOf("/shorts/") === 0) return true; + var v = document.querySelector("#movie_player video"); + return !!(v && isFinite(v.duration) && v.duration > 0 && v.duration <= 60); + } + function updatePanel() { if (!settings || !settings.enabled || !settings.showPanel) { OBPanel.remove(); return; } var videoId = currentWatchId(); @@ -179,7 +204,7 @@ details = d; if (!d.channelId) throw new Error("kanal bulunamadı"); OBStore.writeChannelId("v:" + videoId, d.channelId); - var isShort = d.lengthSeconds != null && d.lengthSeconds <= 60; + var isShort = watchIsShort(); return OBScore.baseline(d.channelId, isShort).then(function (blob) { return { blob: blob, isShort: isShort }; }); diff --git a/src/innertube.js b/src/innertube.js index ec0fc91..288d965 100644 --- a/src/innertube.js +++ b/src/innertube.js @@ -297,16 +297,30 @@ var OBTube = (function () { } /* Videonun kanal kimliği ve tam izlenme sayısı. Kartta kanal bağlantısı - * bulunamadığında son çare olarak kullanılır: video başına bir istek. */ + * bulunamadığında son çare olarak kullanılır: video başına bir istek. + * + * Buna `player` ucu daha uygun görünür ve bir zamanlar öyleydi; oturum + * bilgisi göndermediğimiz için artık videoların büyük çoğunluğunda + * LOGIN_REQUIRED ("Sign in to confirm you're not a bot") dönüyor ve + * videoDetails hiç gelmiyor. `next` aynı iki alanı oturumsuz da veriyor. + * Yanıt büyük olduğu için bu uç bilerek son çare, ilk seçenek değil. */ function videoDetails(videoId) { - return post("player", { videoId: videoId }).then(function (data) { - var d = data.videoDetails || {}; + return post("next", { videoId: videoId }).then(function (data) { + var owner = P.findFirst(data, "videoOwnerRenderer") || {}; + var endpoint = P.findFirst(owner, "browseEndpoint") || {}; + var channelId = endpoint.browseId || ""; + var counter = P.findFirst(data, "videoViewCountRenderer") || {}; + /* viewCount tam sayıyı verir ("63,087 views"); yoksa kısaltılmışa düş. + * Süren canlı yayında aynı alan "12,345 watching now" der; bu izlenme + * değil anlık izleyicidir, sayı sanılırsa skor uydurma çıkar. */ + var countText = P.textOf(counter.viewCount) || P.textOf(counter.shortViewCount); + var views = /watching/i.test(countText) ? null : P.parseCountEn(countText); + var primary = P.findFirst(data, "videoPrimaryInfoRenderer") || {}; return { - channelId: d.channelId || null, - views: d.viewCount ? parseInt(d.viewCount, 10) : null, - title: d.title || "", - lengthSeconds: d.lengthSeconds ? parseInt(d.lengthSeconds, 10) : null, - isLive: !!d.isLiveContent + channelId: /^UC[\w-]{22}$/.test(channelId) ? channelId : null, + views: views, + title: P.textOf(primary.title), + publishedText: P.textOf(primary.dateText) }; }); } diff --git a/test/check.js b/test/check.js index e93d48b..1baccf2 100644 --- a/test/check.js +++ b/test/check.js @@ -98,6 +98,7 @@ 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 */ +let probe = null; /* videoDetails'ı sınamak için kanalın güncel bir videosu */ OBTube.channelTab(CHANNEL, "videos", 30).then((items) => { console.log("videos sekmesi: " + items.length + " video"); const withViews = items.filter((v) => v.views); @@ -108,6 +109,7 @@ OBTube.channelTab(CHANNEL, "videos", 30).then((items) => { console.log(" " + v.videoId + " | " + OBParse.humanCount(v.views) + " | " + (v.ageDays == null ? "?" : v.ageDays.toFixed(1) + " gün") + " | " + v.title.slice(0, 50)); } + probe = withViews.length ? withViews[0] : null; 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"); } @@ -118,10 +120,15 @@ OBTube.channelTab(CHANNEL, "videos", 30).then((items) => { return OBTube.resolveChannel("@LinusTechTips"); }).then((id) => { eq(id, CHANNEL, "handle -> kanal kimliği"); - return OBTube.videoDetails("dQw4w9WgXcQ"); + /* Bilerek kanalın güncel bir videosu: sabit bir klasik ("dQw4w9WgXcQ") + * YouTube tarafında ayrıcalıklı davranıp uç bozulduğunda bile yanıt + * verebiliyor ve testi yanlış yere yeşil gösteriyordu. */ + if (!probe) { fails++; console.log("FAIL sınanacak video bulunamadı"); return {}; } + return OBTube.videoDetails(probe.videoId); }).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("video ucu: kanal " + d.channelId + ", izlenme " + OBParse.humanCount(d.views)); + eq(d.channelId, CHANNEL, "video ucu kanal kimliği"); + if (!d.views) { fails++; console.log("FAIL video ucu izlenme vermedi"); } console.log(fails ? "\n" + fails + " test başarısız" : "\nhepsi geçti"); process.exit(fails ? 1 : 0); }).catch((e) => {