261 lines
6.5 KiB
Python
261 lines
6.5 KiB
Python
|
#!/usr/bin/env python3
|
||
|
"""
|
||
|
Reminds of things.
|
||
|
"""
|
||
|
import os
|
||
|
import re
|
||
|
import time
|
||
|
import sqlite3
|
||
|
import datetime
|
||
|
import threading
|
||
|
import collections
|
||
|
|
||
|
from module import commands, example
|
||
|
|
||
|
class MonitorThread(threading.Thread):
|
||
|
"""
|
||
|
A custom custom thread class for monitoring the time to announce
|
||
|
reminders. It allows itself to be stopped when there are no reminders
|
||
|
to look for.
|
||
|
"""
|
||
|
def __init__(self, bot):
|
||
|
threading.Thread.__init__(self)
|
||
|
self._bot = bot
|
||
|
self.stop = threading.Event()
|
||
|
|
||
|
def run(self):
|
||
|
# while not self._bot.channels.keys():
|
||
|
while not self._bot.stillConnected():
|
||
|
time.sleep(1)
|
||
|
# don't try to say anything if we're not fully connected yet
|
||
|
while not self.stop.is_set():
|
||
|
now = int(time.time())
|
||
|
unixtimes = [int(key) for key in self._bot.memory["remind"].keys()]
|
||
|
oldtimes = [t for t in unixtimes if t <= now]
|
||
|
if oldtimes:
|
||
|
for oldtime in oldtimes:
|
||
|
for reminder in self._bot.memory["remind"][oldtime]:
|
||
|
channel, nick, message = reminder
|
||
|
if message:
|
||
|
self._bot.msg(channel, nick + ': ' + message)
|
||
|
else:
|
||
|
self._bot.msg(channel, nick + '!')
|
||
|
del self._bot.memory["remind"][oldtime]
|
||
|
delete_reminder(self._bot, oldtime)
|
||
|
if not self._bot.memory["remind"] or not self._bot.stillConnected():
|
||
|
self.stop.set()
|
||
|
time.sleep(2.5)
|
||
|
del self._bot.memory["remind_monitor"]
|
||
|
|
||
|
|
||
|
def start_monitor(bot):
|
||
|
"""
|
||
|
Starts the monitor thread. Does nothing if one is already running.
|
||
|
"""
|
||
|
if bot.memory.get("remind_monitor"):
|
||
|
return
|
||
|
t = MonitorThread(bot)
|
||
|
t.start()
|
||
|
bot.memory["remind_monitor"] = t
|
||
|
|
||
|
|
||
|
def load_database(bot):
|
||
|
"""
|
||
|
Loads all entries from the 'remind' table in the bot's database and
|
||
|
stores them in memory
|
||
|
"""
|
||
|
data = {}
|
||
|
reminds = bot.db.execute("SELECT * FROM remind").fetchall()
|
||
|
|
||
|
for remind in reminds:
|
||
|
unixtime, channel, nick, message = remind
|
||
|
reminder = (channel, nick, message)
|
||
|
try:
|
||
|
data[unixtime].append(reminder)
|
||
|
except KeyError:
|
||
|
data[unixtime] = [reminder]
|
||
|
return data
|
||
|
|
||
|
|
||
|
def insert_reminder(bot, unixtime, reminder):
|
||
|
"""
|
||
|
Inserts a new reminder into the 'remind' table in the bot's database.
|
||
|
reminder - a tuple containing (channel, nick, message)
|
||
|
"""
|
||
|
bot.db.execute("INSERT INTO remind (unixtime, channel, nick, message) "
|
||
|
"VALUES(?,?,?,?)", (unixtime,) + reminder)
|
||
|
|
||
|
|
||
|
def delete_reminder(bot, unixtime):
|
||
|
"""
|
||
|
Deletes a reminder from the 'remind' table in the bot's database, using
|
||
|
unixtime as the key.
|
||
|
"""
|
||
|
bot.db.execute("DELETE FROM remind WHERE unixtime = ?", (unixtime,))
|
||
|
|
||
|
|
||
|
def setup(bot):
|
||
|
con = bot.db.connect()
|
||
|
cur = con.cursor()
|
||
|
try:
|
||
|
cur.execute("SELECT * FROM remind").fetchone()
|
||
|
except sqlite3.OperationalError:
|
||
|
cur.execute("CREATE TABLE remind("
|
||
|
"unixtime INTEGER DEFAULT (STRFTIME('%s', 'now')),"
|
||
|
"channel TEXT,"
|
||
|
"nick TEXT,"
|
||
|
"message TEXT)")
|
||
|
con.commit()
|
||
|
con.close()
|
||
|
bot.memory["remind"] = load_database(bot)
|
||
|
start_monitor(bot)
|
||
|
|
||
|
|
||
|
scaling = collections.OrderedDict([
|
||
|
('years', 365.25 * 24 * 3600),
|
||
|
('year', 365.25 * 24 * 3600),
|
||
|
('yrs', 365.25 * 24 * 3600),
|
||
|
('y', 365.25 * 24 * 3600),
|
||
|
|
||
|
('months', 29.53059 * 24 * 3600),
|
||
|
('month', 29.53059 * 24 * 3600),
|
||
|
('mo', 29.53059 * 24 * 3600),
|
||
|
|
||
|
('weeks', 7 * 24 * 3600),
|
||
|
('week', 7 * 24 * 3600),
|
||
|
('wks', 7 * 24 * 3600),
|
||
|
('wk', 7 * 24 * 3600),
|
||
|
('w', 7 * 24 * 3600),
|
||
|
|
||
|
('days', 24 * 3600),
|
||
|
('day', 24 * 3600),
|
||
|
('d', 24 * 3600),
|
||
|
|
||
|
('hours', 3600),
|
||
|
('hour', 3600),
|
||
|
('hrs', 3600),
|
||
|
('hr', 3600),
|
||
|
('h', 3600),
|
||
|
|
||
|
('minutes', 60),
|
||
|
('minute', 60),
|
||
|
('mins', 60),
|
||
|
('min', 60),
|
||
|
('m', 60),
|
||
|
|
||
|
('seconds', 1),
|
||
|
('second', 1),
|
||
|
('secs', 1),
|
||
|
('sec', 1),
|
||
|
('s', 1),
|
||
|
])
|
||
|
|
||
|
periods = '|'.join(scaling.keys())
|
||
|
|
||
|
|
||
|
@commands('remind')
|
||
|
@example('.remind 3h45m Go to class')
|
||
|
def remind(bot, trigger):
|
||
|
"""Gives you a reminder in the given amount of time."""
|
||
|
if not trigger.group(2):
|
||
|
return bot.say("Missing arguments for reminder command.")
|
||
|
if trigger.group(3) and not trigger.group(4):
|
||
|
return bot.say("No message given for reminder.")
|
||
|
|
||
|
duration = 0
|
||
|
message = filter(None, re.split(f"(\d+(?:\.\d+)? ?(?:(?i) {periods})) ?",
|
||
|
trigger.group(2))[1:])
|
||
|
reminder = ''
|
||
|
stop = False
|
||
|
for piece in message:
|
||
|
grp = re.match('(\d+(?:\.\d+)?) ?(.*) ?', piece)
|
||
|
if grp and not stop:
|
||
|
length = float(grp.group(1))
|
||
|
factor = scaling.get(grp.group(2).lower(), 60)
|
||
|
duration += length * factor
|
||
|
else:
|
||
|
reminder = reminder + piece
|
||
|
stop = True
|
||
|
if duration == 0:
|
||
|
return bot.reply("Sorry, didn't understand the input.")
|
||
|
|
||
|
if duration % 1:
|
||
|
duration = int(duration) + 1
|
||
|
else:
|
||
|
duration = int(duration)
|
||
|
create_reminder(bot, trigger, duration, reminder)
|
||
|
|
||
|
|
||
|
@commands('at')
|
||
|
@example('.at 13:47 Do your homework!')
|
||
|
@example('.at 16:30UTC-5 Do cocaine')
|
||
|
@example('.at 14:45:45 Remove dick from oven')
|
||
|
def at(bot, trigger):
|
||
|
"""
|
||
|
Gives you a reminder at the given time. Takes hh:mm:ssUTC+/-##
|
||
|
message. Timezone, if provided, must be in UTC format. 24 hour
|
||
|
clock format only.
|
||
|
"""
|
||
|
if not trigger.group(2):
|
||
|
return bot.say("No arguments given for reminder command.")
|
||
|
if trigger.group(3) and not trigger.group(4):
|
||
|
return bot.say("No message given for reminder.")
|
||
|
|
||
|
regex = re.compile(r"(\d+):(\d+)(?::(\d+))?(?:UTC([+-]\d+))? (.*)")
|
||
|
match = regex.match(trigger.group(2))
|
||
|
if not match:
|
||
|
return bot.reply("Sorry, but I didn't understand your input.")
|
||
|
|
||
|
hour, minute, second, tz, message = match.groups()
|
||
|
if not second:
|
||
|
second = '0'
|
||
|
|
||
|
if tz:
|
||
|
try:
|
||
|
tz = int(tz.replace("UTC", ""))
|
||
|
except ValueError:
|
||
|
bot.say("Invalid timezone. Using the bot's current timezone.")
|
||
|
tz = None
|
||
|
|
||
|
if tz:
|
||
|
timezone = datetime.timezone(datetime.timedelta(hours=tz))
|
||
|
else:
|
||
|
timezone = datetime.datetime.now().astimezone().tzinfo
|
||
|
# current timezone the bot is in
|
||
|
|
||
|
now = datetime.datetime.now(timezone)
|
||
|
at_time = datetime.datetime(now.year, now.month, now.day,
|
||
|
int(hour), int(minute), int(second),
|
||
|
tzinfo=timezone)
|
||
|
timediff = at_time - now
|
||
|
|
||
|
duration = timediff.seconds
|
||
|
|
||
|
if duration < 0:
|
||
|
duration += 86400
|
||
|
create_reminder(bot, trigger, duration, message)
|
||
|
|
||
|
|
||
|
def create_reminder(bot, trigger, duration, message):
|
||
|
"""
|
||
|
Inserts the reminder into the bot's memory and database so it can
|
||
|
eventually announce for it.
|
||
|
"""
|
||
|
t = int(time.time()) + duration
|
||
|
reminder = (trigger.channel, trigger.nick, message)
|
||
|
try:
|
||
|
bot.memory["remind"][t].append(reminder)
|
||
|
except KeyError:
|
||
|
bot.memory["remind"][t] = [reminder]
|
||
|
start_monitor(bot)
|
||
|
insert_reminder(bot, t, reminder)
|
||
|
|
||
|
if duration >= 60:
|
||
|
remind_at = datetime.datetime.fromtimestamp(t)
|
||
|
t_format = bot.config.core.default_time_format
|
||
|
timef = datetime.datetime.strftime(remind_at, t_format)
|
||
|
|
||
|
bot.reply('Okay, will remind at %s' % timef)
|
||
|
else:
|
||
|
bot.reply('Okay, will remind in %s secs' % duration)
|