source/audible/audible.js
2025-11-27 21:03:23 +00:00

227 lines
7.6 KiB
JavaScript

async function searchResults(keyword) {
const results = [];
let pageNumber = 1;
let hasMore = true;
try {
while (hasMore) {
const headers = {
"next-action": await extractNextActionIdentifier(),
"Content-Type": "text/plain"
};
console.log(JSON.stringify(headers));
const postData = `[${pageNumber},"${keyword}",""]`;
const response = await fetchv2("https://mapple.site/audiobooks?search=" + encodeURIComponent(keyword), headers, "POST", postData);
const text = await response.text();
console.log("Page " + pageNumber + " response:", text);
const lines = text.split('\n').filter(line => line.trim());
let foundBooks = false;
for (const line of lines) {
try {
const data = JSON.parse(line.substring(line.indexOf('{')));
if (data.books && Array.isArray(data.books)) {
foundBooks = true;
for (const book of data.books) {
let title = book.title || "Untitled";
title = title.replace(/\s+free\s+online\s*$/i, '')
.replace(/\s+audiobook\s+free\s+online\s*$/i, '')
.replace(/\s+audiobook\s+free\s*$/i, '')
.replace(/\s+free\s+audiobook\s*$/i, '')
.replace(/\s+free\s*$/i, '')
.replace(/\s+\(free\s*\)\s*$/i, '')
.replace(/\s+free\s+read\s+/i, ' read ')
.replace(/\s+\(.*?free.*?\)\s*$/i, '')
.replace(/\s+audiobook\s*$/i, '')
.trim();
results.push({
title: title,
image: book.image || "",
href: "https://mapple.site/listen/" + (book.$id || "") + "/" + encodeURIComponent(book.title || "untitled")
});
}
hasMore = data.hasMore === true;
console.log("hasMore: " + hasMore);
}
} catch (e) {
}
}
if (!foundBooks) {
hasMore = false;
}
pageNumber++;
}
return JSON.stringify(results);
} catch (err) {
console.log("Search error:", err);
return JSON.stringify([{
title: "Error",
image: "Error",
href: "Error"
}]);
}
}
async function extractDetails(url) {
try {
return JSON.stringify([{
description: "",
aliases: "",
airdate: ""
}]);
} catch (err) {
return JSON.stringify([{
description: "Error",
aliases: "Error",
airdate: "Error"
}]);
}
}
async function extractEpisodes(url) {
const results = [];
try {
const fetchResponse = await fetchv2(url, {
"rsc": "1"
});
const fetchText = await fetchResponse.text();
const audioItemsMatch = fetchText.match(/"audio_items":\[([^\]]+)\]/);
if (audioItemsMatch && audioItemsMatch[1]) {
const audioItemsStr = audioItemsMatch[1];
const urlMatches = audioItemsStr.match(/"(https?:\/\/[^"]+)"/g);
if (urlMatches) {
for (let i = 0; i < urlMatches.length; i++) {
const audioUrl = urlMatches[i].replace(/"/g, '');
results.push({
href: audioUrl,
number: i + 1,
title: `Chapter ${i + 1}`
});
}
}
}
return JSON.stringify(results);
} catch (err) {
console.log("Episodes error:", err);
return JSON.stringify([{
href: "Error",
number: "Error",
title: "Error"
}]);
}
}
async function extractStreamUrl(url) {
try {
const headers = {
"Host": "ipaudio.club",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:146.0) Gecko/20100101 Firefox/146.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Connection": "keep-alive",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "cross-site",
"Priority": "u=4",
"Pragma": "no-cache",
"Cache-Control": "no-cache"
};
const streams = [{
title: `Audio Stream 1`,
streamUrl: url,
headers: headers
}];
return JSON.stringify({
streams: streams,
subtitle: ""
});
} catch (err) {
console.log("Stream error:", err);
return JSON.stringify({
streams: [{
title: "Error",
streamUrl: "",
headers: {}
}],
subtitle: ""
});
}
}
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null, encoding: 'utf-8' }) {
try {
return await fetchv2(
url,
options.headers ?? {},
options.method ?? 'GET',
options.body ?? null,
true,
options.encoding ?? 'utf-8'
);
} catch(e) {
try {
return await fetch(url, options);
} catch(error) {
return null;
}
}
}
async function extractNextActionIdentifier() {
const htmlResponse = await soraFetch(atob("aHR0cHM6Ly9tYXBwbGUuc2l0ZS9hdWRpb2Jvb2tz"));
const htmlText = await htmlResponse.text();
const pageMatch = htmlText.match(/<script[^>]*src="([^"]*app\/audiobooks\/page-[^"]*\.js)"[^>]*><\/script>/);
if (!pageMatch || !pageMatch[1]) {
throw new Error("error 1");
}
const beforePageMatch = htmlText.match(/<script[^>]*src="([^"]*\.js)"[^>]*><\/script><script[^>]*src="[^"]*app\/audiobooks\/page-[^"]*\.js"/);
if (!beforePageMatch || !beforePageMatch[1]) {
throw new Error("error 2");
}
let targetUrl = beforePageMatch[1];
if (targetUrl.startsWith('/_next/')) {
targetUrl = 'https://mapple.site' + targetUrl;
} else if (!targetUrl.startsWith('http')) {
targetUrl = 'https://mapple.site/' + targetUrl;
}
try {
const response = await soraFetch(targetUrl);
const text = await response.text();
let actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"[^"]*"getStreamUrl/);
if (!actionMatch) {
actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"/);
}
if (!actionMatch) {
actionMatch = text.match(/"([a-f0-9]{40,})"[^"]*"getStreamUrl/);
}
if (actionMatch && actionMatch[1]) {
return actionMatch[1];
}
} catch (e) {
throw new Error("error 3: " + e);
}
throw new Error("error 4");
}