499 lines
14 KiB
JavaScript
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;
|