Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

ZenIRCBot Thursday, June 28, 12

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Workflow Thursday, June 28, 12

Slide 9

Slide 9 text

Pubsub » Messages are lost if they aren’t being listened for » No history Thursday, June 28, 12

Slide 10

Slide 10 text

•Container for the message •Versioned & has a type JSON { "version": 1, "type": "privmsg", "data": { ... } } Thursday, June 28, 12

Slide 11

Slide 11 text

•privmsg is the only type we care about privmsg JSON "data": { "sender": "", "channel": "", "message": "" } Thursday, June 28, 12

Slide 12

Slide 12 text

Clients » Clients are dumb » Architecture is oriented around the client » ‘in’ tube comes into the client, ‘out’ goes out Thursday, June 28, 12

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

How the server works Thursday, June 28, 12

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

•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

Slide 18

Slide 18 text

•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

Slide 19

Slide 19 text

•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

Slide 20

Slide 20 text

•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

Slide 21

Slide 21 text

Why this is useful Thursday, June 28, 12

Slide 22

Slide 22 text

Used in Real World » Read the Docs » Sits in the IRC channel » !build » !info » !search » about 65 lines of code Thursday, June 28, 12

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Writing a client Thursday, June 28, 12

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

•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

Slide 27

Slide 27 text

•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

Slide 28

Slide 28 text

•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

Slide 29

Slide 29 text

•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

Slide 30

Slide 30 text

Your turn Thursday, June 28, 12

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Remember » You don’t need to worry about the server » Clients just listen to redis & respond appropriately Thursday, June 28, 12

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Games » Duck Duck Goose » Rock Paper Scissors » Shell game » MUD » Russian Roulette Thursday, June 28, 12

Slide 37

Slide 37 text

Useful places » /usr/share/dict » fortune Thursday, June 28, 12