actually git-ize
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | node_modules | ||||||
|  | db.sqlite3 | ||||||
|  | config.toml | ||||||
							
								
								
									
										3347
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3347
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										20
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | { | ||||||
|  |   "name": "ircbot", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "description": "", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "dev": "nodemon src/index.js" | ||||||
|  |   }, | ||||||
|  |   "license": "MIT", | ||||||
|  |   "devDependencies": { | ||||||
|  |     "nodemon": "^2.0.4" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "better-sqlite3": "^7.1.0", | ||||||
|  |     "irc-upd": "^0.11.0", | ||||||
|  |     "pluralize": "^8.0.0", | ||||||
|  |     "ramda": "^0.27.1", | ||||||
|  |     "syllable": "^4.1.0" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								src-old/db.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src-old/db.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | import aiosqlite | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | migrations = [ | ||||||
|  | """ | ||||||
|  | CREATE TABLE deleted_items ( | ||||||
|  |     id INTEGER PRIMARY KEY, | ||||||
|  |     timestamp INTEGER NOT NULL, | ||||||
|  |     item TEXT NOT NULL | ||||||
|  | ); | ||||||
|  | """, | ||||||
|  | """ | ||||||
|  | CREATE INDEX deleted_items_timestamps ON deleted_items(timestamp); | ||||||
|  | """, | ||||||
|  | """ | ||||||
|  | CREATE TABLE reminders ( | ||||||
|  |     id INTEGER PRIMARY KEY, | ||||||
|  |     remind_timestamp INTEGER NOT NULL, | ||||||
|  |     created_timestamp INTEGER NOT NULL, | ||||||
|  |     reminder TEXT NOT NULL, | ||||||
|  |     expired INTEGER NOT NULL, | ||||||
|  |     extra TEXT NOT NULL | ||||||
|  | ); | ||||||
|  | """ | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | async def init(db_path): | ||||||
|  |     db = await aiosqlite.connect(db_path) | ||||||
|  |  | ||||||
|  |     version = (await (await db.execute("PRAGMA user_version")).fetchone())[0] | ||||||
|  |     for i in range(version, len(migrations)): | ||||||
|  |         await db.executescript(migrations[i]) | ||||||
|  |         # Normally this would be a terrible idea because of SQL injection. | ||||||
|  |         # However, in this case there is not an obvious alternative (the parameter-based way apparently doesn't work) | ||||||
|  |         # and i + 1 will always be an integer anyway | ||||||
|  |         await db.execute(f"PRAGMA user_version = {i + 1}") | ||||||
|  |         await db.commit() | ||||||
|  |         logging.info(f"Migrated DB to schema {i + 1}") | ||||||
|  |  | ||||||
|  |     return db | ||||||
							
								
								
									
										60
									
								
								src-old/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src-old/main.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import irc.bot | ||||||
|  | import irc.strings | ||||||
|  | import jaraco.logging | ||||||
|  | import hashlib | ||||||
|  | import toml | ||||||
|  | import ssl | ||||||
|  | import string | ||||||
|  | import random | ||||||
|  |  | ||||||
|  | conf = toml.load(open("config.toml")) | ||||||
|  |  | ||||||
|  | class Bot(irc.bot.SingleServerIRCBot): | ||||||
|  |     def __init__(self, servers, nick, channel): | ||||||
|  |         super().__init__(servers, nick, nick, **{"ipv6": True}) | ||||||
|  |         print(dir(self)) | ||||||
|  |         self.channel = channel | ||||||
|  |  | ||||||
|  |     def on_nicknameinuse(self, c, e): | ||||||
|  |         c.nick("".join(random.choices(string.ascii_lowercase ,k=8))) | ||||||
|  |         print("nick in use, changing") | ||||||
|  |  | ||||||
|  |     def on_welcome(self, c, e): | ||||||
|  |         c.join(self.channel) | ||||||
|  |         print("joined", self.channel) | ||||||
|  |  | ||||||
|  |     def on_privmsg(self, c, e): | ||||||
|  |         self.do_command(e, e.arguments[0]) | ||||||
|  |  | ||||||
|  |     def on_pubmsg(self, c, e): | ||||||
|  |         a = e.arguments[0].split(":", 1) | ||||||
|  |         if len(a) > 1 and irc.strings.lower(a[0]) == irc.strings.lower(self.connection.get_nickname()): | ||||||
|  |             self.do_command(e, a[1].strip()) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     def do_command(self, e, cmd): | ||||||
|  |         nick = e.source.nick | ||||||
|  |         c = self.connection | ||||||
|  |  | ||||||
|  |         print("exec", nick, cmd) | ||||||
|  |  | ||||||
|  |         if cmd == "stats": | ||||||
|  |             for chname, chobj in self.channels.items(): | ||||||
|  |                 c.notice(nick, "--- Channel statistics ---") | ||||||
|  |                 c.notice(nick, "Channel: " + chname) | ||||||
|  |                 users = sorted(chobj.users()) | ||||||
|  |                 c.notice(nick, "Users: " + ", ".join(users)) | ||||||
|  |                 opers = sorted(chobj.opers()) | ||||||
|  |                 c.notice(nick, "Opers: " + ", ".join(opers)) | ||||||
|  |                 voiced = sorted(chobj.voiced()) | ||||||
|  |                 c.notice(nick, "Voiced: " + ", ".join(voiced)) | ||||||
|  |         elif cmd == "starch": | ||||||
|  |             c.notice(nick, "starch: it exists.") | ||||||
|  |         else: | ||||||
|  |             c.notice(nick, "Not understood: " + cmd) | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     s = conf["server"] | ||||||
|  |     bot = Bot([(s["host"], s["port"])], conf["nickname"], "#a") | ||||||
|  |      | ||||||
|  |     bot.start() | ||||||
							
								
								
									
										60
									
								
								src/db.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										60
									
								
								src/db.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | const Database = require("better-sqlite3") | ||||||
|  |  | ||||||
|  | const DB = Database(process.env.DB || "./db.sqlite3") | ||||||
|  |  | ||||||
|  | const migrations = [ | ||||||
|  | ` | ||||||
|  | CREATE TABLE thing_log ( | ||||||
|  |     id INTEGER PRIMARY KEY, | ||||||
|  |     timestamp INTEGER NOT NULL, | ||||||
|  |     type TEXT NOT NULL, | ||||||
|  |     thing TEXT NOT NULL | ||||||
|  | ); | ||||||
|  | `, | ||||||
|  | ` | ||||||
|  | CREATE TABLE inventory ( | ||||||
|  |     thing TEXT NOT NULL UNIQUE, | ||||||
|  |     obtained_at INTEGER NOT NULL, | ||||||
|  |     quantity REAL NOT NULL | ||||||
|  | ); | ||||||
|  | ` | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | const executeMigration = DB.transaction((i) => { | ||||||
|  |     const migration = migrations[i] | ||||||
|  |     DB.exec(migration) | ||||||
|  |     DB.pragma(`user_version = ${i + 1}`) | ||||||
|  |     console.log(`Migrated to schema ${i + 1}`) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const schemaVersion = DB.pragma("user_version", { simple: true }) | ||||||
|  | if (schemaVersion < migrations.length) { | ||||||
|  |     console.log(`Migrating DB - schema ${schemaVersion} used, schema ${migrations.length} available`) | ||||||
|  |     for (let i = schemaVersion; i < migrations.length; i++) { | ||||||
|  |         executeMigration(i) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | DB.pragma("foreign_keys = 1") | ||||||
|  |  | ||||||
|  | const preparedStatements = new Map() | ||||||
|  |  | ||||||
|  | const SQL = (strings, ...params) => { | ||||||
|  |     const sql = strings.join("?") | ||||||
|  |     let stmt | ||||||
|  |     const cachedValue = preparedStatements.get(sql) | ||||||
|  |     if (!cachedValue) { | ||||||
|  |         stmt = DB.prepare(sql) | ||||||
|  |         preparedStatements.set(sql, stmt) | ||||||
|  |     } else { | ||||||
|  |         stmt = cachedValue | ||||||
|  |     } | ||||||
|  |     return { | ||||||
|  |         get: () => stmt.get.apply(stmt, params), | ||||||
|  |         run: () => stmt.run.apply(stmt, params), | ||||||
|  |         all: () => stmt.all.apply(stmt, params), | ||||||
|  |         statement: stmt | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { DB, SQL } | ||||||
							
								
								
									
										135
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | const irc = require("irc-upd") | ||||||
|  | const childProcess = require("child_process") | ||||||
|  | const syllables = require("syllable") | ||||||
|  | const R = require("ramda") | ||||||
|  | const pluralize = require("pluralize") | ||||||
|  |  | ||||||
|  | const { DB, SQL } = require("./db") | ||||||
|  |  | ||||||
|  | var client = new irc.Client(process.argv[2] || "207:1473:146:ae77:a3be:d39f:8e59:d256", "testbot", { | ||||||
|  |     channels: ["#a", "#botrobots", "#b"], | ||||||
|  |     userName: "testbot", | ||||||
|  |     encoding: "utf8", | ||||||
|  |     port: 6667 | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | let links = null | ||||||
|  | let linksResolve = null | ||||||
|  |  | ||||||
|  | const logEv = (ty, thing) => SQL`INSERT INTO thing_log (timestamp, type, thing) VALUES (${Date.now()}, ${ty}, ${thing})`.run() | ||||||
|  |  | ||||||
|  | const scanlinks = () => { | ||||||
|  |     return new Promise(resolve => { | ||||||
|  |         if (!linksResolve) { | ||||||
|  |             client.send("LINKS") | ||||||
|  |             linksResolve = [resolve] | ||||||
|  |             links = [] | ||||||
|  |         } else { | ||||||
|  |             linksResolve.push(resolve) | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | let sylhist = [] | ||||||
|  | let mhist = [] | ||||||
|  |  | ||||||
|  | // pluralize seems to drop unicode sometimes | ||||||
|  | // hackily cope with this | ||||||
|  | const safelyOperate = (s, fn) => { | ||||||
|  |     let res = fn(s) | ||||||
|  |     if (res.trim() === "") { return s } | ||||||
|  |     return res | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const handleOf = (thing, fn) => { | ||||||
|  |     const ofsplt = /^(.*)\W+of\W+(.*)$/.exec(thing) | ||||||
|  |     if (ofsplt) { | ||||||
|  |         return `${fn(ofsplt[1])} of ${ofsplt[2]}` | ||||||
|  |     } else { | ||||||
|  |         return fn(thing) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const renderItem = x => x.quantity === 1 ? x.thing : `${x.quantity} ${handleOf(x.thing, pluralize.plural)}` | ||||||
|  |  | ||||||
|  | client.addListener("message", (nick, channel, message, ev) => { | ||||||
|  |     const e = /^<([^>]+)> (.*)$/.exec(message) | ||||||
|  |     if (e) { | ||||||
|  |         nick = e[1] | ||||||
|  |         message = e[2] | ||||||
|  |     } | ||||||
|  |     const res = /^([A-Za-z0-9_-]+)[:, ]*([A-Za-z0-9_-]+) *(.*)$/.exec(message) | ||||||
|  |     if (res) { | ||||||
|  |         let [_, tnick, cmd, args] = res | ||||||
|  |         tnick = tnick.toLowerCase() | ||||||
|  |         args = args.trim().replace(/[.!?]$/, "") | ||||||
|  |         cmd = cmd.toLowerCase() | ||||||
|  |         if (tnick === client.nick) { | ||||||
|  |             console.log(nick, cmd, channel) | ||||||
|  |             if (cmd === "starch") { | ||||||
|  |                 client.say(channel, `starch exists (${Math.random() * 20 + 80}% confidence).`) | ||||||
|  |             } else if (cmd === "stats") { | ||||||
|  |                 client.say(channel, "haha no") | ||||||
|  |             } else if (cmd === "help") { | ||||||
|  |                 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") | ||||||
|  |             } else if (cmd === "servers") { | ||||||
|  |                 scanlinks().then(links => 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) { | ||||||
|  |                     let qty = 1 | ||||||
|  |                     const res = /^([-0-9.]+|some|many)\b\s*(.*)$/iu.exec(thing) | ||||||
|  |                     if (res) { | ||||||
|  |                         const [_, rrqty, rthing] = res | ||||||
|  |                         thing = rthing | ||||||
|  |                         const rqty = rrqty.toLowerCase() | ||||||
|  |                         if (rqty === "some") { | ||||||
|  |                             qty = Math.floor(Math.random() * 17) | ||||||
|  |                         } else if (rqty === "many") { | ||||||
|  |                             qty = Math.floor(Math.random() * 2300) | ||||||
|  |                         } else { | ||||||
|  |                             qty = parseFloat(rqty) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     thing = handleOf(thing, pluralize.singular) | ||||||
|  |                     const currQty = SQL`SELECT * FROM inventory WHERE thing = ${thing}`.get()?.quantity | ||||||
|  |                     if (currQty) { qty += currQty } | ||||||
|  |                     qty = qty || 0 | ||||||
|  |                     SQL`INSERT OR REPLACE INTO inventory VALUES (${thing}, ${Date.now()}, ${qty})`.run() | ||||||
|  |                     console.log("attained", qty, thing) | ||||||
|  |                     client.say(channel, `I have ${renderItem({ thing, quantity: qty })}.`) | ||||||
|  |                 } | ||||||
|  |             } else if (cmd.startsWith("inv")) { | ||||||
|  |                 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}`) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sylhist = R.takeLast(3, R.append(syllables(message), sylhist)) | ||||||
|  |     mhist = R.takeLast(50, R.append(message, mhist)) | ||||||
|  |     if (R.equals(sylhist, [5, 7, 5])) { | ||||||
|  |         client.say(channel, "haiku detected!") | ||||||
|  |         logEv("haiku", R.takeLast(3, mhist).join("\n")) | ||||||
|  |     } | ||||||
|  |     console.log(sylhist) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | client.addListener("raw", ev => { | ||||||
|  |     if (ev.command === "rpl_links") { | ||||||
|  |         links.push(ev.args) | ||||||
|  |     } else if (ev.command === "rpl_endoflinks" && linksResolve) { | ||||||
|  |         linksResolve.forEach(r => r(links)) | ||||||
|  |         linksResolve = null | ||||||
|  |         links = [] | ||||||
|  |     } | ||||||
|  | }) | ||||||
		Reference in New Issue
	
	Block a user