Update tidal/tidal.js
This commit is contained in:
+89
-157
@@ -1,26 +1,31 @@
|
|||||||
async function searchResults(keyword) {
|
async function searchResults(keyword) {
|
||||||
const results = [];
|
const results = [];
|
||||||
try {
|
try {
|
||||||
const response = await fetchv2("https://london.monochrome.tf/search/?s=" + encodeURIComponent(keyword));
|
const response = await fetchv2("https://triton.squid.wtf/search/?s=" + encodeURIComponent(keyword));
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.items && Array.isArray(data.items)) {
|
const items = data.data?.items || data.items || [];
|
||||||
for (const item of data.items) {
|
if (Array.isArray(items)) {
|
||||||
|
for (const item of items) {
|
||||||
|
|
||||||
let imageUrl = "";
|
let imageUrl = "";
|
||||||
if (item.album && item.album.cover) {
|
if (item.album && item.album.cover) {
|
||||||
const cover = item.album.cover.replace(/-/g, '/');
|
const cover = item.album.cover.replace(/-/g, '/');
|
||||||
imageUrl = `https://resources.tidal.com/images/${cover}/1280x1280.jpg`;
|
imageUrl = `https://resources.tidal.com/images/${cover}/1280x1280.jpg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qualityDisplay = getQualityDisplay(item.audioQuality);
|
||||||
|
const title = `${item.title || "Unknown Title"} • ${qualityDisplay}`;
|
||||||
|
const href = `https://hund.qqdl.site/track/?id=${item.id}&quality=${item.audioQuality}`;
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
title: item.title || "Unknown Title",
|
title: title,
|
||||||
image: imageUrl,
|
image: imageUrl,
|
||||||
href: item.id ? item.id.toString() : ""
|
href: href
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify(results);
|
return JSON.stringify(results);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Search error:", err);
|
console.error("Search error:", err);
|
||||||
@@ -32,112 +37,32 @@ async function searchResults(keyword) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractDetails(ID) {
|
function getQualityDisplay(quality) {
|
||||||
const endpoints = [
|
const qualityMap = {
|
||||||
"https://tidal.kinoplus.online/track/",
|
"LOSSLESS": "Hi-Res",
|
||||||
"https://katze.qqdl.site/track/",
|
"HIRES_LOSSLESS": "Hi-Res Lossless",
|
||||||
"https://wolf.qqdl.site/track/",
|
"HIGH": "High",
|
||||||
"https://london.monochrome.tf/track/"
|
"LOW": "Low",
|
||||||
];
|
"DOLBY_ATMOS": "Dolby Atmos"
|
||||||
|
};
|
||||||
const startTime = Date.now();
|
return qualityMap[quality] || quality || "Unknown";
|
||||||
let json = null;
|
|
||||||
|
|
||||||
async function tryEndpoints() {
|
|
||||||
const promises = endpoints.map(async endpoint => {
|
|
||||||
try {
|
|
||||||
const response = await fetchv2(`${endpoint}?id=${encodeURIComponent(ID)}&quality=LOSSLESS`);
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.detail === "Too Many Requests") return null;
|
|
||||||
if (Array.isArray(data) && data.length > 0) return data;
|
|
||||||
return null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = await Promise.allSettled(promises);
|
|
||||||
for (const result of results) {
|
|
||||||
if (result.status === 'fulfilled' && result.value !== null) {
|
|
||||||
return result.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (Date.now() - startTime < 10000) {
|
|
||||||
json = await tryEndpoints();
|
|
||||||
if (json) break;
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!json) {
|
|
||||||
return JSON.stringify([{
|
|
||||||
description: "Error loading track details",
|
|
||||||
aliases: "Error",
|
|
||||||
airdate: "Error"
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let trackInfo = json.find(item => item.duration && item.title);
|
|
||||||
if (!trackInfo) throw new Error("Track info not found");
|
|
||||||
|
|
||||||
const artists = trackInfo.artists ? trackInfo.artists.map(a => a.name).join(", ") : "Unknown Artist";
|
|
||||||
const album = trackInfo.album ? trackInfo.album.title : "Unknown Album";
|
|
||||||
const duration = trackInfo.duration ? Math.floor(trackInfo.duration / 60) + ":" + String(trackInfo.duration % 60).padStart(2, '0') : "Unknown";
|
|
||||||
const audioQuality = trackInfo.audioQuality || "Unknown";
|
|
||||||
const audioModes = trackInfo.audioModes ? trackInfo.audioModes.join(", ") : "STEREO";
|
|
||||||
const copyright = trackInfo.copyright || "";
|
|
||||||
const bpm = trackInfo.bpm ? `${trackInfo.bpm} BPM` : "";
|
|
||||||
const key = trackInfo.key && trackInfo.keyScale ? `${trackInfo.key} ${trackInfo.keyScale}` : "";
|
|
||||||
const explicit = trackInfo.explicit ? "🅴 Explicit" : "";
|
|
||||||
|
|
||||||
let description = `🎵 ${trackInfo.title}\n`;
|
|
||||||
description += `👤 Artist: ${artists}\n`;
|
|
||||||
description += `💿 Album: ${album}\n`;
|
|
||||||
description += `⏱️ Duration: ${duration}\n`;
|
|
||||||
description += `🎧 Quality: ${audioQuality} (${audioModes})\n`;
|
|
||||||
if (trackInfo.popularity) description += `📊 Popularity: ${trackInfo.popularity}%\n`;
|
|
||||||
if (bpm) {
|
|
||||||
description += `🎼 ${bpm}`;
|
|
||||||
if (key) description += ` | Key: ${key}`;
|
|
||||||
description += `\n`;
|
|
||||||
}
|
|
||||||
if (explicit) description += `${explicit}\n`;
|
|
||||||
if (copyright) description += `\n© ${copyright}`;
|
|
||||||
|
|
||||||
const airdate = trackInfo.streamStartDate
|
|
||||||
? new Date(trackInfo.streamStartDate).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
|
|
||||||
: "N/A";
|
|
||||||
|
|
||||||
const featuredArtists = trackInfo.artists
|
|
||||||
? trackInfo.artists.filter(a => a.type === "FEATURED").map(a => a.name).join(", ")
|
|
||||||
: "N/A";
|
|
||||||
|
|
||||||
return JSON.stringify([{
|
|
||||||
description: description.trim(),
|
|
||||||
aliases: featuredArtists || "N/A",
|
|
||||||
airdate: airdate
|
|
||||||
}]);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Extract details error:", err);
|
|
||||||
return JSON.stringify([{
|
|
||||||
description: "Error loading track details",
|
|
||||||
aliases: "Error",
|
|
||||||
airdate: "Error"
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractEpisodes(id) {
|
async function extractDetails(url) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: "",
|
||||||
|
aliases: "",
|
||||||
|
airdate: ""
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
const results = [];
|
const results = [];
|
||||||
try {
|
try {
|
||||||
results.push({
|
results.push({
|
||||||
href: id,
|
href: url,
|
||||||
number: 1
|
number: 1
|
||||||
});
|
});
|
||||||
return JSON.stringify(results);
|
return JSON.stringify(results);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return JSON.stringify([{
|
return JSON.stringify([{
|
||||||
@@ -147,58 +72,65 @@ async function extractEpisodes(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractStreamUrl(ID) {
|
function decodeBase64(str) {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
let result = '';
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
str = str.replace(/=+$/, '');
|
||||||
|
|
||||||
|
while (i < str.length) {
|
||||||
|
const a = chars.indexOf(str.charAt(i++));
|
||||||
|
const b = chars.indexOf(str.charAt(i++));
|
||||||
|
const c = i < str.length ? chars.indexOf(str.charAt(i++)) : 0;
|
||||||
|
const d = i < str.length ? chars.indexOf(str.charAt(i++)) : 0;
|
||||||
|
|
||||||
|
const bitmap = (a << 18) | (b << 12) | (c << 6) | d;
|
||||||
|
|
||||||
|
result += String.fromCharCode((bitmap >> 16) & 255);
|
||||||
|
if (c != 64) result += String.fromCharCode((bitmap >> 8) & 255);
|
||||||
|
if (d != 64) result += String.fromCharCode(bitmap & 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(url) {
|
||||||
try {
|
try {
|
||||||
const endpoints = [
|
const response = await fetchv2(url);
|
||||||
"https://tidal.kinoplus.online/track/",
|
const data = await response.json();
|
||||||
"https://katze.qqdl.site/track/",
|
|
||||||
"https://wolf.qqdl.site/track/",
|
if (!data.data) {
|
||||||
"https://london.monochrome.tf/track/"
|
console.error("No data field in response");
|
||||||
];
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
let json = null;
|
|
||||||
|
|
||||||
async function tryEndpoints() {
|
|
||||||
const promises = endpoints.map(async endpoint => {
|
|
||||||
try {
|
|
||||||
const response = await fetchv2(`${endpoint}?id=${encodeURIComponent(ID)}&quality=LOSSLESS`);
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.detail === "Too Many Requests") return null;
|
|
||||||
if (Array.isArray(data) && data.length > 0) return data;
|
|
||||||
return null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = await Promise.allSettled(promises);
|
|
||||||
for (const result of results) {
|
|
||||||
if (result.status === 'fulfilled' && result.value !== null) {
|
|
||||||
return result.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (Date.now() - startTime < 10000) {
|
|
||||||
json = await tryEndpoints();
|
|
||||||
if (json) break;
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!json) {
|
|
||||||
return "https://error.org/";
|
return "https://error.org/";
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamObj = json.find(item => item.OriginalTrackUrl);
|
if (!data.data.manifest) {
|
||||||
if (streamObj && streamObj.OriginalTrackUrl) {
|
console.error("No manifest field in response");
|
||||||
return streamObj.OriginalTrackUrl;
|
return "https://error.org/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const manifestString = data.data.manifest.trim();
|
||||||
|
|
||||||
|
let decodedManifest = decodeBase64(manifestString);
|
||||||
|
|
||||||
|
decodedManifest = decodedManifest.replace(/\0/g, '').trim();
|
||||||
|
|
||||||
|
const manifestJson = JSON.parse(decodedManifest);
|
||||||
|
|
||||||
|
if (manifestJson.urls && manifestJson.urls.length > 0) {
|
||||||
|
return manifestJson.urls[0];
|
||||||
|
} else {
|
||||||
|
console.error("No URLs found in manifest");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to decode/parse manifest:", err.message, err.stack);
|
||||||
|
}
|
||||||
|
|
||||||
return "https://error.org/";
|
return "https://error.org/";
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("Extract stream URL error:", err.message, err.stack);
|
||||||
return "https://error.org/";
|
return "https://error.org/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user