From 5880f2faa89e4e7df79b1695068f62f953da13e3 Mon Sep 17 00:00:00 2001 From: ashley Date: Sun, 16 Nov 2025 20:44:53 +0100 Subject: [PATCH] Add src/libpoketube/init/telemetry.js --- src/libpoketube/init/telemetry.js | 492 ++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 src/libpoketube/init/telemetry.js diff --git a/src/libpoketube/init/telemetry.js b/src/libpoketube/init/telemetry.js new file mode 100644 index 00000000..0668f063 --- /dev/null +++ b/src/libpoketube/init/telemetry.js @@ -0,0 +1,492 @@ +const fs = require("fs") +const path = require("path") + +const telemetryConfig = { telemetry: true } + +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 } +} + +module.exports = function (app, config, renderTemplate) { + 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() + + 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 + }) + } + + 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 +

+
+ + + +`) + } + + return res.send(` + + + + Improving Poke + + + + + +
+ Poke logo +

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 +

+
+ +`) + }) +}