mirror of
https://github.com/yusufipk/YouTubeOutlierBadge.git
synced 2026-09-12 03:06:13 +00:00
Compare commits
6
Commits
522998a73e
...
5e35cf1074
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e35cf1074 | ||
|
|
aee1748aba | ||
|
|
079513d5bf | ||
|
|
1e1c092055 | ||
|
|
51a83a7e96 | ||
|
|
3e70662ef8 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_extName__",
|
"name": "__MSG_extName__",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"description": "__MSG_extDescription__",
|
"description": "__MSG_extDescription__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
|
|||||||
+48
-12
@@ -91,21 +91,47 @@
|
|||||||
|
|
||||||
/* --- 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. Arama sonuçlarında aynı kanaldan onlarca kart aynı anda ekrana
|
||||||
|
* giriyor ve depo okuması asenkron olduğu için hepsi önbelleği ıskalayıp
|
||||||
|
* aynı çözümü ayrı ayrı isterdi; devam eden istek paylaşılıyor. */
|
||||||
|
var resolving = {};
|
||||||
|
|
||||||
|
function resolveHandle(handle) {
|
||||||
|
if (resolving[handle]) return resolving[handle];
|
||||||
|
var promise = OBStore.readChannelId(handle).then(function (cached) {
|
||||||
|
if (cached) return cached;
|
||||||
|
return OBTube.resolveChannel(handle).then(function (id) {
|
||||||
|
OBStore.writeChannelId(handle, id);
|
||||||
|
return id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
resolving[handle] = promise;
|
||||||
|
promise.catch(function () {}).then(function () { delete resolving[handle]; });
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* İ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 resolveHandle(found.handle);
|
||||||
return OBStore.readChannelId(found.handle).then(function (cached) {
|
|
||||||
if (cached) return cached;
|
|
||||||
return OBTube.resolveChannel(found.handle).then(function (id) {
|
|
||||||
OBStore.writeChannelId(found.handle, id);
|
|
||||||
return id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var key = "v:" + videoId;
|
var key = "v:" + videoId;
|
||||||
return OBStore.readChannelId(key).then(function (cached) {
|
return OBStore.readChannelId(key).then(function (cached) {
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
@@ -166,6 +192,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 +215,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 };
|
||||||
});
|
});
|
||||||
|
|||||||
+56
-27
@@ -268,45 +268,74 @@ var OBTube = (function () {
|
|||||||
return post("browse", { browseId: channelId, params: params }).then(round);
|
return post("browse", { browseId: channelId, params: params }).then(round);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @handle veya kanal adresini UC... kimliğine çevirir. */
|
function channelIdIn(url) {
|
||||||
function resolveChannel(raw) {
|
if (!url || url.indexOf("/channel/") < 0) return null;
|
||||||
raw = (raw || "").trim();
|
var tail = url.split("/channel/")[1].split("/")[0].split("?")[0];
|
||||||
if (!raw) return Promise.reject(new Error("boş kanal adresi"));
|
return /^UC[\w-]{22}$/.test(tail) ? tail : null;
|
||||||
if (/^UC[\w-]{22}$/.test(raw)) return Promise.resolve(raw);
|
}
|
||||||
if (raw.indexOf("/channel/") >= 0) {
|
|
||||||
var tail = raw.split("/channel/")[1].split("/")[0].split("?")[0];
|
/* Tek istekte bitmeyebilir: eski özel adresi olan kanallarda YouTube
|
||||||
if (tail.indexOf("UC") === 0) return Promise.resolve(tail);
|
* @handle'ı önce youtube.com/eskiad adresine yolluyor, kanal kimliği ancak
|
||||||
}
|
* bir sonraki adımda geliyor. Zincir kısa, sıçrama sayısı sınırlı: hem
|
||||||
var url = raw;
|
* döngüye girmeyelim hem de tek kart için istek yağdırmayalım. */
|
||||||
if (url.indexOf("http") !== 0) {
|
var MAX_RESOLVE_HOPS = 3;
|
||||||
url = "https://www.youtube.com/" + (url.charAt(0) === "@" ? url : "@" + url.replace(/^\/+/, ""));
|
|
||||||
}
|
function resolveStep(url, raw, hops) {
|
||||||
return post("navigation/resolve_url", { url: url }).then(function (data) {
|
return post("navigation/resolve_url", { url: url }).then(function (data) {
|
||||||
var endpoint = P.findFirst(data, "browseEndpoint") || {};
|
var endpoint = P.findFirst(data, "browseEndpoint") || {};
|
||||||
var id = endpoint.browseId || "";
|
var id = endpoint.browseId || "";
|
||||||
if (id.indexOf("UC") === 0) return id;
|
if (/^UC[\w-]{22}$/.test(id)) return id;
|
||||||
/* Eski /user/ adresi olan kanallarda YouTube browseEndpoint yerine
|
|
||||||
* urlEndpoint ile başka bir adrese yolluyor. */
|
|
||||||
var next = (P.findFirst(data, "urlEndpoint") || {}).url || "";
|
var next = (P.findFirst(data, "urlEndpoint") || {}).url || "";
|
||||||
if (next.indexOf("/channel/") >= 0) {
|
var direct = channelIdIn(next);
|
||||||
var t = next.split("/channel/")[1].split("/")[0].split("?")[0];
|
if (direct) return direct;
|
||||||
if (t.indexOf("UC") === 0) return t;
|
/* Sadece YouTube içinde kalan yönlendirmeler izlenir. */
|
||||||
|
var internal = /^https?:\/\/(www\.)?youtube\.com\//.test(next);
|
||||||
|
if (internal && next !== url && hops < MAX_RESOLVE_HOPS) {
|
||||||
|
return resolveStep(next, raw, hops + 1);
|
||||||
}
|
}
|
||||||
throw new Error("kanal çözülemedi: " + raw);
|
throw new Error("kanal çözülemedi: " + raw);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @handle veya kanal adresini UC... kimliğine çevirir. */
|
||||||
|
function resolveChannel(raw) {
|
||||||
|
raw = (raw || "").trim();
|
||||||
|
if (!raw) return Promise.reject(new Error("boş kanal adresi"));
|
||||||
|
if (/^UC[\w-]{22}$/.test(raw)) return Promise.resolve(raw);
|
||||||
|
var direct = channelIdIn(raw);
|
||||||
|
if (direct) return Promise.resolve(direct);
|
||||||
|
var url = raw;
|
||||||
|
if (url.indexOf("http") !== 0) {
|
||||||
|
url = "https://www.youtube.com/" + (url.charAt(0) === "@" ? url : "@" + url.replace(/^\/+/, ""));
|
||||||
|
}
|
||||||
|
return resolveStep(url, raw, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* 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
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-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,22 @@ 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");
|
return OBTube.resolveChannel("@MesutCevik");
|
||||||
|
}).then((id) => {
|
||||||
|
/* Eski özel adresi olan bir kanal: YouTube @handle'ı önce
|
||||||
|
* youtube.com/mesutcevik adresine yolluyor, kimlik ancak ikinci adımda
|
||||||
|
* geliyor. Tek adımda çözen kod bu kanallarda "kanal çözülemedi" veriyor ve
|
||||||
|
* o kanalın hiçbir kartı puanlanmıyordu. */
|
||||||
|
eq(id, "UCOFafpmI_dt8SxisbKniN4A", "yönlendirmeli handle -> kanal kimliği");
|
||||||
|
/* 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