diff --git a/src/db.js b/src/db.mjs similarity index 85% rename from src/db.js rename to src/db.mjs index c652f12..60364e5 100755 --- a/src/db.js +++ b/src/db.mjs @@ -1,6 +1,6 @@ -const Database = require("better-sqlite3") +import Database from "better-sqlite3" -const DB = Database(process.env.DB || "./db.sqlite3") +export const DB = Database(process.env.DB || "./db.sqlite3") const migrations = [ ` @@ -17,6 +17,12 @@ CREATE TABLE inventory ( obtained_at INTEGER NOT NULL, quantity REAL NOT NULL ); +`, +` +CREATE TABLE stats ( + stat TEXT PRIMARY KEY, + value BLOB NOT NULL +); ` ] @@ -39,7 +45,7 @@ DB.pragma("foreign_keys = 1") const preparedStatements = new Map() -const SQL = (strings, ...params) => { +export const SQL = (strings, ...params) => { const sql = strings.join("?") let stmt const cachedValue = preparedStatements.get(sql) @@ -55,6 +61,4 @@ const SQL = (strings, ...params) => { all: () => stmt.all.apply(stmt, params), statement: stmt } -} - -module.exports = { DB, SQL } \ No newline at end of file +} \ No newline at end of file diff --git a/src/index.js b/src/index.mjs similarity index 75% rename from src/index.js rename to src/index.mjs index ce4b658..f0e70e0 100644 --- a/src/index.js +++ b/src/index.mjs @@ -1,10 +1,11 @@ -const irc = require("irc-upd") -const childProcess = require("child_process") -const syllables = require("syllable") -const R = require("ramda") -const pluralize = require("pluralize") +import irc from "irc-upd" +import * as childProcess from "child_process" +import syllables from "syllable" +import * as R from "ramda" +import pluralize from "pluralize" +const { HyperLogLog } = await import("hyperlolo") -const { DB, SQL } = require("./db") +const { DB, SQL } = await import("./db.mjs") var client = new irc.Client(process.argv[2] || "irc.osmarks.net", "testbot", { channels: ["#a", "#botrobots", "#b"], @@ -62,12 +63,12 @@ const PLURALS = [ ["Category:English irregular plurals", "Categories:English irregular plurals"], ["enigma", "enigmas"], ["radix", "radixes"], - ["water", "water"], ["funny", "funnies"], ["octopus", "octopoda"], ["the place in the world which is northernmost", "the places in the world which are northernmost"], ["datum", "data"], ["testbot", "testbots"], + ["notion of LLM use", "notions of LLM use"], ["oil of vitriol", "oil of vitriol"], ["noun phrase", "noun phrases"], ["information", "information"], @@ -76,6 +77,8 @@ const PLURALS = [ ["potions of cryoapiocity", "potions of cryoapiocity"], ["sulfuric acid", "sulfuric acid"], ["dollar", "dollars"], + ["acid", "acid"], + ["water", "water"], ] const PLURALS_KEEP_SAME = [ "mice", @@ -84,7 +87,8 @@ const PLURALS_KEEP_SAME = [ "dodecahedra", "those things which must not be seen", "announcements", - "rotations" + "rotations", + "spider-based countermeasures" ] const SINGULARS_KEEP_SAME = [ "mouse", @@ -93,12 +97,13 @@ const SINGULARS_KEEP_SAME = [ "dodecahedron", "that thing which must not be seen", "announcement", - "rotation" + "rotation", + "spider-based countermeasure" ] const spliceIn = (xs, ys) => { const locations = new Map(ys.map((y, i) => [Math.floor(xs.length * (i / ys.length)), y])) - const out = xs.map((x, i) => (locations.get(i) ? [x, locations.get(i)] : [x])) + const out = xs.map((x, i) => (locations.get(i) ? [locations.get(i), x] : [x])) const realOut = [] for (const x of out) { for (const y of x) { @@ -144,7 +149,17 @@ client.addListener("message", async (nick, channel, message, ev) => { nick = e[1] message = e[2] } + const allStatsRaw = SQL`SELECT * FROM stats`.all().map(x => [x.stat, x.value]) + const origStats = new Map(allStatsRaw) + const allStats = new Map(allStatsRaw) + allStats.set("messages", (allStats.get("messages") ?? 0) + 1) const res = /^([A-Za-z0-9_-]+)[:, ]*([A-Za-z0-9_-]+) *(.*)$/.exec(message) + const uniqueUsersCounter = allStats.get("users") ? HyperLogLog.deserialize(allStats.get("users")) : new HyperLogLog({ hasherId: "jenkins32", precision: 5 }) + uniqueUsersCounter.add(nick) + allStats.set("users", uniqueUsersCounter.serialize()) + const uniqueMessagesCounter = allStats.get("uniqueMessages") ? HyperLogLog.deserialize(allStats.get("uniqueMessages")) : new HyperLogLog({ hasherId: "jenkins32", precision: 5 }) + uniqueMessagesCounter.add(message) + allStats.set("uniqueMessages", uniqueMessagesCounter.serialize()) if (res) { let [_, tnick, cmd, args] = res tnick = tnick.toLowerCase() @@ -152,21 +167,23 @@ client.addListener("message", async (nick, channel, message, ev) => { cmd = cmd.toLowerCase() if (tnick === client.nick) { console.log(nick, cmd, channel) + allStats.set("commands", (allStats.get("commands") ?? 0) + 1) if (cmd === "starch") { - client.say(channel, `starch exists (${Math.random() * 20 + 80}% confidence).`) + client.say(channel, `Starch exists (${Math.random() * 20 + 80}% confidence).`) } else if (cmd === "stats") { - client.say(channel, "haha no") + client.say(channel, `${allStats.get("commands")} run, ${allStats.get("messages")} messages seen, about ${Math.floor(uniqueUsersCounter.count())} nicks seen, about ${Math.floor(uniqueMessagesCounter.count())} unique messages seen.`) } else if (cmd === "help") { - client.say(channel, "nobody can help you now") + client.say(channel, "Nobody can help you now.") } else if (cmd === "fortune") { childProcess.exec("fortune", (err, out) => { if (err) { return console.warn(err) } client.say(channel, out) }) } else if (cmd === "cat") { - client.say(channel, "meow") + client.say(channel, "Meow.") } else if (cmd === "servers") { - scanlinks().then(links => client.say(channel, links.map(x => `${x[1]}: ${/^[0-9]+ (.*)$/.exec(x[3])[1]}`).join("\n"))) + const links = await scanlinks() + client.say(channel, links.map(x => `${x[1]}: ${/^[0-9]+ (.*)$/.exec(x[3])[1]}`).join("\n")) } else if (cmd === "take" || cmd === "have") { const things = args.split(/(,| and )/).map(x => x.trim().replace(",", "").replace(/^(an?|the) /, "")).filter(x => x !== "" && x !== "and") for (let thing of things) { @@ -199,7 +216,7 @@ client.addListener("message", async (nick, channel, message, ev) => { const inv = SQL`SELECT * FROM inventory ORDER BY obtained_at DESC LIMIT 10`.all() client.say(channel, inv.map(renderItem).join("\n")) } else if (cmd === "deploy") { - client.say(channel, `Deploying ${args}`) + client.say(channel, `Deploying ${args}.`) } } } @@ -208,10 +225,18 @@ client.addListener("message", async (nick, channel, message, ev) => { sylhist = R.takeLast(3, R.append(syllables(messageContent), sylhist)) mhist = R.takeLast(50, R.append(messageContent, mhist)) if (R.equals(sylhist, [5, 7, 5])) { - client.say(channel, "haiku detected!") + client.say(channel, "Haiku detected!") logEv("haiku", R.takeLast(3, mhist).join("\n")) } console.log(sylhist) + + DB.transaction(() => { + for (const [k, v] of allStats) { + if (origStats.get(k) !== v) { + SQL`INSERT OR REPLACE INTO stats VALUES (${k}, ${v})`.run() + } + } + })() }) client.addListener("raw", ev => {