#!/usr/bin/env python3 """ When was this user last seen. """ import os import time import argparse import threading from datetime import datetime from sqlite3 import OperationalError from requests.structures import CaseInsensitiveDict from tools.time import relativeTime from module import commands, example, hook, require_chanmsg, rate def load_database(bot): """ Loads all entries from the 'seen' table in the bot's database and returns them. """ data = CaseInsensitiveDict() seens = bot.db.execute("SELECT * FROM seen").fetchall() for seen in seens: nick, seen = seen[0].lower(), seen[1:] data[nick] = seen return data def setup(bot): con = bot.db.connect() cur = con.cursor() try: cur.execute("SELECT * FROM seen").fetchone() except OperationalError: cur.execute("CREATE TABLE seen(" "nick TEXT PRIMARY KEY," "first_timestamp INTEGER," "first_channel TEXT," "first_message TEXT," "last_timestamp INTEGER," "last_channel TEXT," "last_message TEXT)") con.commit() con.close() bot.memory["seen_lock"] = threading.Lock() bot.memory["seen"] = load_database(bot) bot.memory["seen_last_dump"] = time.time() @rate(1) @commands('seen') @example(".seen Nigger -l", "Last heard from Nigger at [1997-03-12 16:30:00] "\ +"with \"Just going to the store for some smokes babe I'll be right back\"") @example(".seen Soma_QM", "I haven't seen Soma_QM") def seen(bot, trigger): """ Reports when and where the user was last/first seen. --last [-l] reports when the user was last seen. This is the default. --first [-f] reports when the user was first seen. --message [-m] includes the first/last message the user sent. --context [-c] includes irc logs before and after their last message as context. Implies --message. """ if not trigger.group(2): return bot.reply("Seen who?") parser = argparse.ArgumentParser() parser.add_argument("nick") parser.add_argument("-l", "--last", action="store_true", default=True) parser.add_argument("-f", "--first", action="store_true") parser.add_argument("-m", "--message", action="store_true") parser.add_argument("-c", "--context", action="store_true") args = parser.parse_args(trigger.group[3:]) if args.nick == bot.nick: return bot.reply("I'm right here!") if args.nick in bot.memory["seen"]: if args.first: timestamp, channel, message = bot.memory["seen"][args.nick][:3] else: timestamp, channel, message = bot.memory["seen"][args.nick][3:] else: return bot.msg(f"I haven't seen \x0308{args.nick}") timestamp = datetime.fromtimestamp(timestamp) t_format = bot.config.core.default_time_format timestamp = datetime.strftime(timestamp, t_format) reltime = relativeTime(bot.config, datetime.now(), timestamp) if args.first: msg = "First" else: msg = "Last" msg += f" heard from \x0308{args.nick}\x03 at {timestamp} " \ + f"(\x0312{reltime} ago\x03) in \x0312{channel}" if args.message: msg += f'\x03 with "\x0308{message}\x03"' bot.msg(msg) if args.context: num_con_lines = 2 fname = os.path.join(bot.log_path, bot.hostname,channel,timestamp[1:11]) fname += ".log" if not os.path.isfile(fname): return bot.msg("Context not available.") with open(fname, "r") as file: data = file.read().splitlines() matches = [line for line in data if line.startswith(timestamp)] if not matches: return bot.msg("Context not available") index = data.index(matches[0]) # a bit lazy, but w/e start = max([index - num_con_lines, 0]) end = min([index + num_con_lines+1, len(data) - 1]) bot.msg("Context:") bot.msg("\n".join(data[start:end])) del data def dump_seen_db(bot): """ Dumps the seen database into the bot's database. """ bot.memory["seen_lock"].acquire() for nick, seen in bot.memory["seen"].items(): bot.db.execute("INSERT OR REPLACE INTO seen " "(nick, first_timestamp, first_channel, first_message," "last_timestamp, last_channel, last_message)" "VALUES (?, ?, ?, ?, ?, ?, ?)", (nick,) + seen) bot.memory["seen_lock"].release() @hook(True) @require_chanmsg def seen_hook(bot, trigger): last = (time.time(), trigger.channel, trigger.group(0)) if not trigger.nick in bot.memory["seen"]: first = (time.time(), trigger.channel, trigger.group(0)) else: first = bot.memory["seen"][trigger.nick][:3] seen = first + last bot.memory["seen"][trigger.nick] = seen if time.time() - bot.memory["seen_last_dump"] > 1: # only dump once a minute at most dump_seen_db(bot) bot.memory["seen_last_dump"] = time.time()