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(/]*src="([^"]*app\/audiobooks\/page-[^"]*\.js)"[^>]*><\/script>/); if (!pageMatch || !pageMatch[1]) { throw new Error("error 1"); } const beforePageMatch = htmlText.match(/]*src="([^"]*\.js)"[^>]*><\/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"); }