This commit is contained in:
osmarks 2024-05-23 00:30:18 +01:00
parent 06285689d7
commit 3a6c14961a
2 changed files with 52 additions and 23 deletions

View File

@ -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 = [ const migrations = [
` `
@ -17,6 +17,12 @@ CREATE TABLE inventory (
obtained_at INTEGER NOT NULL, obtained_at INTEGER NOT NULL,
quantity REAL 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 preparedStatements = new Map()
const SQL = (strings, ...params) => { export const SQL = (strings, ...params) => {
const sql = strings.join("?") const sql = strings.join("?")
let stmt let stmt
const cachedValue = preparedStatements.get(sql) const cachedValue = preparedStatements.get(sql)
@ -55,6 +61,4 @@ const SQL = (strings, ...params) => {
all: () => stmt.all.apply(stmt, params), all: () => stmt.all.apply(stmt, params),
statement: stmt statement: stmt
} }
} }
module.exports = { DB, SQL }

View File

@ -1,10 +1,11 @@
const irc = require("irc-upd") import irc from "irc-upd"
const childProcess = require("child_process") import * as childProcess from "child_process"
const syllables = require("syllable") import syllables from "syllable"
const R = require("ramda") import * as R from "ramda"
const pluralize = require("pluralize") 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", { var client = new irc.Client(process.argv[2] || "irc.osmarks.net", "testbot", {
channels: ["#a", "#botrobots", "#b"], channels: ["#a", "#botrobots", "#b"],
@ -62,12 +63,12 @@ const PLURALS = [
["Category:English irregular plurals", "Categories:English irregular plurals"], ["Category:English irregular plurals", "Categories:English irregular plurals"],
["enigma", "enigmas"], ["enigma", "enigmas"],
["radix", "radixes"], ["radix", "radixes"],
["water", "water"],
["funny", "funnies"], ["funny", "funnies"],
["octopus", "octopoda"], ["octopus", "octopoda"],
["the place in the world which is northernmost", "the places in the world which are northernmost"], ["the place in the world which is northernmost", "the places in the world which are northernmost"],
["datum", "data"], ["datum", "data"],
["testbot", "testbots"], ["testbot", "testbots"],
["notion of LLM use", "notions of LLM use"],
["oil of vitriol", "oil of vitriol"], ["oil of vitriol", "oil of vitriol"],
["noun phrase", "noun phrases"], ["noun phrase", "noun phrases"],
["information", "information"], ["information", "information"],
@ -76,6 +77,8 @@ const PLURALS = [
["potions of cryoapiocity", "potions of cryoapiocity"], ["potions of cryoapiocity", "potions of cryoapiocity"],
["sulfuric acid", "sulfuric acid"], ["sulfuric acid", "sulfuric acid"],
["dollar", "dollars"], ["dollar", "dollars"],
["acid", "acid"],
["water", "water"],
] ]
const PLURALS_KEEP_SAME = [ const PLURALS_KEEP_SAME = [
"mice", "mice",
@ -84,7 +87,8 @@ const PLURALS_KEEP_SAME = [
"dodecahedra", "dodecahedra",
"those things which must not be seen", "those things which must not be seen",
"announcements", "announcements",
"rotations" "rotations",
"spider-based countermeasures"
] ]
const SINGULARS_KEEP_SAME = [ const SINGULARS_KEEP_SAME = [
"mouse", "mouse",
@ -93,12 +97,13 @@ const SINGULARS_KEEP_SAME = [
"dodecahedron", "dodecahedron",
"that thing which must not be seen", "that thing which must not be seen",
"announcement", "announcement",
"rotation" "rotation",
"spider-based countermeasure"
] ]
const spliceIn = (xs, ys) => { const spliceIn = (xs, ys) => {
const locations = new Map(ys.map((y, i) => [Math.floor(xs.length * (i / ys.length)), y])) 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 = [] const realOut = []
for (const x of out) { for (const x of out) {
for (const y of x) { for (const y of x) {
@ -144,7 +149,17 @@ client.addListener("message", async (nick, channel, message, ev) => {
nick = e[1] nick = e[1]
message = e[2] 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 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) { if (res) {
let [_, tnick, cmd, args] = res let [_, tnick, cmd, args] = res
tnick = tnick.toLowerCase() tnick = tnick.toLowerCase()
@ -152,21 +167,23 @@ client.addListener("message", async (nick, channel, message, ev) => {
cmd = cmd.toLowerCase() cmd = cmd.toLowerCase()
if (tnick === client.nick) { if (tnick === client.nick) {
console.log(nick, cmd, channel) console.log(nick, cmd, channel)
allStats.set("commands", (allStats.get("commands") ?? 0) + 1)
if (cmd === "starch") { 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") { } 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") { } else if (cmd === "help") {
client.say(channel, "nobody can help you now") client.say(channel, "Nobody can help you now.")
} else if (cmd === "fortune") { } else if (cmd === "fortune") {
childProcess.exec("fortune", (err, out) => { childProcess.exec("fortune", (err, out) => {
if (err) { return console.warn(err) } if (err) { return console.warn(err) }
client.say(channel, out) client.say(channel, out)
}) })
} else if (cmd === "cat") { } else if (cmd === "cat") {
client.say(channel, "meow") client.say(channel, "Meow.")
} else if (cmd === "servers") { } 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") { } else if (cmd === "take" || cmd === "have") {
const things = args.split(/(,| and )/).map(x => x.trim().replace(",", "").replace(/^(an?|the) /, "")).filter(x => x !== "" && x !== "and") const things = args.split(/(,| and )/).map(x => x.trim().replace(",", "").replace(/^(an?|the) /, "")).filter(x => x !== "" && x !== "and")
for (let thing of things) { 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() const inv = SQL`SELECT * FROM inventory ORDER BY obtained_at DESC LIMIT 10`.all()
client.say(channel, inv.map(renderItem).join("\n")) client.say(channel, inv.map(renderItem).join("\n"))
} else if (cmd === "deploy") { } 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)) sylhist = R.takeLast(3, R.append(syllables(messageContent), sylhist))
mhist = R.takeLast(50, R.append(messageContent, mhist)) mhist = R.takeLast(50, R.append(messageContent, mhist))
if (R.equals(sylhist, [5, 7, 5])) { 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")) logEv("haiku", R.takeLast(3, mhist).join("\n"))
} }
console.log(sylhist) 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 => { client.addListener("raw", ev => {