Merge pull request #2 from yusufipk/claude/vanity-handle-resolve

Follow the redirect when a handle resolves to a legacy vanity address
This commit is contained in:
2026-08-22 15:37:12 +03:00
committed by GitHub
3 changed files with 60 additions and 27 deletions
+21 -10
View File
@@ -107,20 +107,31 @@
} }
/* 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) || pageChannel(); 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;
+32 -17
View File
@@ -268,32 +268,47 @@ var OBTube = (function () {
return post("browse", { browseId: channelId, params: params }).then(round); return post("browse", { browseId: channelId, params: params }).then(round);
} }
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 (/^UC[\w-]{22}$/.test(id)) return id;
var next = (P.findFirst(data, "urlEndpoint") || {}).url || "";
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. */ /* @handle veya kanal adresini UC... kimliğine çevirir. */
function resolveChannel(raw) { function resolveChannel(raw) {
raw = (raw || "").trim(); raw = (raw || "").trim();
if (!raw) return Promise.reject(new Error("boş kanal adresi")); if (!raw) return Promise.reject(new Error("boş kanal adresi"));
if (/^UC[\w-]{22}$/.test(raw)) return Promise.resolve(raw); if (/^UC[\w-]{22}$/.test(raw)) return Promise.resolve(raw);
if (raw.indexOf("/channel/") >= 0) { var direct = channelIdIn(raw);
var tail = raw.split("/channel/")[1].split("/")[0].split("?")[0]; if (direct) return Promise.resolve(direct);
if (tail.indexOf("UC") === 0) return Promise.resolve(tail);
}
var url = raw; var url = raw;
if (url.indexOf("http") !== 0) { if (url.indexOf("http") !== 0) {
url = "https://www.youtube.com/" + (url.charAt(0) === "@" ? url : "@" + url.replace(/^\/+/, "")); url = "https://www.youtube.com/" + (url.charAt(0) === "@" ? url : "@" + url.replace(/^\/+/, ""));
} }
return post("navigation/resolve_url", { url: url }).then(function (data) { return resolveStep(url, raw, 0);
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. */
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;
}
throw new Error("kanal çözülemedi: " + raw);
});
} }
/* 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ı
+7
View File
@@ -120,6 +120,13 @@ 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.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") /* 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 * YouTube tarafında ayrıcalıklı davranıp uç bozulduğunda bile yanıt
* verebiliyor ve testi yanlış yere yeşil gösteriyordu. */ * verebiliyor ve testi yanlış yere yeşil gösteriyordu. */