def roulette(cls, roulette_type): """Get a random image or video link""" # take all the messages in the channel, filtered for links messages = DB.get_messages(cls.channels, senders=cls.users, patterns=[_URL_PATT]) if len(messages) == 0: raise MyFaultError("I didn't find any URLs in the " "selection criteria.") # then reduce strings containing urls to urls urls = [ re.search(_URL_PATT, message, re.IGNORECASE).group(0) for message in messages ] # make urls unique urls = list(set(urls)) # then filter by either images or videos if roulette_type == 'image': urls = [url for url in urls if _IMG_PATT.search(url)] if len(urls) == 0: raise MyFaultError("I didn't find any images.") if roulette_type in ['video', 'youtube', 'yt']: urls = [url for url in urls if _YT_PATT.search(url)] if len(urls) == 0: raise MyFaultError("I didn't find any video links.") return urls
def command(cls, irc_c, msg, cmd): if(defer.check(cmd, 'jarvis', 'Secretary_Helen')): return if len(cmd.args['root']) > 1 or not all(map(isint, cmd.args['root'])): raise CommandError("Specify the number of the article you want " "(or none to see the choices)") elif len(cmd.args['root']) == 0: number = 0 else: number = int(cmd.args['root'][0]) page_ids = DB.get_showmore_list(msg.raw_channel) if len(page_ids) == 0: raise MyFaultError("I have nothing to show more of.") if number > len(page_ids): raise MyFaultError("I only have {} results for the last search." .format(len(page_ids))) pages = [DB.get_article_info(p_id) for p_id in page_ids] if number == 0: msg.reply("{} saved results (use ..sm to choose): {}".format( len(pages), showmore.parse_multiple_titles(pages))) else: msg.reply("{}/{} · {}".format( number, len(page_ids), showmore.parse_title(pages[number-1])))
def command(cls, irc_c, msg, cmd): if len(cmd.args['root']) < 1: raise CommandError("Specify a page's URL whose shortest search " "term you want to find.") pages = [DB.get_article_info(p_id)['title'] for p_id in DB.get_articles([])] try: title = DB.get_article_info( DB.get_articles( [{'type': 'url', 'term': cmd.args['root'][0]}] )[0])['title'] except IndexError: raise MyFaultError("I couldn't find the page with that URL.") single_string = shortest.get_substring(title, pages) print("Single string:", single_string) helen_style = shortest.get_multi_substring(title, pages) if single_string is None and helen_style is None: raise MyFaultError("There's no unique search for {} (\"{}\")" .format(cmd.args['root'][0], title)) msg.reply("Shortest search for \x02{}\x0F · {}" .format(cmd.args['root'][0], shortest.pick_answer(single_string, helen_style)))
def get_gib_sentence(cls, attempts=0, limit=7500, minlength=0, patterns=None): print("Getting a gib sentence") # messages = [] # for channel in cls.channels: # print("Iterating channels") # for user in cls.users: # print("Iterating users") # messages.extend(DB.get_messages(channel, user)) messages = DB.get_messages( cls.channels, minlength=40, limit=limit, senders=None if cls.users == [None] else cls.users, patterns=patterns) print("messages found: {}".format(len(messages))) if len(messages) == 0: raise AttributeError for decr in range(0, cls.size): print("Making model from messages, size {}".format(cls.size - decr)) cls.model = cls.make_model(messages, decrement=decr) print("Making sentence") sentence = cls.model.make_short_sentence(400, minlength, tries=200, force_result=False) if sentence is not None: break print("Sentence is None") if not cls.nocache and sentence in DB.get_gibs(): print("Sentence already sent, {} attempts remaining".format( cls.ATTEMPT_LIMIT - attempts)) try: if attempts < cls.ATTEMPT_LIMIT: sentence = cls.get_gib_sentence(attempts + 1, limit, minlength, patterns) else: raise RecursionError except RecursionError: raise MyFaultError("I didn't find any gibs for that selection " "that haven't already been said.") if sentence is not None: DB.add_gib(sentence) return sentence
def command(irc_c, msg, cmd): if defer.check(cmd, 'Secretary_Helen'): return cmd.expandargs(["first f", "count c"]) # have to account for .seen -f name if 'first' in cmd: cmd.args['root'].extend(cmd.args['first']) if 'count' in cmd: cmd.args['root'].extend(cmd.args['count']) if len(cmd.args['root']) < 1: raise CommandError("Specify a user and I'll tell you when I last " "saw them") nick = cmd.args['root'][0] messages = DB.get_messages_from_user(nick, msg.raw_channel) if len(messages) == 0: raise MyFaultError("I've never seen {} in this channel." .format(nick)) if 'count' in cmd: msg.reply("I've seen {} {} times in this channel." .format(nick, len(messages))) return if 'first' in cmd: message = messages[0] response = "I first saw {} {} saying: {}" else: if nick == msg.sender: msg.reply("I can see you right now, {}.".format(msg.sender)) return message = messages[-1] response = "I last saw {} {} saying: {}" response = response.format( nick if nick == message['sender'] else "{} as {}".format(nick, message['sender']), pd.from_timestamp(message['timestamp']).diff_for_humans(), gib.obfuscate(message['message'], DB.get_channel_members(msg.raw_channel))) msg.reply(response)
def command(cls, irc_c, msg, cmd): if (defer.check(cmd, 'jarvis')): return cmd.expandargs([ "no-cache n", "user u author a", "channel c", "size s", "roulette r", "regex x", "minlength length l", "me", "help h" ]) if 'help' in cmd: msg.reply("Usage: .gib [--channel #channel] [--user user] " "[--no-cache]") return channels = [msg.raw_channel] users = [] # root has 1 num, 1 string, 1 string startswith # for arg in cmd.args['root']: if arg.startswith('#'): raise CommandError("Try .gib -c {}".format(arg)) else: raise CommandError("Try .gib -u {}".format(arg)) if 'channel' in cmd: if len(cmd['channel']) == 0: raise CommandError("When using the --channel/-c filter, " "at least one channel must be specified") if cmd['channel'][0] == "all": if defer.controller(cmd): channels = DB.get_all_channels() msg.reply("Gibbing from all channels I'm in:") else: msg.reply("Gibbing from all channels you're in:") # get all channels this user is in raise MyFaultError("This isn't implemented yet.") else: for channel in cmd['channel']: if not channel.startswith('#'): raise CommandError("Channel names must start with #.") channels = cmd['channel'] elif msg.raw_channel is None: raise CommandError("Specify a channel to gib from with " "--channel/-c") if 'user' in cmd: if len(cmd['user']) == 0: raise CommandError("When using the --user/-u filter, " "at least one user must be specified") users = cmd['user'] if 'size' in cmd: try: cls.size = int(cmd['size'][0]) except ValueError: raise CommandError("Sizes must be numbers") else: cls.size = 3 # ignore gib cache? if 'no-cache' in cmd: cls.nocache = True else: cls.nocache = False if 'limit' in cmd: try: limit = int(cmd['limit'][0]) except ValueError: raise CommandError( "When using --limit, the limit must be an int") if limit < 200: raise CommandError("When using --limit, the limit cannot be " "lower than 200") else: limit = CONFIG['gib']['limit'] if not limit: limit = 5000 if 'roulette' in cmd: if len(cmd['roulette']) == 0: raise CommandError("When using roulette mode, you must " "specify a roulette type") roulette_type = cmd['roulette'][0] if roulette_type not in ['video', 'image', 'youtube', 'yt']: raise CommandError("The roulette type must be either " "'image' or one of 'video','youtube','yt'") limit = None # can only gib a channel both the user and the bot are in for channel in channels: if channel is msg.raw_channel: continue if msg.raw_channel is not None \ and cmd['channel'][0] != 'all' \ and not all(x in DB.get_channel_members(channel) for x in [msg.sender, CONFIG.nick]): raise CommandError("Both you and the bot must be in a channel " "in order to gib it.") if msg.raw_channel is not None \ and channel != msg.raw_channel \ and not defer.controller(cmd): raise CommandError("You can only gib the current channel (or " "any channel from PMs)") # Run a check to see if we need to reevaluate the model or not if cls.channels == channels and cls.users == users \ and not cls.nocache: print("Reusing Markov model") else: cls.model = None cls.channels = channels if len(cls.channels) == 0: cls.channels = [msg.raw_channel] cls.users = users if len(cls.users) == 0: cls.users = [None] # are we gibbing or rouletting? if 'roulette' in cmd: urls = cls.roulette(roulette_type) msg.reply("{} {} · ({} link{} found)".format( emojize(":game_die:"), random.choice(urls), len(urls), ("s" if len(urls) > 1 else ""))) return if 'regex' in cmd: if len(cmd['regex']) == 0: raise CommandError("When using the regex filter, you must " "specify a regex") patterns = cmd['regex'] for pattern in patterns: try: re.compile(pattern) except re.error as e: raise CommandError("'{}' isn't a valid regular " "expression: {}".format(pattern, e)) else: patterns = [] if 'me' in cmd: patterns.append(r"\u0001ACTION ") if 'minlength' in cmd: if len(cmd['minlength']) == 0: raise CommandError("When using the minimum length modifier " "(--length/-l), you must specify a " "minimum length") minlength = cmd['minlength'][0] if not isint(minlength): raise CommandError("When using the minimum length modifier " "(--length/-l), the minimum length must be " "an integer") minlength = int(minlength) else: minlength = 0 # gibbing: try: sentence = cls.get_gib_sentence(limit=limit, minlength=minlength, patterns=patterns) if sentence is None: raise AttributeError except (RuntimeError, AttributeError): raise MyFaultError( "Looks like {} spoken enough in {} just yet.{}".format( ("you haven't" if msg.sender in users and len(users) == 1 else "nobody has" if len(users) == 0 else "{} hasn't". format(users[0]) if len(users) == 1 else "they haven't"), (channels[0] if len(channels) == 1 and channels[0] == msg.raw_channel else "that channel" if len(channels) == 1 else "those channels"), " ({} messages)".format( len(cls.model.to_dict()['parsed_sentences']) if cls. model is not None else 0))) # first: remove a ping at the beginning of the sentence pattern = r"^(\S+[:,]\s+)(.*)$" match = re.match(pattern, sentence) if match: sentence = match.group(2).strip() # second: modify any words that match the names of channel members sentence = gib.obfuscate(sentence, DB.get_channel_members(msg.raw_channel)) # match any unmatched pairs sentence = gib.bracketify(sentence, (r"\"\b", "\""), (r"\b[.!?]*\"", "\"")) sentence = gib.bracketify(sentence, (r"`\b", "`"), (r"\b[.!?]*`", "`")) sentence = gib.bracketify(sentence, (r"\(", "("), (r"\)", ")")) sentence = gib.bracketify(sentence, (r"\[", "["), (r"\}", "]")) sentence = gib.bracketify(sentence, (r"\{", "{"), (r"\}", "}")) cmd.command = cmd.command.lower() if "oo" in cmd.command: sentence = re.sub(r"[aeiou]", "oob", sentence) elif "o" in cmd.command: sentence = re.sub(r"[aeiou]", "ob", sentence) if cmd.command.startswith("b") and cmd.command.endswith("g"): sentence = sentence.upper() msg.reply(sentence)
def command(cls, irc_c, msg, cmd): """Record and broadcast messages""" if not defer.controller(cmd): raise CommandError("You're not authorised to do that") cmd.expandargs(["output o", "format f", "restrict-channel-name"]) # get the action - start, stop or status if len(cmd.args['root']) == 0: raise CommandError("Usage: .record [start|stop|status|page] " "[--output location] [--format format]") if len(cmd.args['root']) > 1: raise CommandError("Only one action can be taken at a time.") action = cmd.args['root'][0] if action == 'page': msg.reply( "Output page: http://topia.wikidot.com/tars:recording-output") return if action == 'status': if msg.raw_channel in cls.recording_channels(): msg.reply("Currently recording in this channel. " "Use `.record stop` to stop the recording.") else: msg.reply("Not currently recording in this channel.") if defer.controller(cmd) and msg.raw_channel is None: # if a controller asks in pm, show all channels msg.reply("Currently recording in: {}".format(", ".join( [s['channel'] for s in cls.settings if s['recording']]))) return elif action == 'start': if msg.raw_channel is None: raise CommandError("You can't record PMs.") if msg.raw_channel in cls.recording_channels(): raise CommandError("Already recording in {}".format( msg.raw_channel)) else: msg.reply("Starting recording messages in {}".format( msg.raw_channel)) if 'restrict-channel-name' in cmd: msg.reply("I will hide the channel name from the output.") elif action == 'stop': if msg.raw_channel not in cls.recording_channels(): raise CommandError("Not recording in {}".format( msg.raw_channel)) else: msg.reply("Stopping recording in {}".format(msg.raw_channel)) if 'restrict-channel-name' in cmd: msg.reply("I will hide the channel name from the output.") else: raise CommandError("Action must be one of start, stop, status") # get arguments and save to vars output_location = None if 'output' in cmd: if cmd['output'][0] not in ['topia', 'here', 'both']: raise CommandError( "Output location must be topia, here or both") output_location = cmd['output'][0] output_format = None if 'format' in cmd: if cmd['format'][0] not in ['json', 'txt', 'ftml']: raise CommandError("Format type must be json, txt or ftml") output_format = cmd['format'][0] # after everything else, set this channel as recording or not if action == 'start': # get the most recent message id in this channel start_id = DB.get_most_recent_message(msg.raw_channel) # add this channel to the settings list cls.settings.append({ 'channel': msg.raw_channel, 'recording': True, 'location': output_location, 'format': output_format, 'start_id': start_id, 'hide': 'restrict-channel-name' in cmd }) if action == 'stop': sett = [ s for s in cls.settings if s['channel'] == msg.raw_channel ][0] end_id = DB.get_most_recent_message(msg.raw_channel) messages = DB.get_messages_between(msg.raw_channel, sett['start_id'], end_id) if sett['location'] in ['topia', None]: msg.reply("Uploading {} messages to topia...".format( len(messages))) # get page content page = Topia.get_page({'page': "tars:recording-output"}) content = page['content'] content += "\r\n\r\n====\r\n\r\n" content += "+ Recording on {} from {}".format( datetime.today().strftime('%Y-%m-%d'), sett['channel'] if not sett['hide'] else "[REDACTED]") content += "\r\n\r\n" for message in messages: if message['kind'] == 'PRIVMSG': content += "||~ {} ||~ ##{}|{}## || {} ||".format( (datetime.fromtimestamp( message['timestamp']).strftime("%H:%M:%S")), nickColor(message['sender'], True), message['sender'], message['message'].replace("||", "@@||@@")) elif message['kind'] == 'NICK': content += "||~ {} ||||~ ##{}|{}## → ##{}|{}## ||".format( (datetime.fromtimestamp( message['timestamp']).strftime("%H:%M:%S")), nickColor(message['sender'], True), message['sender'], nickColor(message['message'], True), message['message']) elif message['kind'] in ['JOIN', 'PART', 'QUIT']: content += "||~ {} ||||~ {} ##{}|{}## {} ||".format( (datetime.fromtimestamp( message['timestamp']).strftime("%H:%M:%S")), "→" if message['kind'] == 'JOIN' else "←", nickColor(message['sender'], True), message['sender'], "joined" if message['kind'] == 'JOIN' else "left") else: content += "|||||| Error code {} ||".format( message['id']) content += "\r\n" content += "\r\n" okchars = string.printable + "→←" content = "".join(filter(lambda x: x in okchars, content)) # then format for wikidot Topia.save_page({ 'page': "tars:recording-output", 'content': content, 'revision_comment': "New recording", 'notify_watchers': "true" }) msg.reply( "Done! http://topia.wikidot.com/tars:recording-output") else: raise MyFaultError("Unknown location") cls.settings.remove(sett)
def command(cls, irc_c, msg, cmd): """Ping everyone in the channel""" cmd.expandargs([ "message msg m", # message to be PM'd "target t", # channel op level target "channel c", # channel to get names from "help h", ]) # TODO remove this check in the argparse refactor if len(cmd.args['root']) > 0 or 'help' in cmd: raise CommandError("Usage: ..pingall [--target level] [--message " "message]. If -m is not set, ping will happen " "in this channel. If -m is set, message will " "be sent to users in PM.") # TODO extend this to channel operator if not defer.controller(cmd): raise CommandError("You're not authorised to do that") cmd.expandargs(["channel c"]) if 'channel' in cmd: if not defer.controller(cmd): raise MyFaultError("You're not authorised to extract the " "nicks of another channel") channel = cmd['channel'][0] else: channel = msg.raw_channel if 'target' in cmd: if len(cmd['target']) != 1: raise CommandError("Specify a target as a channel user mode " "symbol: one of +, %, @, &, ~") if not cmd['target'][0] in '+%@&~' and len(cmd['target'][0]) == 1: raise CommandError("When using the --target/-t argument, the " "target must be a channel user mode: one " "of +, %, @, &, ~") # Issue a fresh NAMES request and await the response defer.get_users(irc_c, channel) try: response = await_signal(irc_c, 'NAMES_RESPONSE', timeout=5.0) # returned data is the channel name assert response == channel except (TimeoutError, AssertionError): # response to success/failure is the same, so doesn't matter pass finally: members = DB.get_occupants(channel, True, levels=True) if 'target' in cmd: modes = '+%@&~' members = [ nick for nick, mode in members if mode is not None and modes.find(mode) >= modes.find(cmd['target'][0]) ] else: members = [nick for nick, mode in members] if 'message' in cmd: message = " ".join(cmd.args['message']) for member in members: irc_c.PRIVMSG( member, "{} (from {} in {})".format(" ".join(cmd.args['root']), msg.sender, msg.raw_channel)) msg.reply("Message sent to selected users.") else: msg.reply("{}: ping!".format(", ".join(members)))