|  | @@ -0,0 +1,172 @@
 | 
	
		
			
			|  | 1 | +# encoding: UTF-8
 | 
	
		
			
			|  | 2 | +###
 | 
	
		
			
			|  | 3 | +# Copyright (c) 2016, Toni Fadjukoff
 | 
	
		
			
			|  | 4 | +# All rights reserved.
 | 
	
		
			
			|  | 5 | +#
 | 
	
		
			
			|  | 6 | +# Redistribution and use in source and binary forms, with or without
 | 
	
		
			
			|  | 7 | +# modification, are permitted provided that the following conditions are met:
 | 
	
		
			
			|  | 8 | +#
 | 
	
		
			
			|  | 9 | +#   * Redistributions of source code must retain the above copyright notice,
 | 
	
		
			
			|  | 10 | +#     this list of conditions, and the following disclaimer.
 | 
	
		
			
			|  | 11 | +#   * Redistributions in binary form must reproduce the above copyright notice,
 | 
	
		
			
			|  | 12 | +#     this list of conditions, and the following disclaimer in the
 | 
	
		
			
			|  | 13 | +#     documentation and/or other materials provided with the distribution.
 | 
	
		
			
			|  | 14 | +#   * Neither the name of the author of this software nor the name of
 | 
	
		
			
			|  | 15 | +#     contributors to this software may be used to endorse or promote products
 | 
	
		
			
			|  | 16 | +#     derived from this software without specific prior written consent.
 | 
	
		
			
			|  | 17 | +#
 | 
	
		
			
			|  | 18 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | 
	
		
			
			|  | 19 | +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
	
		
			
			|  | 20 | +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
	
		
			
			|  | 21 | +# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 | 
	
		
			
			|  | 22 | +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
	
		
			
			|  | 23 | +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
	
		
			
			|  | 24 | +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
	
		
			
			|  | 25 | +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | 
	
		
			
			|  | 26 | +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | 
	
		
			
			|  | 27 | +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
	
		
			
			|  | 28 | +# POSSIBILITY OF SUCH DAMAGE.
 | 
	
		
			
			|  | 29 | +
 | 
	
		
			
			|  | 30 | +###
 | 
	
		
			
			|  | 31 | +
 | 
	
		
			
			|  | 32 | +import time
 | 
	
		
			
			|  | 33 | +from dateutil.rrule import *
 | 
	
		
			
			|  | 34 | +
 | 
	
		
			
			|  | 35 | +import supybot.utils as utils
 | 
	
		
			
			|  | 36 | +from supybot.commands import *
 | 
	
		
			
			|  | 37 | +import supybot.plugins as plugins
 | 
	
		
			
			|  | 38 | +import supybot.ircutils as ircutils
 | 
	
		
			
			|  | 39 | +import supybot.schedule as schedule
 | 
	
		
			
			|  | 40 | +import supybot.callbacks as callbacks
 | 
	
		
			
			|  | 41 | +try:
 | 
	
		
			
			|  | 42 | +    from supybot.i18n import PluginInternationalization
 | 
	
		
			
			|  | 43 | +    _ = PluginInternationalization('Timer')
 | 
	
		
			
			|  | 44 | +except ImportError:
 | 
	
		
			
			|  | 45 | +    # Placeholder that allows to run the plugin on a bot
 | 
	
		
			
			|  | 46 | +    # without the i18n module
 | 
	
		
			
			|  | 47 | +    _ = lambda x: x
 | 
	
		
			
			|  | 48 | +
 | 
	
		
			
			|  | 49 | +
 | 
	
		
			
			|  | 50 | +class Timer(callbacks.Plugin):
 | 
	
		
			
			|  | 51 | +    """Command based scheduling for triggers"""
 | 
	
		
			
			|  | 52 | +
 | 
	
		
			
			|  | 53 | +    def __init__(self, irc):
 | 
	
		
			
			|  | 54 | +        self.__parent = super(Timer, self)
 | 
	
		
			
			|  | 55 | +        self.__parent.__init__(irc)
 | 
	
		
			
			|  | 56 | +        self.events = {}
 | 
	
		
			
			|  | 57 | +
 | 
	
		
			
			|  | 58 | +    def _makeCommandFunction(self, irc, msg, message):
 | 
	
		
			
			|  | 59 | +        nickprefix = msg.prefix.split("!")[0] + ": " if ircutils.isChannel(msg.args[0]) else ""
 | 
	
		
			
			|  | 60 | +        message = format("echo %s%s", nickprefix, message)
 | 
	
		
			
			|  | 61 | +        tokens = callbacks.tokenize(message)
 | 
	
		
			
			|  | 62 | +        def f():
 | 
	
		
			
			|  | 63 | +            del self.events[str(f.eventId)]
 | 
	
		
			
			|  | 64 | +            self.Proxy(irc.irc, msg, tokens)
 | 
	
		
			
			|  | 65 | +        return f
 | 
	
		
			
			|  | 66 | +
 | 
	
		
			
			|  | 67 | +    @staticmethod
 | 
	
		
			
			|  | 68 | +    def _parseDate(arg):
 | 
	
		
			
			|  | 69 | +        day = 0
 | 
	
		
			
			|  | 70 | +        month = 0
 | 
	
		
			
			|  | 71 | +        if len(arg) == 6 and arg[2] == arg[5] == "." and arg[0:2].isdigit() and arg[3:5].isdigit():
 | 
	
		
			
			|  | 72 | +                day = int(arg[0:2])
 | 
	
		
			
			|  | 73 | +                month = int(arg[3:5])
 | 
	
		
			
			|  | 74 | +        elif len(arg) == 5 and arg[2] == arg[4] == "." and arg[0:2].isdigit() and arg[3:4].isdigit():
 | 
	
		
			
			|  | 75 | +                day = int(arg[0:2])
 | 
	
		
			
			|  | 76 | +                month = int(arg[3:4])
 | 
	
		
			
			|  | 77 | +        elif len(arg) == 5 and arg[1] == arg[4] == "." and arg[0:1].isdigit() and arg[2:4].isdigit():
 | 
	
		
			
			|  | 78 | +                day = int(arg[0:1])
 | 
	
		
			
			|  | 79 | +                month = int(arg[2:4])
 | 
	
		
			
			|  | 80 | +        elif len(arg) == 4 and arg[1] == arg[3] == "." and arg[0:1].isdigit() and arg[2:3].isdigit():
 | 
	
		
			
			|  | 81 | +                day = int(arg[0:1])
 | 
	
		
			
			|  | 82 | +                month = int(arg[2:3])
 | 
	
		
			
			|  | 83 | +        if 0 < day < 32 and 0 < month < 13:
 | 
	
		
			
			|  | 84 | +                return month, day
 | 
	
		
			
			|  | 85 | +        return None
 | 
	
		
			
			|  | 86 | +
 | 
	
		
			
			|  | 87 | +    @staticmethod
 | 
	
		
			
			|  | 88 | +    def _parseTime(arg):
 | 
	
		
			
			|  | 89 | +        if ":" in arg:
 | 
	
		
			
			|  | 90 | +            hours, minutes = arg.split(":")
 | 
	
		
			
			|  | 91 | +        elif "." in arg:
 | 
	
		
			
			|  | 92 | +            hours, minutes = arg.split(".")
 | 
	
		
			
			|  | 93 | +        elif len(arg) == 4:
 | 
	
		
			
			|  | 94 | +            hours = arg[0:2]
 | 
	
		
			
			|  | 95 | +            minutes = arg[2:4]
 | 
	
		
			
			|  | 96 | +        elif len(arg) == 3:
 | 
	
		
			
			|  | 97 | +            hours = arg[0]
 | 
	
		
			
			|  | 98 | +            minutes = arg[1:3]
 | 
	
		
			
			|  | 99 | +        elif len(arg) in (1,2):
 | 
	
		
			
			|  | 100 | +            hours = arg
 | 
	
		
			
			|  | 101 | +            minutes = "0"
 | 
	
		
			
			|  | 102 | +        else:
 | 
	
		
			
			|  | 103 | +            hours = None
 | 
	
		
			
			|  | 104 | +            minutes = None
 | 
	
		
			
			|  | 105 | +        if hours == None or len(hours) not in (1,2) or len(minutes) not in (1,2):
 | 
	
		
			
			|  | 106 | +            return
 | 
	
		
			
			|  | 107 | +
 | 
	
		
			
			|  | 108 | +        try:
 | 
	
		
			
			|  | 109 | +            hours = int(hours)
 | 
	
		
			
			|  | 110 | +        except ValueError as e:
 | 
	
		
			
			|  | 111 | +            return
 | 
	
		
			
			|  | 112 | +        try:
 | 
	
		
			
			|  | 113 | +            minutes = int(minutes)
 | 
	
		
			
			|  | 114 | +        except ValueError as e:
 | 
	
		
			
			|  | 115 | +            return
 | 
	
		
			
			|  | 116 | +
 | 
	
		
			
			|  | 117 | +        if minutes >= 60:
 | 
	
		
			
			|  | 118 | +            hours += int(minutes / 60)
 | 
	
		
			
			|  | 119 | +            minutes = minutes % 60
 | 
	
		
			
			|  | 120 | +        if hours >= 24:
 | 
	
		
			
			|  | 121 | +            hours = hours % 24
 | 
	
		
			
			|  | 122 | +
 | 
	
		
			
			|  | 123 | +        return hours, minutes 
 | 
	
		
			
			|  | 124 | +
 | 
	
		
			
			|  | 125 | +    def _resolveTime(self, irc, dateArg, timeArg):
 | 
	
		
			
			|  | 126 | +        month = mday = hours = minutes = None
 | 
	
		
			
			|  | 127 | +        if dateArg != None:
 | 
	
		
			
			|  | 128 | +            (month, mday) = self._parseDate(dateArg)
 | 
	
		
			
			|  | 129 | +
 | 
	
		
			
			|  | 130 | +        (hours, minutes) = self._parseTime(timeArg)
 | 
	
		
			
			|  | 131 | +
 | 
	
		
			
			|  | 132 | +        return list(rrule(MINUTELY, count = 1, bymonth = month, bymonthday = mday,
 | 
	
		
			
			|  | 133 | +                byhour = hours, byminute = minutes))[0]
 | 
	
		
			
			|  | 134 | +
 | 
	
		
			
			|  | 135 | +    def _testDate(date):
 | 
	
		
			
			|  | 136 | +        return Timer._parseDate(date) != None
 | 
	
		
			
			|  | 137 | +
 | 
	
		
			
			|  | 138 | +    def _testTime(time):
 | 
	
		
			
			|  | 139 | +        return Timer._parseTime(time) != None
 | 
	
		
			
			|  | 140 | +
 | 
	
		
			
			|  | 141 | +    def _scheduleOnce(self, irc, msg, message, scheduleTime):
 | 
	
		
			
			|  | 142 | +        #irc.reply(format("Scheduled at %s", scheduleTime))
 | 
	
		
			
			|  | 143 | +        f = self._makeCommandFunction(irc, msg, message)
 | 
	
		
			
			|  | 144 | +        id = schedule.addEvent(f, scheduleTime)
 | 
	
		
			
			|  | 145 | +        f.eventId = id
 | 
	
		
			
			|  | 146 | +        self.events[str(id)] = f
 | 
	
		
			
			|  | 147 | +
 | 
	
		
			
			|  | 148 | +    def timer(self, irc, msg, args, dateArg, timeArg, message):
 | 
	
		
			
			|  | 149 | +        """[date] <time> <message>
 | 
	
		
			
			|  | 150 | +
 | 
	
		
			
			|  | 151 | +        Set a message to be played back later.
 | 
	
		
			
			|  | 152 | +        """
 | 
	
		
			
			|  | 153 | +        scheduleTime = self._resolveTime(irc, dateArg, timeArg).timestamp()
 | 
	
		
			
			|  | 154 | +        self._scheduleOnce(irc, msg, message, scheduleTime)
 | 
	
		
			
			|  | 155 | +
 | 
	
		
			
			|  | 156 | +    timer = wrap(timer, [optional(('something', None, _testDate)), ('something', None, _testTime), 'text'])
 | 
	
		
			
			|  | 157 | +
 | 
	
		
			
			|  | 158 | +    def food(self, irc, msg, args, minutes, message):
 | 
	
		
			
			|  | 159 | +        """[minuutit] [viesti]
 | 
	
		
			
			|  | 160 | +
 | 
	
		
			
			|  | 161 | +        Huomauta viestillä muutaman minuutin päästä.
 | 
	
		
			
			|  | 162 | +        Pikakomennot: pizza 12 minuuttia, makaronilaatikko 60 minuuttia, ranskalaiset 30 minuuttia
 | 
	
		
			
			|  | 163 | +        """
 | 
	
		
			
			|  | 164 | +        irc.reply(format('Meikä kiekaisee sitten %i minuutin päästä %s', minutes, message))
 | 
	
		
			
			|  | 165 | +        scheduleTime = time.time() + minutes * 60
 | 
	
		
			
			|  | 166 | +        self._scheduleOnce(irc, msg, message, scheduleTime)
 | 
	
		
			
			|  | 167 | +    food = wrap(food, ['positiveInt', 'text'])
 | 
	
		
			
			|  | 168 | +
 | 
	
		
			
			|  | 169 | +Class = Timer
 | 
	
		
			
			|  | 170 | +
 | 
	
		
			
			|  | 171 | +
 | 
	
		
			
			|  | 172 | +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
 |