def trigger_MODE(code, line): re_tmp = r'^\:(.*?)\!(.*?)\@(.*?) MODE (.*?)$' try: nick, ident, host, args = re.compile(re_tmp).match(line).groups() except: return output.normal('%s sets MODE %s' % (nick, args), 'MODE') # Stuff for user_list data = line.split('MODE', 1)[1] if len(data.split()) >= 3: channel, modes, users = data.strip().split(' ', 2) users = users.split() tmp = [] def remove(old, sign): tmp = [] modes = [] for char in old: modes.append(char) while sign in modes: i = modes.index(sign) tmp.append(i) del modes[i] return tmp, ''.join(modes) if modes.startswith('+'): _plus, new_modes = remove(modes, '+') _minus, new_modes = remove(new_modes, '-') else: _minus, new_modes = remove(modes, '-') _plus, new_modes = remove(new_modes, '+') for index in range(len(users)): _usr = users[index] _mode = new_modes[index] _sign = '' if index in _plus: _sign = '+' if index in _minus: _sign = '-' tmp.append({'name': _usr, 'mode': _mode, 'sign': _sign}) last_used = '' for index in range(len(tmp)): if not last_used: last_used = tmp[index]['sign'] if not tmp[index]['sign'] or len(tmp[index]['sign']) == 0: tmp[index]['sign'] = last_used else: last_used = tmp[index]['sign'] names = {'v': 'voiced', 'o': 'op', '+': True, '-': False} for user in tmp: if user['mode'] in names and user['sign'] in names: mode, name, sign = names[user['mode']], user['name'], names[user['sign']] code.chan[channel][name][mode] = sign if mode == 'op' and sign: code.chan[channel][name]['voiced'] = True
def trigger_NOTICE(code, origin, line, args, text): """ ID: NOTICE Decription: The NOTICE message is used similarly to PRIVMSG. The difference between NOTICE and PRIVMSG is that automatic replies must never be sent in response to a NOTICE message. This rule applies to servers too - they must not send any error reply back to the client on receipt of a notice. Format: <nickname> <text> """ if 'Invalid password for ' in text: if not code.debug: output.error('Invalid NickServ password') os._exit(1) if 'AUTHENTICATION SUCCESSFUL as ' in args[2]: if code.config('undernet_hostmask'): code.write(('MODE', code.nick, '+x')) if not code.debug: output.normal('({}) {}'.format(origin.nick, text), 'NOTICE') # Add notices to the bot logs tmp = { 'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': 'NOTICE' } code.logs['bot'].append(tmp)
def trigger_NICK(code, origin, line, args, text): """ ID: NICK Decription: NICK message is used to give user a nickname or change the previous one. The <hopcount> parameter is only used by servers to indicate how far away a nick is from its home server. A local connection has a hopcount of 0. If supplied by a client, it must be ignored. Format: <nickname> [ <hopcount> ] Numeric Replies: - ERR_NONICKNAMEGIVEN - ERR_ERRONEUSNICKNAME - ERR_NICKNAMEINUSE - ERR_NICKCOLLISION """ if not code.debug: output.normal('{} is now known as {}'.format(origin.nick, args[1]), 'NICK') # Rename old users to new ones in the database... for channel in code.chan: if origin.nick in code.chan[channel]: old = code.chan[channel][origin.nick] del code.chan[channel][origin.nick] code.chan[channel][args[1]] = old code.chan[channel][args[1]]['last_seen'] = int(time.time()) tmp = { 'message': 'is now known as {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'NICK' } code.logs['bot'].append(tmp)
def trigger_PART(code, origin, line, args, text): """ ID: PART Decription: The PART message causes the client sending the message to be removed from the list of active users for all given channels listed in the parameter string. Format: <channel>{,<channel>} Numeric Replies: - ERR_NEEDMOREPARAMS - ERR_NOSUCHCHANNEL - ERR_NOTONCHANNEL """ if origin.nick == code.nick: del code.chan[args[1]] del code.logs['channel'][args[1]] else: del code.chan[args[1]][origin.nick] if len(args) == 3: reason = args[2] else: reason = 'Unknown' if not code.debug: output.normal( '{} has part {}. Reason: {}'.format(origin.nick, args[1], reason), args[1]) tmp = { 'message': 'left {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'PART' } code.logs['bot'].append(tmp)
def trigger_PART(code, origin, line, args, text): """ ID: PART Decription: The PART message causes the client sending the message to be removed from the list of active users for all given channels listed in the parameter string. Format: <channel>{,<channel>} Numeric Replies: - ERR_NEEDMOREPARAMS - ERR_NOSUCHCHANNEL - ERR_NOTONCHANNEL """ if origin.nick == code.nick: del code.chan[args[1]] del code.logs['channel'][args[1]] else: del code.chan[args[1]][origin.nick] if len(args) == 3: reason = args[2] else: reason = 'Unknown' if not code.debug: output.normal('{} has part {}. Reason: {}'.format(origin.nick, args[1], reason), args[1]) tmp = { 'message': 'left {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'PART' } code.logs['bot'].append(tmp)
def trigger_PRIVMSG(code, origin, line, args, text): text = code.stripcolors(text) if text.startswith('\x01ACTION'): text = '(me) ' + text.split(' ', 1)[1].strip('\x01') output.normal('({}) {}'.format(origin.nick, text), args[1]) # Stuff for user_list if args[1].startswith('#'): if origin.nick not in code.chan[args[1]]: code.chan[args[1]][origin.nick] = {'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': []} code.chan[args[1]][origin.nick]['count'] += 1 # 1. per-channel-per-user message storing... code.chan[args[1]][origin.nick]['messages'].append( {'time': int(time.time()), 'message': text}) # Ensure it's not more than 20 of the last messages code.chan[args[1]][origin.nick]['messages'] = code.chan[ args[1]][origin.nick]['messages'][-20:] # 2. Per channel message storing... tmp = {'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': args[1]} code.logs['channel'][args[1]].append(tmp) code.logs['channel'][args[1]] = code.logs['channel'][args[1]][-20:] # 3. All bot messages in/out, maxed out by n * 100 (n being number of # channels) code.logs['bot'].append(tmp) code.logs['bot'] = code.logs['bot'][-(100 * len(code.channels)):]
def initiate_connect(self, host, port): count = 0 max_attempts = 5 if hasattr(self.config, 'delay'): delay = int(self.config.delay) else: delay = 20 while True: if count >= max_attempts: break try: count += 1 if count > 1: output.error( 'Failed to connect! Trying again in ' '%s seconds.' % str(delay) ) time.sleep(delay) if self.verbose: output.normal('Connecting to %s:%s... (try %s)' % (host, port, str(count)), 'STATUS') self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) count = 0 asyncore.loop() except: pass output.error('Too many failed attempts. Exiting.') os._exit(1)
def trigger_MODE(code, origin, line, args, text): if len(args) == 3: output.normal('{} sets MODE {}'.format(origin.nick, text), 'MODE') return else: output.normal('{} sets MODE {}'.format(origin.nick, args[2]), args[1]) # Stuff for user_list data = ' '.join(args[1:]) channel, modes, users = data.strip().split(' ', 2) users = users.split() tmp = [] def remove(old, sign): tmp = [] modes = [] for char in old: modes.append(char) while sign in modes: i = modes.index(sign) tmp.append(i) del modes[i] return tmp, ''.join(modes) if modes.startswith('+'): _plus, new_modes = remove(modes, '+') _minus, new_modes = remove(new_modes, '-') else: _minus, new_modes = remove(modes, '-') _plus, new_modes = remove(new_modes, '+') for index in range(len(users)): _usr = users[index] _mode = new_modes[index] _sign = '' if index in _plus: _sign = '+' if index in _minus: _sign = '-' tmp.append({'name': _usr, 'mode': _mode, 'sign': _sign}) last_used = '' for index in range(len(tmp)): if not last_used: last_used = tmp[index]['sign'] if not tmp[index]['sign'] or len(tmp[index]['sign']) == 0: tmp[index]['sign'] = last_used else: last_used = tmp[index]['sign'] names = {'v': 'voiced', 'o': 'op', '+': True, '-': False} for user in tmp: if user['mode'] in names and user['sign'] in names: mode, name, sign = names[user['mode'] ], user['name'], names[user['sign']] code.chan[channel][name][mode] = sign if mode == 'op' and sign: code.chan[channel][name]['voiced'] = True
def trigger_250(code, origin, line, args, text): """ ID: RPL_STATSCONN """ if not code.debug: output.normal('({}) {}'.format(origin.nick, text), 'NOTICE')
def trigger_JOIN(code, origin, line, args, text): """ ID: JOIN Decription: The JOIN command is used by client to start listening a specific channel. Whether or not a client is allowed to join a channel is checked only by the server the client is connected to; all other servers automatically add the user to the channel when it is received from other servers. The conditions which affect this are as follows: 1. the user must be invited if the channel is invite-only; Format: <channel>{,<channel>} [<key>{,<key>}] """ if origin.nick != code.nick: code.chan[args[1]][origin.nick] = { 'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': [] } if not code.debug: output.normal('{} has joined {}'.format(origin.nick, args[1]), args[1]) tmp = { 'message': 'joined {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'JOIN' } code.logs['bot'].append(tmp) code.write(('WHO', origin.nick, '%tcuhn,1'))
def initiate_connect(self, host, port): count = 0 max_attempts = 5 if self.config('connect_delay'): delay = int(self.config('connect_delay')) else: delay = 20 while True: if count >= max_attempts: break try: count += 1 if count > 1: output.error('Failed to connect! Trying again in ' '%s seconds.' % str(delay)) time.sleep(delay) if self.verbose: output.normal( 'Connecting to %s:%s... (try %s)' % (host, port, str(count)), 'STATUS') self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) count = 0 asyncore.loop() except: pass output.error('Too many failed attempts. Exiting.') os._exit(1)
def trigger_003(code, origin, line, args, text): """ ID: RPL_CREATED Decription: Part of the post-registration greeting. Text varies widely. Format: This server was created <date> """ output.normal('({}) {}'.format(origin.nick, text), 'NOTICE')
def trigger_001(code, origin, line, args, text): """ ID: RPL_WELCOME Decription: The first message sent after client registration. The text used varies widely. Format: Welcome to the Internet Relay Network <nick>!<user>@<host> """ if not code.debug: output.normal('({}) {}'.format(origin.nick, text), 'NOTICE')
def trigger_KICK(code, line): re_tmp = r'^\:(.*?)\!(.*?)\@(.*?) KICK (.*?) (.*?) \:(.*?)$' nick, ident, host, sender, kicked, reason = re.compile(re_tmp).match(line).groups() output.normal('%s has kicked %s from %s. Reason: %s' % (nick, kicked, sender, reason), 'KICK', 'red') # Stuff for user_list tmp = line.split('#', 1)[1].split() channel, name = '#' + tmp[0], tmp[1] del code.chan[channel][name]
def trigger_002(code, origin, line, args, text): """ ID: RPL_YOURHOST Decription: Part of the post-registration greeting. Text varies. widely Format: Your host is <servername>, running version <version> """ if not code.debug: output.normal('({}) {}'.format(origin.nick, text), 'NOTICE')
def msg(self, recipient, text, x=False, shorten_urls=True, bypass_loop=False, colors=True): """ Sends most messages to a direct location or recipient auto shortens URLs by default unless specified in the config """ self.sending.acquire() if colors: text = self.format(text, shorten_urls=shorten_urls) if isinstance(text, unicode): try: text = text.encode('utf-8') except UnicodeEncodeError as e: text = e.__class__ + ': ' + str(e) if isinstance(recipient, unicode): try: recipient = recipient.encode('utf-8') except UnicodeEncodeError as e: return if not x: text = text.replace('\x01', '') # No messages within the last 3 seconds? Go ahead! # Otherwise, wait so it's been at least 0.5 seconds <nope>+ penalty</nope> if not bypass_loop: # Used if you want to bypass the global rate limiter def wait(sk, txt): if sk: elapsed = time.time() - sk[-1][0] if elapsed < 3: # penalty = float(max(0, len(txt) - 50)) / 70 wait = 0.5 # + penalty if elapsed < wait: time.sleep(wait - elapsed) wait(self.stack, text) # Loop detection messages = [m[1] for m in self.stack[-8:]] if messages.count(text) >= 5: text = '...' if messages.count('...') > 2: self.sending.release() return self.__write(('PRIVMSG', self.safe(recipient)), self.safe(text)) output.normal('(%s) %s' % (self.nick, self.stripcolors(self.clear_format(self.safe(text)))), self.safe(recipient)) if self.safe(recipient).startswith('#') and self.safe(recipient) in self.logs['channel']: self.add_logs(text, recipient, self.nick) self.stack.append((time.time(), text)) self.stack = self.stack[-10:] self.sending.release()
def trigger_KICK(code, origin, line, args, text): """ ID: KICK Decription: The KICK command can be used to forcibly remove a user from a channel. It 'kicks them out' of the channel (forced PART). Format: <channel> <user> [<comment>] """ output.normal('{} has kicked {} from {}. Reason: {}'.format( origin.nick, args[2], args[1], args[3]), 'KICK', 'red') del code.chan[args[1]][args[2]]
def trigger_JOIN(code, origin, line, args, text): if origin.nick != code.nick: code.chan[args[1]][origin.nick] = {'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': []} output.normal('{} has joined {}'.format(origin.nick, args[1]), args[1]) tmp = { 'message': 'joined {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'JOIN' } code.logs['bot'].append(tmp)
def initiate_connect(self, host, port): output.normal('Connecting to %s:%s... ' % (host, port), 'STATUS') try: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) asyncore.loop() except KeyboardInterrupt: os._exit(0) except: output.error('Failed to keep connection to %s:%s!' % (host, port)) sys.exit()
def trigger_NOTICE(code, origin, line, args, text): if 'Invalid password for ' in text: output.error('Invalid NickServ password') os._exit(1) output.normal('({}) {}'.format(origin.nick, text), 'NOTICE') # Add notices to the bot logs tmp = { 'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': 'NOTICE' } code.logs['bot'].append(tmp)
def trigger_KICK(code, origin, line, args, text): """ ID: KICK Decription: The KICK command can be used to forcibly remove a user from a channel. It 'kicks them out' of the channel (forced PART). Format: <channel> <user> [<comment>] """ output.normal( '{} has kicked {} from {}. Reason: {}'.format(origin.nick, args[2], args[1], args[3]), 'KICK', 'red') del code.chan[args[1]][args[2]]
def initiate_connect(self, host, port): output.normal('Connecting to %s:%s... ' % (host, port), 'STATUS') try: # self.create_socket(socket.AF_INET, socket.SOCK_STREAM) source_address = ((self.config('bind_ip'), 0) if self.config('bind_ip') else None) self.set_socket(socket.create_connection((host, port), source_address=source_address)) self.connect((host, port)) asyncore.loop() except KeyboardInterrupt: os._exit(0) except Exception as e: output.error('Failed to keep connection to %s:%s! (%s)' % (host, port, str(e))) sys.exit()
def trigger_PRIVMSG(code, origin, line, args, text): """ ID: PRIVMSG Decription: PRIVMSG is used to send private messages between users. <receiver> is the nickname of the receiver of the message. <receiver> can also be a list of names or channels separated with commas. Format: <receiver>{,<receiver>} :<text to be sent> Numeric Replies: - ERR_NORECIPIENT - ERR_NOTEXTTOSEND - ERR_CANNOTSENDTOCHAN - ERR_NOTOPLEVEL - ERR_WILDTOPLEVEL - ERR_TOOMANYTARGETS - ERR_NOSUCHNICK - RPL_AWAY """ text = code.stripcolors(text) if text.startswith('\x01ACTION'): text = '(me) ' + text.split(' ', 1)[1].strip('\x01') if not code.debug: output.normal('({}) {}'.format(origin.nick, text), args[1]) # Stuff for user_list if args[1].startswith('#'): if origin.nick not in code.chan[args[1]]: code.chan[args[1]][origin.nick] = {'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': []} code.chan[args[1]][origin.nick]['count'] += 1 # 1. per-channel-per-user message storing... code.chan[args[1]][origin.nick]['messages'].append( {'time': int(time.time()), 'message': text}) # Ensure it's not more than 20 of the last messages code.chan[args[1]][origin.nick]['messages'] = code.chan[ args[1]][origin.nick]['messages'][-20:] # 2. Per channel message storing... tmp = {'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': args[1]} code.logs['channel'][args[1]].append(tmp) code.logs['channel'][args[1]] = code.logs['channel'][args[1]][-20:] # 3. All bot messages in/out, maxed out by n * 100 (n being number of # channels) code.logs['bot'].append(tmp) code.logs['bot'] = code.logs['bot'][-(100 * len(code.channels)):] for channel in code.chan: if origin.nick in code.chan[channel]: code.chan[channel][origin.nick]['last_seen'] = int(time.time())
def trigger_PRIVMSG(code, line): re_tmp = r'^\:(.*?)\!(.*?)\@(.*?) PRIVMSG (.*?) \:(.*?)$' nick, ident, host, sender, msg = re.compile(re_tmp).match(line).groups() msg = code.stripcolors(msg) if msg.startswith('\x01'): msg = '(me) ' + msg.split(' ', 1)[1].strip('\x01') output.normal('(%s) %s' % (nick, msg), sender) # Stuff for user_list if sender.startswith('#'): if nick not in code.chan[sender]: code.chan[sender][nick] = {'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': []} code.chan[sender][nick]['count'] += 1 code.chan[sender][nick]['messages'].append({'time': int(time.time()), 'message': msg}) # Ensure it's not more than 20 of the last messages code.chan[sender][nick]['messages'] = code.chan[sender][nick]['messages'][-20:]
def initiate_connect(self, host, port): output.normal('Connecting to %s:%s...' % (host, port), 'STATUS') try: # self.create_socket(socket.AF_INET, socket.SOCK_STREAM) source_address = ((self.config('bind_ip'), 0) if self.config('bind_ip') else None) self.set_socket( socket.create_connection((host, port), source_address=source_address)) self.connect((host, port)) asyncore.loop() except KeyboardInterrupt: os._exit(0) except Exception as e: output.error('Connection to %s:%s failed! (%s)' % (host, port, str(e))) os._exit(1)
def trigger_NICK(code, origin, line, args, text): output.normal('{} is now known as {}'.format(origin.nick, args[1]), 'NICK') # Rename old users to new ones in the database... for channel in code.chan: if origin.nick in code.chan[channel]: old = code.chan[channel][origin.nick] del code.chan[channel][origin.nick] code.chan[channel][args[1]] = old tmp = { 'message': 'is now known as {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'NICK' } code.logs['bot'].append(tmp)
def trigger_PART(code, origin, line, args, text): if origin.nick == code.nick: del code.chan[args[1]] del code.logs['channel'][args[1]] else: del code.chan[args[1]][origin.nick] if len(args) == 3: reason = args[2] else: reason = 'Unknown' output.normal('{} has part {}. Reason: {}'.format( origin.nick, args[1], reason), args[1]) tmp = { 'message': 'left {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'PART' } code.logs['bot'].append(tmp)
def docstring(): symbol = '*' lines = __doc__.strip().split('\n') largest = 0 for line in lines: if len(line) > largest: largest = len(line) outer = (largest + (1 * 4)) * symbol output.normal(outer, False) for line in lines: tmp = symbol + (1 * ' ') + line sidedif = (largest + (1 * 4)) - len(tmp) - 1 tmp += ' ' * sidedif + symbol output.normal(tmp, False) output.normal(outer, False) output.normal('Initializing the bot', 'START')
def trigger_JOIN(code, origin, line, args, text): """ ID: JOIN Decription: The JOIN command is used by client to start listening a specific channel. Whether or not a client is allowed to join a channel is checked only by the server the client is connected to; all other servers automatically add the user to the channel when it is received from other servers. The conditions which affect this are as follows: 1. the user must be invited if the channel is invite-only; Format: <channel>{,<channel>} [<key>{,<key>}] """ if origin.nick != code.nick: code.chan[args[1]][origin.nick] = {'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': []} if not code.debug: output.normal('{} has joined {}'.format(origin.nick, args[1]), args[1]) tmp = { 'message': 'joined {}'.format(args[1]), 'nick': origin.nick, 'time': int(time.time()), 'channel': 'JOIN' } code.logs['bot'].append(tmp) code.write(('WHO', origin.nick, '%tcuhn,1'))
def docstring(): symbol = '*' lines = __doc__.strip().split('\n') largest = 0 for line in lines: if len(line) > largest: largest = len(line) outer = (largest + (1 * 4)) * symbol output.normal(outer, False) for line in lines: tmp = symbol + (1 * ' ') + line sidedif = (largest + (1 * 4)) - len(tmp) - 1 tmp += ' ' * sidedif + symbol output.normal(tmp, False) output.normal(outer, False) output.success('Initializing the bot', 'START')
def trigger_write_KICK(code, args, text, raw): output.normal('I have kicked {} from {}'.format(args[2], args[1]), 'KICK', 'red') if args[2] in code.chan[args[1]]: del code.chan[args[1]][args[2]]
if log: wait(self.stack_log, text) else: wait(self.stack, text) # Loop detection if not log: messages = [m[1] for m in self.stack[-8:]] if messages.count(text) >= 5: text = '...' if messages.count('...') > 2: self.sending.release() return self.__write(('PRIVMSG', self.safe(recipient)), self.safe(text)) output.normal('(%s) %s' % (self.nick, self.stripcolors(self.clear_format(self.safe(text)))), self.safe(recipient)) if log: self.stack_log.append((time.time(), text)) else: self.stack.append((time.time(), text)) self.stack = self.stack[-10:] self.stack_log = self.stack_log[-10:] self.sending.release() def notice(self, dest, text): '''Send an IRC NOTICE to a user or a channel. See IRC protocol documentation for more information''' text = self.format(text) self.write(('NOTICE', dest), text)
def trigger_MODE(code, origin, line, args, text): """ ID: MODE Decription: The MODE command is a dual-purpose command in IRC. It allows both usernames and channels to have their mode changed. The rationale for this choice is that one day nicknames will be obsolete and the equivalent property will be the channel. When parsing MODE messages, it is recommended that the entire message be parsed first and then the changes which resulted then passed on. Format: <channel> {[+|-]|o|p|s|i|t|n|b|v} [<limit>] [<user>] [<ban mask>] <nickname> {[+|-]|i|w|s|o} """ if len(args) == 4: if args[1].startswith('#') and '+b' in args: code.bans[args[1]] = [] code.write(['MODE', args[1], '+b']) if len(args) == 3: if not code.debug: output.normal('{} sets MODE {}'.format(origin.nick, text), 'MODE') return else: if not code.debug: output.normal('{} sets MODE {}'.format(origin.nick, args[2]), args[1]) # Stuff for user_list data = ' '.join(args[1:]) channel, modes, users = data.strip().split(' ', 2) users = users.split() tmp = [] def remove(old, sign): tmp = [] modes = [] for char in old: modes.append(char) while sign in modes: i = modes.index(sign) tmp.append(i) del modes[i] return tmp, ''.join(modes) if modes.startswith('+'): _plus, new_modes = remove(modes, '+') _minus, new_modes = remove(new_modes, '-') else: _minus, new_modes = remove(modes, '-') _plus, new_modes = remove(new_modes, '+') for index in range(len(users)): _usr = users[index] _mode = new_modes[index] _sign = '' if index in _plus: _sign = '+' if index in _minus: _sign = '-' tmp.append({'name': _usr, 'mode': _mode, 'sign': _sign}) last_used = '' for index in range(len(tmp)): if not last_used: last_used = tmp[index]['sign'] if not tmp[index]['sign'] or len(tmp[index]['sign']) == 0: tmp[index]['sign'] = last_used else: last_used = tmp[index]['sign'] names = {'v': 'voiced', 'o': 'op', '+': True, '-': False} for user in tmp: if user['mode'] in names and user['sign'] in names: mode, name, sign = names[user['mode']], user['name'], names[user['sign']] code.chan[channel][name][mode] = sign if mode == 'op' and sign: code.chan[channel][name]['voiced'] = True
def trigger_PRIVMSG(code, origin, line, args, text): """ ID: PRIVMSG Decription: PRIVMSG is used to send private messages between users. <receiver> is the nickname of the receiver of the message. <receiver> can also be a list of names or channels separated with commas. Format: <receiver>{,<receiver>} :<text to be sent> Numeric Replies: - ERR_NORECIPIENT - ERR_NOTEXTTOSEND - ERR_CANNOTSENDTOCHAN - ERR_NOTOPLEVEL - ERR_WILDTOPLEVEL - ERR_TOOMANYTARGETS - ERR_NOSUCHNICK - RPL_AWAY """ text = code.stripcolors(text) if text.startswith('\x01ACTION'): text = '(me) ' + text.split(' ', 1)[1].strip('\x01') if not code.debug: output.normal('({}) {}'.format(origin.nick, text), args[1]) # Stuff for user_list if args[1].startswith('#'): if origin.nick not in code.chan[args[1]]: code.chan[args[1]][origin.nick] = { 'normal': True, 'voiced': False, 'op': False, 'count': 0, 'messages': [] } code.chan[args[1]][origin.nick]['count'] += 1 # 1. per-channel-per-user message storing... code.chan[args[1]][origin.nick]['messages'].append({ 'time': int(time.time()), 'message': text }) # Ensure it's not more than 20 of the last messages code.chan[args[1]][origin.nick]['messages'] = code.chan[args[1]][ origin.nick]['messages'][-20:] # 2. Per channel message storing... tmp = { 'message': text, 'nick': origin.nick, 'time': int(time.time()), 'channel': args[1] } code.logs['channel'][args[1]].append(tmp) code.logs['channel'][args[1]] = code.logs['channel'][args[1]][-20:] # 3. All bot messages in/out, maxed out by n * 100 (n being number of # channels) code.logs['bot'].append(tmp) code.logs['bot'] = code.logs['bot'][-(100 * len(code.channels)):] for channel in code.chan: if origin.nick in code.chan[channel]: code.chan[channel][origin.nick]['last_seen'] = int(time.time())
def trigger_MODE(code, origin, line, args, text): """ ID: MODE Decription: The MODE command is a dual-purpose command in IRC. It allows both usernames and channels to have their mode changed. The rationale for this choice is that one day nicknames will be obsolete and the equivalent property will be the channel. When parsing MODE messages, it is recommended that the entire message be parsed first and then the changes which resulted then passed on. Format: <channel> {[+|-]|o|p|s|i|t|n|b|v} [<limit>] [<user>] [<ban mask>] <nickname> {[+|-]|i|w|s|o} """ if len(args) == 4: if args[1].startswith('#') and '+b' in args: code.bans[args[1]] = [] code.write(['MODE', args[1], '+b']) if len(args) == 3: if not code.debug: output.normal('{} sets MODE {}'.format(origin.nick, text), 'MODE') return else: if not code.debug: output.normal('{} sets MODE {}'.format(origin.nick, args[2]), args[1]) # Stuff for user_list data = ' '.join(args[1:]) channel, modes, users = data.strip().split(' ', 2) users = users.split() tmp = [] def remove(old, sign): tmp = [] modes = [] for char in old: modes.append(char) while sign in modes: i = modes.index(sign) tmp.append(i) del modes[i] return tmp, ''.join(modes) if modes.startswith('+'): _plus, new_modes = remove(modes, '+') _minus, new_modes = remove(new_modes, '-') else: _minus, new_modes = remove(modes, '-') _plus, new_modes = remove(new_modes, '+') for index in range(len(users)): _usr = users[index] _mode = new_modes[index] _sign = '' if index in _plus: _sign = '+' if index in _minus: _sign = '-' tmp.append({'name': _usr, 'mode': _mode, 'sign': _sign}) last_used = '' for index in range(len(tmp)): if not last_used: last_used = tmp[index]['sign'] if not tmp[index]['sign'] or len(tmp[index]['sign']) == 0: tmp[index]['sign'] = last_used else: last_used = tmp[index]['sign'] names = {'v': 'voiced', 'o': 'op', '+': True, '-': False} for user in tmp: if user['mode'] in names and user['sign'] in names: mode, name, sign = names[user['mode']], user['name'], names[ user['sign']] code.chan[channel][name][mode] = sign if mode == 'op' and sign: code.chan[channel][name]['voiced'] = True
def trigger_NOTICE(code, line): re_tmp = r'^\:(.*?) NOTICE (.*?) \:(.*?)$' nick, sender, msg = re.compile(re_tmp).match(line).groups() output.normal('(%s) %s' % (nick.split('!')[0], msg), 'NOTICE')
def trigger_NICK(code, line): nick = line[1::].split('!', 1)[0] new_nick = line[1::].split(':', 1)[1] output.normal('%s is now known as %s' % (nick, new_nick), 'NICK')
def trigger_250(code, line): msg, sender = line.split(':', 2)[2], line.split(':', 2)[1].split()[0] output.normal('(%s) %s' % (sender, msg), 'NOTICE')
if elapsed < wait: time.sleep(wait - elapsed) wait(self.stack, text) # Loop detection messages = [m[1] for m in self.stack[-8:]] if messages.count(text) >= 5: text = '...' if messages.count('...') > 2: self.sending.release() return self.__write(('PRIVMSG', self.safe(recipient)), self.safe(text)) output.normal( '(%s) %s' % (self.nick, self.stripcolors(self.clear_format(self.safe(text)))), self.safe(recipient)) self.stack.append((time.time(), text)) self.stack = self.stack[-10:] self.sending.release() def notice(self, dest, text): '''Send an IRC NOTICE to a user or a channel. See IRC protocol documentation for more information''' text = self.format(text) self.write(('NOTICE', dest), text) def action(self, dest, text): '''Send an action (/me) to a user or a channel''' text = self.format(text)
def msg(self, recipient, text, x=False, shorten_urls=True, bypass_loop=False, colors=True): """ Sends most messages to a direct location or recipient auto shortens URLs by default unless specified in the config """ self.sending.acquire() if colors: text = self.format(text, shorten_urls=shorten_urls) if isinstance(text, unicode): try: text = text.encode('utf-8') except UnicodeEncodeError as e: text = e.__class__ + ': ' + str(e) if isinstance(recipient, unicode): try: recipient = recipient.encode('utf-8') except UnicodeEncodeError as e: return if not x: text = text.replace('\x01', '') # No messages within the last 3 seconds? Go ahead! # Otherwise, wait so it's been at least 0.5 seconds <nope>+ penalty</nope> if not bypass_loop: # Used if you want to bypass the global rate limiter def wait(sk, txt): if sk: elapsed = time.time() - sk[-1][0] if elapsed < 3: # penalty = float(max(0, len(txt) - 50)) / 70 wait = 0.5 # + penalty if elapsed < wait: time.sleep(wait - elapsed) wait(self.stack, text) # Loop detection messages = [m[1] for m in self.stack[-8:]] if messages.count(text) >= 5: text = '...' if messages.count('...') > 2: self.sending.release() return self.__write(('PRIVMSG', self.safe(recipient)), self.safe(text)) output.normal( '(%s) %s' % (self.nick, self.stripcolors(self.clear_format(self.safe(text)))), self.safe(recipient)) if self.safe(recipient).startswith('#') and self.safe( recipient) in self.logs['channel']: self.add_logs(text, recipient, self.nick) self.stack.append((time.time(), text)) self.stack = self.stack[-10:] self.sending.release()