Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Let's make an IRC bot

Let's make an IRC bot

The beginning of a tutorial I gave at OS Bridge 2012 in Portland, Or.

ericholscher

June 28, 2012
Tweet

More Decks by ericholscher

Other Decks in Programming

Transcript

  1. Let’s make an IRC Bot Eric Holscher http://ericholscher.com OS Bridge

    2012 June 28 WIRELESS: OSBridge http://192.168.30.35:8000 Thursday, June 28, 12
  2. Overview » A tutorial where we build an IRC bot

    » Explain the concepts of distributed systems » Explain the client and server » Explain how to write a client » Help you write a client » Experiment Thursday, June 28, 12
  3. What this isn’t » An explanation of IRC » A

    deep dive into Redis or PubSub » Something where you sit and don’t build something » You need a computer Thursday, June 28, 12
  4. Timeline » about 30 minutes of me talking » 5-10

    minutes for questions » 20 minutes of getting stuff set up » 45 minutes of building things Thursday, June 28, 12
  5. ZenIRCBot » An IRC bot conceived in the unix philosophy

    » Have services run as independent processes, using a message bus to send/ receive messages » http://zenircbot.rtfd.org Thursday, June 28, 12
  6. Architecture » Process that connects to IRC, sends messages to

    ‘in’ tube, publishes to IRC from ‘out’ tube » Client subscribes to ‘in’ tube, publishes to ‘out’ tube » Redis is used as the pubsub middle man Thursday, June 28, 12
  7. Pubsub » Messages are lost if they aren’t being listened

    for » No history Thursday, June 28, 12
  8. •Container for the message •Versioned & has a type JSON

    { "version": 1, "type": "privmsg", "data": { ... } } Thursday, June 28, 12
  9. •privmsg is the only type we care about privmsg JSON

    "data": { "sender": "", "channel": "", "message": "" } Thursday, June 28, 12
  10. Clients » Clients are dumb » Architecture is oriented around

    the client » ‘in’ tube comes into the client, ‘out’ goes out Thursday, June 28, 12
  11. History » Created in Portland » Wanted an easy way

    to talk to IRC » Multi-lingual » Abstraction works for other protocols too, in theory :) Thursday, June 28, 12
  12. Basics » Multi-threaded » One thread sits in IRC channels

    & sends messages to redis » Other thread listens to redis and pipes incoming messages to IRC » Otherwise, very simple (about 30 lines of Python) Thursday, June 28, 12
  13. Python Server import gevent import redis import json from irc

    import IRCBot, run_bot from gevent import monkey monkey.patch_all() r = redis.StrictRedis(host='localhost', port=6379, db=0) class RelayBot(IRCBot): def __init__(self, *args, **kwargs): super(RelayBot, self).__init__(*args, **kwargs) gevent.spawn(self.do_sub) def do_sub(self): r = redis.StrictRedis(host='localhost', port=6379, db=0) self.pubsub = r.pubsub() self.pubsub.subscribe('out') for msg in self.pubsub.listen(): message = json.loads(msg['data']) print "Got %s" % message self.respond(message['data']['message'], channel=message['data']['to']) def command_patterns(self): return ( ('.*', self.do_pub), ) def do_pub(self, nick, message, channel): to_publish = json.dumps({ 'data': { 'to': channel, 'message': message, } }) r.publish('in', to_publish) print "Sending to in %s" % to_publish host = 'irc.freenode.net' port = 6667 nick = 'relaybot' run_bot(RelayBot, host, port, nick, ['#pdxbots']) Thursday, June 28, 12
  14. •Initialize gevent & redis Setup import gevent import redis import

    json from irc import IRCBot, run_bot from gevent import monkey monkey.patch_all() r = redis.StrictRedis(host='localhost', port=6379, db=0) Thursday, June 28, 12
  15. •On initialization, subscibe to IRC •do_sub subscribes to the ‘out’

    tube •For every message, it sends it to IRC Create the Bot class RelayBot(IRCBot): def __init__(self, *args, **kwargs): super(RelayBot, self).__init__(*args, **kwargs) gevent.spawn(self.do_sub) def do_sub(self): r = redis.StrictRedis(host='localhost', port=6379, db=0) self.pubsub = r.pubsub() self.pubsub.subscribe('out') for msg in self.pubsub.listen(): message = json.loads(msg['data']) print "Got %s" % message self.respond(message['data']['message'], channel=message['data']['to']) Thursday, June 28, 12
  16. •do_pub happens on every channel message, as defined in command_patterns

    •It turns the message into the correct format •Then sends it to the ‘in’ tube Publish incoming messages def command_patterns(self): return ( ('.*', self.do_pub), ) def do_pub(self, nick, message, channel): to_publish = json.dumps({ 'data': { ‘sender’: nick, 'to': channel, 'message': message, } }) r.publish('in', to_publish) print "Sending to in %s" % to_publish Thursday, June 28, 12
  17. •Point the bot at freenode, and the #pdxbots channel on

    IRC Start the bot host = 'irc.freenode.net' port = 6667 nick = 'relaybot' run_bot(RelayBot, host, port, nick, ['#pdxbots']) Thursday, June 28, 12
  18. Used in Real World » Read the Docs » Sits

    in the IRC channel » !build » !info » !search » about 65 lines of code Thursday, June 28, 12
  19. At work » We use IRC for most of our

    inter-team communication » Any process can easily send messages to IRC in a few lines of code » Deploys » Updates » Errors » Monitoring Thursday, June 28, 12
  20. Client Code import json import redis host = "localhost" r

    = redis.Redis(host=host) def send(to, message): r.publish('out', json.dumps({ 'version': 1, 'type': 'privmsg', 'data': { 'to': to, 'message': message, } })) pubsub = r.pubsub() pubsub.subscribe('in') for msg in pubsub.listen(): data = json.loads(msg['data'])['data'] print "Got %s in %s from %s" % (data['message'], data['channel'], data['sender']) if data['message'] == "hello": send(data['channel'], "Hello %s" % data['sender']) elif data['message'] == '!woot': send(data['channel'],"Indeed!") else: send(data['channel'],"Wat") Thursday, June 28, 12
  21. •Import json & redis libraries •Connect to Redis on localhost

    Connect import json import redis host = "localhost" r = redis.Redis(host=host) Thursday, June 28, 12
  22. •Send messages to IRC •Publish to the ‘out’ channel •JSON

    ‘data’ attribute has ‘to’ and ‘message’ keys; these are the meat of the message Send function def send(to, message): r.publish('out', json.dumps({ 'version': 1, 'type': 'privmsg', 'data': { 'to': to, 'message': message, } })) Thursday, June 28, 12
  23. •Subscribe to the ‘in’ channel •For each message that comes

    in: •Read the ‘data’ attribute as json •Print out a debug message with the pieces of data that we care about Receive function pubsub = r.pubsub() pubsub.subscribe('in') for msg in pubsub.listen(): data = json.loads(msg['data'])['data'] print "Got %s in %s from %s" % (data['message'], data['channel'], data['sender']) Thursday, June 28, 12
  24. •Check ‘message’ for your commands •Implements basic ‘!woot’ and ‘hello’

    commands •Generally commands start with ‘!’, but don’t have to Do work if data['message'] == "hello": send(data['channel'], "Hello %s" % data['sender']) elif data['message'] == '!woot': send(data['channel'],"Indeed!") else: send(data['channel'],"Wat") Thursday, June 28, 12
  25. Let’s build a bot! » I am running a local

    IRC server » It has redis & a ZenIRCBot instance running » You just need to connect a client to redis » Connect the test client, get that working, then build your own Thursday, June 28, 12
  26. What does it do? » That example responds to ‘hi’

    in the channel » It will greet the person who said hi, along with saying its hostname » If this example works, you should see your hostname in the flood of output :) Thursday, June 28, 12
  27. Remember » You don’t need to worry about the server

    » Clients just listen to redis & respond appropriately Thursday, June 28, 12
  28. Questions? » Here is a basic Python client example: goo.gl/5M89w

    » WIRELESS: OSBbridge Network » http://192.168.30.35:8000 » To run this you should just need the redis client library » (sudo) easy_install redis » host = ‘192.168.30.35’ Thursday, June 28, 12
  29. Bot Command Ideas !8ball !acronym !annoy !bing !blame !bless !boo

    !calc !cheer !compliment !ctlaltdel !curse !dance !define !excuse !fight !fwp !google !googlecalc !hire !hurr !karma !lunch !meaculpa !motivate !thanks !oregontrail !panic !ping !paste !pick !quote !roll !markov !ticker !time !translate !slap !urbandictionary !weather !zing Thursday, June 28, 12
  30. Games » Duck Duck Goose » Rock Paper Scissors »

    Shell game » MUD » Russian Roulette Thursday, June 28, 12