Fixed timer plugin, now checks every hour (with aiocron) for timers in the next hour and sets them
This commit is contained in:
parent
4fdcad950c
commit
9d8405092e
|
@ -1,15 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
import irc3
|
import irc3
|
||||||
|
from aiocron import crontab
|
||||||
from docopt import Dict as DocOptDict
|
from docopt import Dict as DocOptDict
|
||||||
from irc3.plugins.command import command
|
from irc3.plugins.command import command
|
||||||
from irc3.utils import IrcString
|
from irc3.utils import IrcString
|
||||||
from psycopg2 import Error
|
from psycopg2 import Error
|
||||||
|
from psycopg2.extras import DictRow
|
||||||
|
|
||||||
from . import DatabasePlugin
|
from . import DatabasePlugin
|
||||||
from ..utils import time_delta
|
|
||||||
|
|
||||||
|
|
||||||
@irc3.plugin
|
@irc3.plugin
|
||||||
|
@ -19,23 +21,25 @@ class Timer(DatabasePlugin):
|
||||||
|
|
||||||
def __init__(self, bot: irc3.IrcBot):
|
def __init__(self, bot: irc3.IrcBot):
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
|
self.timers = set()
|
||||||
|
self.set_timers()
|
||||||
|
crontab('0 * * * *', func=self.set_timers)
|
||||||
|
|
||||||
# Fetch timers from database
|
def set_timers(self):
|
||||||
|
"""Function which queries all timers in the next hour and schedules them."""
|
||||||
|
self.log.debug('Fetching timers')
|
||||||
self.cur.execute('''
|
self.cur.execute('''
|
||||||
select
|
SELECT
|
||||||
id, mask, target, message, delay, ends_at
|
*
|
||||||
from
|
FROM
|
||||||
timers
|
timers
|
||||||
|
WHERE
|
||||||
|
ends_at >= now()
|
||||||
|
AND ends_at < now() + INTERVAL '1h'
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# Recreate timers
|
for timer in self.cur.fetchall():
|
||||||
for res in self.cur.fetchall():
|
asyncio.ensure_future(self.exec_timer(timer))
|
||||||
self.start_timer(IrcString(res['mask']),
|
|
||||||
res['target'],
|
|
||||||
res['message'],
|
|
||||||
res['delay'],
|
|
||||||
res['ends_at'] - datetime.now(),
|
|
||||||
res['id'])
|
|
||||||
|
|
||||||
@command
|
@command
|
||||||
def timer(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
def timer(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||||
|
@ -44,63 +48,51 @@ class Timer(DatabasePlugin):
|
||||||
%%timer <delay> <message>...
|
%%timer <delay> <message>...
|
||||||
"""
|
"""
|
||||||
delay = args['<delay>']
|
delay = args['<delay>']
|
||||||
delta = time_delta(delay)
|
|
||||||
|
|
||||||
if not delta:
|
|
||||||
self.bot.privmsg(target, 'Invalid timer delay')
|
|
||||||
else:
|
|
||||||
message = ' '.join(args['<message>'])
|
message = ' '.join(args['<message>'])
|
||||||
|
|
||||||
|
if not re.match(r'\d+[smhdwy]|mon', delay):
|
||||||
|
return 'Invalid timer delay: {}'.format(delay)
|
||||||
|
|
||||||
values = [mask, target, message, delay]
|
values = [mask, target, message, delay]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Insert into database (add now + delta)
|
|
||||||
self.cur.execute('''
|
self.cur.execute('''
|
||||||
insert into
|
INSERT INTO
|
||||||
timers (mask, target, message, delay, ends_at)
|
timers (mask, target, message, delay, ends_at)
|
||||||
values
|
VALUES
|
||||||
(%s, %s, %s, %s, %s)
|
(%s, %s, %s, %s, now() + INTERVAL %s)
|
||||||
returning id
|
RETURNING *
|
||||||
''', values + [datetime.now() + delta])
|
''', values + [delay])
|
||||||
self.con.commit()
|
self.con.commit()
|
||||||
|
|
||||||
# Add delta and id from inserted and start timer
|
asyncio.ensure_future(self.exec_timer(self.cur.fetchone()))
|
||||||
values.extend([delta, self.cur.fetchone()['id']])
|
|
||||||
self.start_timer(*values)
|
|
||||||
|
|
||||||
# Send notice to user that timer has been set
|
self.bot.notice(mask.nick, 'Timer in {delay} set: {message}'.format(delay=delay, message=message))
|
||||||
self.bot.notice(mask.nick, 'Timer in {delay} set: {message}'
|
except Error as ex:
|
||||||
.format(delay=delay, message=message))
|
self.log.error(ex)
|
||||||
except Error:
|
|
||||||
# Rollback transaction on error
|
|
||||||
self.con.rollback()
|
self.con.rollback()
|
||||||
|
|
||||||
def start_timer(self, mask: IrcString, target: IrcString, message: str,
|
async def exec_timer(self, timer: DictRow):
|
||||||
delay: str, delta: timedelta, row_id: int):
|
"""Sets the actual timer (sleeps until it fires), sends the reminder and deletes the timer from database."""
|
||||||
"""Async function, sleeps for `delay` seconds and sends notification"""
|
if timer['id'] in self.timers:
|
||||||
|
return
|
||||||
|
|
||||||
async def callback():
|
self.timers.add(timer['id'])
|
||||||
# Sleep if necessary until timed
|
seconds = (timer['ends_at'] - datetime.now()).total_seconds()
|
||||||
seconds = delta.total_seconds()
|
if seconds > 0.0:
|
||||||
if seconds > 0:
|
|
||||||
await asyncio.sleep(seconds)
|
await asyncio.sleep(seconds)
|
||||||
|
|
||||||
# Send reminder
|
self.bot.privmsg(timer['target'], '\x02[Timer]\x02 {nick}: {message} ({delay})'.format(
|
||||||
self.bot.privmsg(target, '\x02[Timer]\x0F {nick}: {message} '
|
message=timer['message'],
|
||||||
'({delay})'.format(message=message,
|
nick=IrcString(timer['mask']).nick,
|
||||||
nick=mask.nick,
|
delay=timer['delay'],
|
||||||
delay=delay))
|
))
|
||||||
|
|
||||||
try:
|
self.timers.remove(timer['id'])
|
||||||
# Delete timer from database
|
|
||||||
self.cur.execute('''
|
self.cur.execute('''
|
||||||
delete from
|
DELETE FROM
|
||||||
timers
|
timers
|
||||||
where
|
WHERE
|
||||||
id = %s
|
id = %s
|
||||||
''', [row_id])
|
''', [timer['id']])
|
||||||
self.con.commit()
|
self.con.commit()
|
||||||
except Error:
|
|
||||||
# Rollback transaction on error
|
|
||||||
self.con.rollback()
|
|
||||||
|
|
||||||
asyncio.ensure_future(callback())
|
|
||||||
|
|
45
bot/utils.py
45
bot/utils.py
|
@ -1,51 +1,20 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
|
||||||
import random
|
import random
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
TIME_UNITS = {
|
|
||||||
's': 'seconds',
|
|
||||||
'm': 'minutes',
|
|
||||||
'h': 'hours',
|
|
||||||
'd': 'days',
|
|
||||||
'w': 'weeks',
|
|
||||||
'mon': 'months',
|
|
||||||
'y': 'years',
|
|
||||||
}
|
|
||||||
|
|
||||||
pp = pprint
|
pp = pprint
|
||||||
|
|
||||||
|
|
||||||
|
def re_generator(low: int = 5, high: int = 20) -> str:
|
||||||
|
return 'R{}'.format('E' * random.randint(low, high))
|
||||||
|
|
||||||
|
|
||||||
def date_from_iso(date: str) -> datetime:
|
def date_from_iso(date: str) -> datetime:
|
||||||
return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%fZ')
|
return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||||
|
|
||||||
|
|
||||||
def time_delta(text: str) -> timedelta:
|
|
||||||
match = re.match(r'(\d+)(s|m|h|d|mon|w|y)', text)
|
|
||||||
if match:
|
|
||||||
num, unit = match.groups()
|
|
||||||
num = int(num)
|
|
||||||
if unit == 's':
|
|
||||||
unit = 'seconds'
|
|
||||||
elif unit == 'm':
|
|
||||||
unit = 'minutes'
|
|
||||||
elif unit == 'h':
|
|
||||||
unit = 'hours'
|
|
||||||
elif unit == 'd':
|
|
||||||
unit = 'days'
|
|
||||||
elif unit == 'w':
|
|
||||||
unit = 'weeks'
|
|
||||||
elif unit == 'mon':
|
|
||||||
unit = 'weeks'
|
|
||||||
num *= 4
|
|
||||||
elif unit == 'y':
|
|
||||||
unit = 'weeks'
|
|
||||||
num *= 52
|
|
||||||
return timedelta(**{unit: num})
|
|
||||||
|
|
||||||
|
|
||||||
def parse_int(val: str, select: bool = True) -> Tuple[int, str]:
|
def parse_int(val: str, select: bool = True) -> Tuple[int, str]:
|
||||||
try:
|
try:
|
||||||
val = int(val)
|
val = int(val)
|
||||||
|
@ -60,7 +29,3 @@ def parse_int(val: str, select: bool = True) -> Tuple[int, str]:
|
||||||
return val, order
|
return val, order
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def re_generator(low: int = 5, high: int = 20) -> str:
|
|
||||||
return 'R{}'.format('E' * random.randint(low, high))
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#git+https://github.com/gawel/irc3.git#egg=irc3
|
#git+https://github.com/gawel/irc3.git#egg=irc3
|
||||||
git+https://github.com/mrhanky17/irc3.git#egg=irc3
|
git+https://github.com/mrhanky17/irc3.git#egg=irc3
|
||||||
git+https://github.com/mrhanky17/docopt.git#egg=docopt
|
git+https://github.com/mrhanky17/docopt.git#egg=docopt
|
||||||
|
aiocron==0.6
|
||||||
psycopg2==2.7.1
|
psycopg2==2.7.1
|
||||||
requests==2.14.2
|
requests==2.14.2
|
||||||
feedparser==5.2.1
|
feedparser==5.2.1
|
||||||
|
|
Loading…
Reference in New Issue
Block a user