mirror of
https://github.com/yusufipk/YouTubeOutlierBadge.git
synced 2026-09-11 10:56:14 +00:00
Score cards and the watch panel again after the player endpoint closed
YouTube now answers the InnerTube `player` endpoint with LOGIN_REQUIRED for requests that carry no session, and the extension deliberately sends none. `videoDetails` therefore returned nothing for almost every video, which broke two things at once: cards on channel pages, where YouTube omits the channel link because you are already on the channel, and the watch page panel, which starts every render with that same call. `next` returns the channel id and the exact view count without a session, so videoDetails reads from there instead. A live stream reports "watching now" in the same field, so that text is filtered out rather than read as a view count. Channel pages no longer need the fallback at all: the cards belong to the channel in the address bar, so pageChannel() reads it from the URL. That also drops one request per card. The panel took the video length from the player response to tell Shorts apart; `next` does not carry it, so it comes from the page's own player element. test/check.js probed videoDetails with dQw4w9WgXcQ, which keeps answering even on the broken endpoint and kept the suite green through all of this. It now probes a current video from the channel it just listed, and asserts the view count as well as the channel id.
This commit is contained in:
+27
-2
@@ -91,11 +91,26 @@
|
|||||||
|
|
||||||
/* --- Kanal kimliği --------------------------------------------------- */
|
/* --- 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
|
/* 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ç
|
* saklanır. İzleme sayfasının yan listesinde kartta kanal bağlantısı hiç
|
||||||
* olmayabiliyor; orada son çare videonun kendi ucundan sorulur. */
|
* olmayabiliyor; orada son çare videonun kendi ucundan sorulur. */
|
||||||
function channelIdOf(card, videoId) {
|
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.channelId) return Promise.resolve(found.channelId);
|
||||||
if (found && found.handle) {
|
if (found && found.handle) {
|
||||||
return OBStore.readChannelId(found.handle).then(function (cached) {
|
return OBStore.readChannelId(found.handle).then(function (cached) {
|
||||||
@@ -166,6 +181,16 @@
|
|||||||
return m ? m[1] : null;
|
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() {
|
function updatePanel() {
|
||||||
if (!settings || !settings.enabled || !settings.showPanel) { OBPanel.remove(); return; }
|
if (!settings || !settings.enabled || !settings.showPanel) { OBPanel.remove(); return; }
|
||||||
var videoId = currentWatchId();
|
var videoId = currentWatchId();
|
||||||
@@ -179,7 +204,7 @@
|
|||||||
details = d;
|
details = d;
|
||||||
if (!d.channelId) throw new Error("kanal bulunamadı");
|
if (!d.channelId) throw new Error("kanal bulunamadı");
|
||||||
OBStore.writeChannelId("v:" + videoId, d.channelId);
|
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 OBScore.baseline(d.channelId, isShort).then(function (blob) {
|
||||||
return { blob: blob, isShort: isShort };
|
return { blob: blob, isShort: isShort };
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-8
@@ -297,16 +297,30 @@ var OBTube = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Videonun kanal kimliği ve tam izlenme sayısı. Kartta kanal bağlantısı
|
/* 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) {
|
function videoDetails(videoId) {
|
||||||
return post("player", { videoId: videoId }).then(function (data) {
|
return post("next", { videoId: videoId }).then(function (data) {
|
||||||
var d = data.videoDetails || {};
|
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 {
|
return {
|
||||||
channelId: d.channelId || null,
|
channelId: /^UC[\w-]{22}$/.test(channelId) ? channelId : null,
|
||||||
views: d.viewCount ? parseInt(d.viewCount, 10) : null,
|
views: views,
|
||||||
title: d.title || "",
|
title: P.textOf(primary.title),
|
||||||
lengthSeconds: d.lengthSeconds ? parseInt(d.lengthSeconds, 10) : null,
|
publishedText: P.textOf(primary.dateText)
|
||||||
isLive: !!d.isLiveContent
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -98,6 +98,7 @@ eq(tiny.score, null, "3'ten az örnek: skor yok");
|
|||||||
|
|
||||||
console.log("\n--- gerçek InnerTube çağrısı ---");
|
console.log("\n--- gerçek InnerTube çağrısı ---");
|
||||||
const CHANNEL = process.argv[2] || "UCXuqSBlHAE6Xw-yeJA0Tunw"; /* Linus Tech Tips */
|
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) => {
|
OBTube.channelTab(CHANNEL, "videos", 30).then((items) => {
|
||||||
console.log("videos sekmesi: " + items.length + " video");
|
console.log("videos sekmesi: " + items.length + " video");
|
||||||
const withViews = items.filter((v) => v.views);
|
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) + " | " +
|
console.log(" " + v.videoId + " | " + OBParse.humanCount(v.views) + " | " +
|
||||||
(v.ageDays == null ? "?" : v.ageDays.toFixed(1) + " gün") + " | " + v.title.slice(0, 50));
|
(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);
|
const views = withViews.map((v) => v.views);
|
||||||
console.log("medyan: " + OBParse.humanCount(OBParse.median(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"); }
|
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");
|
return OBTube.resolveChannel("@LinusTechTips");
|
||||||
}).then((id) => {
|
}).then((id) => {
|
||||||
eq(id, CHANNEL, "handle -> kanal kimliği");
|
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) => {
|
}).then((d) => {
|
||||||
console.log("player ucu: kanal " + d.channelId + ", izlenme " + OBParse.humanCount(d.views));
|
console.log("video ucu: kanal " + d.channelId + ", izlenme " + OBParse.humanCount(d.views));
|
||||||
if (!d.channelId) { fails++; console.log("FAIL player ucu kanal kimliği vermedi"); }
|
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");
|
console.log(fails ? "\n" + fails + " test başarısız" : "\nhepsi geçti");
|
||||||
process.exit(fails ? 1 : 0);
|
process.exit(fails ? 1 : 0);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user