import * as https from "https"; import * as dom from "fake-dom" import * as fs from "fs"; class Main { public static async main() { if (dom === undefined) { console.log("Fakedom not loaded") } const csvEntries: string[] = ["issue, twitter and nitter, en.osm.town, other mastodon"] const targetYear = (new Date()).getUTCFullYear() for (let year = 2020; year <= targetYear; year++) { for (let month = 1; month <= 12; month++) { console.log("Analyzing",year, month) if (year === targetYear && month > (new Date().getUTCMonth() + 1)) { console.log("Stopping now") break } let m = "" + month if (m.length == 1) { m = "0" + m } const baseUrl = `https://weeklyosm.eu/archives/date/${year}/${m}` let r: { twitter: number, osmtown: number, mastodon: number } = { twitter: 0, osmtown: 0, mastodon: 0 } try { const issue = await Main.analyse(baseUrl) r = Main.sum(r, issue) } catch (e) { console.log("SKipping ", baseUrl) } for (let i = 5; i >= 2; i--) { try { const issue = await Main.analyse(baseUrl + "/page/" + i) r = Main.sum(r, issue) } catch (e) { console.log("SKipping ", baseUrl + "/page/" + i) } } const entry = year + "-" + month + "," + r.twitter + "," + r.osmtown + "," + r.mastodon csvEntries.push(entry) } } fs.writeFileSync("entries.csv", csvEntries.join("\n")) } private static sum<X extends Record<string, number>>(a: X, b: X): X { const r: X = { ...a } for (const key in b) { // @ts-ignore a[key] = (a[key] ?? 0) + (b[key] ?? 0) } return a } private static async Download(url: string, headers?: any): Promise<{ content: string }> { const cache = "./cache/" + url.replace(/[./\\:?]/g, "_") if (fs.existsSync(cache)) { return {content: fs.readFileSync(cache, {encoding: "utf-8"})} } console.log("> Downloading", url) return new Promise((resolve, reject) => { try { headers = headers ?? {} headers.accept = "application/json" const urlObj = new URL(url) https.get( { host: urlObj.host, path: urlObj.pathname + urlObj.search, port: urlObj.port, headers: headers, }, (res) => { const parts: string[] = [] res.setEncoding("utf8") res.on("data", function (chunk) { // @ts-ignore parts.push(chunk) }) res.addListener("end", function () { fs.writeFileSync(cache, parts.join("")) resolve({content: parts.join("")}) }) } ) } catch (e) { reject(e) } }) } private static async analyse(url: string): Promise<{ twitter: number, osmtown: number, mastodon: number }> { const data = await this.Download(url) const doc = document.createElement("html") doc.innerHTML = data.content const article = doc.getElementsByTagName("article")[0] const asides = Array.from(article.getElementsByTagName("aside")) for (const aside of asides) { aside.parentElement.removeChild(aside) } const links = Array.from(article.getElementsByTagName("a")) const hosts: Record<string, number> = {} for (const link of links) { const url = new URL(link.href) hosts[url.host] = 1 + (hosts[url.host] ?? 0) } const result = { twitter: (hosts["twitter.com"] ?? 0) + (hosts["nitter.net"] ?? 0), osmtown: hosts["en.osm.town"] ?? 0, mastodon: 0 } for (let host in hosts) { const count = hosts[host] host = host.toLowerCase() if (host.endsWith("translate.goog")) { continue } if (host.indexOf('masto') >= 0 || host.indexOf('mapstodon') >= 0 || host.endsWith(".social") || host.endsWith(".town") || host.endsWith("botsin.space")) { console.log(host) result.mastodon += count } } return result } } Main.main().then(_ => console.log("All done"))