diff --git a/src/libpoketube/init/pages-api.js b/src/libpoketube/init/pages-api.js
index e2ad05f6..731f9812 100644
--- a/src/libpoketube/init/pages-api.js
+++ b/src/libpoketube/init/pages-api.js
@@ -41,508 +41,6 @@ module.exports = function (app, config, renderTemplate) {
f.body.pipe(res);
});
- const telemetryConfig = { telemetry: true }
-
- const path = require("path")
-
-const statsFile = path.join(__dirname, "stats.json")
-
-if (!fs.existsSync(statsFile)) {
- fs.writeFileSync(
- statsFile,
- JSON.stringify({ videos: {}, browsers: {}, os: {}, users: {} }, null, 2)
- )
-}
-
-function parseUA(ua) {
- let browser = "unknown"
- let os = "unknown"
-
- if (/firefox/i.test(ua)) browser = "firefox"
- else if (/chrome|chromium|crios/i.test(ua)) browser = "chrome"
- else if (/safari/i.test(ua)) browser = "safari"
- else if (/edge/i.test(ua)) browser = "edge"
-
- if (/windows/i.test(ua)) os = "windows"
- else if (/android/i.test(ua)) os = "android"
- else if (/mac os|macintosh/i.test(ua)) os = "macos"
- else if (/linux/i.test(ua)) os = "gnu-linux"
- else if (/iphone|ipad|ios/i.test(ua)) os = "ios"
-
- return { browser, os }
-}
-
- app.post("/api/stats", (req, res) => {
- if (!telemetryConfig.telemetry) return res.status(200).json({ ok: true })
-
- const { videoId, userId } = req.body
- if (!videoId) return res.status(400).json({ error: "missing videoId" })
- if (!userId) return res.status(400).json({ error: "missing userId" })
-
- const ua = req.headers["user-agent"] || ""
- const { browser, os } = parseUA(ua)
-
- const raw = fs.readFileSync(statsFile, "utf8")
- const data = JSON.parse(raw)
-if (!data.users) data.users = {}
-
- if (!data.videos[videoId]) data.videos[videoId] = 0
- data.videos[videoId]++
-
- if (!data.browsers[browser]) data.browsers[browser] = 0
- data.browsers[browser]++
-
- if (!data.os[os]) data.os[os] = 0
- data.os[os]++
-
- if (!data.users[userId]) data.users[userId] = true
-
- fs.writeFileSync(statsFile, JSON.stringify(data, null, 2))
- res.json({ ok: true })
-})
-app.get("/api/stats/optout", (req, res) => {
- res.send(`
-
-
-
- Poke – Opt out of stats
-
-
-
-
-
-
-
Stats opt-out
-
- This page lets you turn off anonymous usage stats for this browser.
- Poke will remember this choice using localStorage only (no cookies).
-
-
-
- Anonymous stats help us understand which videos are popular and which platforms people use —
- without collecting personal data. You can read the full details here:
- Privacy Policy .
-
-
-
Opt out of anonymous stats
-
-
-
- • To see the stats UI (if enabled on this instance), visit
- /api/stats?view=human .
- • For raw JSON, use /api/stats?view=json .
-
-
-
-
-
-`)
-})
-
-app.get("/api/stats", (req, res) => {
- const view = (req.query.view || "").toString()
-
- // JSON view – explicit: /api/stats?view=json
- if (view === "json") {
- if (!telemetryConfig.telemetry) {
- return res.json({ videos: {}, browsers: {}, os: {}, totalUsers: 0 })
- }
-
- const raw = fs.readFileSync(statsFile, "utf8")
- const data = JSON.parse(raw)
-
- if (!data.videos) data.videos = {}
- if (!data.browsers) data.browsers = {}
- if (!data.os) data.os = {}
- if (!data.users) data.users = {}
-
- const sortedVideos = Object.entries(data.videos)
- .sort((a, b) => b[1] - a[1])
- .slice(0, 10)
-
- const topVideos = Object.fromEntries(sortedVideos)
-
- return res.json({
- videos: topVideos,
- browsers: data.browsers,
- os: data.os,
- totalUsers: Object.keys(data.users).length
- })
- }
-
- // Human view – /api/stats?view=human (just stats UI)
- if (view === "human") {
- const telemetryOn = telemetryConfig.telemetry
-
- return res.send(`
-
-
-
- Improving Poke – Stats
-
-
-
-
-
-
-
Anonymous stats
-
- These stats are aggregated locally on this Poke instance. For what is collected (and what is not),
- see privacy policy .
-
-
-
Current anonymous stats
-
Loading…
-
-
-
Top videos (local-only)
-
Up to 10 most watched videos on this instance.
-
-
-
-
-
API usage
-
- • Human view (this page): /api/stats?view=human
- • JSON view (for scripts/tools): /api/stats?view=json
- • Opt out for this browser: /api/stats/optout
-
-
-
-
-
-`)
- }
-
- // any other view value (including "/api/stats" with no ?view) -> landing page HTML
- return res.send(`
-
-
-
- Improving Poke
-
-
-
-
-
-
-
-
Improving Poke
-
Private by design
-
-
- At Poke , we do not collect or share any personal information.
- That's our privacy promise in a nutshell.
- To improve Poke we use a completely anonymous, local-only way to figure out how the site is being used.
-
-
-
- Any anonymous stats recorded by this instance come from the /api/stats system.
- You can read exactly what is measured (and what is not ) in our privacy policy:
- here .
-
-
-
-
-
API usage
-
- • Human view (stats UI): /api/stats?view=human
- • JSON view (for scripts/tools): /api/stats?view=json
- • Opt out for this browser: /api/stats/optout
-
-
-
-`)
-})
-
-
app.get("/avatars/:v", async function (req, res) {
var url = `https://yt3.ggpht.com/${req.params.v}`;
@@ -556,35 +54,35 @@ app.get("/api/stats", (req, res) => {
});
-app.get("/api/geo", async (req, res) => {
- try {
- let ip =
- req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
- req.socket.remoteAddress;
+ app.get("/api/geo", async (req, res) => {
+ try {
+ let ip =
+ req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
+ req.socket.remoteAddress;
- if (ip && ip.startsWith("::ffff:")) {
- ip = ip.slice(7);
+ if (ip && ip.startsWith("::ffff:")) {
+ ip = ip.slice(7);
+ }
+
+ if (!ip) {
+ return res.status(400).json({ error: "No IP found" });
+ }
+
+ const response = await fetch(`https://ip2c.org/${ip}`);
+ const text = await response.text();
+ const parts = text.trim().split(";");
+
+ const countryCode = parts[1] || "ZZ";
+
+ res.setHeader("Content-Type", "application/json");
+ res.setHeader("Access-Control-Allow-Origin", "*");
+ res.json({ countryCode });
+ } catch (err) {
+ res.setHeader("Content-Type", "application/json");
+ res.setHeader("Access-Control-Allow-Origin", "*");
+ res.status(500).json({ countryCode: "ZZ", error: true, details: String(err) });
}
-
- if (!ip) {
- return res.status(400).json({ error: "No IP found" });
- }
-
- const response = await fetch(`https://ip2c.org/${ip}`);
- const text = await response.text();
- const parts = text.trim().split(";");
-
- const countryCode = parts[1] || "ZZ";
-
- res.setHeader("Content-Type", "application/json");
- res.setHeader("Access-Control-Allow-Origin", "*");
- res.json({ countryCode });
- } catch (err) {
- res.setHeader("Content-Type", "application/json");
- res.setHeader("Access-Control-Allow-Origin", "*");
- res.status(500).json({ countryCode: "ZZ", error: true, details: String(err) });
- }
-});
+ });
@@ -610,44 +108,41 @@ app.get("/api/geo", async (req, res) => {
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);
+ app.get("/api/nominatim/search", async (req, res) => {
+ try {
+ const url = new URL("https://nominatim.openstreetmap.org/search");
+ 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" });
}
- // 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" });
- }
-});
+ 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");
-// 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);
+ 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" });
}
- 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", "")}`;
@@ -680,39 +175,30 @@ app.get("/api/nominatim/reverse", async (req, res) => {
try {
let url = `${config.videourl}/companion/api/v1/captions/${id}?label=${l}`;
- /*
-let f = await fetch(url, {
- headers: headers,
- });
-
- const body = await f.text();
-
- res.send(body);
- */
res.send("j");
} 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);
- }
+ 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 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" });
- }
-});
+ 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");
@@ -742,23 +228,15 @@ app.get("/api/weather", async (req, res) => {
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";
- }
+ 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";
- }
+ if (percentage >= 50) return "red";
+ else if (percentage >= 20) return "orange";
+ else return "green";
};
const likeColor = getLikePercentageColor(likePercentage);
@@ -770,24 +248,19 @@ app.get("/api/weather", async (req, res) => {
).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";
- }
+ 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";
+ userScore >= 80 ? "green" :
+ userScore >= 50 ? "orange" :
+ "red";
const respon = {
like_count: likes,
@@ -825,7 +298,7 @@ app.get("/api/weather", async (req, res) => {
let f = await modules.fetch(url, {
method: req.method,
- headers: headers, // Add headers to the fetch request
+ headers: headers,
});
f.body.pipe(res);
@@ -861,138 +334,139 @@ app.get("/api/weather", async (req, res) => {
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;
- }
- }
+ app.get("/api/version.json", async (req, res) => {
+ let latestCommitHash = 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;
+ function getLatestCommitHash() {
+ try {
+ const out = execSync("git rev-parse --short HEAD", { stdio: ["ignore", "pipe", "pipe"] })
+ .toString()
+ .trim();
+ return out || null;
+ } catch {
+ return null;
}
- 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();
+ 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 cpus = os.cpus() || [];
- const totalMemoryGB = os.totalmem() / (1024 ** 3);
- const freeMemoryGB = os.freemem() / (1024 ** 3);
+ const osr = readOsRelease();
- const roundedTotalGB = totalMemoryGB.toFixed(2);
- const roundedFreeGB = freeMemoryGB.toFixed(2);
+ const cpus = os.cpus() || [];
+ const totalMemoryGB = os.totalmem() / (1024 ** 3);
+ const freeMemoryGB = os.freemem() / (1024 ** 3);
- const loadavg = os.loadavg(); // [1m, 5m, 15m]
- const uptimeSeconds = Math.floor(os.uptime());
+ const roundedTotalGB = totalMemoryGB.toFixed(2);
+ const roundedFreeGB = freeMemoryGB.toFixed(2);
- let platform = os.platform(); // 'linux', 'darwin', 'win32', etc.
- if (platform === 'linux') platform = 'gnu/linux';
+ const loadavg = os.loadavg();
+ const uptimeSeconds = Math.floor(os.uptime());
- 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;
+ let platform = os.platform();
+ if (platform === 'linux') platform = 'gnu/linux';
- latestCommitHash = getLatestCommitHash();
+ const kernelRelease = os.release();
+ const arch = os.arch();
+ const hostname = os.hostname();
+ const cpuModel = cpus[0]?.model || "Unknown CPU";
+ const cpuCount = cpus.length;
- 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;
- }
+ latestCommitHash = getLatestCommitHash();
- const { useragent, ...configWithoutUA } = cnf;
+ 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 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),
+ const { useragent, ...configWithoutUA } = cnf;
+
+ const response = {
+ pt_version: {
+ version: versmol,
+ version_full: verfull,
+ commit: latestCommitHash,
},
- 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));
- })(),
- };
+ 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);
-});
+ res.json(response);
+ });
app.get("/api/instances.json", async (req, res) => {