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,
|
||||
"name": "__MSG_extName__",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "__MSG_extDescription__",
|
||||
"default_locale": "en",
|
||||
"browser_specific_settings": {
|
||||
|
||||
+48
-12
@@ -91,21 +91,47 @@
|
||||
|
||||
/* --- 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ç
|
||||
* 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. */
|
||||
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) {
|
||||
if (cached) return cached;
|
||||
return OBTube.resolveChannel(found.handle).then(function (id) {
|
||||
OBStore.writeChannelId(found.handle, id);
|
||||
return id;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (found && found.handle) return resolveHandle(found.handle);
|
||||
var key = "v:" + videoId;
|
||||
return OBStore.readChannelId(key).then(function (cached) {
|
||||
if (cached) return cached;
|
||||
@@ -166,6 +192,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 +215,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 };
|
||||
});
|
||||
|
||||
+56
-27
@@ -268,45 +268,74 @@ var OBTube = (function () {
|
||||
return post("browse", { browseId: channelId, params: params }).then(round);
|
||||
}
|
||||
|
||||
/* @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);
|
||||
if (raw.indexOf("/channel/") >= 0) {
|
||||
var tail = raw.split("/channel/")[1].split("/")[0].split("?")[0];
|
||||
if (tail.indexOf("UC") === 0) return Promise.resolve(tail);
|
||||
}
|
||||
var url = raw;
|
||||
if (url.indexOf("http") !== 0) {
|
||||
url = "https://www.youtube.com/" + (url.charAt(0) === "@" ? url : "@" + url.replace(/^\/+/, ""));
|
||||
}
|
||||
function channelIdIn(url) {
|
||||
if (!url || url.indexOf("/channel/") < 0) return null;
|
||||
var tail = url.split("/channel/")[1].split("/")[0].split("?")[0];
|
||||
return /^UC[\w-]{22}$/.test(tail) ? tail : null;
|
||||
}
|
||||
|
||||
/* Tek istekte bitmeyebilir: eski özel adresi olan kanallarda YouTube
|
||||
* @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
|
||||
* döngüye girmeyelim hem de tek kart için istek yağdırmayalım. */
|
||||
var MAX_RESOLVE_HOPS = 3;
|
||||
|
||||
function resolveStep(url, raw, hops) {
|
||||
return post("navigation/resolve_url", { url: url }).then(function (data) {
|
||||
var endpoint = P.findFirst(data, "browseEndpoint") || {};
|
||||
var id = endpoint.browseId || "";
|
||||
if (id.indexOf("UC") === 0) return id;
|
||||
/* Eski /user/ adresi olan kanallarda YouTube browseEndpoint yerine
|
||||
* urlEndpoint ile başka bir adrese yolluyor. */
|
||||
if (/^UC[\w-]{22}$/.test(id)) return id;
|
||||
var next = (P.findFirst(data, "urlEndpoint") || {}).url || "";
|
||||
if (next.indexOf("/channel/") >= 0) {
|
||||
var t = next.split("/channel/")[1].split("/")[0].split("?")[0];
|
||||
if (t.indexOf("UC") === 0) return t;
|
||||
var direct = channelIdIn(next);
|
||||
if (direct) return direct;
|
||||
/* 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);
|
||||
});
|
||||
}
|
||||
|
||||
/* @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ı
|
||||
* 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)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
+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ı ---");
|
||||
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,22 @@ OBTube.channelTab(CHANNEL, "videos", 30).then((items) => {
|
||||
return OBTube.resolveChannel("@LinusTechTips");
|
||||
}).then((id) => {
|
||||
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) => {
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user