Exemple #1
0
async def convrename(bot, event, *args):
    """renames a single specified conversation"""
    posix_args = get_posix_args(args)

    if len(posix_args) > 1:
        if not posix_args[0].startswith(("id:", "text:")):
            # always force explicit search for single conversation on vague user request
            posix_args[0] = "id:" + posix_args[0]
        convlist = bot.conversations.get(filter=posix_args[0])
        title = ' '.join(posix_args[1:])

        # only act on the first matching conversation

        await bot._client.rename_conversation(
            hangups.hangouts_pb2.RenameConversationRequest(
                request_header=bot._client.get_request_header(),
                new_name=title,
                event_request_header=hangups.hangouts_pb2.EventRequestHeader(
                    conversation_id=hangups.hangouts_pb2.ConversationId(
                        id=list(convlist.keys())[0]),
                    client_generated_id=bot._client.get_client_generated_id()))
        )

    elif len(posix_args) == 1 and posix_args[0].startswith("id:"):
        """specialised error message for /bot rename (implied convid: <event.conv_id>)"""
        raise Help(_("<em>missing title</em>"))
    else:
        """general error"""
        raise Help(_("<em>required parameters: convfilter title</em>"))
Exemple #2
0
async def foursquare(bot, event, *args):
    '''Explore places near you with Foursquare!
<b>/bot foursquare <location></b>: Display up to 10 of the recommended places near the specified location.
<b>/bot foursquare [type] <location></b>: Display up to 10 places near the provided location of the type specified. <i>Valid types: food, drinks, coffee, shops, arts, outdoors, sights, trending, specials</i>'''
    if not args:
        raise Help()

    try:
        clid = bot.memory.get_by_path(["foursquare", "id"])
        secret = bot.memory.get_by_path(["foursquare", "secret"])
    except:
        return _(
            "Something went wrong - make sure the Foursquare plugin is correctly configured."
        )

    types = [
        "food", "drinks", "coffee", "shops", "arts", "outdoors", "sights",
        "trending", "specials"
    ]
    if args[0] in types:
        places = getplaces(urllib.parse.quote(" ".join(args[1:])), clid,
                           secret, args[0])
    else:
        places = getplaces(urllib.parse.quote(" ".join(args)), clid, secret)

    if places:
        return places
    else:
        return _("Something went wrong.")
Exemple #3
0
def diceroll(dummy, event, dice="1d6"):
    """get random numbers from a fake diceroll

    Args:
        dummy: HangupsBot instance
        event: event.ConversationEvent instance
        dice: string, the diceroll request
    """
    try:
        repeat, sides = dice.split('d')
    except ValueError:
        sides = None
    if not sides:
        raise Help('Check argument!')
    if not repeat:
        repeat = 1
    repeat = int(repeat)
    sides = int(sides)
    if not 1 <= repeat < 100:
        return _("number of dice must be between 1 and 99")
    if not 2 <= sides < 10000:
        return _("number of sides must be between 2 and 9999")

    msg = _("<i>{} rolled ").format(event.user.full_name)
    numbers = [randint(1, sides) for i in range(repeat)]
    total = sum(numbers)
    msg += "<b>{}</b>".format(", ".join([str(item) for item in numbers]))
    if repeat != 1:
        msg += _(" totalling <b>{}</b></i>").format(total)
    else:
        msg += "</i>"
    return msg
Exemple #4
0
def echo(bot, event, *args):
    """echo a message back to the current or given conversation

    Args:
        bot: HangupsBot instance
        dummy: event.ConversationEvent instance, not needed
        args: tuple, a tuple of strings, request body

    Returns:
        tuple of strings, the conversation target and the message
    """
    if not args:
        raise Help(_('supply a message!'))

    text = None
    if len(args) > 1 and args[0] in bot.conversations:
        # /bot echo <convid> <text>
        # only admins can echo messages into other conversations
        admins_list = bot.get_config_suboption(args[0], 'admins')
        if event.user_id.chat_id in admins_list:
            convid = args[0]
            text = tuple(args[1:])
        else:
            convid = event.conv_id
            text = (_("<b>only admins can echo other conversations</b>"), )

    else:
        # /bot echo <text>
        convid = event.conv_id
        text = args

    return (convid, " ".join(text))
Exemple #5
0
def user(bot, event, *args):
    """find people by name"""

    search = " ".join(args)

    if not search:
        raise Help(_("supply search term"))

    search_lower = search.strip().lower()
    search_upper = search.strip().upper()

    segments = [
        hangups.ChatMessageSegment(
            _('results for user named "{}":').format(search), is_bold=True),
        hangups.ChatMessageSegment(
            '\n', hangups.hangouts_pb2.SEGMENT_TYPE_LINE_BREAK)
    ]

    all_known_users = {}
    for chat_id in bot.memory["user_data"]:
        all_known_users[chat_id] = bot.get_hangups_user(chat_id)

    for u in sorted(all_known_users.values(),
                    key=lambda x: x.full_name.split()[-1]):
        fullname_lower = u.full_name.lower()
        fullname_upper = u.full_name.upper()
        unspaced_lower = re.sub(r'\s+', '', fullname_lower)
        unspaced_upper = re.sub(r'\s+', '', u.full_name.upper())

        if (search_lower in fullname_lower or search_lower in unspaced_lower
                # XXX: turkish alphabet special case: converstion works better when uppercase
                or search_upper in remove_accents(fullname_upper) or
                search_upper in remove_accents(unspaced_upper)):

            link = 'https://plus.google.com/u/0/{}/about'.format(u.id_.chat_id)
            segments.append(
                hangups.ChatMessageSegment(
                    u.full_name,
                    hangups.hangouts_pb2.SEGMENT_TYPE_LINK,
                    link_target=link))
            if u.emails:
                segments.append(hangups.ChatMessageSegment(' ('))
                segments.append(
                    hangups.ChatMessageSegment(
                        u.emails[0],
                        hangups.hangouts_pb2.SEGMENT_TYPE_LINK,
                        link_target='mailto:{}'.format(u.emails[0])))
                segments.append(hangups.ChatMessageSegment(')'))
            segments.append(
                hangups.ChatMessageSegment(' ... {}'.format(u.id_.chat_id)))
            segments.append(
                hangups.ChatMessageSegment(
                    '\n', hangups.hangouts_pb2.SEGMENT_TYPE_LINE_BREAK))

    return segments
Exemple #6
0
def _get_delay(args):
    """check if enough args are specified and whether a valid delay is given

    Args:
        args: tuple of string, argument passed to the command

    Returns:
        float, the specified delay

    Raises:
        commands.Help: not enough args specified or invalid delay given
    """
    if len(args) < 2:
        raise Help(_("specify delay and message"))

    try:
        return float(args[0]) * 60.0
    except ValueError:
        raise Help(
            _('invalid delay "%s" given, integer or float required') % args[0])
Exemple #7
0
async def convecho(bot, event, *args):
    """echo back text into filtered conversations"""
    posix_args = get_posix_args(args)

    if (len(posix_args) > 1):
        if not posix_args[0]:
            """block spamming ALL conversations"""
            return _("<em>sending to ALL conversations not allowed</em>")
        convlist = bot.conversations.get(filter=posix_args[0])
        text = ' '.join(posix_args[1:])
    elif len(posix_args) == 1 and posix_args[0].startswith("id:"):
        """specialised error message for /bot echo (implied convid: <event.conv_id>)"""
        raise Help(_("<em>missing text</em>"))
    else:
        """general error"""
        raise Help(_("<em>required parameters: convfilter text</em>"))

    if not convlist:
        return _("<em>no conversations filtered</em>")

    for convid in convlist:
        await bot.coro_send_message(convid, text)
Exemple #8
0
async def convfilter(bot, event, *args):
    """test filter and return matched conversations"""
    posix_args = get_posix_args(args)

    if len(posix_args) > 1:
        raise Help(
            _("<em>1 parameter required, {} supplied - enclose parameter"
              " in double-quotes</em>").format(len(posix_args)))
    elif not posix_args:
        raise Help(_("<em>supply 1 parameter</em>"))
    else:
        lines = []
        for convid, convdata in bot.conversations.get(
                filter=posix_args[0]).items():
            lines.append("`{}` <b>{}</b> ({})".format(
                convid, convdata["title"], len(convdata["participants"])))
        lines.append(_('<b>Total: {}</b>').format(len(lines)))
        message = '\n'.join(lines)

        await bot.coro_send_message(event.conv_id, message)

        return {"api.response": message}
Exemple #9
0
def tldr_shared(bot, args):
    """
    Shares tldr functionality with other plugins
    :param bot: hangouts bot
    :param args: a dictionary which holds arguments.
    Must contain 'params' (tldr command parameters) and 'conv_id' (Hangouts conv_id)
    :return:
    """
    if not isinstance(args, dict):
        raise Help("args must be a dictionary")

    if 'params' not in args:
        raise Help("'params' key missing in args")

    if 'conv_id' not in args:
        raise Help("'conv_id' key missing in args")

    params = args['params']
    conv_id = args['conv_id']

    return_data, display = tldr_base(bot, conv_id, params)

    return return_data
Exemple #10
0
    def init_commands(self):
        self.cmd_handlers = {
            'help': Help(),
            'server': ServerUrl(),
            'start': Start(),
            'stop': Stop(),
            'status': Status(),
            'quit': Quit(),
            'exit': Quit(),
            'config': Config(),
            'use': Use(),
            'remove': Remove()
        }

        for cmd in self.cmd_handlers:
            self.cmd_handlers[cmd].console = self
Exemple #11
0
async def convleave(bot, event, *args):
    """leave specified conversation(s)"""
    posix_args = get_posix_args(args)

    if (len(posix_args) >= 1):
        if not posix_args[0]:
            """block leaving ALL conversations"""
            return _("<em>cannot leave ALL conversations</em>")
        convlist = bot.conversations.get(filter=posix_args[0])
    else:
        """general error"""
        raise Help(_("<em>required parameters: convfilter</em>"))

    for convid, convdata in convlist.items():
        if convdata["type"] == "GROUP":
            if not "quietly" in posix_args:
                await bot.coro_send_message(convid, _('I\'ll be back!'))

            try:
                await bot._client.remove_user(
                    hangups.hangouts_pb2.RemoveUserRequest(
                        request_header=bot._client.get_request_header(),
                        event_request_header=hangups.hangouts_pb2.
                        EventRequestHeader(
                            conversation_id=hangups.hangouts_pb2.
                            ConversationId(id=convid),
                            client_generated_id=bot._client.
                            get_client_generated_id())))

                if convid in bot._conv_list._conv_dict:
                    # replicate hangups behaviour - remove conversation from internal dict
                    del bot._conv_list._conv_dict[convid]
                bot.conversations.remove(convid)

            except hangups.NetworkError as e:
                logging.exception("CONVLEAVE: error leaving {} {}".format(
                    convid, convdata["title"]))

        else:
            logging.warning("CONVLEAVE: cannot leave {} {} {}".format(
                convdata["type"], convid, convdata["title"]))
Exemple #12
0
async def convusers(bot, event, *args):
    """gets list of users for specified conversation filter"""
    posix_args = get_posix_args(args)

    if len(posix_args) != 1:
        raise Help(
            _("<em>should be 1 parameter, {} supplied</em>").format(
                len(posix_args)))
    if not posix_args[0]:
        """don't do it in all conversations - might crash hangups"""
        return _("<em>retrieving ALL conversations blocked</em>")

    chunks = []  # one "chunk" = info for 1 hangout
    for convdata in bot.conversations.get(filter=posix_args[0]).values():
        lines = []
        lines.append(
            _('Users in <b>{}</b>').format(convdata["title"],
                                           len(convdata["participants"])))
        for chat_id in convdata["participants"]:
            User = bot.get_hangups_user(chat_id)
            # name and G+ link
            _line = '<b><a href="https://plus.google.com/{}">{}</a></b>'.format(
                User.id_.chat_id, User.full_name)
            # email from hangups UserList (if available)
            if User.emails:
                _line += '\n... (<a href="mailto:{0}">{0}</a>)'.format(
                    User.emails[0])
            # user id
            _line += "\n... {}".format(User.id_.chat_id)  # user id
            lines.append(_line)
        lines.append(
            _('<b>Users: {}</b>').format(len(convdata["participants"])))
        chunks.append('\n'.join(lines))
    message = '\n\n'.join(chunks)

    await bot.coro_send_message(event.conv_id, message)

    return {"api.response": message}
Exemple #13
0
from linebot.exceptions import (InvalidSignatureError)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage,
                            QuickReply, QuickReplyButton, MessageAction)

from commands import (About, Help, Wolfram, Translate, Profile, Language,
                      Wikipedia)
from config import LINE_CHANNEL_ACCESS, LINE_CHANNEL_SECRET
from util.commandutil import CommandUtil
from database.database import init_db, get_or_create
from database.models import User
import gettext

Commands = {
    'help': {
        'alias': ['?', '幫助'],
        'command': Help()
    },
    'about': {
        'alias': [],
        'command': About()
    },
    'wolfram': {
        'alias': ['wolf'],
        'command': Wolfram()
    },
    'translate': {
        'alias': ['翻譯'],
        'command': Translate()
    },
    'profile': {
        'alias': [],
Exemple #14
0
def prepare(bot, event, *args):
    """prepares a bundle of "things" for a random lottery.
    parameter: optional "things", draw definitions. if "things" is not specified, "default" will
    be used. draw definitions can be a simple range such as 1-8; a specific list of things to draw
    such as a,b,c,d,e; or a shorthand list such as 2abc1xyz (which prepares list abc,abc,xyz). any
    user can draw once from the default lottery with command /me draws. if multiple lotteries
    (non-default) are active, the user should use: /me draws a "thing". special keywords for
    draw definitions: COMPASS creates list based on the cardinal and ordinal directions.
    """

    max_items = 100

    listname = "default"
    listdef = args[0]
    if len(args) == 2:
        listname = args[0]
        listdef = args[1]
    global_draw_name = _get_global_lottery_name(bot, event.conv.id_, listname)

    draw_lists = _load_lottery_state(bot) # load any existing draws

    draw_lists[global_draw_name] = {"box": [], "users": {}}

    """special types
        /bot prepare [thing] COMPASS - 4 cardinal + 4 ordinal

        XXX: add more useful shortcuts here!
    """
    if listdef == "COMPASS":
        listdef = "north,north-east,east,south-east,south,south-west,west,north-west"

    # parse listdef

    if "," in listdef:
        # comma-separated single tokens
        draw_lists[global_draw_name]["box"] = listdef.split(",")

    elif re.match(r"\d+-\d+", listdef):
        # sequential range: <integer> to <integer>
        _range = listdef.split("-")
        min = int(_range[0])
        max = int(_range[1])
        if min == max:
            raise Help(_("prepare: min and max are the same ({})").format(min))
        if max < min:
            min, max = max, min
        max = max + 1 # inclusive
        draw_lists[global_draw_name]["box"] = list(range(min, max))

    else:
        # numberTokens: <integer><name>
        pattern = re.compile(r"((\d+)([a-z\-_]+))", re.IGNORECASE)
        matches = pattern.findall(listdef)
        if len(matches) > 1:
            for tokendef in matches:
                tcount = int(tokendef[1])
                tname = tokendef[2]
                for i in range(0, tcount):
                    draw_lists[global_draw_name]["box"].append(tname)

        else:
            raise Help(_("prepare: unrecognised match (!csv, !range, !numberToken): {}").format(listdef))

    if len(draw_lists[global_draw_name]["box"]) > max_items:
        del draw_lists[global_draw_name]
        message = _("Wow! Too many items to draw in <b>{}</b> lottery. Try {} items or less...").format(listname, max_items)
    elif draw_lists[global_draw_name]["box"]:
        shuffle(draw_lists[global_draw_name]["box"])
        message = _("The <b>{}</b> lottery is ready: {} items loaded and shuffled into the box.").format(listname, len(draw_lists[global_draw_name]["box"]))
    else:
        raise Help(_("prepare: {} was initialised empty").format(global_draw_name))

    _save_lottery_state(bot, draw_lists) # persist lottery drawings
    return message
Exemple #15
0
async def config(bot, event, cmd=None, *args):
    """retrive or edit a config entry

    Args:
        bot: HangupsBot instance
        event: event.ConversationEvent instance
        args: tuple of strings, additional words to for a request

    Returns:
        string, request result or None if the output was redirected to a pHO

    Raises:
        command.Help: invalid request
        KeyError: the given path does not exist
        ValueError: the given value is not a valid json
    """
    #TODO(das7pad): refactor into smaller parts and validate the new value/path

    # consume arguments and differentiate beginning of a json array or object
    tokens = list(args)
    parameters = []
    value = []
    state = "key"

    # allow admin to override default output to 1-on-1
    chat_response_private = True
    if cmd == 'here':
        chat_response_private = False
        if tokens:
            cmd = tokens.pop(0)
        else:
            cmd = None

    for token in tokens:
        if token.startswith(("{", "[", '"', "'")):
            # apparent start of json object, consume into a single list item
            state = "json"
        if state == "key":
            parameters.append(token)
        elif state == "json":
            value.append(token)
        else:
            raise Help("unknown state")
    if value:
        parameters.append(" ".join(value))

    if cmd == 'get' or cmd is None:
        config_args = list(parameters)
        value = (bot.config.get_by_path(config_args)
                 if config_args else dict(bot.config))

    elif cmd == 'test':
        num_parameters = len(parameters)
        text_parameters = []
        last = num_parameters - 1
        for num, token in enumerate(parameters):
            if num == last:
                try:
                    json.loads(token)
                    token += " <b>(valid json)</b>"
                except ValueError:
                    token += " <em>(INVALID)</em>"
            text_parameters.append(str(num + 1) + ": " + token)
        text_parameters.insert(0, "<b>config test</b>")

        if num_parameters == 1:
            text_parameters.append(
                _("<i>note: testing single parameter as json</i>"))
        elif num_parameters < 1:
            await command.unknown_command(bot, event)
            return

        return "\n".join(text_parameters)

    elif cmd == 'set':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            bot.config.set_by_path(config_args, json.loads(parameters[-1]))
            bot.config.save()
            value = bot.config.get_by_path(config_args)
        else:
            await command.unknown_command(bot, event)
            return

    elif cmd == 'append':
        config_args = list(parameters[:-1])
        if len(parameters) < 2:
            await command.unknown_command(bot, event)
            return

        value = bot.config.get_by_path(config_args)
        if isinstance(value, list):
            value.append(json.loads(parameters[-1]))
            bot.config.set_by_path(config_args, value)
            bot.config.save()
        else:
            value = _('append failed on non-list')

    elif cmd == 'remove':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            value = bot.config.get_by_path(config_args)
            if isinstance(value, list):
                value.remove(json.loads(parameters[-1]))
                bot.config.set_by_path(config_args, value)
                bot.config.save()
            else:
                value = _('remove failed on non-list')
        else:
            await command.unknown_command(bot, event)
            return

    else:
        await command.unknown_command(bot, event)
        return

    if value is None:
        value = _('Parameter does not exist!')

    config_path = ' '.join(k for k in ['config'] + config_args)
    output = '\n'.join([
        '<b>{}:</b>'.format(config_path),
        json.dumps(value, indent=2, sort_keys=True)
    ])

    if chat_response_private:
        await bot.coro_send_to_user(event.user_id.chat_id, output)
    else:
        return output
Exemple #16
0
async def broadcast(bot, dummy, *args):
    """broadcast a message to multiple chats, schedule here

    Args:
        bot: HangupsBot instance
        dummy: event.ConversationEvent instance, not needed
        args: tuple, a tuple of strings, request body

    Returns:
        string, user output

    Raises:
        commands.Help: bad request
    """
    if not args:
        raise Help(_('Your request is missing'))
    subcmd = args[0]
    parameters = args[1:]
    if subcmd == "info":
        # display broadcast data such as message and target rooms

        conv_info = [
            "<b><i>{}</i></b> ... <i>{}</i>".format(
                bot.conversations.get_name(convid, '~'), convid)
            for convid in _internal["broadcast"]["conversations"]
        ]

        if not _internal["broadcast"]["message"]:
            text = [_("broadcast: no message set")]

        elif not conv_info:
            text = [_("broadcast: no conversations available")]

        else:
            text = [
                _("<b>message:</b>"), _internal["broadcast"]["message"],
                _("<b>to:</b>")
            ]
            text.extend(conv_info)

    elif subcmd == "message":
        # set broadcast message
        message = ' '.join(parameters)
        if message:
            if args[1] in bot._handlers.bot_command:
                text = [_("broadcast: message not allowed")]
            else:
                _internal["broadcast"]["message"] = message
                text = [_("{} saved").format(message)]

        else:
            text = [_("broadcast: message must be supplied after subcommand")]

    elif subcmd == "add":
        # add conversations to a broadcast
        if parameters[0] == "groups":
            # add all groups
            _internal["broadcast"]["conversations"].extend(
                list(bot.conversations.get("type:group")))

        elif parameters[0] == "ALL":
            # add EVERYTHING - try not to use this, will message 1-to-1s as well
            _internal["broadcast"]["conversations"].extend(
                list(bot.conversations.get()))

        else:
            # add by wild card search of title or id
            search = " ".join(parameters)
            for convid, convdata in bot.conversations.get().items():
                if (search.lower() in convdata["title"].lower()
                        or search in convid):
                    _internal["broadcast"]["conversations"].append(convid)

        _internal["broadcast"]["conversations"] = list(
            set(_internal["broadcast"]["conversations"]))
        text = [
            _("broadcast: {} conversation(s)".format(
                len(_internal["broadcast"]["conversations"])))
        ]

    elif subcmd == "remove":
        if parameters[0].lower() == "all":
            # remove all conversations from broadcast
            _internal["broadcast"]["conversations"] = []
            text = [_("broadcast: cleared all conversations")]

        else:
            # remove by wild card search of title or id
            search = " ".join(parameters)
            removed = []
            for convid in _internal["broadcast"]["conversations"]:
                if (search.lower()
                        in bot.conversations.get_name(convid).lower()
                        or search in convid):
                    _internal["broadcast"]["conversations"].remove(convid)
                    removed.append("<b><i>{}</i></b> (<i>{}</i>)".format(
                        bot.conversations.get_name(convid), convid))

            text = [_("broadcast: removed {}".format(", ".join(removed)))]

    elif subcmd == "NOW":
        # send the broadcast
        context = {
            "syncroom_no_repeat": True
        }  # prevent echos across syncrooms
        for convid in _internal["broadcast"]["conversations"]:
            await bot.coro_send_message(convid,
                                        _internal["broadcast"]["message"],
                                        context=context)
        text = [
            _("broadcast: message sent to {} chats".format(
                len(_internal["broadcast"]["conversations"])))
        ]

    else:
        raise Help()

    return "\n".join(text)