def check(cls, cmd, *bots): """Check whether the given bots are in the channel""" # bots should be a list of bot names? defer.get_users(cmd.context, cmd.channel) if cmd.pinged: return False if cmd.force: return False members = DB.get_channel_members(cmd.channel) return set(members) & set(bots)
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): # Check that we are actually able to do this # (might have to move to end for defer check) if (defer.check(cmd, 'jarvis', 'Secretary_Helen')): return # Parse the command itself search.expandargs(cmd) # check to see if there are any arguments if len(cmd.args) == 1 and len(cmd.args['root']) == 0: raise CommandError("Must specify at least one search term") # fullname is deprecated for tars if 'fullname' in cmd: raise CommandError("TARS does not support fullname search - " "wrap your search in quotemarks instead") # Set the return mode of the output selection = { 'ignorepromoted': 'ignorepromoted' in cmd, 'order': 'fuzzy', 'limit': None, 'offset': 0 } # order, limit, offset if 'order' in cmd: if len(cmd['order']) != 1: raise CommandError("When using the order argument " "(--order/-o), exactly one order type must " "be specified") if cmd['order'][0] in ['recent', 'recommend', 'random', 'fuzzy', 'none']: if cmd['order'] == 'none': selection['order'] = None else: selection['order'] = cmd['order'][0] else: raise CommandError("Selection return order ('{}') must be " "one of: recent, recommend, random, " "fuzzy, none".format(cmd['order'][0])) if 'limit' in cmd: if len(cmd['limit']) != 1: raise CommandError("When using the limit argument " "(--limit/-l), exactly one limit must " "be specified") if isint(cmd['limit'][0]): if int(cmd['limit'][0]) > 0: selection['limit'] = int(cmd['limit'][0]) elif int(cmd['limit'][0]) == 0: selection['limit'] = None else: raise CommandError("When using the limit argument " "(--limit/-l), the limit must be at " "least 0") else: raise CommandError("When using the limit argument " "(--limit/-l), the limit must be an integer") if 'offset' in cmd: if len(cmd['offset']) != 1: raise CommandError("When using the offset argument " "(--offset/-f), exactly one offset must " "be specified") if isint(cmd['offset'][0]): if int(cmd['offset'][0]) >= 0: selection['offset'] = int(cmd['offset'][0]) else: raise CommandError("When using the offset argument " "(--offset/-f), the offset must be at " "least 0") else: raise CommandError("When using the offset argument " "(--offset/-f), the offset must be an integer") if 'random' in cmd: selection['order'] = 'random' selection['limit'] = 1 if 'recommend' in cmd: selection['order'] = 'recommend' selection['limit'] = 1 if 'newest' in cmd: selection['order'] = 'recent' selection['limit'] = 1 # What are we searching for? searches = [] strings = [] if len(cmd.args['root']) > 0: strings = cmd.args['root'] searches.extend([{'term': s, 'type': None} for s in strings]) # Add any regexes regexes = [] if 'regex' in cmd: if len(cmd['regex']) == 0: raise CommandError( "When using the regular expression filter " "(--regex/-x), at least one regex must " "be specified" ) for regex in cmd['regex']: try: re.compile(regex) except re.error as e: raise CommandError( "'{}' isn't a valid regular expression: {}" .format(regex, e) ) regexes.append(regex) # don't append the compiled - SQL doesn't like that searches.extend([{'term': r, 'type': 'regex'} for r in regexes]) # Set the tags tags = {'include': [], 'exclude': []} if 'tags' in cmd: if len(cmd['tags']) == 0: raise CommandError( "When using the tag filter (--tag/-t), at " "least one tag must be specified" ) for tag in cmd['tags']: if tag[0] == "-": tags['exclude'].append(tag[1:]) continue if tag[0] == "+": tags['include'].append(tag[1:]) continue tags['include'].append(tag) searches.append({'term': tags, 'type': 'tags'}) # Set the author authors = {'include': [], 'exclude': []} if 'author' in cmd: if len(cmd['author']) == 0: raise CommandError( "When using the author filter " "(--author/-a), at least one author must " "be specified" ) for author in cmd['author']: if author[0] == "-": authors['exclude'].append(author[1:]) continue if author[0] == "+": authors['include'].append(author[1:]) continue authors['include'].append(author) searches.append({'term': authors, 'type': 'author'}) # Set the rating # Cases to account for: modifiers, range, combination ratings = MinMax() if 'rating' in cmd: if len(cmd['rating']) == 0: raise CommandError( "When using the rating filter " "(--rating/-r), at least one rating must " "be specified" ) for rating in cmd['rating']: if ".." in rating: rating = rating.split("..") if len(rating) > 2: raise CommandError("Too many ratings in range") try: rating = [int(x) for x in rating] except ValueError: raise CommandError( "Ratings in a range must be plain numbers" ) try: ratings >= min(rating) ratings <= max(rating) except MinMaxError as e: raise CommandError(str(e).format("rating")) elif rating[0] in [">", "<", "="]: pattern = r"^(?P<comp>[<>=]{1,2})(?P<value>[0-9]+)" match = re.search(pattern, rating) if match: try: rating = int(match.group('value')) except ValueError: raise CommandError("Invalid rating comparison") comp = match.group('comp') try: if comp == ">=": ratings >= rating elif comp == "<=": ratings <= rating elif comp == "<": ratings < rating elif comp == ">": ratings > rating elif comp == "=": ratings >= rating ratings <= rating else: raise CommandError( "Unknown operator in rating comparison" ) except MinMaxError as e: raise CommandError(str(e).format("rating")) else: raise CommandError("Invalid rating comparison") else: try: rating = int(rating) except ValueError: raise CommandError( "Rating must be a range, comparison, or number" ) # Assume =, assign both try: ratings >= rating ratings <= rating except MinMaxError as e: raise CommandError(str(e).format("rating")) searches.append({'term': ratings, 'type': 'rating'}) # Set created date # Cases to handle: absolute, relative, range (which can be both) createds = MinMax() if 'created' in cmd: if len(cmd['created']) == 0: raise CommandError( "When using the date of creation filter " "(--created/-c), at least one date must " "be specified" ) created = cmd['created'] # created is a list of date selectors - ranges, abs and rels # but ALL dates are ranges! created = [DateRange(c) for c in created] # created is now a list of DateRanges with min and max try: for key, selector in enumerate(created): if selector.max is not None: createds <= selector.max if selector.min is not None: createds >= selector.min except MinMaxError as e: raise CommandError(str(e).format("date")) searches.append({'term': createds, 'type': 'date'}) # Set category categories = {'include': [], 'exclude': []} if 'category' in cmd: if len(cmd['category']) == 0: raise CommandError( "When using the category filter " "(--category/-y), at least one category " "must be specified" ) for category in cmd['category']: if category[0] == "-": categories['exclude'].append(category[1:]) continue if category[0] == "+": categories['include'].append(category[1:]) continue categories['include'].append(category) searches.append({'term': categories, 'type': 'category'}) # Set parent page parents = None if 'parent' in cmd: if len(cmd['parent']) != 1: raise CommandError( "When using the parent page filter " "(--parent/-p), exactly one parent URL " "must be specified" ) parents = cmd['parent'][0] searches.append({'term': parents, 'type': 'parent'}) # FINAL BIT - summarise commands if 'verbose' in cmd: verbose = "Searching for articles " if len(strings) > 0: verbose += ( "containing \"{}\"; ".format("\", \"".join(strings)) ) if len(regexes) > 0: verbose += "matching the regex /{}/; ".format( "/ & /".join(regexes) ) if parents is not None: verbose += ("whose parent page is '{}'; ".format(parents)) if len(categories['include']) == 1: verbose += ( "in the category '" + categories['include'][0] + "'; " ) elif len(categories['include']) > 1: verbose += ( "in the categories '" + "', '".join(categories) + "; " ) if len(categories['exclude']) == 1: verbose += ( "not in the category '" + categories['exclude'][0] + "'; " ) elif len(categories['exclude']) > 1: verbose += ( "not in the categories '" + "', '".join(categories) + "; " ) if len(tags['include']) > 0: verbose += ( "with the tags '" + "', '".join(tags['include']) + "'; " ) if len(tags['exclude']) > 0: verbose += ( "without the tags '" + "', '".join(tags['exclude']) + "'; " ) if len(authors['include']) > 0: verbose += ("by " + " & ".join(authors['include']) + "; ") if len(authors['exclude']) > 0: verbose += ("not by " + " or ".join(authors['exclude']) + "; ") if ratings['max'] is not None and ratings['min'] is not None: if ratings['max'] == ratings['min']: verbose += ( "with a rating of " + str(ratings['max']) + "; " ) else: verbose += ( "with a rating between " + str(ratings['min']) + " and " + str(ratings['max']) + "; " ) elif ratings['max'] is not None: verbose += ( "with a rating less than " + str(ratings['max'] + 1) + "; " ) elif ratings['min'] is not None: verbose += ( "with a rating greater than " + str(ratings['min'] - 1) + "; " ) if createds['min'] is not None and createds['max'] is not None: verbose += ( "created between " + createds['min'].to_datetime_string() + " and " + createds['max'].to_datetime_string() + "; " ) elif createds['max'] is not None: verbose += ( "created before " + createds['max'].to_datetime_string() + "; " ) elif createds['min'] is not None: verbose += ( "created after " + createds['min'].to_datetime_string() + "; " ) if verbose.endswith("; "): verbose = verbose[:-2] msg.reply(verbose) pprint(searches) page_ids = DB.get_articles(searches) pages = [DB.get_article_info(p_id) for p_id in page_ids] pages = search.order(pages, search_term=strings, **selection) if len(pages) >= 50: msg.reply("{} results found - you're going to have to be more " "specific!".format(len(pages))) return if len(pages) > 3: msg.reply("{} results (use ..sm to choose): {}".format( len(pages), showmore.parse_multiple_titles(pages))) DB.set_showmore_list(msg.raw_channel, [p['id'] for p in pages]) return if len(pages) == 0: # check if there's no args other than --verbose if set(cmd.args).issubset({'root', 'verbose'}): # google only takes 10 args url = google_search( '"' + '" "'.join(cmd.args['root'][:10]) + '"', num=1 )[0] if url is None: msg.reply("No matches found.") return #pprint.pprint(url) if url['title'].endswith(" - SCP Foundation"): url['title'] = url['title'][:-17] msg.reply( "No matches found. Did you mean \x02{}\x0F? {}" .format(url['title'], url['link']) ) else: msg.reply("No matches found.") return for page in pages: msg.reply(gib.obfuscate(showmore.parse_title(page), DB.get_channel_members(msg.raw_channel)))
def command(cls, irc_c, msg, cmd): # Recieves text in msg.message message = cmd.unping ##### ping matches ##### if cmd.pinged: if any(x in message.lower() for x in [ "f**k you", "piss off", "f**k off", ]): msg.reply("{}: no u".format(msg.nick)) return ##### ping-optional text matches ##### if message.startswith("?? "): # CROM compatibility getattr(commands.COMMANDS, 'search').command(irc_c, msg, cmd) if message.lower() == "{}!".format(CONFIG.nick.lower()): msg.reply("{}!".format(msg.nick)) return if strip(message.lower()) in [ strip("{}{}".format(g, CONFIG.nick.lower())) for g in greets ]: if msg.sender == 'XilasCrowe': msg.reply("toast") return msg.reply(greet(msg.nick)) return if CONFIG.nick == "TARS" and matches_any_of(message, [ "what does tars stand for?", "is tars an acronym?", ]) and "TARS" in message.upper(): msg.reply(acronym()) return if CONFIG.nick == "TARS" and matches_any_of(message, [ "is tars a bot?", "tars are you a bot?", ]) and "TARS" in message.upper(): msg.reply("Yep.") return if CONFIG.nick == "TARS" and matches_any_of(message, [ "is tars a person?", "tars are you a person?", ]) and "TARS" in message.upper(): msg.reply("Nope. I'm a bot.") return if CONFIG.nick == "TARS" and matches_any_of(message, [ "what is your iq", ]) and "TARS" in message.upper(): msg.reply("big") return ##### regex matches ##### # give url for reddit links match = re.search(r"(?:^|\s)/?r/(\S*)", message, re.IGNORECASE) if match: msg.reply("https://www.reddit.com/r/{}".format(match.group(1))) return # tell me about new acronyms match = re.search( r"(\s+|(?:\s*[{0}]+\s*))".join([ r"([{{0}}]*)\b({})(\S*)\b([{{0}}]*)".format(l) for l in CONFIG['IRC']['nick'] ]).format(re.escape(string.punctuation)), message, re.IGNORECASE | re.VERBOSE) if match: raw_acronym = "".join(match.groups()) submatches = list(chunks(list(match.groups()), 5)) # the match is made up of 5 repeating parts: # 0. punctation before word # 1. first letter of word # 2. rest of word # 3. punctuation after word # 4. stuff between this word and the next word # for the last word (submatch), however, 4 is not present submatches[-1].append("") with open(CONFIG['converse']['acronyms'], 'r+') as acro: existing_acronyms = [strip(line.rstrip('\n')) for line in acro] if strip(raw_acronym) not in existing_acronyms: for submatch in submatches: submatch[1] = submatch[1].upper() bold_acronym = "".join([ "{}\x02{}\x0F{}{}{}".format(*submatch) for submatch in submatches ]) msg.reply(bold_acronym) if msg.raw_channel != CONFIG['channels']['home']: defer.report(cmd, bold_acronym) with open(CONFIG['converse']['acronyms'], 'a') as acro: acro.write(raw_acronym) acro.write("\n") return ##### custom matches ##### if (msg.sender == "Jazstar" and "slime" in msg.message and "XilasCrowe" in DB.get_channel_members(msg.raw_channel)): msg.reply("Oy xilas I heard you like slime!") return # after all attempts, must indicate failure if pinged if cmd.pinged: return 1