Browse Source

Add initial Timer plugin

Toni Fadjukoff 7 years ago
commit
0454ec705b
6 changed files with 354 additions and 0 deletions
  1. 1 0
      Timer/README.md
  2. 68 0
      Timer/__init__.py
  3. 57 0
      Timer/config.py
  4. 1 0
      Timer/local/__init__.py
  5. 172 0
      Timer/plugin.py
  6. 55 0
      Timer/test.py

+ 1 - 0
Timer/README.md View File

@@ -0,0 +1 @@
1
+Command based scheduling for triggers

+ 68 - 0
Timer/__init__.py View File

@@ -0,0 +1,68 @@
1
+###
2
+# Copyright (c) 2016, Toni Fadjukoff
3
+# All rights reserved.
4
+#
5
+# Redistribution and use in source and binary forms, with or without
6
+# modification, are permitted provided that the following conditions are met:
7
+#
8
+#   * Redistributions of source code must retain the above copyright notice,
9
+#     this list of conditions, and the following disclaimer.
10
+#   * Redistributions in binary form must reproduce the above copyright notice,
11
+#     this list of conditions, and the following disclaimer in the
12
+#     documentation and/or other materials provided with the distribution.
13
+#   * Neither the name of the author of this software nor the name of
14
+#     contributors to this software may be used to endorse or promote products
15
+#     derived from this software without specific prior written consent.
16
+#
17
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+# POSSIBILITY OF SUCH DAMAGE.
28
+
29
+###
30
+
31
+"""
32
+Timer: Command based scheduling for triggers
33
+"""
34
+
35
+import supybot
36
+import supybot.world as world
37
+
38
+# Use this for the version of this plugin.  You may wish to put a CVS keyword
39
+# in here if you're keeping the plugin in CVS or some similar system.
40
+__version__ = ""
41
+
42
+# XXX Replace this with an appropriate author or supybot.Author instance.
43
+__author__ = supybot.authors.unknown
44
+
45
+# This is a dictionary mapping supybot.Author instances to lists of
46
+# contributions.
47
+__contributors__ = {}
48
+
49
+# This is a url where the most recent plugin package can be downloaded.
50
+__url__ = ''
51
+
52
+from . import config
53
+from . import plugin
54
+from imp import reload
55
+# In case we're being reloaded.
56
+reload(config)
57
+reload(plugin)
58
+# Add more reloads here if you add third-party modules and want them to be
59
+# reloaded when this plugin is reloaded.  Don't forget to import them as well!
60
+
61
+if world.testing:
62
+    from . import test
63
+
64
+Class = plugin.Class
65
+configure = config.configure
66
+
67
+
68
+# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

+ 57 - 0
Timer/config.py View File

@@ -0,0 +1,57 @@
1
+###
2
+# Copyright (c) 2016, Toni Fadjukoff
3
+# All rights reserved.
4
+#
5
+# Redistribution and use in source and binary forms, with or without
6
+# modification, are permitted provided that the following conditions are met:
7
+#
8
+#   * Redistributions of source code must retain the above copyright notice,
9
+#     this list of conditions, and the following disclaimer.
10
+#   * Redistributions in binary form must reproduce the above copyright notice,
11
+#     this list of conditions, and the following disclaimer in the
12
+#     documentation and/or other materials provided with the distribution.
13
+#   * Neither the name of the author of this software nor the name of
14
+#     contributors to this software may be used to endorse or promote products
15
+#     derived from this software without specific prior written consent.
16
+#
17
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+# POSSIBILITY OF SUCH DAMAGE.
28
+
29
+###
30
+
31
+import supybot.conf as conf
32
+import supybot.registry as registry
33
+try:
34
+    from supybot.i18n import PluginInternationalization
35
+    _ = PluginInternationalization('Timer')
36
+except:
37
+    # Placeholder that allows to run the plugin on a bot
38
+    # without the i18n module
39
+    _ = lambda x: x
40
+
41
+
42
+def configure(advanced):
43
+    # This will be called by supybot to configure this module.  advanced is
44
+    # a bool that specifies whether the user identified themself as an advanced
45
+    # user or not.  You should effect your configuration by manipulating the
46
+    # registry as appropriate.
47
+    from supybot.questions import expect, anything, something, yn
48
+    conf.registerPlugin('Timer', True)
49
+
50
+
51
+Timer = conf.registerPlugin('Timer')
52
+# This is where your configuration variables (if any) should go.  For example:
53
+# conf.registerGlobalValue(Timer, 'someConfigVariableName',
54
+#     registry.Boolean(False, _("""Help for someConfigVariableName.""")))
55
+
56
+
57
+# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

+ 1 - 0
Timer/local/__init__.py View File

@@ -0,0 +1 @@
1
+# Stub so local is a module, used for third-party modules

+ 172 - 0
Timer/plugin.py View File

@@ -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:

+ 55 - 0
Timer/test.py View File

@@ -0,0 +1,55 @@
1
+###
2
+# Copyright (c) 2016, Toni Fadjukoff
3
+# All rights reserved.
4
+#
5
+# Redistribution and use in source and binary forms, with or without
6
+# modification, are permitted provided that the following conditions are met:
7
+#
8
+#   * Redistributions of source code must retain the above copyright notice,
9
+#     this list of conditions, and the following disclaimer.
10
+#   * Redistributions in binary form must reproduce the above copyright notice,
11
+#     this list of conditions, and the following disclaimer in the
12
+#     documentation and/or other materials provided with the distribution.
13
+#   * Neither the name of the author of this software nor the name of
14
+#     contributors to this software may be used to endorse or promote products
15
+#     derived from this software without specific prior written consent.
16
+#
17
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+# POSSIBILITY OF SUCH DAMAGE.
28
+
29
+###
30
+
31
+from supybot.test import *
32
+
33
+
34
+class TimerTestCase(PluginTestCase):
35
+    plugins = ('Timer',)
36
+
37
+    def testTimer(self):
38
+        self.assertNoResponse("timer 1.1. 00:00 Happy New Year")
39
+
40
+    def testTimerWithTime(self):
41
+        self.assertNoResponse("timer 18:30 Hejssan")
42
+
43
+    def testTimerWithDateAndTime(self):
44
+        self.assertNoResponse("timer 22.8. 18:30 Hejssan")
45
+
46
+    def testFoodWithPremade(self):
47
+        self.assertResponse("pizza", "Meikä kiekaisee sitten 12 minuutin päästä Pizza")
48
+
49
+    def testFoodWithArgs(self):
50
+        self.assertResponse("food 15 whatever", "Meikä kiekaisee sitten 15 minuutin päästä whatever")
51
+
52
+    def testFoodWithArgs(self):
53
+        self.assertResponse("ruoka 15 pyykit", "Meikä kiekaisee sitten 15 minuutin päästä pyykit")
54
+
55
+# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: