Compare commits
No commits in common. "ba683bcdf64da60c34e5ec359cdfea476723345a" and "8cc65f8cbb2599e9063c9af4d4afa75196501cf3" have entirely different histories.
ba683bcdf6
...
8cc65f8cbb
4
bot.py
4
bot.py
|
@ -16,7 +16,6 @@ import db
|
|||
import tools
|
||||
import config
|
||||
import loader
|
||||
import scheduler
|
||||
from trigger import Trigger
|
||||
|
||||
class Fulvia(irc.IRCClient):
|
||||
|
@ -98,9 +97,6 @@ class Fulvia(irc.IRCClient):
|
|||
self._disabled_modules = config.disabled_modules
|
||||
"""These modules will NOT be loaded when load_modules() is called."""
|
||||
|
||||
self.scheduler = scheduler.Scheduler(self)
|
||||
"""The bot's task scheduler."""
|
||||
|
||||
self.load_modules()
|
||||
|
||||
|
||||
|
|
|
@ -4,79 +4,193 @@ Reminds of things.
|
|||
"""
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import sqlite3
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
import config
|
||||
import module
|
||||
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.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()
|
||||
self.stop.wait(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)
|
||||
|
||||
|
||||
regex = (
|
||||
"(?=\d+[ywdhms])"
|
||||
"(\d+y)?"
|
||||
"(\d+w)?"
|
||||
"(\d+d)?"
|
||||
"(\d+h)?"
|
||||
"(\d+m)?"
|
||||
"(\d+s)?"
|
||||
"(?:(\d+)y)?"
|
||||
"(?:(\d+)w)?"
|
||||
"(?:(\d+)d)?"
|
||||
"(?:(\d+)h)?"
|
||||
"(?:(\d+)m)?"
|
||||
"(?:(\d+)s)?"
|
||||
)
|
||||
shorthand = {
|
||||
'y': 'years',
|
||||
'w': 'weeks',
|
||||
'd': 'days',
|
||||
'h': 'hours',
|
||||
'm': 'minutes',
|
||||
's': 'seconds',
|
||||
}
|
||||
multiplier = [
|
||||
60*60*24*365,
|
||||
60*60*24*7,
|
||||
60*60*24,
|
||||
60*60,
|
||||
60,
|
||||
1
|
||||
]
|
||||
|
||||
@module.commands('remind')
|
||||
@module.example('.remind 3h45m Go to class')
|
||||
@commands('remind')
|
||||
@example('.remind 3h45m Go to class')
|
||||
def remind(bot, trigger):
|
||||
"""Gives you a reminder in the given amount of time."""
|
||||
if len(trigger.args) == 1:
|
||||
return bot.msg("Missing arguments for reminder command.")
|
||||
|
||||
reg = re.search(regex, trigger.args[1])
|
||||
if not reg:
|
||||
return bot.reply("I didn't understand that.")
|
||||
args = {shorthand[g[-1]]: int(g[:-1]) for g in reg.groups() if g}
|
||||
delta = timedelta(**args)
|
||||
dt = datetime.now() + delta
|
||||
if len(trigger.args) == 2:
|
||||
reminder = ''
|
||||
else:
|
||||
reminder = ' '.join(trigger.args[2:])
|
||||
args = (trigger.channel, trigger.nick, reminder)
|
||||
bot.scheduler.add_task(announce_reminder, dt, args)
|
||||
bot.reply("Okay, will remind at", dt.strftime('[%Y-%m-%d %H:%M:%S]'))
|
||||
|
||||
duration = 0
|
||||
for n, group in enumerate(re.search(regex, trigger.args[1]).groups()):
|
||||
if not group:
|
||||
continue
|
||||
duration += int(group) * multiplier[n]
|
||||
create_reminder(bot, trigger, duration, reminder)
|
||||
|
||||
|
||||
@module.commands('at')
|
||||
@module.example('.at 2012-12-21 18:00:00 End the world.')
|
||||
@commands('at')
|
||||
@example('.at 2012-12-21 18:00:00 End the world.')
|
||||
def at(bot, trigger):
|
||||
"""
|
||||
Gives you a reminder at the given time and date. Datetime must be in
|
||||
YYYY-MM-DD HH:MM:SS format. Only the bot's timezone is used. If
|
||||
YYYY-MM-DD is excluded, it is assumed to be today's date.
|
||||
YYYY-MM-DD HH:MM:SS format. Only the bot's timezone is used.
|
||||
"""
|
||||
if len(trigger.args) < 2:
|
||||
if len(trigger.args) < 3:
|
||||
return bot.msg("Missing arguments for reminder command.")
|
||||
if ':' in trigger.args[1]:
|
||||
at_time = datetime.now().strftime('%Y-%m-%d') + ' ' + trigger.args[1]
|
||||
reminder = ' '.join(trigger.args[2:])
|
||||
if len(trigger.args) < 4:
|
||||
reminder = ''
|
||||
else:
|
||||
at_time = ' '.join(trigger.args[1:3])
|
||||
reminder = ' '.join(trigger.args[3:])
|
||||
|
||||
try:
|
||||
dt = datetime.strptime(at_time, '%Y-%m-%d %H:%M:%S')
|
||||
at_time = datetime.strptime(' '.join(trigger.args[1:2]),
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
return bot.msg("Datetime improperly formatted.")
|
||||
diff = at_time - datetime.now()
|
||||
duration = diff.seconds
|
||||
|
||||
args = (trigger.channel, trigger.nick, reminder)
|
||||
bot.scheduler.add_task(announce_reminder, dt, args)
|
||||
bot.reply("Okay, will remind at", dt.strftime('[%Y-%m-%d %H:%M:%S]'))
|
||||
create_reminder(bot, trigger, duration, reminder)
|
||||
|
||||
|
||||
def announce_reminder(bot, channel, remindee, reminder):
|
||||
"""Announce the reminder."""
|
||||
if reminder:
|
||||
msg = f"{remindee}: {reminder}"
|
||||
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.fromtimestamp(t)
|
||||
t_format = config.default_time_format
|
||||
timef = datetime.strftime(remind_at, t_format)
|
||||
|
||||
bot.reply('Okay, will remind at %s' % timef)
|
||||
else:
|
||||
msg = f"{remindee}!"
|
||||
bot.msg(channel, msg)
|
||||
bot.reply('Okay, will remind in %s secs' % duration)
|
||||
|
|
43
scheduler.py
43
scheduler.py
|
@ -1,43 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Allows the bot to scheduler tasks to be performed later.
|
||||
"""
|
||||
import time
|
||||
import pickle
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
class Scheduler:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.tasks = []
|
||||
self.lock = threading.Lock()
|
||||
self._t = threading.Thread(target=self.loop)
|
||||
self._t.start()
|
||||
|
||||
def loop(self):
|
||||
while True:
|
||||
self.lock.acquire()
|
||||
tasks_due = [t for t in self.tasks if t[1] <= datetime.now()]
|
||||
for task in tasks_due:
|
||||
args = (self.bot,) + task[2]
|
||||
t = threading.Thread(target=task[0], args=args)
|
||||
t.start()
|
||||
self.tasks.remove(task)
|
||||
self.lock.release()
|
||||
time.sleep(5)
|
||||
|
||||
def add_task(self, func, dt, args):
|
||||
"""
|
||||
`func` - The function to call. Must accept `bot` as the first
|
||||
argument. Must be picklable.
|
||||
`dt` - A datetime object representing when to call the function.
|
||||
`args` - Arguments to call the function with, not including `bot`.
|
||||
"""
|
||||
self.lock.acquire()
|
||||
self.tasks.append((func, dt, args))
|
||||
self.lock.release()
|
||||
# db
|
||||
|
||||
def add_periodic_task(self):
|
||||
pass
|
Loading…
Reference in New Issue
Block a user