fulvia/modules/seen.py

160 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""
When was this user last seen.
"""
import os
import time
import threading
from datetime import datetime
from sqlite3 import OperationalError
from requests.structures import CaseInsensitiveDict
import tools
import config
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 Dad -l", "Last heard from Dad at [1997-03-12 16:30:00] "\
+"with \"Just going to the store for some cigarettes 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.
-l, --last - reports when the user was last seen. This is the default.
-f, --first - reports when the user was first seen.
-m, --message - includes the first/last message the user sent.
-c, --context - includes irc logs before and after their last message as context. Implies --message.
"""
if len(trigger.args) < 2:
return bot.reply("Seen who?")
parser = tools.FulviaArgparse()
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")
try:
args = parser.parse_args(trigger.args[1:])
except Exception as e:
return bot.reply(type(e).__name__ + ": " + str(e))
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)
reltime = tools.relative_time(datetime.now(), timestamp)
t_format = config.default_time_format
timestamp = datetime.strftime(timestamp, t_format)
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):
bot.memory["seen_lock"].acquire()
last = (time.time(), trigger.channel, ' '.join(trigger.args))
if not trigger.nick in bot.memory["seen"]:
first = (time.time(), trigger.channel, ' '.join(trigger.args))
else:
first = bot.memory["seen"][trigger.nick][:3]
seen = first + last
bot.memory["seen"][trigger.nick] = seen
bot.memory["seen_lock"].release()
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()