def nick(bot, event, *args): '''Adds a nickname. Format is /bot nick <nickname>''' if len(args) == 1: added = add_nickname(bot, event, event.user.id_.chat_id, args[0].lower()) if added: yield from bot.coro_send_message(event.conv, _(added)) elif len(args) == 3 and is_admin(bot, event) and args[0] == '--set': added = add_nickname(bot, event, args[1], args[2].lower()) if added: yield from bot.coro_send_message(event.conv, _(added)) else: yield from bot.coro_send_message(event.conv, _("Too many args"))
def score(bot, event, *args): '''Get the score for a user. Format is /bot score <name> To increment scores, do <name>++ or <name>--''' if len(args) == 1: if args[0].lower() == '--high': msg = get_high_score(bot) elif args[0].lower() == '--low': msg = get_low_score(bot) else: name = args[0].lower() msg = get_score(bot, name) elif args[0].lower() == '--set' and len(args) == 3 and is_admin(bot, event): name = args[1].lower() val = args[2].lower() msg = set_score(bot, name, int(val)) else: msg = _("Wrong number of arguments!") yield from bot.coro_send_message(event.conv, msg)
def handle_command(parts): is_admin = admin.is_admin(request.form['user_id']) cmd_name = parts[0] if cmd_name in command_map: cmd_data = command_map[cmd_name] if cmd_data['admin'] and not is_admin: return msg("You don't have permission to run {}".format(cmd_name)) if cmd_data['args'] is not None and len(parts) < (1 + len(cmd_data['args'])): return msg("Need {} arg(s): {}".format(len(cmd_data['args']), args_to_string(cmd_data['args']))) args = parts[1:] r = None if cmd_data['args'] is None: r = cmd_data['func'](args) else: r = cmd_data['func'](*args) if r: return r return None return "Unknown ++ command " + parts[0]
def quote(bot, event, *args): '''Manipulate quotes. Format is /bot quote [-a, -l] To add quotes: /bot quote -a quote - author To list all quotes: /bot quote -l To list specific person's quotes: /bot quote -l name To get a specific quote: /bot quote quote_number To get a random quote: /bot quote To get a random quote for a specifc author: /bot quote name''' msg = None try: conn = sqlite3.connect('bot.db') if not args: msg = retrieve(conn, None, None, full=False) elif args[0] not in ['-a', '-d', '-l', '-e'] and args[0].startswith('-'): msg = "Invalid Flag" elif args[0] in ['-d', '-e']: if len(args) < 2: msg = "You're missing arguments!" elif is_admin(bot, event) or is_tag(bot, event, 'quote-admin'): # admin only quote functions if args[0] == "-d": # delete quotes delete(conn, args[1]) msg = "Successfully deleted quote" elif args[0] == "-e": # edit quotes msg = edit(conn, args[1], " ".join(args[2:])) else: msg = "You're not an admin!" elif args[0] == "-l": # list quotes from author if len(args) == 1: msg = str(retrieve(conn, None, True)) else: msg = str(retrieve(conn, args[1], True)) elif args[0] == "-a": text = " ".join(args[1:]).split(' - ') if event.user.first_name.lower() == text[1]: msg = "You can't submit your own quote!" # self-submission else: if is_admin(bot, event) or is_tag(bot, event, 'quote-admin'): msg = add(conn, text[0], text[1]) else: rply = add(conn, text[0], text[1], admin=False) yield from bot.coro_send_message(CONTROL, _(rply[1])) msg = rply[0] else: text = " ".join(args) author = True if not text.isnumeric() else False msg = retrieve(conn, text, author, full=False) #to_send = _(msg) print(msg) split = msg.split('\n') if len(split) > 4: msg = "<b>Message truncated:</b> See full message at {}\nHere are the last 4 lines: \n{}".format( ixio(msg), "\n".join(split[-4:])) #yield from bot.coro_send_message(event.conv, _(str(len(leng)))) yield from bot.coro_send_message(event.conv, _(msg)) conn.close() except TypeError: msg = 'No such quote' yield from bot.coro_send_message(event.conv, _(msg)) except BaseException as e: msg = '{} -- {}'.format(str(e), event.text) raise e yield from bot.coro_send_message(CONTROL, _(msg))
def test_is_admin_empty_json_list(self): self.set_admins_file('[]') self.assertFalse(admin.is_admin('lol', log=self.flog)) self.assertFalse(self.flog.logged)
def test_is_admin_invalid_user(self): self.set_admins_file('["blah"]') self.assertFalse(admin.is_admin('', log=self.flog.log)) self.assertFalse(self.flog.logged)
def test_is_admin_empty_user(self): nick = 'testnick!blablabla.324.324.890' self.set_admins_file('["{}"]'.format(nick)) self.assertTrue(admin.is_admin(nick, log=self.flog.log)) self.assertFalse(self.flog.logged)
def assert_fail_and_log(self): self.assertFalse(admin.is_admin('test', log=self.flog.log)) self.assertTrue(self.flog.logged)
def poll(bot, event, *args): '''Creates a poll. Format is /bot poll [--add, --delete, --list, --vote] [pollnum, pollname] [vote]''' if args: if args[0] == '--add' and is_admin(bot, event): if len(args) > 1: name = ' '.join(args[1:]) msg = add(bot, name) elif args[0] == '--add' and not is_admin(bot, event): request = submit_for_approval(bot, event) msg = request[0] yield from bot.coro_send_message(CONTROL, _(request[1])) #msg = _("{}: Can't do that.").format(event.user.full_name) elif args[0] == '--delete' and is_admin(bot, event): name = ' '.join(args[1:]) msg = delete(bot, name) elif args[0] == '--delete' and not is_admin(bot, event): msg = _("{}: Can't do that.").format(event.user.full_name) elif args[0] == '--vote': if args[1].isdigit(): pollnum = int(args[1]) - 1 msg = vote(bot, event, ' '.join(args[2:]), "default", pollnum) else: vote_ = ' '.join(args[1:]).split(' - ')[0] name = ' '.join(args[1:]).split(' - ')[1] msg = vote(bot, event, vote_, name, -1) elif args[0] == '--list': msg = list(bot) elif args[0] == '--results': if args[1].isdigit(): path = bot.memory.get_by_path(['polls']) pollnum = int(args[1]) - 1 keys = [] for poll in path: keys.append(poll) if len(keys) > 0 and len(keys) >= pollnum: poll = keys[pollnum] msg = results(bot, poll) else: msg = _("Not that many polls") else: poll = ' '.join(args[1:]) msg = results(bot, poll) elif args[0] == '--help': if args[1] == '--set' and is_admin(bot, event): if args[2].isdigit(): pollnum = int(args[2]) - 1 msg = set_help(bot, pollnum, ' '.join(args[3:])) else: msg = _("What number poll do you want to add help for?") elif args[1] == '--set' and not is_admin(bot, event): request = submit_for_approval(bot, event) msg = request[0] yield from bot.coro_send_message(CONTROL, _(request[1])) else: if args[1].isdigit(): pollnum = int(args[1]) - 1 msg = get_help(bot, "default", pollnum) else: msg = get_help(bot, ' '.join(args[1:]), -1) else: if args[0].isdigit(): pollnum = int(args[0]) - 1 msg = vote(bot, event, ' '.join(args[1:]), "default", pollnum) else: vote_ = ' '.join(args).split(' - ')[0] name = ' '.join(args).split(' - ')[1] msg = vote(bot, event, vote_, name, -1) else: msg = _( "Creates a poll. Format is /bot poll [--add, --delete, --list, --vote, --results] [pollnum, pollname] [vote]") yield from bot.coro_send_message(event.conv, msg) bot.memory.save()
def urban(bot, event, *args): """lookup a term on Urban Dictionary. supplying no parameters will get you a random term. DISCLAIMER: all definitions are from http://www.urbandictionary.com/ - the bot and its creators/maintainers take no responsibility for any hurt feelings. """ if not bot.memory.exists(["blacklisted"]): bot.memory.set_by_path(["blacklisted"], []) bot.memory.save() blacklisted = bot.memory.get_by_path(["blacklisted"]) if (args and not args[0] == '--blacklist') or not args: term = " ".join(args) if not term: url = "http://www.urbandictionary.com/random.php" else: url = "http://www.urbandictionary.com/define.php?term=%s" % \ urlquote(term) f = urlopen(url) data = f.read().decode('utf-8') urbanDictParser = UrbanDictParser() try: urbanDictParser.feed(data) except IndexError: # apparently, nothing was returned pass if len(urbanDictParser.translations) > 0: the_definition = urbanDictParser.translations[0] if the_definition["word"].lower() not in blacklisted: html_text = "" html_text += '<b>"' + \ the_definition["word"] + '"</b><br /><br />' if "def" in the_definition: html_text += _("<b>definition:</b> ") + the_definition[ "def"].strip().replace("\n", "<br />") + '<br /><br />' if "example" in the_definition: html_text += _("<b>example:</b> ") + \ the_definition["example"].strip( ).replace("\n", "<br />") yield from bot.coro_send_message(event.conv, html_text) else: word = urbanDictParser.translations[0]["word"] yield from bot.coro_send_message(event.conv, _("{} is blacklisted").format(word)) else: if term: yield from bot.coro_send_message(event.conv, _('<i>no urban dictionary definition for "{}"</i>').format(term)) else: yield from bot.coro_send_message(event.conv, _('<i>no term from urban dictionary</i>')) elif args[0] == '--blacklist' and is_admin(bot, event): term = ' '.join(args[1:]).lower() if term not in blacklisted: blacklisted.append(term) bot.memory.set_by_path(["blacklisted"], blacklisted) bot.memory.save() yield from bot.coro_send_message(event.conv, _("{} blacklisted").format(term)) else: yield from bot.coro_send_message(event.conv, _("{} is already blacklisted").format(term)) elif args[0] == '--blacklist' and not is_admin(bot, event): yield from bot.coro_send_message(event.conv, _("Ask an admin to do that"))
def mention(bot, event, *args): """alert a @mentioned user""" """allow mentions to be disabled via global or per-conversation config""" config_mentions_enabled = False if bot.get_config_suboption( event.conv.id_, 'mentions.enabled') is False else True if not config_mentions_enabled: logger.info( "mentions explicitly disabled by config for {}".format(event.conv_id)) return """minimum length check for @mention""" minimum_length = bot.get_config_suboption( event.conv_id, 'mentionminlength') if not minimum_length: minimum_length = 2 username = args[0].strip() if len(username) < minimum_length: logger.debug("@mention from {} ({}) too short (== '{}')".format( event.user.full_name, event.user.id_.chat_id, username)) return users_in_chat = event.conv.users mention_chat_ids = [] """sync room support""" if bot.get_config_option('syncing_enabled'): sync_room_list = bot.get_config_option('sync_rooms') if sync_room_list: """scan through each room group""" for rooms_group in sync_room_list: if event.conv_id in rooms_group: """current conversation is part of a syncroom group, add "external" users""" for syncedroom in rooms_group: if event.conv_id is not syncedroom: users_in_chat += bot.get_users_in_conversation( syncedroom) users_in_chat = list(set(users_in_chat)) # make unique logger.debug("@mention in a syncroom: {} user(s) present".format( len(users_in_chat))) break """ /bot mention <fragment> test """ noisy_mention_test = False if len(args) == 2 and args[1] == "test": noisy_mention_test = True initiator_has_dnd = _user_has_dnd(bot, event.user.id_.chat_id) """ quidproquo: users can only @mention if they themselves are @mentionable (i.e. have a 1-on-1 with the bot) """ conv_1on1_initiator = yield from bot.get_1to1(event.user.id_.chat_id) if bot.get_config_option("mentionquidproquo"): if conv_1on1_initiator: if initiator_has_dnd: logger.info("quidproquo: user {} ({}) has DND active".format( event.user.full_name, event.user.id_.chat_id)) if noisy_mention_test or bot.get_config_suboption(event.conv_id, 'mentionerrors'): yield from bot.coro_send_message( event.conv, _("<b>{}</b>, you cannot @mention anyone until your DND status is toggled off.").format( event.user.full_name)) return else: logger.debug("quidproquo: user {} ({}) has 1-on-1".format( event.user.full_name, event.user.id_.chat_id)) else: logger.info("quidproquo: user {} ({}) has no 1-on-1".format( event.user.full_name, event.user.id_.chat_id)) if noisy_mention_test or bot.get_config_suboption(event.conv_id, 'mentionerrors'): yield from bot.coro_send_message( event.conv, _("<b>{}</b> cannot @mention anyone until they say something to me first.").format( event.user.full_name)) return """track mention statistics""" user_tracking = { "mentioned": [], "ignored": [], "failed": { "pushbullet": [], "one2one": [], } } """ begin mentioning users as long as they exist in the current conversation... """ conversation_name = bot.conversations.get_name(event.conv) logger.info("@mention '{}' in '{}' ({})".format( username, conversation_name, event.conv.id_)) username_lower = username.lower() username_upper = username.upper() """is @all available globally/per-conversation/initiator?""" if username_lower == "all": if not bot.get_config_suboption(event.conv.id_, 'mentionall'): """global toggle is off/not set, check admins""" logger.debug( "@all in {}: disabled/unset global/per-conversation".format(event.conv.id_)) admins_list = bot.get_config_suboption(event.conv_id, 'admins') if not is_admin(bot, event): """initiator is not an admin, check whitelist""" logger.debug("@all in {}: user {} ({}) is not admin".format( event.conv.id_, event.user.full_name, event.user.id_.chat_id)) all_whitelist = bot.get_config_suboption( event.conv_id, 'mentionallwhitelist') if all_whitelist is None or event.user_id.chat_id not in all_whitelist: logger.warning("@all in {}: user {} ({}) blocked".format( event.conv.id_, event.user.full_name, event.user.id_.chat_id)) if conv_1on1_initiator: yield from bot.coro_send_message( conv_1on1_initiator, _("You are not allowed to use @all in <b>{}</b>").format( conversation_name)) if noisy_mention_test or bot.get_config_suboption(event.conv_id, 'mentionerrors'): yield from bot.coro_send_message( event.conv, _("<b>{}</b> blocked from using <i>@all</i>").format( event.user.full_name)) return else: logger.info("@all in {}: allowed, {} ({}) is whitelisted".format( event.conv.id_, event.user.full_name, event.user.id_.chat_id)) else: logger.info("@all in {}: allowed, {} ({}) is an admin".format( event.conv.id_, event.user.full_name, event.user.id_.chat_id)) else: logger.info( "@all in {}: enabled global/per-conversation".format(event.conv.id_)) """generate a list of users to be @mentioned""" exact_nickname_matches = [] exact_fragment_matches = [] mention_list = [] for u in users_in_chat: # mentions also checks nicknames if one is configured # exact matches only! see following IF block nickname = "" nickname_lower = "" if bot.memory.exists(['user_data', u.id_.chat_id, "nickname"]): nickname = bot.memory.get_by_path( ['user_data', u.id_.chat_id, "nickname"]) nickname_lower = nickname.lower() _normalised_full_name_upper = remove_accents(u.full_name.upper()) if (username_lower == "all" or username_lower in u.full_name.replace(" ", "").lower() or username_upper in _normalised_full_name_upper.replace(" ", "") or username_lower in u.full_name.replace(" ", "_").lower() or username_upper in _normalised_full_name_upper.replace(" ", "_") or username_lower == nickname_lower or username in u.full_name.split(" ")) and is_admin(bot, event): logger.info("user {} ({}) is present".format( u.full_name, u.id_.chat_id)) if u.is_self: """bot cannot be @mentioned""" logger.debug("suppressing bot mention by {} ({})".format( event.user.full_name, event.user.id_.chat_id)) continue if u.id_.chat_id == event.user.id_.chat_id and username_lower == "all": """prevent initiating user from receiving duplicate @all""" logger.debug("suppressing @all for {} ({})".format( event.user.full_name, event.user.id_.chat_id)) continue if u.id_.chat_id == event.user.id_.chat_id and not noisy_mention_test: """prevent initiating user from mentioning themselves""" logger.debug("suppressing @self for {} ({})".format( event.user.full_name, event.user.id_.chat_id)) continue if u.id_.chat_id in mention_chat_ids: """prevent most duplicate mentions (in the case of syncouts)""" logger.debug("suppressing duplicate mention for {} ({})".format( event.user.full_name, event.user.id_.chat_id)) continue if bot.memory.exists(["donotdisturb"]): if _user_has_dnd(bot, u.id_.chat_id): logger.info("suppressing @mention for {} ({})".format( u.full_name, u.id_.chat_id)) user_tracking["ignored"].append(u.full_name) continue if username_lower == nickname_lower: if u not in exact_nickname_matches: exact_nickname_matches.append(u) if (username in u.full_name.split(" ") or username_upper in _normalised_full_name_upper.split(" ")): if u not in exact_fragment_matches: exact_fragment_matches.append(u) if u not in mention_list: mention_list.append(u) if len(exact_nickname_matches) == 1: """prioritise exact nickname matches""" logger.info("prioritising nickname match for {}".format( exact_nickname_matches[0].full_name)) mention_list = exact_nickname_matches elif len(exact_fragment_matches) == 1: """prioritise case-sensitive fragment matches""" logger.info("prioritising single case-sensitive fragment match for {}".format( exact_fragment_matches[0].full_name)) mention_list = exact_fragment_matches elif len(exact_fragment_matches) > 1 and len(exact_fragment_matches) < len(mention_list): logger.info("prioritising multiple case-sensitive fragment match for {}".format( exact_fragment_matches[0].full_name)) mention_list = exact_fragment_matches if len(mention_list) > 1 and username_lower != "all": send_multiple_user_message = True if bot.memory.exists(['user_data', event.user.id_.chat_id, "mentionmultipleusermessage"]): send_multiple_user_message = bot.memory.get_by_path( ['user_data', event.user.id_.chat_id, "mentionmultipleusermessage"]) if send_multiple_user_message or noisy_mention_test: if conv_1on1_initiator: text_html = _('{} users would be mentioned with "@{}"! Be more specific. List of matching users:<br />').format( len(mention_list), username, conversation_name) for u in mention_list: text_html += u.full_name if bot.memory.exists(['user_data', u.id_.chat_id, "nickname"]): text_html += ' (' + bot.memory.get_by_path( ['user_data', u.id_.chat_id, "nickname"]) + ')' text_html += '<br />' text_html += "<br /><em>To toggle this message on/off, use <b>/bot bemorespecific</b></em>" yield from bot.coro_send_message(conv_1on1_initiator, text_html) logger.warning( "@{} not sent due to multiple recipients".format(username_lower)) return # SHORT-CIRCUIT """support for reprocessor override the source name by defining event._external_source""" source_name = event.user.full_name if hasattr(event, '_external_source'): source_name = event._external_source """send @mention alerts""" for u in mention_list: alert_via_1on1 = True """pushbullet integration""" if bot.memory.exists(['user_data', u.id_.chat_id, "pushbullet"]): pushbullet_config = bot.memory.get_by_path( ['user_data', u.id_.chat_id, "pushbullet"]) if pushbullet_config is not None: if pushbullet_config["api"] is not None: success = False try: pb = PushBullet(pushbullet_config["api"]) push = pb.push_note( _("{} mentioned you in {}").format( source_name, conversation_name), event.text) if isinstance(push, tuple): # backward-compatibility for pushbullet library < # 0.8.0 success = push[0] elif isinstance(push, dict): success = True else: raise TypeError( "unknown return from pushbullet library: {}".format(push)) except Exception as e: logger.exception("pushbullet error") if success: user_tracking["mentioned"].append(u.full_name) logger.info("{} ({}) alerted via pushbullet".format( u.full_name, u.id_.chat_id)) alert_via_1on1 = False # disable 1on1 alert else: user_tracking["failed"][ "pushbullet"].append(u.full_name) logger.warning("pushbullet alert failed for {} ({})".format( u.full_name, u.id_.chat_id)) if alert_via_1on1: """send alert with 1on1 conversation""" conv_1on1 = yield from bot.get_1to1(u.id_.chat_id) if conv_1on1: yield from bot.coro_send_message( conv_1on1, _("<b>{}</b> @mentioned you in <i>{}</i>:<br />{}").format( source_name, conversation_name, event.text)) # prevent internal parser from removing <tags> mention_chat_ids.append(u.id_.chat_id) user_tracking["mentioned"].append(u.full_name) logger.info("{} ({}) alerted via 1on1 ({})".format( u.full_name, u.id_.chat_id, conv_1on1.id_)) else: user_tracking["failed"]["one2one"].append(u.full_name) if bot.get_config_suboption(event.conv_id, 'mentionerrors'): yield from bot.coro_send_message( event.conv, _("@mention didn't work for <b>{}</b>. User must say something to me first.").format( u.full_name)) logger.warning("user {} ({}) could not be alerted via 1on1".format( u.full_name, u.id_.chat_id)) if noisy_mention_test: text_html = _("<b>@mentions:</b><br />") if len(user_tracking["failed"]["one2one"]) > 0: text_html = text_html + \ _("1-to-1 fail: <i>{}</i><br />").format( ", ".join(user_tracking["failed"]["one2one"])) if len(user_tracking["failed"]["pushbullet"]) > 0: text_html = text_html + _("PushBullet fail: <i>{}</i><br />").format( ", ".join(user_tracking["failed"]["pushbullet"])) if len(user_tracking["ignored"]) > 0: text_html = text_html + \ _("Ignored (DND): <i>{}</i><br />").format( ", ".join(user_tracking["ignored"])) if len(user_tracking["mentioned"]) > 0: text_html = text_html + \ _("Alerted: <i>{}</i><br />").format( ", ".join(user_tracking["mentioned"])) else: text_html = text_html + \ _("Nobody was successfully @mentioned ;-(<br />") if len(user_tracking["failed"]["one2one"]) > 0: text_html = text_html + \ _("Users failing 1-to-1 need to say something to me privately first.<br />") yield from bot.coro_send_message(event.conv, text_html)
def handle(line, settings, state, log=logger.log): # TODO: Write docstring about how this yields responses user, command, arguments = ircparser.split(line) nick = ircparser.get_nick(user) if command == 'PING': yield 'PONG :' + arguments[0] if command == 'PONG': state['pinged'] = False if command == '433': def new_nick(nick): nick = nick[:min(len(nick), 6)] # determine how much to shave off to make room for random chars return '{}_{}'.format(nick, ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(2))) log('warning', '[statekeeping] Nick {} already in use, trying another one.'.format(state['nick'])) state['nick'] = new_nick(settings['irc']['nick']) yield 'NICK {}'.format(state['nick']) if command == 'JOIN' and nick == state['nick']: # TODO: Fancy logging print('--> joined {}'.format(arguments[0])) if state['joined_channel']: # TODO: raise some sort of illegal state exception. remember to test for it # then what? have we joined two channels? what the shit are we supposed to do? pass state['joined_channel'] = arguments[0] if command == 'KICK' and arguments[1] == state['nick']: log('warning', '[statekeeping] Kicked from channel {} because {}'.format(arguments[0], ' '.join(arguments[1:]))) if arguments[0] != state['joined_channel']: # TODO: raise some sort of illegal state exception pass state['joined_channel'] = None settings['irc']['channel'] = None if command == 'PRIVMSG': # Make the author the target for replies if it is a private message channel = nick if arguments[0] == state['nick'] else arguments[0] message = ' '.join(arguments[1:]) # TODO: Turn this into some sort of cooler logging print('{}> {}'.format(channel, message)) # TODO: Better admin shit, this is just poc/temporary if admin.is_admin(user): admin_result = admin.parse_admin_command(message, state['nick']) if admin_result: yield ircparser.make_privmsg(channel, admin_result) if message == 'hello, world': yield ircparser.make_privmsg(channel, 'why, hello!') else: log('raw', line) # TODO: Fix the state object so this isn't needed if 'joined_channel' not in state: state['joined_channel'] = None if not state['joined_channel'] and settings['irc']['channel']: yield 'JOIN {}'.format(settings['irc']['channel'])