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>"))
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.")
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
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))
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
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])
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)
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}
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
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
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"]))
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}
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': [],
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
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
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)