| 1 | # dramabot |
|---|
| 2 | # a text generation IRC technology |
|---|
| 3 | # by rolloffle, 2006 |
|---|
| 4 | |
|---|
| 5 | from random import choice, randrange |
|---|
| 6 | from re import compile |
|---|
| 7 | from socks import socks4a # if using Tor on 127.0.0.1:9050 |
|---|
| 8 | import threading |
|---|
| 9 | from time import sleep |
|---|
| 10 | |
|---|
| 11 | DENOUEMENTS = ('oops', 'oh hi', 'oh... hi', 'eurgh, shit', 'oh shit', 'oh fuck', 'speak of the devil', 'good timing', 'well this is akward, heh', 'this is awkward...') |
|---|
| 12 | INSULTS = ( |
|---|
| 13 | '%s is such a fat faggot', |
|---|
| 14 | '%s is such a faggot', |
|---|
| 15 | '%s\'s a fucking tool', |
|---|
| 16 | '%s is a judenspy', |
|---|
| 17 | 'good thing %s isn\'t here to see that', |
|---|
| 18 | 'im gonna tell %s you said that', |
|---|
| 19 | 'tell %s to fuck off already', |
|---|
| 20 | '%s is a complete attention whore', |
|---|
| 21 | '%s is a worthless jew, why do we even keep him around?', |
|---|
| 22 | 'who the fuck invited %s here', |
|---|
| 23 | '%s is a useless goblinkike', |
|---|
| 24 | 'nobody wants %s around but don\'t mention it', |
|---|
| 25 | '%s is a complete cocksucking aspie', |
|---|
| 26 | 'i know exactly what you mean about %s', |
|---|
| 27 | 'did you hear the rumours about %s?', |
|---|
| 28 | '%s is a loser too', |
|---|
| 29 | 'but I won\'t tell %s you said that', |
|---|
| 30 | ) |
|---|
| 31 | OPENINGS = ( |
|---|
| 32 | 'yeah, %s,', |
|---|
| 33 | 'yeah %s', |
|---|
| 34 | 'totally %s,', |
|---|
| 35 | 'srsly %s,', |
|---|
| 36 | 'agree 100%%, %s,', |
|---|
| 37 | 'agreed %s,', |
|---|
| 38 | '%s backs me up on this,', |
|---|
| 39 | 'yah, %s told me,', |
|---|
| 40 | 'i agree %s,' |
|---|
| 41 | ) |
|---|
| 42 | |
|---|
| 43 | def rand_char(): |
|---|
| 44 | return chr(randrange(ord('a'), ord('z'))) |
|---|
| 45 | |
|---|
| 46 | def gen_rand_username(): |
|---|
| 47 | return 'wqat' |
|---|
| 48 | #return ''.join([rand_char() for i in range(8)]) |
|---|
| 49 | |
|---|
| 50 | class channel: |
|---|
| 51 | |
|---|
| 52 | def __init__(self, con, chan): |
|---|
| 53 | """Initialise, join the channel.""" |
|---|
| 54 | |
|---|
| 55 | self.con = con |
|---|
| 56 | self.chan = chan |
|---|
| 57 | self.nicks = [ ] |
|---|
| 58 | self.con.lsend('JOIN ' + chan) |
|---|
| 59 | |
|---|
| 60 | def rand_nick(self): |
|---|
| 61 | """Returns a random nick present in this channel.""" |
|---|
| 62 | return choice(self.nicks) |
|---|
| 63 | |
|---|
| 64 | def strip_nick_status_symbol(self, nick): |
|---|
| 65 | """If present, removes the status character from a nick.""" |
|---|
| 66 | if nick[0] in '@%+': |
|---|
| 67 | nick = nick[1:] |
|---|
| 68 | return nick |
|---|
| 69 | |
|---|
| 70 | def add_nicks(self, nicks): |
|---|
| 71 | """Add a list of nicks to this channel's nick list.""" |
|---|
| 72 | for nick in nicks: |
|---|
| 73 | nick = self.strip_nick_status_symbol(nick) |
|---|
| 74 | if (nick != self.con.nick) and (nick not in self.nicks): |
|---|
| 75 | self.nicks += [ nick ] |
|---|
| 76 | |
|---|
| 77 | def del_nick(self, nick): |
|---|
| 78 | """Delete a nick from this channel's nick list.""" |
|---|
| 79 | nick = self.strip_nick_status_symbol(nick) |
|---|
| 80 | try: |
|---|
| 81 | self.nicks.remove(nick) |
|---|
| 82 | except: |
|---|
| 83 | print 'Error: failed to remove \'%s\' from nick list for %s' % (nick, self.chan) |
|---|
| 84 | |
|---|
| 85 | def drama_bomb(self, target): |
|---|
| 86 | """Say something in the channel to a target nick to provoke them.""" |
|---|
| 87 | global DENOUEMENTS, INSULTS, OPENINGS |
|---|
| 88 | |
|---|
| 89 | template = 'PRIVMSG %s :' + choice(OPENINGS) + ' ' + choice(INSULTS) |
|---|
| 90 | print "TEMPLATE: " + template |
|---|
| 91 | bomb = template % (self.chan, self.rand_nick(), target) |
|---|
| 92 | |
|---|
| 93 | print '%s: drama bomb "%s"' % (self.chan, bomb) |
|---|
| 94 | self.con.lsend(bomb) |
|---|
| 95 | |
|---|
| 96 | if randrange(1, 2) != 1: |
|---|
| 97 | # chance of saying a denouement |
|---|
| 98 | sleep(1) |
|---|
| 99 | self.con.lsend('PRIVMSG %s :%s' % (self.chan, choice(DENOUEMENTS))) |
|---|
| 100 | |
|---|
| 101 | def display_status(self): |
|---|
| 102 | """Write a status report for this channel on stdout.""" |
|---|
| 103 | print ' %s: new nick list length %u' % (self.chan, len(self.nicks)) |
|---|
| 104 | |
|---|
| 105 | class con: |
|---|
| 106 | |
|---|
| 107 | def __init__(self, server, port, channels, nick, socks4a_proxy = None): |
|---|
| 108 | |
|---|
| 109 | self.server, self.port, self.nick = server, port, nick |
|---|
| 110 | |
|---|
| 111 | self.r = { } |
|---|
| 112 | self.r['001'] = compile(':[^ ]+ 001 ') |
|---|
| 113 | self.r['353'] = compile(':[^ ]+ 353 [^ ]+ . ([^ ]+) :(.+)') |
|---|
| 114 | self.r['433'] = compile(':[^ ]+ 433 . [^ ]+ :.+') |
|---|
| 115 | self.r['join'] = compile(':([^!~:]+)![^ ]+ JOIN :([^ ]+)') |
|---|
| 116 | self.r['part'] = compile(':([^!~:]+)![^ ]+ PART ([^ ]+)( :(.+))?') |
|---|
| 117 | self.r['kick'] = compile(':([^!~:]+)![^ ]+ KICK ([^ ]+) ([^ ]+)( :(.+))?') |
|---|
| 118 | self.r['privmsg'] = compile(':([^!~:]+)![^ ]+ PRIVMSG ([^ ]+) :(.+)') |
|---|
| 119 | |
|---|
| 120 | self.connect(socks4a_proxy) |
|---|
| 121 | |
|---|
| 122 | self.channels = { } |
|---|
| 123 | for chan in channels: |
|---|
| 124 | self.channels[chan] = channel(self, chan) |
|---|
| 125 | |
|---|
| 126 | def lsend(self, s): |
|---|
| 127 | self.sock.send(s + '\r\n') |
|---|
| 128 | |
|---|
| 129 | def lrecv(self): |
|---|
| 130 | c, s = '', '' |
|---|
| 131 | while c != '\n': |
|---|
| 132 | c = self.sock.recv(1) |
|---|
| 133 | if c == '': # connection closed |
|---|
| 134 | break |
|---|
| 135 | s += c |
|---|
| 136 | return s.strip('\r\n') |
|---|
| 137 | |
|---|
| 138 | def connect(self, socks4a_proxy = None): |
|---|
| 139 | """Make the initial connection to the server and try to register.""" |
|---|
| 140 | |
|---|
| 141 | self.sock = socks4a() |
|---|
| 142 | |
|---|
| 143 | print 'Connecting to %s:%u' % (self.server, self.port) |
|---|
| 144 | if socks4a_proxy == None: |
|---|
| 145 | self.sock.connect((self.server, self.port)) |
|---|
| 146 | else: |
|---|
| 147 | self.sock.proxy_connect(socks4a_proxy, (self.server, self.port)) |
|---|
| 148 | |
|---|
| 149 | username = gen_rand_username() |
|---|
| 150 | print ' username = ' + username |
|---|
| 151 | self.lsend('USER ' + username + ' 0 0 :Unknown') |
|---|
| 152 | self.lsend('NICK ' + self.nick + ' 0') |
|---|
| 153 | |
|---|
| 154 | # Wait for the 001 status reply. |
|---|
| 155 | while 1: |
|---|
| 156 | line = self.lrecv() |
|---|
| 157 | if self.r['001'].match(line): |
|---|
| 158 | # We got the 001, break out of the loop and proceed. |
|---|
| 159 | break |
|---|
| 160 | elif self.r['433'].match(line): |
|---|
| 161 | # This nick is taken. Change the last 2 chars and retry. |
|---|
| 162 | self.nick = self.nick[:-2] + rand_char() + rand_char() |
|---|
| 163 | self.lsend('NICK ' + self.nick + ' 0') |
|---|
| 164 | elif line == '': |
|---|
| 165 | raise 'ConnectError', (self.server, self.port, 'EOFBefore001') |
|---|
| 166 | |
|---|
| 167 | print ' got 001' |
|---|
| 168 | |
|---|
| 169 | def go(self): |
|---|
| 170 | |
|---|
| 171 | global rand_char |
|---|
| 172 | |
|---|
| 173 | # Join the channels. |
|---|
| 174 | print ' joining channels' |
|---|
| 175 | for channel in self.channels: |
|---|
| 176 | self.lsend('JOIN ' + channel) |
|---|
| 177 | print ' joined all channels' |
|---|
| 178 | |
|---|
| 179 | # Sit and watch at the socket for incoming data and act on it. |
|---|
| 180 | |
|---|
| 181 | while 1: |
|---|
| 182 | |
|---|
| 183 | line = self.lrecv() |
|---|
| 184 | |
|---|
| 185 | if line == '': |
|---|
| 186 | raise 'ConnectionClose', (self.server, self.port) |
|---|
| 187 | |
|---|
| 188 | elif line[:6] == 'PING :': |
|---|
| 189 | print ' PONG :' + line[6:] |
|---|
| 190 | self.lsend('PONG :' + line[6:]) |
|---|
| 191 | continue |
|---|
| 192 | |
|---|
| 193 | m = self.r['join'].match(line) |
|---|
| 194 | if m != None: |
|---|
| 195 | # Somebody joined. If it's not us, start the drama and add |
|---|
| 196 | # their nick to the master list of nicks in this channel. |
|---|
| 197 | print 'Received: JOIN' |
|---|
| 198 | |
|---|
| 199 | if m.group(1) == self.nick: |
|---|
| 200 | continue |
|---|
| 201 | |
|---|
| 202 | chan = self.channels[m.group(2)] |
|---|
| 203 | |
|---|
| 204 | chan.add_nicks([ m.group(1) ]) |
|---|
| 205 | chan.display_status() |
|---|
| 206 | chan.drama_bomb(m.group(1)) |
|---|
| 207 | |
|---|
| 208 | continue |
|---|
| 209 | |
|---|
| 210 | m = self.r['part'].match(line) |
|---|
| 211 | if m != None: |
|---|
| 212 | # Somebody parted, remove their nick from the master list of |
|---|
| 213 | # this channel's nicks. |
|---|
| 214 | print 'Received: PART' |
|---|
| 215 | chan = self.channels[m.group(2)] |
|---|
| 216 | chan.del_nick(m.group(1)) |
|---|
| 217 | chan.display_status() |
|---|
| 218 | |
|---|
| 219 | m = self.r['353'].match(line) |
|---|
| 220 | if m != None: |
|---|
| 221 | # 353 lists the nicks of the user in a channel we just |
|---|
| 222 | # joined. Add the list of nicks given to the master list of |
|---|
| 223 | # nicks present in the channel. |
|---|
| 224 | print 'Received: 353' |
|---|
| 225 | chan = self.channels[m.group(1)] |
|---|
| 226 | chan.add_nicks(m.group(2).split()) |
|---|
| 227 | chan.display_status() |
|---|
| 228 | continue |
|---|
| 229 | |
|---|
| 230 | m = self.r['kick'].match(line) |
|---|
| 231 | if m != None: |
|---|
| 232 | # Someone was kicked. |
|---|
| 233 | if m.group(3) == self.nick: |
|---|
| 234 | # It was us! Rejoin. |
|---|
| 235 | print 'Kicked! Attempting to rejoin ' + m.group(2) |
|---|
| 236 | self.lsend('JOIN ' + m.group(2)) |
|---|
| 237 | continue |
|---|
| 238 | |
|---|
| 239 | class con_thread(threading.Thread): |
|---|
| 240 | |
|---|
| 241 | def __init__(self, server, port, channels, nick): |
|---|
| 242 | self.co = con(server, port, channels, nick) |
|---|
| 243 | threading.Thread.__init__(self) |
|---|
| 244 | |
|---|
| 245 | def run(self): |
|---|
| 246 | self.co.go() |
|---|
| 247 | #self.co.go(('127.0.0.1', 9050)) # connect via Tor |
|---|
| 248 | |
|---|
| 249 | threads = [ |
|---|
| 250 | con_thread('irc.buttes.org', 6667, ['#cockes'], 'orangedrink'), |
|---|
| 251 | #con_thread('irc.chir.pn', 6667, ['#chirp'], 'dramabot') |
|---|
| 252 | |
|---|
| 253 | # The wacky .onion hostname is because it's Freenode's hidden Tor service |
|---|
| 254 | #con_thread('mejokbp2brhw4omd.onion', 6667, ['#wikipedia'], 'fade') |
|---|
| 255 | ] |
|---|
| 256 | |
|---|
| 257 | for thread in threads: |
|---|
| 258 | thread.start() |
|---|