poke/src/libpoketube/init/pages-api.js
2025-10-07 16:30:22 +02:00

499 lines
14 KiB
JavaScript

const { modules, version } = require("../libpoketube-initsys.js");
function getJson(str) {
try {
return JSON.parse(str);
} catch {
return null;
}
}
const pkg = require("../../../package.json");
const os = require('os');
const cnf = require("../../../config.json");
const ip2c = require("../../modules/ipapi");
const innertube = require("../libpoketube-youtubei-objects.json");
const { execSync } = require('child_process'); // DO NOT ABBRV THIS :SOB:
const fs = require('fs');
const verfull = "v25.2705-luna-MAJOR_UPDATE-stable-dev-nonLTS-git-MTc0NTcwNjc4MA==";
const versmol = "v25.2705-luna";
const branch = "dev/master";
const codename = "luna";
const versionnumber = "294";
const relaseunixdate = "MTc0NTcwNjc4MA==";
const updatequote = "i created this world.....to feel some control...";
module.exports = function (app, config, renderTemplate) {
const headers = {
'User-Agent': config.useragent,
};
app.get("/vi/:v/:t", async function (req, res) {
var url = `https://i.ytimg.com/vi/${req.params.v}/${req.params.t}`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/avatars/:v", async function (req, res) {
var url = `https://yt3.ggpht.com/${req.params.v}`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/api/geo", async (_req, res) => {
try {
const r = await fetch("https://ip2c.org/self");
const t = await r.text();
const parts = t.trim().split(";");
const countryCode = parts[1] || "ZZ";
res.setHeader("Content-Type", "application/json");
res.setHeader("Access-Control-Allow-Origin", "*");
res.send(JSON.stringify({ countryCode }));
} catch {
res.setHeader("Content-Type", "application/json");
res.setHeader("Access-Control-Allow-Origin", "*");
res.send(JSON.stringify({ countryCode: "ZZ" }));
}
});
app.get("/ggpht/:v", async function (req, res) {
var url = `https://yt3.ggpht.com/${req.params.v}`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/s/player/:playerid/player_ias.vflset/en_US/base.js", async function (req, res) {
var url = `https://www.youtube.com/s/player/${req.params.playerid}/player_ias.vflset/en_US/base.js`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/api/nominatim/search", async (req, res) => {
try {
const url = new URL("https://nominatim.openstreetmap.org/search");
// Forward all query params (format, q, limit, etc.)
for (const [key, value] of Object.entries(req.query)) {
url.searchParams.set(key, value);
}
// Force JSON output if not specified
if (!url.searchParams.has("format")) url.searchParams.set("format", "json");
const r = await fetch(url.toString(), {
headers: { "Accept-Language": req.headers["accept-language"] || "en" }
});
const data = await r.json();
res.json(data);
} catch (err) {
res.status(500).json({ error: "Failed to fetch from nominatim" });
}
});
// Proxy for reverse geocoding
app.get("/api/nominatim/reverse", async (req, res) => {
try {
const url = new URL("https://nominatim.openstreetmap.org/reverse");
for (const [key, value] of Object.entries(req.query)) {
url.searchParams.set(key, value);
}
if (!url.searchParams.has("format")) url.searchParams.set("format", "json");
const r = await fetch(url.toString(), {
headers: { "Accept-Language": req.headers["accept-language"] || "en" }
});
const data = await r.json();
res.json(data);
} catch (err) {
res.status(500).json({ error: "Failed to fetch from nominatim" });
}
});
app.get("/avatars/ytc/:v", async function (req, res) {
var url = `https://yt3.googleusercontent.com/ytc/${req.params.v.replace("ytc", "")}`;
let f = await modules.fetch(url + `?cachefixer=${btoa(Date.now())}`, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/api/video/download", async function (req, res) {
var v = req.query.v;
var q = "18";
if (req.query.q) q = req.query.q;
const url = `${config.videourl}/companion/latest_version?id=${v}&itag=${q}&local=true`;
res.redirect(url);
});
app.get("/api/subtitles", async (req, res) => {
const { fetch } = await import("undici");
const id = req.query.v;
const l = req.query.h;
try {
let url = `${config.videourl}/api/v1/captions/${id}?label=${l}`;
let f = await fetch(url, {
headers: headers,
});
const body = await f.text();
res.send(body);
} catch {}
});
app.get("/api/weather", async (req, res) => {
try {
const url = new URL("https://api.open-meteo.com/v1/forecast");
for (const [key, value] of Object.entries(req.query)) {
url.searchParams.set(key, value);
}
const response = await fetch(url.toString());
if (!response.ok) {
return res.status(response.status).json({ error: "Upstream error" });
}
const data = await response.json();
res.json(data);
} catch (err) {
console.error(err);
res.status(500).json({ error: "Proxy error" });
}
});
app.get("/api/getEngagementData", async (req, res) => {
const { fetch } = await import("undici");
const id = req.query.v;
try {
if (id) {
const apiUrl = `https://ryd-proxy.kavin.rocks/votes/${id}&hash=d0550b6e28c8f93533a569c314d5b4e2`;
const response = await fetch(apiUrl, {
headers: headers,
});
if (response.status === 400) {
const error = await response.json();
return res.status(400).send(error);
}
const engagement = await response.json();
const likes = parseInt(engagement.likes) || 0;
const dislikes = parseInt(engagement.dislikes) || 0;
const total = likes + dislikes;
const likePercentage = total > 0 ? ((likes / total) * 100).toFixed(2) : 0;
const dislikePercentage = total > 0 ? ((dislikes / total) * 100).toFixed(2) : 0;
const getLikePercentageColor = (percentage) => {
if (percentage >= 80) {
return "green";
} else if (percentage >= 50) {
return "orange";
} else {
return "red";
}
};
const getDislikePercentageColor = (percentage) => {
if (percentage >= 50) {
return "red";
} else if (percentage >= 20) {
return "orange";
} else {
return "green";
}
};
const likeColor = getLikePercentageColor(likePercentage);
const dislikeColor = getDislikePercentageColor(dislikePercentage);
const userScore = (
parseFloat(likePercentage) -
parseFloat(dislikePercentage) / 2
).toFixed(2);
const getUserScoreLabel = (score) => {
if (score >= 98) {
return "Masterpiece Video";
} else if (score >= 80) {
return "Overwhelmingly Positive";
} else if (score >= 60) {
return "Positive";
} else if (score >= 40) {
return "Mixed";
} else if (score >= 20) {
return "Negative";
} else {
return "Overwhelmingly Negative";
}
};
const userScoreLabel = getUserScoreLabel(userScore);
const userScoreColor =
userScore >= 80 ? "green" : userScore >= 50 ? "orange" : "red";
const respon = {
like_count: likes,
dislike_count: dislikes,
rating: engagement.rating,
userScore: {
label: userScoreLabel,
score: userScore,
color: userScoreColor,
},
engagement: {
likeColor: likeColor,
dislikeColor: dislikeColor,
percentage: {
likePercentage: `${likePercentage}%`,
dislikePercentage: `${dislikePercentage}%`,
},
},
ReturnYouTubeDislikesApiRawResponse: engagement,
};
res.send(respon);
} else {
res.status(400).json("pls gib ID :3");
}
} catch (error) {
res.status(500).json("whoops (error 500) >~<");
}
});
app.get("/feeds/videos.xml", async (req, res) => {
const id = req.query.channel_id;
let url = `https://youtube.com/feeds/videos.xml?channel_id=${id}`;
let f = await modules.fetch(url, {
method: req.method,
headers: headers, // Add headers to the fetch request
});
f.body.pipe(res);
});
app.get("/api/manifest/dash/id/:id", async (req, res) => {
const id = req.params.id;
let url = `https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/manifest/dash/id/${id}`;
let f = await modules.fetch(url, {
method: req.method,
headers: headers,
});
f.body.pipe(res);
});
app.get("/api/redirect", async (req, res) => {
const red_url = atob(req.query.u);
if (!red_url) {
res.redirect("/");
}
res.redirect(red_url + "?f=" + req.query.u);
});
app.get("/api", async (req, res) => {
res.redirect("/api/version.json");
});
app.get("/api/v1", async (req, res) => {
res.redirect("https://invid-api.poketube.fun/api/v1/stats");
});
app.get("/api/version.json", async (req, res) => {
let latestCommitHash = null;
function getLatestCommitHash() {
try {
const out = execSync("git rev-parse --short HEAD", { stdio: ["ignore", "pipe", "pipe"] })
.toString()
.trim();
return out || null;
} catch {
return null;
}
}
function readOsRelease() {
try {
const text = fs.readFileSync("/etc/os-release", "utf8");
const map = {};
for (const line of text.split("\n")) {
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
if (!m) continue;
let v = m[2];
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
v = v.slice(1, -1);
}
map[m[1]] = v;
}
return {
id: map.ID || null,
id_like: map.ID_LIKE || null,
version_id: map.VERSION_ID || null,
name: map.NAME || null,
pretty_name: map.PRETTY_NAME || null,
};
} catch {
return null;
}
}
const osr = readOsRelease();
const cpus = os.cpus() || [];
const totalMemoryGB = os.totalmem() / (1024 ** 3);
const freeMemoryGB = os.freemem() / (1024 ** 3);
const roundedTotalGB = totalMemoryGB.toFixed(2);
const roundedFreeGB = freeMemoryGB.toFixed(2);
const loadavg = os.loadavg(); // [1m, 5m, 15m]
const uptimeSeconds = Math.floor(os.uptime());
let platform = os.platform(); // 'linux', 'darwin', 'win32', etc.
if (platform === 'linux') platform = 'gnu/linux';
const kernelRelease = os.release(); // e.g., '6.8.0-40-generic'
const arch = os.arch(); // e.g., 'x64', 'arm64'
const hostname = os.hostname();
const cpuModel = cpus[0]?.model || "Unknown CPU";
const cpuCount = cpus.length;
latestCommitHash = getLatestCommitHash();
let invidious = null;
try {
const invTxt = await modules
.fetch("https://invid-api.poketube.fun/bHj665PpYhUdPWuKPfZuQGoX/api/v1/stats", { headers })
.then(r => r.text());
invidious = getJson(invTxt);
} catch {
invidious = null;
}
const { useragent, ...configWithoutUA } = cnf;
const response = {
pt_version: {
version: versmol,
version_full: verfull,
commit: latestCommitHash,
},
branch,
updatequote,
relaseunixdate,
vernum: versionnumber,
codename,
config: configWithoutUA,
system: {
os_name: osr?.pretty_name || osr?.name || (platform === "linux" ? "GNU/Linux" : os.type()),
distro: osr ? {
pretty_name: osr.pretty_name,
name: osr.name,
id: osr.id,
id_like: osr.id_like,
version_id: osr.version_id,
} : null,
platform,
kernel_release: kernelRelease,
arch,
hostname,
ram_total: `${roundedTotalGB} GB`,
ram_free: `${roundedFreeGB} GB`,
cpu: cpuModel,
cpu_cores: cpuCount,
loadavg: {
"1m": Number(loadavg[0]?.toFixed(2) || 0),
"5m": Number(loadavg[1]?.toFixed(2) || 0),
"15m": Number(loadavg[2]?.toFixed(2) || 0),
},
uptime_seconds: uptimeSeconds,
},
packages: {
libpt: version,
node: process.version,
v8: process.versions.v8,
},
invidious,
innertube,
flac: {
poketube_flac: "1.2a",
apple_musickit: "1.2.3",
poketube_normalize_volume: "1.2.23-yt",
},
process: process.versions,
dependencies: pkg.dependencies,
poketubeapicode: (() => {
const invVer = invidious?.software?.version || "0";
return btoa(String(Date.now()) + String(invVer));
})(),
};
res.json(response);
});
app.get("/api/instances.json", async (req, res) => {
const { fetch } = await import("undici");
try {
const url = `https://raw.githubusercontent.com/ashley0143/poke/main/instances.json`;
let f = await fetch(url, {
headers: headers,
})
.then((res) => res.text())
.then((json) => JSON.parse(json));
res.json(f);
} catch {
res.json("error while fetching instances");
}
});
};
module.exports.api = versionnumber;