Skip to content

Instantly share code, notes, and snippets.

@habnabit
Last active January 26, 2023 09:09
Show Gist options
  • Save habnabit/5823693 to your computer and use it in GitHub Desktop.
Save habnabit/5823693 to your computer and use it in GitHub Desktop.
an example IRC bot using twisted
import sys
from twisted.internet import defer, endpoints, protocol, reactor, task
from twisted.python import log
from twisted.words.protocols import irc
class MyFirstIRCProtocol(irc.IRCClient):
nickname = 'MyFirstIrcBot'
def __init__(self):
self.deferred = defer.Deferred()
def connectionLost(self, reason):
self.deferred.errback(reason)
def signedOn(self):
# This is called once the server has acknowledged that we sent
# both NICK and USER.
for channel in self.factory.channels:
self.join(channel)
# Obviously, called when a PRIVMSG is received.
def privmsg(self, user, channel, message):
nick, _, host = user.partition('!')
message = message.strip()
if not message.startswith('!'): # not a trigger command
return # so do nothing
command, sep, rest = message.lstrip('!').partition(' ')
# Get the function corresponding to the command given.
func = getattr(self, 'command_' + command, None)
# Or, if there was no function, ignore the message.
if func is None:
return
# maybeDeferred will always return a Deferred. It calls func(rest), and
# if that returned a Deferred, return that. Otherwise, return the
# return value of the function wrapped in
# twisted.internet.defer.succeed. If an exception was raised, wrap the
# traceback in twisted.internet.defer.fail and return that.
d = defer.maybeDeferred(func, rest)
# Add callbacks to deal with whatever the command results are.
# If the command gives error, the _show_error callback will turn the
# error into a terse message first:
d.addErrback(self._showError)
# Whatever is returned is sent back as a reply:
if channel == self.nickname:
# When channel == self.nickname, the message was sent to the bot
# directly and not to a channel. So we will answer directly too:
d.addCallback(self._sendMessage, nick)
else:
# Otherwise, send the answer to the channel, and use the nick
# as addressing in the message itself:
d.addCallback(self._sendMessage, channel, nick)
def _sendMessage(self, msg, target, nick=None):
if nick:
msg = '%s, %s' % (nick, msg)
self.msg(target, msg)
def _showError(self, failure):
return failure.getErrorMessage()
def command_ping(self, rest):
return 'Pong.'
def command_saylater(self, rest):
when, sep, msg = rest.partition(' ')
when = int(when)
d = defer.Deferred()
# A small example of how to defer the reply from a command. callLater
# will callback the Deferred with the reply after so many seconds.
reactor.callLater(when, d.callback, msg)
# Returning the Deferred here means that it'll be returned from
# maybeDeferred in privmsg.
return d
class MyFirstIRCFactory(protocol.ReconnectingClientFactory):
protocol = MyFirstIRCProtocol
channels = ['##MyFirstIrcBot']
def main(reactor, description):
endpoint = endpoints.clientFromString(reactor, description)
factory = MyFirstIRCFactory()
d = endpoint.connect(factory)
d.addCallback(lambda protocol: protocol.deferred)
return d
if __name__ == '__main__':
log.startLogging(sys.stderr)
task.react(main, ['tcp:irc.freenode.net:6667'])
@habnabit
Copy link
Author

Surprised this doesn't use 6697 by default 😄

there's a few other revisions i'd like to make, which would include that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment