示例#1
0
文件: log.py 项目: rossjrw/tars
 def log(self, irc_c, msg):
     chname = "private" if msg.raw_channel is None else msg.raw_channel
     if msg.kind == 'PRIVMSG':
         print("[{}] {} <{}> {}".format(
             time.strftime("%H:%M:%S"),
             parse.nickColor(chname),
             parse.nickColor(msg.nick),
             msg.message,
         ))
     elif msg.kind == 'NICK':
         print("[{}] {} changed their name to {}".format(
             time.strftime("%H:%M:%S"),
             parse.nickColor(msg.nick),
             parse.nickColor(msg.args),
         ))
     else:
         print("[{}] {} {} {}".format(
             time.strftime("%H:%M:%S"),
             parse.nickColor(msg.nick),
             "joined" if msg.kind == 'JOIN' else "left",
             parse.nickColor(chname),
         ))
     try:
         if not gimmick(msg.message):
             DB.log_message(msg)
     except:
         irc_c.RAW("PRIVMSG #tars A logging error has occurred.")
         raise
示例#2
0
文件: log.py 项目: rossjrw/tars
 def debug(self, irc_c, msg):
     msg = parse.output(msg)
     if not msg:
         return
     print("[{}] --> {}: {}".format(
         time.strftime('%H:%M:%S'),
         parse.nickColor(msg['channel']),
         msg['message'],
     ))
     if "IDENTIFY" in msg['message']:
         return
     msg = {
         'channel':
         msg['channel'] if msg['channel'].startswith('#') else None,
         'sender': CONFIG.nick,
         'kind': "PRIVMSG",
         'message': msg['message'],
         'nick': CONFIG.nick,
         'timestamp': int(time.time()),
     }
     try:
         DB.log_message(msg)
     except:
         irc_c.RAW("PRIVMSG #tars A logging error has occurred.")
         raise
示例#3
0
文件: names.py 项目: rossjrw/tars
 def record_names(self, irc_c, msg):
     # msg.args is a string
     # "TARS = #channel :name1 name2 name3 name4"
     nicks = re.split(r"\s:?", msg.args.strip())
     nicks = nicks[2:]
     channel = nicks.pop(0)
     names = [{'nick': name} for name in nicks]
     # chatstaff names start with a punctuation
     for key, name in enumerate(names):
         if name['nick'][0] in '+%@&~':
             names[key] = {
                 'nick': name['nick'][1:],
                 'mode': name['nick'][0],
             }
         else:
             names[key] = {'nick': name['nick'], 'mode': None}
     # just need to log these names to the db now
     nameprint("Updating NAMES for {}: {}".format(
         nickColor(channel),
         ", ".join(nickColor(nick) for nick in sorted(nicks)),
     ))
     try:
         DB.sort_names(channel, names)
     except Exception as e:
         irc_c.RAW("PRIVMSG #tars NAMES error: " + str(e))
         raise
     # broadcast this info to whatever needs it
     emit_signal(irc_c, 'NAMES_RESPONSE', data=channel)
示例#4
0
文件: shortest.py 项目: rossjrw/tars
 def execute(self, irc_c, msg, cmd):
     if 'title' not in self and 'url' not in self:
         raise CommandError(
             "At least one of --title or --url must be given.")
     if 'title' in self:
         title = self['title']
     elif 'url' in self:
         try:
             title = DB.get_article_info(
                 DB.get_articles([{
                     'type': 'url',
                     # Handle case where the whole URL was entered
                     'term': self['url'].split("/")[-1],
                 }])[0])['title']
         except IndexError as error:
             raise MyFaultError("I don't see any page at '{}'.".format(
                 self['url'])) from error
     pages = [
         DB.get_article_info(p_id)['title'] for p_id in DB.get_articles([])
     ]
     single_string = Shortest.get_substring(title, pages)
     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(title))
     msg.reply("Shortest search for \"\x1d{}\x0F\" · {}".format(
         title,
         Shortest.pick_answer(single_string, helen_style),
     ))
示例#5
0
文件: admin.py 项目: rossjrw/tars
 def execute(self, irc_c, msg, cmd):
     # Note that the INVITE event is in plugins/parsemessages.py
     irc_c.JOIN(self['channel'])
     msg.reply("Joining {}".format(self['channel']))
     irc_c.PRIVMSG(self['channel'],
                   "Joining by request of {}".format(msg.nick))
     DB.join_channel(self['channel'])
示例#6
0
文件: names.py 项目: rossjrw/tars
 def change_name(self, irc_c, msg):
     assert msg.kind == 'NICK'
     nameprint("{} changed their name to {}".format(msg.nick, msg.args))
     try:
         DB.rename_user(msg.nick, msg.args)
     except Exception as e:
         irc_c.RAW("PRIVMSG #tars NAMES error: " + str(e))
         raise
示例#7
0
文件: propagate.py 项目: rossjrw/tars
 def get_wiki_data_for_pages(slugs, **kwargs):
     """Gets wiki data for the pages indicated by the list of slugs."""
     reply = kwargs.pop('reply', lambda _: None)
     reply(f"Gettings wiki data for {len(slugs)} specific pages")
     for slug in slugs:
         page = SCPWiki.get_one_page_meta(slug)
         DB.add_article(page, commit=False)
     DB.commit()
示例#8
0
文件: admin.py 项目: rossjrw/tars
 def execute(self, irc_c, msg, cmd):
     if 'message' in self:
         leavemsg = self['message']
     else:
         leavemsg = None
     if self['channel'] is not None:
         channel = self['channel']
         msg.reply("Leaving {}".format(self['channel']))
     else:
         channel = msg.raw_channel
     irc_c.PART(channel, message=leavemsg)
     DB.leave_channel(channel)
示例#9
0
 def execute(self, irc_c, msg, cmd):
     if Refactor.has_refactored and not self['force']:
         raise CommandError("Already refactored once this reload.")
     try:
         if 'sql' in self:
             DB.issue(self['sql'], callback=msg.reply)
         else:
             Refactor.refactor_database()
             Refactor.has_refactored = True
     except:
         msg.reply("Refactoring failed.")
         raise
     msg.reply("Refactoring succeeded.")
示例#10
0
文件: gib.py 项目: rossjrw/tars
 def get_gib_sentence(self, attempts=0, limit=7500):
     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(
         self['channel'],
         minlength=40,
         limit=limit,
         senders=None if self['user'] == [] else self['user'],
         patterns=[r.pattern for r in self['regex']],
     )
     print("messages found: {}".format(len(messages)))
     if len(messages) == 0:
         raise AttributeError
     # Automatically decrement the state size if the higher state size fails
     for decr in range(0, self['size']):
         print("Making model from messages, size {}".format(self['size'] -
                                                            decr))
         # The model cache depends on the state size, so if there is a state
         # size decrement, discard it anyway
         if decr != 0:
             Gib.cache['model'] = None
         model = (Gib.cache['model'] if Gib.cache['model'] is not None else
                  Gib.make_model(messages, self['size'], decrement=decr))
         print("Making sentence")
         sentence = model.make_short_sentence(400,
                                              self['minlength'],
                                              tries=200,
                                              force_result=False)
         if sentence is not None:
             break
         print("Sentence is None")
     if not self['no_cache'] and sentence in DB.get_gibs():
         print("Sentence already sent, {} attempts remaining".format(
             CONFIG['gib']['attempt_limit'] - attempts))
         try:
             if attempts < CONFIG['gib']['attempt_limit']:
                 sentence = self.get_gib_sentence(attempts + 1, limit)
             else:
                 raise RecursionError
         except RecursionError as error:
             raise MyFaultError(
                 "I didn't find any gibs for that selection "
                 "that haven't already been said.") from error
     if sentence is not None:
         DB.add_gib(sentence)
     return sentence
示例#11
0
 def execute(self, irc_c, msg, cmd):
     if 'channel' in self:
         channel = self['channel']
     else:
         channel = msg.raw_channel
     # Issue a fresh NAMES request and await the response
     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)
     modes = "+%@&~"
     members = [
         nick for nick, mode in members if mode is not None
         and modes.find(mode) >= modes.find(self['target'])
         # Unspecified target is "" so this is genius honestly
     ]
     if 'message' in self:
         for member in members:
             irc_c.PRIVMSG(
                 member,
                 "{1} in {2} says: {0}".format(self['message'], msg.sender,
                                               msg.raw_channel),
             )
         msg.reply("Message sent to selected users.")
     else:
         msg.reply("{}: ping!".format(", ".join(members)))
示例#12
0
文件: defer.py 项目: rossjrw/tars
def should_defer(cmd):
    """Evaluates whether or not a given command should execute. Returns True if
    the command should defer. Returns False if the command should not defer,
    i.e. it is okay to execute.

    The command does not execute if the prefix and alias are shared by another
    bot, unless the bot was pinged.
    """
    # If there is no command string, there's no need to defer
    if cmd.command is None:
        return False
    # If the bot was pinged, forget everything else - this command is happening
    if cmd.ping:
        return False
    # Filter the deferral configs by the bots that are currently present
    members = DB.get_channel_members(cmd.channel)
    defer_configs = [
        config for config in CONFIG['deferral'] if config['name'] in members
        and config['name'] != CONFIG['nick'] and config['prefix'] == cmd.prefix
    ]
    # Check if this command has the same name as any matching commands
    if any(cmd.command.lower() in
           [command.lower() for command in config['commands']]
           for config in defer_configs):
        return True
    return False
示例#13
0
文件: gib.py 项目: rossjrw/tars
 def media_roulette(self):
     """Get a random image or video link."""
     # take all the messages in the channel, filtered for links
     messages = DB.get_messages(self['channel'],
                                senders=self['user'],
                                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 self['media'] == '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 self['media'] == 'youtube':
         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
示例#14
0
文件: defer.py 项目: rossjrw/tars
def is_controller(cmd):
    """Checks if the sender of the given command is a bot controller.

    Avoid using this if possible. Prefer defining permission levels on commands
    and arguments. Only use this if finer control is needed (e.g. if the
    permission level depends on the value of an argument).
    """
    return cmd.sender in DB.get_controllers()
示例#15
0
文件: propagate.py 项目: rossjrw/tars
 def get_wiki_data(**kwargs):
     """Gets wiki data for all pages."""
     reply = kwargs.pop('reply', lambda _: None)
     if 'seconds' in kwargs:
         reply(f"Getting wiki data for last {kwargs['seconds']} seconds")
     else:
         reply("Getting wiki data for all pages")
     pages_generator = SCPWiki.get_all_pages(**kwargs)
     for pages in pages_generator:
         for page in pages:
             if page['fullname'].startswith("fragment:"):
                 # Don't want to track fragments
                 continue
             DB.add_article(page, commit=False)
         # Give the API a moment to rest
         gevent.sleep(5)
     DB.commit()
示例#16
0
 def execute(self, irc_c, msg, cmd):
     page_ids = DB.get_showmore_list(msg.raw_channel)
     if len(page_ids) == 0:
         raise MyFaultError("I have nothing to show more of.")
     if self['index'] > len(page_ids):
         raise MyFaultError(
             "I only have {} results for the last search.".format(
                 len(page_ids)))
     pages = [DB.get_article_info(page_id) for page_id in page_ids]
     if self['index'] == 0:
         msg.reply("{} saved results (use ..sm to choose): {}".format(
             len(pages), Showmore.parse_multiple_titles(pages)))
     else:
         msg.reply("{}/{} · {}".format(
             self['index'],
             len(page_ids),
             Showmore.parse_title(pages[self['index'] - 1]),
         ))
示例#17
0
文件: admin.py 项目: rossjrw/tars
 def execute(self, irc_c, msg, cmd):
     message = " ".join(self['message'])
     if message.startswith("/"):
         if msg.raw_channel != self['recipient']:
             raise CommandError(
                 "When executing a command with ..say, the recipient must "
                 "be the current channel ({}), as a safety check.".format(
                     msg.raw_channel))
         # This is an IRC command
         msg.reply("Issuing that...")
         # Send the message without the leading slash
         irc_c.RAW(message[1:])
     else:
         if self['obfuscate'] and msg.raw_channel is not None:
             message = Gib.obfuscate(message,
                                     DB.get_aliases(None) + ["ops"])
         irc_c.PRIVMSG(self['recipient'], message)
         if self['recipient'] != msg.raw_channel:
             msg.reply("Saying that to {}".format(self['recipient']))
示例#18
0
 def autojoin(self, irc_c, msg):
     if ("Password accepted" in msg.message or "You are now identified"
             in msg.message) and CONFIG.channels.db:
         for channel in DB.get_autojoins():
             irc_c.JOIN(channel)
             nsprint("Joining " + str(channel))
示例#19
0
文件: gib.py 项目: rossjrw/tars
    def execute(self, irc_c, msg, cmd):
        self['channel'] = self['channel']
        if len(self['channel']) == 0:
            if msg.raw_channel is None:
                # Happens when gibbing from PMs
                raise CommandError(
                    "Specify a channel to gib from with --channel/-c.")
            # Default channel is the current one
            self['channel'] = [msg.raw_channel]
        limit = self['limit'] or CONFIG['gib']['message_limit'] or 5000
        if 'media' in self:
            limit = -1
        if self['me']:
            self['regex'].append(re.compile(r"^\u0001ACTION "))
        # can only gib a channel both the user and the bot are in
        for channel in self['channel']:
            if channel == msg.raw_channel:
                continue
            if (msg.raw_channel is not None and self['channel'][0] != 'all'
                    and not all(nick in DB.get_channel_members(channel)
                                for nick in [msg.sender, CONFIG.nick])):
                raise CommandError(
                    "We both must be in a channel in order to gib it.")
            if (msg.raw_channel is not None and channel != msg.raw_channel
                    and not is_controller(cmd)):
                raise CommandError(
                    "You can only gib the current channel (or "
                    "any channel from if you do it in PMs with me).")
        # Does the model need to be regenerated?
        if not self['no_cache'] and all([
                Gib.cache['model'] is not None,
                Gib.cache['channels'] == self['channel'],
                Gib.cache['users'] == self['user'],
                Gib.cache['size'] == self['size'],
                Gib.cache['limit'] == limit,
        ]):
            print("Reusing Markov model")
        else:
            Gib.cache['model'] = None
            Gib.cache['channels'] = self['channel']
            Gib.cache['users'] = self['user']
            Gib.cache['size'] = self['size']
            Gib.cache['limit'] = limit
        # are we gibbing or rouletting?
        if 'media' in self:
            urls = self.media_roulette()
            msg.reply("{} {} · ({} link{} found)".format(
                emojize(":game_die:"),
                random.choice(urls),
                len(urls),
                "s" if len(urls) > 1 else "",
            ))
            return
        # gibbing:
        try:
            sentence = self.get_gib_sentence(limit=limit)
            if sentence is None:
                raise AttributeError
        except (RuntimeError, AttributeError) as error:
            raise MyFaultError(
                "Looks like {} spoken enough in {} just yet.{}".format(
                    ("you haven't"
                     if msg.sender in self['user'] and len(self['user']) == 1
                     else "nobody has" if len(self['user']) == 0 else
                     "{} hasn't".format(self['user'][0])
                     if len(self['user']) == 1 else "they haven't"),
                    (self['channel'][0] if
                     (len(self['channel']) == 1 and self['channel'][0]
                      == msg.raw_channel) else "that channel"
                     if len(self['channel']) == 1 else "those channels"),
                    " ({} messages)".format(
                        len(Gib.cache['model'].to_dict()['parsed_sentences']
                            ) if Gib.cache['model'] is not None else 0),
                )) from error
        # 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"\}", "}"))
        sentence = Gib.bracketify(sentence, ("“", "“"), (r"”", "”"))
        sentence = Gib.bracketify(sentence, ("‘", "‘"), (r"’", "’"))
        sentence = Gib.bracketify(sentence, ("«", "«"), (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)
示例#20
0
文件: names.py 项目: rossjrw/tars
 def part_names(self, irc_c, msg):
     # make sure the names are always up to date
     for channel in DB.get_all_channels():
         get_users(irc_c, channel)
示例#21
0
    def execute(self, irc_c, msg, cmd):
        if self['nick'] == "":
            self['nick'] = msg.sender
        # 1. Check if this is the right nick
        # get the user ID
        user_id = DB.get_user_id(self['nick'])
        if user_id is None:
            msg.reply("I don't know anyone called '{}'.".format(self['nick']))
            return
        # 2. Add new aliases
        if len(self['add']) > 0:
            if self['nick'].lower() != msg.sender.lower(
            ) and not is_controller(cmd):
                raise CommandError("You can't add an alias for someone else.")
            for alias in self['add']:
                if DB.add_alias(user_id, alias, 1):
                    msg.reply("{} already has the alias {}!".format(
                        self['nick'], alias))
            msg.reply("Added aliases to {}: {}".format(
                self['nick'], ", ".join(self['alias'])))
            irc_c.PRIVMSG(
                CONFIG['channels']['home'],
                "{} added alias {}".format(self['nick'], self['alias']),
            )
        if len(self['remove']) > 0:
            if self['nick'].lower() != msg.sender.lower(
            ) and not is_controller(cmd):
                raise CommandError(
                    "You can't remove an alias from someone else.")
            for alias in self['remove']:
                if not DB.remove_alias(user_id, alias, 1):
                    msg.reply("{} didn't have the alias {}!".format(
                        self['nick'], alias))
            msg.reply("Removed aliases from {}: {}".format(
                self['nick'], ", ".join(self['remove'])))
        if 'wikiname' in self:
            if self['nick'].lower() != msg.sender.lower(
            ) and not is_controller(cmd):
                raise CommandError("You can't change someone else's wikiname.")
            # The wikiname might be taken by another user
            wikiname_owner = DB.get_wikiname_owner(self['wikiname'])
            if wikiname_owner is not None and wikiname_owner != user_id:
                raise MyFaultError(
                    "Another user has claimed that Wikidot username. "
                    "If you think this was in error, contact {} "
                    "immediately.".format(CONFIG['IRC']['owner']))

            current_wikiname = DB.get_wikiname(user_id)
            if current_wikiname == self['wikiname']:
                raise MyFaultError("{} Wikidot username is already {}.".format(
                    "{}'s".format(self['nick'])
                    if self['nick'].lower() != msg.sender.lower() else "Your",
                    self['wikiname'],
                ))
            DB.set_wikiname(user_id, self['wikiname'])
            msg.reply("Updated {} Wikidot username {}to {}.".format(
                "{}'s".format(self['nick'])
                if self['nick'].lower() != msg.sender.lower() else "your",
                "" if current_wikiname is None else
                "from {} ".format(current_wikiname),
                self['wikiname'],
            ))
            irc_c.PRIVMSG(
                CONFIG['channels']['home'],
                "{} set wikiname {}".format(self['nick'], self['wikiname']),
            )
        if self['list']:
            # get all aliases associated with the user
            aliases = DB.get_aliases(user_id)
            wikiname = DB.get_wikiname(user_id)
            msg.reply("I've seen {} go by the names: {}. {}".format(
                self['nick'] if self['nick'] != msg.sender else "you",
                ", ".join(aliases),
                "I don't know {} Wikidot username.".format(
                    "their" if self['nick'] != msg.sender else "your", )
                if wikiname is None else "{} Wikidot username is {}.".format(
                    "Their" if self['nick'] != msg.sender else "Your",
                    wikiname,
                ),
            ))
示例#22
0
    def execute(self, irc_c, msg, cmd):
        # Set the return mode of the output
        selection = {
            'ignorepromoted': self['ignorepromoted'],
            'order': self['order'],
            'limit': self['limit'],
            'offset': self['offset'],
        }
        if self['random']:
            selection['order'] = 'random'
            selection['limit'] = 1
        # if self['recommend']:
        #     selection['order'] = 'recommend'
        #     selection['limit'] = 1
        if self['newest']:
            selection['order'] = 'recent'
            selection['limit'] = 1
        # What are we searching for?
        searches = []
        strings = []
        if len(self['title']) > 0:
            strings = self['title']
            searches.extend([{'term': s, 'type': None} for s in strings])
        # Add any regexes
        regexes = []
        for regex in self['regex']:
            try:
                re.compile(regex)
            except re.error as e:
                raise CommandError(
                    "'{}' isn't a valid regular expression: {}.".format(
                        regex, e
                    )
                ) from e
            regexes.append(regex)
            # don't append compiled regex - SQL doesn't like that
        searches.extend([{'term': r, 'type': 'regex'} for r in regexes])
        # Set the tags
        tags = {'include': [], 'exclude': []}
        for tag in self['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': []}
        for author in self['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()
        for rating in self['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 as e:
                    raise CommandError(
                        "Ratings in a range must be integers."
                    ) from e
                try:
                    ratings >= min(rating)
                    ratings <= max(rating)
                except MinMaxError as e:
                    raise CommandError(str(e).format("rating")) from e
            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 as e:
                        raise CommandError("Invalid rating comparison.") from e
                    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 rating comparison.")
                    except MinMaxError as e:
                        raise CommandError(str(e).format("rating")) from e
                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:
                    raise CommandError("Invalid rating comparison.")
            else:
                try:
                    rating = int(rating)
                except ValueError as e:
                    raise CommandError(
                        "Rating must be a range, comparison, or number."
                    ) from e
                # 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()
        created = self['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 selector in 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': []}
        for category in self['category']:
            if category[0] == "-":
                categories['exclude'].append(category[1:])
                continue
            else:
                if category[0] == "+":
                    categories['include'].append(category[1:])
                    continue
                categories['include'].append(category)
                continue
            categories['include'].append(category)
        searches.append({'term': categories, 'type': 'category'})
        # Set parent page
        parents = self['parent']
        if parents is not None:
            searches.append({'term': parents, 'type': 'parent'})
        # FINAL BIT - summarise commands
        if self['verbose']:
            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)

        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 len(self['title']) > 0:
                # google only takes 10 args
                url = google_search(
                    '"' + '" "'.join(self['title'][:10]) + '"', num=1
                )[0]
                if url is None:
                    msg.reply("No matches found.")
                    return
                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),
                )
            )
示例#23
0
文件: converse.py 项目: rossjrw/tars
    def execute(self, irc_c, msg, cmd):

        ##### ping matches #####

        if cmd.ping:
            if any(x in cmd.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 cmd.message.startswith("?? "):
            # CROM compatibility
            # Override defer by pretending the bot was pinged
            cmd.ping = True
            # Manually parse and instantiate a search command
            return parsemessages.try_command(irc_c, cmd.message[3:], cmd,
                                             'search')
        if msg.message.lower() == "{}!".format(CONFIG.nick.lower()):
            msg.reply("{}!".format(msg.nick))
            return
        if strip(msg.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(
                msg.message,
            [
                "what does tars stand for?",
                "is tars an acronym?",
            ],
        ) and "TARS" in msg.message.upper()):
            msg.reply(acronym())
            return
        if (CONFIG.nick == "TARS" and matches_any_of(msg.message, [
                "is tars a bot?",
                "tars are you a bot?",
        ]) and "TARS" in msg.message.upper()):
            msg.reply("Yep.")
            return
        if (CONFIG.nick == "TARS" and matches_any_of(msg.message, [
                "is tars a person?",
                "tars are you a person?",
        ]) and "TARS" in msg.message.upper()):
            msg.reply("Nope. I'm a bot.")
            return
        if (CONFIG.nick == "TARS" and matches_any_of(msg.message, [
                "what is your iq",
        ]) and "TARS" in msg.message.upper()):
            msg.reply("big")
            return
        if (CONFIG.nick == "TARS" and matches_any_of(msg.message, [
                "damn you to hell",
        ]) and "TARS" in msg.message.upper()):
            msg.reply("damn me to hell")
            return

        ##### regex matches #####

        # give url for reddit links
        match = re.search(r"(?:^|\s)/?r/(\S*)", msg.message, re.IGNORECASE)
        if match:
            msg.reply("https://www.reddit.com/r/{}".format(match.group(1)))
            return
        # tell me about new acronyms
        acronyms = find_acronym(msg.message, CONFIG['IRC']['nick'])
        if acronyms:
            raw_acronym, bold_acronym = acronyms
            with open(CONFIG['converse']['acronyms'], 'r') as acro_file:
                acros = json.load(acro_file)
                existing_acronyms = [acro['acronym'] for acro in acros]
            if raw_acronym not in existing_acronyms:
                msg.reply(bold_acronym)
                if msg.raw_channel != CONFIG['channels']['home']:
                    cmd.context.RAW("PRIVMSG {} {}".format(
                        CONFIG['channels']['home'], bold_acronym))
                with open(CONFIG['converse']['acronyms'], 'w') as acro_file:
                    acros.append({
                        'acronym': raw_acronym,
                        'sender': msg.nick,
                        'channel': msg.raw_channel,
                        'date': msg.timestamp,
                    })
                    json.dump(acros, acro_file, indent=2)
                return

        ##### custom matches #####

        if (msg.sender == "Jazstar" and "slime" in msg.message
                and "XilasCrowe" in DB.get_channel_members(msg.raw_channel)):
            msg.reply("Oi xilas I heard you like slime!")
            return

        # after all attempts, must indicate failure if pinged
        if cmd.ping:
            return 1