def command(cls, irc_c, msg, cmd): if len(cmd.args['root']) == 1: msg.reply("http://www.scp-wiki.net/system:page-tags/tag/{}".format( cmd.args['root'][0])) elif len(cmd.args['root']) == 0: raise CommandError("Specify a tag") else: raise CommandError("Specify just one tag")
def command(cls, irc_c, msg, cmd): if (defer.check(cmd, 'Secretary_Helen')): return if len(cmd.args['root']) > 0: msg.reply("http://www.wikidot.com/user:info/{}".format("-".join( cmd.args['root']))) else: raise CommandError("Specify a user's Wikidot username")
def command(cls, irc_c, msg, cmd): cmd.expandargs(["add a", "remove r", "list l"]) if len(cmd.args['root']) > 0: nick = cmd.args['root'][0] else: nick = msg.sender # 1. Check if this is the right nick # get the user ID user_id = DB.get_user_id(nick) if user_id is None: msg.reply("I don't know anyone called '{}'.".format(nick)) return # 2. Add new aliases if 'add' in cmd: if nick.lower() != msg.sender.lower() and not defer.controller(cmd): raise CommandError("You can't add an alias for someone else.") aliases = cmd['add'] # db has add_alias, but that needs user ID for alias in aliases: if alias.lower() == msg.sender.lower(): continue if DB.add_alias(user_id, alias, 1): msg.reply("{} already has the alias {}!".format(nick,alias)) msg.reply("Added aliases to {}: {}".format(nick, ", ".join(aliases))) irc_c.PRIVMSG(CONFIG.home, "{} added alias {}".format(nick,alias)) if 'remove' in cmd: if nick.lower() != msg.sender.lower() and not defer.controller(cmd): raise CommandError("You can't remove an alias from someone else.") aliases = cmd['remove'] # db has add_alias, but that needs user ID for alias in aliases: if not DB.remove_alias(user_id, alias, 1): msg.reply("{} didn't have the alias {}!".format(nick,alias)) msg.reply("Removed aliases from {}: {}".format(nick, ", ".join(aliases))) if 'list' in cmd: # get all aliases associated with the user aliases = DB.get_aliases(user_id) msg.reply("I've seen {} go by the names: {}" .format(nick if nick != msg.sender else "you", ", ".join(aliases))) if not any(['add' in cmd,'remove' in cmd,'list' in cmd]): raise CommandError("Add or remove aliases to a nick with --add " "and --remove. See all nicks with --list")
def command(cls, irc_c, msg, cmd): if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return if (defer.check(cmd, 'jarvis', 'Secretary_Helen')): return msg.reply(kill_bye()) irc_c.RAW("QUIT See you on the other side") irc_c.client.die()
def issue_raw(irc_c, msg, cmd): if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return if cmd.args['root'][1][0] == '/': cmd.args['root'].pop(0) msg.reply("Issuing that...") msg.reply(" ".join(cmd.args['root'])[1:]) irc_c.RAW(" ".join(cmd.args['root'])[1:])
def command(cls, irc_c, msg, cmd): if (defer.check(cmd, 'jarvis', 'Secretary_Helen')): return # reboot the bot completely if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return msg.reply("Rebooting...") irc_c.RAW("QUIT Rebooting, will be back soon!") os.execl(sys.executable, sys.executable, *sys.argv)
def date_is_absolute(self): try: self.date = parse_edtf(self.input) except EDTFParseException: try: pd.parse(self.input) except pd.parsing.exceptions.ParserError: return False else: raise CommandError("Absolute dates must be of the format " "YYYY, YYYY-MM or YYYY-MM-DD") else: return True
def command(cls, irc_c, msg, cmd): cmd.expandargs(["obfuscate o", "colour color c"]) if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return if len(cmd.args['root']) == 0: raise CommandError("Must specify a recipient and message") if len(cmd.args['root']) == 1: raise CommandError("Must specify a message") if cmd.args['root'][0][0] == '/' or cmd.args['root'][1][0] == '/': # This is an IRC command say.issue_raw(irc_c, msg, cmd) else: message = " ".join(cmd.args['root'][1:]) if 'obfuscate' in cmd and msg.raw_channel is not None: message = gib.obfuscate(message, DB.get_aliases(None) + ["ops"]) if 'colour' in cmd: print(nickColor(message)) msg.reply("Printed that to console") irc_c.PRIVMSG(cmd.args['root'][0], message) if not cmd.args['root'][0] == msg.raw_channel: msg.reply("Saying that to {}".format(cmd.args['root'][0]))
def command(cls, irc_c, msg, cmd): # arg 1 should be a url name if 'sample' in cmd: samples = [ 'scp-173', 'scp-1111', 'scp-3939', 'cone', 'scp-series', 'listpages-magic-and-you', 'scp-4205', 'omega-k', 'component:ar-theme', 'fragment:scp-3939-64' ] msg.reply("Adding sample data...") propagate.get_wiki_data_for(samples, reply=msg.reply) elif 'tales' in cmd: if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") msg.reply("Fetching all tales... this will take a few minutes.") tales = SCPWiki.select({'tags_all': ['tale']}) pprint(tales) propagate.get_wiki_data_for(tales, reply=msg.reply) elif 'all' in cmd: if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") msg.reply("Propagating all pages...") propagate.get_all_pages(reply=msg.reply) elif 'metadata' in cmd: meta_urls = [ 'attribution-metadata', 'scp-series', 'scp-series-2', 'scp-series-3', 'scp-series-4', 'scp-series-5', 'scp-series-6' ] # meta_urls = ['attribution-metadata'] # XXX TODO replace with getting pages tagged "metadata" msg.reply("Propagating metadata...") for url in meta_urls: propagate.get_metadata(url, reply=msg.reply) elif len(cmd.args['root']) > 0: propagate.get_wiki_data_for(cmd.args['root'], reply=msg.reply) else: raise CommandError("Bad command") msg.reply("Done!")
def command(cls, irc_c, msg, cmd): """Update from github""" if (defer.check(cmd, 'jarvis', 'Secretary_Helen')): return if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return msg.reply("Updating...") try: g = git.cmd.Git(".") g.pull() except Exception as e: msg.reply("Update failed.") raise msg.reply("Update successful - now would be a good time to reboot.")
def command(cls, irc_c, msg, cmd): if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return if cls.has_refactored: raise CommandError("Already refactored once this reload.") if 'callback' in cmd: print(cmd['callback']) if cmd['callback'][0] == "msg.reply": callback = msg.reply else: raise CommandError("Unknown callback") else: callback = None try: if 'sql' in cmd: DB.issue(" ".join(cmd['sql']), callback=callback) else: refactor.refactor_database(irc_c) cls.has_refactored = True except: msg.reply("Refactoring failed.") raise msg.reply("Refactoring succeeded.")
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 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 not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") return if len(cmd.args['root']) < 1: raise CommandError("Must specify the wiki to analyse") if len(cmd.args['root']) == 2: abort_limit = int(cmd.args['root'][1]) else: abort_limit = -1 if msg.sender != 'Croquembouche': msg.reply("Only Croquembouche can do that, sorry!") return target = WikidotAPI(cmd.args['root'][0]) msg.reply("Fetching data from {}...".format(cmd.args['root'][0])) # 1. get a list of all pages # 2. get the filenames associated with those pages # 3. get the file metas associated with those files # 4. save everything to a spreadsheet (csv would be fine) # --- # 1. get a list of all pages pages = target.select({}) msg.reply("I found {} pages.".format(len(pages))) msg.reply("Getting file names...") # pages is now a list of page names # make a list for files files_list = [] # this is the master list percents = [math.ceil(i*len(pages)) for i in numpy.linspace(0, 1, 101)] # get select_files per 1 page for i,page in enumerate(pages): if i in percents: msg.reply("{}% complete".format(percents.index(i))) try: pages[i] = {'page': page, 'files': target.select_files({'page': page})} except Exception as e: msg.reply("Error on {}: {}".format(page['page'],str(e))) print("Found {} files for {}".format(len(pages[i]['files']),page)) if i == abort_limit: msg.reply("Process aborted after {} pages".format(abort_limit)) break # TODO loop over pages and remove errored entries msg.reply("Getting info for files...") for i,page in enumerate(pages): if i in percents: msg.reply("{}% complete".format(percents.index(i))) # for each page, get_files_meta can take up to 10 files # no problem for 10 or less files - what to do when there's more? # chunks the file into 10s but then we need to get the index? # or do we for files in chunks(page['files'], 10): print(files) try: f = target.get_files_meta({'page': page['page'], 'files': files}) pprint(f) for filename in files: f[filename]['page'] = page['page'] files_list.append(f[filename]) except Exception as e: msg.reply("Error on {} of {}: {}".format(files,page['page'],str(e))) if i == abort_limit: msg.reply("Process aborted after {} pages".format(abort_limit)) break msg.reply("List of files created.") msg.reply("Outputting to .csv...") with open("wiki_analysis.csv",'w') as f: w = csv.DictWriter(f, files_list[0].keys()) w.writeheader() w.writerows(files_list) msg.reply("Done.")
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): cmd.expandargs(["table tables t", "user users u"]) # No argument given - show the db structure if len(cmd.args) == 1: msg.reply("https://raw.githubusercontent.com/" "rossjrw/tars/master/database.png") return # Table - print a list of tables, or a given table if 'table' in cmd: if len(cmd['table']) > 0: # print a specific table msg.reply("Printing contents of table {} to console." .format(cmd['table'][0])) DB.print_one_table(cmd['table'][0]) else: # print a list of all tables tables = DB.get_all_tables() msg.reply("Printed a list of tables to console. {} total." .format(len(tables))) print(" ".join(tables)) # Users - print a list of users, or from a given channel if 'user' in cmd: users = DB.get_all_users() if len(users) == 0: msg.reply("There are no users.") else: msg.reply("Printed a list of users to console. {} total." .format(len(users))) print(" ".join([nickColor(u) for u in users])) if 'id' in cmd: # we want to find the id of something if len(cmd.args['root']) != 2: search = msg.sender else: search = cmd.args['root'][1] id, type = DB.get_generic_id(search) if id: if type == 'user' and search == msg.sender: msg.reply("{}, your ID is {}.".format(msg.sender, id)) elif search == "TARS": msg.reply("My ID is {}.".format(id)) else: msg.reply("{}'s ID is {}.".format(search, id)) else: if type == 'channel': msg.reply("I don't know the channel '{}'." .format(search)) else: msg.reply("I don't know anything called '{}'." .format(search)) if 'alias' in cmd: search = cmd.args['root'][1] if len(cmd.args['root']) > 1 \ else msg.sender aliases = DB.get_aliases(search) # should be None or a list of lists if aliases is None: msg.reply("I don't know anyone with the alias '{}'." .format(search)) else: msg.reply("I know {} users with the alias '{}'." .format(len(aliases), search)) for i,group in enumerate(aliases): msg.reply("\x02{}.\x0F {}" .format(i+1, ", ".join(group))) if 'occ' in cmd: if len(cmd.args['root']) < 2: raise CommandError("Specify a channel to get the occupants of") msg.reply("Printing occupants of {} to console" .format(cmd.args['root'][1])) users = DB.get_occupants(cmd.args['root'][1], True) if isinstance(users[0], int): pprint(users) else: pprint([nickColor(user) for user in users]) if 'sql' in cmd: if not defer.controller(cmd): raise CommandError("I'm afriad I can't let you do that.") try: DB.print_selection(" ".join(cmd['sql']), 'str' in cmd) msg.reply("Printing that selection to console") except: msg.reply("There was a problem with the selection") raise
def __init__(self, input_date): self.input = input_date self.min = None self.max = None self.compare = "=" # possible values: # 1. absolute date # 2. relative date # 3. range (relative or absolute) # for absolute: # parse # create max and min # for relative: # create a timedelta # subtract that from now # no need for max and min, subtraction is precise # for range: # create a DateRange for each # select max and min from both to create largest possible range # first let's handle the range if ".." in self.input: self.input = self.input.split("..") if len(self.input) != 2: raise CommandError("Date ranges must have 2 dates") # if the date is a manual range, convert to a DateRange self.max = [] self.min = [] for date in self.input: date = DateRange(date) self.max.append(date.max) self.min.append(date.min) # max and min are now both lists of possible dates # pick max and min to yield the biggest date # max: None is always Now # min: None is alwyas The Beginning of Time # for 2 absolute dates this is easy, just pick biggest diff # for 2 relative dates, pick both of whichever is not None # for 1:1, pick not None of relative then ->largest of absolute # filter None from lists self.max = [i for i in self.max if i] self.min = [i for i in self.min if i] # special case for 2 relative dates - both will only have max if len(self.max) == 2 and len(self.min) == 0: self.min = min(self.max) self.max = max(self.max) return diffs = [] for i, minimum in enumerate(self.min): for j, maximum in enumerate(self.max): diffs.append({ 'i': i, 'j': j, 'diff': self.min[i].diff(self.max[j]).in_seconds() }) diffs = max(diffs, key=lambda x: x['diff']) self.max = self.max[diffs['j']] self.min = self.min[diffs['i']] # do other stuff return # strip the comparison match = re.match(r"([>=<]{1,2})(.*)", self.input) if match: self.compare = match.group(1) self.input = match.group(2) if self.date_is_absolute(): # the date is absolute # minimise the date minimum = pd.datetime(*self.date.lower_strict()[:6]) minimum = minimum.set(hour=0, minute=0, second=0) # maximise the date maximum = pd.datetime(*self.date.upper_strict()[:6]) maximum = maximum.set(hour=23, minute=59, second=59) if self.compare == "<": self.max = minimum elif self.compare == "<=": self.max = maximum elif self.compare == ">": self.min = maximum elif self.compare == ">=": self.min = minimum elif self.compare == "=": # = means between maximum and minimum self.min = minimum self.max = maximum else: raise CommandError("Unknown operator in absolute date " "comparison ({})".format(self.compare)) elif re.match(r"([0-9]+[A-Za-z])+$", self.input): # the date is relative sel = [i for i in re.split(r"([0-9]+)", self.input) if i] # sel is now a number-letter-repeat list # convert list to dict via pairwise sel = DateRange.reverse_pairwise(sel) # convert all numbers to int sel = dict([a, int(x)] for a, x in sel.items()) self.date = pd.now() # check time units for key in sel: if key not in 'smhdwMy': raise CommandError( "'{}' isn't a valid unit of time in a relative date. " "Valid units are s, m, h, d, w, M, and y." .format(key) ) self.date = pd.now().subtract( years=sel.get('y', 0), months=sel.get('M', 0), weeks=sel.get('w', 0), days=sel.get('d', 0), hours=sel.get('h', 0), minutes=sel.get('m', 0), seconds=sel.get('s', 0), ) if self.compare in ["<", "<="]: self.min = self.date elif self.compare in [">", ">="]: self.max = self.date elif self.compare == "=": self.max = self.date self.min = self.date # possible broken - may match to the second else: raise CommandError("Unknown operator in relative date " "comparison ({})".format(self.compare)) else: raise CommandError( "'{}' isn't a valid absolute or relative date " "type".format(self.input) )
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): """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)))