diff --git a/develop/index.rst b/develop/index.rst index 4b22e0a..89520a1 100644 --- a/develop/index.rst +++ b/develop/index.rst @@ -18,6 +18,7 @@ Plugin Developer Guide capabilities.rst events.rst httpserver.rst + schedule.rst faq.rst diff --git a/develop/schedule.rst b/develop/schedule.rst new file mode 100644 index 0000000..6216dfc --- /dev/null +++ b/develop/schedule.rst @@ -0,0 +1,161 @@ +.. _supybot-schedule: + +*************************************** +Event scheduling using supybot.schedule +*************************************** + +.. code-block:: python + + ### + # This is an example plugin that sends a message to a channel every 60 seconds, + # includes commands to stop, start, and reset the spammer, and a command to + # schedule a one-off event + ### + + # these are the default plugin modules + import supybot.utils as utils + from supybot.commands import * + import supybot.plugins as plugins + import supybot.ircutils as ircutils + import supybot.callbacks as callbacks + # these are the extra modules we'll be using + import time + import supybot.ircmsgs as ircmsgs + import supybot.schedule as schedule + + class Spam(callbacks.Plugin): + """Add the help for "@plugin help Spam" here + This should describe *how* to use this plugin.""" + + def __init__(self, irc): + # these two lines are required if you have a custom __init__() + self.__parent = super(Spam, self) + self.__parent.__init__(irc) + # this is the channel we want to spam, and how frequently we want to do it. + # It would be nicer to put it in a supybot config variable instead, but for + # this demonstration, defining it in the plugin itself is fine. + self.spamChannel = '#testytest' + self.spamTime = 60 + # scheduler events are global, so we want to test to make sure the event doesn't + # already exist. That is, even if the plugin is reloaded, the event sticks + # around. That means that you also have to be a little careful with your + # event names, especially if you have multiple plugins adding events. It also + # means that events will stick around even if the plugin they originated in + # is unloaded. I don't know how to delete them automatically on an unload, but + # it's not normally an issue. Just make sure to stop the event before unloading + # the plugin if that's what you want. + try: + schedule.removeEvent('mySpamEvent') + except KeyError: + pass + # now that we know there's no event by that name scheduled, we can create one. + # but first, we need to define a local helper function that will do the thing + # that we want. You can put the full contents into here, but I prefer to use + # separate methods, as it makes the code easier to get around in. We need + # the helper function because when you add events, you can't include arguments. + def myEventCaller(): + self.spamEvent(irc) + # and now we can schedule the actual event + # schedule.addPeriodicEvent(f, t, name=None, now=True) + # f is the method, t is the time in seconds, name gives it a name and is optional + # (but highly recommended, so that you can refer to the event in the future. + # otherwise, it's easy to accumulate duplicate events), and 'now' specifies + # whether to perform the action immediately, or to wait until time is up to + # perform it for the first time. Default is True. + schedule.addPeriodicEvent(myEventCaller, self.spamTime, 'mySpamEvent') + self.irc = irc + + # make sure to have a capital letter or underscore or something, as it's not a method + # that we want turned into an IRC command + def spamEvent(self, irc): + # we need to use queueMsg() rather than reply(), because when the event is + # scheduled on loading the plugin (as opposed to scheduling it with one of the + # commands that we'll define next), it recieves its irc object from __init__(). + # When the bot is started, the irc object that comes from __init__() doesn't + # include a reply() method, because it's not loading in response to a command; + # it's loading on the bot startup. If you don't want your event to be scheduled + # automatically and so don't schedule it from __init__(), but only from an IRC + # command, then it's safe to use irc.reply(), as there are no circumstances + # under which the irc object won't have a reply() method. + irc.queueMsg(ircmsgs.privmsg(self.spamChannel, 'I\'m spamming the channel!')) + + def start(self, irc, msg, args): + """takes no arguments + + A command to start the spammer.""" + # don't forget to redefine the event wrapper + def myEventCaller(): + self.spamEvent(irc) + try: + schedule.addPeriodicEvent(myEventCaller, self.spamTime, 'mySpamEvent', False) + except AssertionError: + irc.reply('Error: the spammer was already running!') + else: + irc.reply('Spammer started!') + start = wrap(start) + + def stop(self, irc, msg, args): + """takes no arguments + + A command to stop the spammer.""" + try: + schedule.removeEvent('mySpamEvent') + except KeyError: + irc.reply('Error: the spammer wasn\'t running!') + else: + irc.reply('Spammer stopped.') + stop = wrap(stop) + + def reset(self, irc, msg, args): + """takes no arguments + + Resets the spammer. Can be useful if something changes and you want the + spam to reflect that. For example, if you defined the spamChannel as a + supybot config, and changed it while the spammer was running, it would still + keep going on the same channel until you reset it.""" + def myEventCaller(): + self.spamEvent(irc) + try: + schedule.removeEvent('mySpamEvent') + except KeyError: + irc.reply('Spammer wasn\'t running') + schedule.addPeriodicEvent(myEventCaller, self.spamTime, 'mySpamEvent', False) + irc.reply('Spammer reset sucessfully!') + reset = wrap(reset) + + # Here's an example of a one-off event, scheduled by an IRC command + def sayhi(self, irc, msg, args, delay): + """<time delay> + + Says hi after the specified delay""" + def myEventCaller(): + self.Hello(irc) + # for a one-off event, the time is an absolute time, not relative. So we need + # to get the current time and add to it however long we want to wait + t = time.time() + delay + # since we don't specify a name, we won't be able to reference the events in + # the future, but that's ok, because these are one-off events, so even if you + # do call it multiple times, it'll just reply that same number of times and + # then stop. But in some circumstances you might want to name them. Just + # remember that it'll give an AssertionError if you try to create two events + # with the same name + schedule.addEvent(myEventCaller, t) + irc.reply('"hi" scheduled for %d seconds from now!' % delay) + sayhi = wrap(sayhi, ['positiveInt']) + + def Hello(self, irc): + # since the irc object is coming from an IRC command, rather than from __init__(), + # it's guaranteed to have a reply() method, so it's safe to use that. It + # might be better to to use queueMsg() instead, regardless, but I don't know + # enough about the supybot internals to say whether one is prefered over + # the other + irc.reply('hi!') + + Class = Spam + +This example comes from the Gribble Wiki: +https://sourceforge.net/p/gribble/wiki/Supybot.schedule/history + +Copyright 2010, 2015, nanotube and quantumlemur +licensed under the `Creative Commons Attribution ShareAlike 3.0 Unported license `_ +and/or the `GNU Free Documentation License v 1.3 or later `_