def game(client: discord.Client, message: discord.Message, name: Annotate.Content=None): """ Stop playing or set game to `name`. """ yield from client.change_status(discord.Game(name=name, type=0)) if name: yield from client.say(message, "*Set the game to* **{}**.".format(name)) else: yield from client.say(message, "*No longer playing.*")
def disable(client: discord.Client, message: discord.Message, trigger: str.lower): """ Disable a command. """ # If the specified trigger is not in the blacklist, we add it if trigger not in lambda_config.data["blacklist"]: lambda_config.data["blacklist"].append(trigger) lambda_config.save() yield from client.say(message, "Command `{}` disabled.".format(trigger)) else: assert trigger in lambdas.data, "Command `{}` does not exist.".format(trigger) # The command exists so surely it must be disabled yield from client.say(message, "Command `{}` is already disabled.".format(trigger))
def import_(client: discord.Client, message: discord.Message, module: str, attr: str=None): """ Import the specified module. Specifying `attr` will act like `from attr import module`. """ try: import_module(module, attr) except ImportError: yield from client.say(message, "Unable to import `{}`.".format(module)) except KeyError: yield from client.say(message, "Unable to import `{}` from `{}`.".format(attr, module)) else: # There were no errors when importing, so we add the name to our startup imports lambda_config.data["imports"].append((module, attr)) lambda_config.save() yield from client.say(message, "Imported and setup `{}` for import.".format(attr or module))
def pp_(client: discord.Client, message: discord.Message, beatmap_url: str.lower, *options): """ Calculate and return the would be pp using `oppai`. Options are a parsed set of command-line arguments: / `([acc]% | [num_100s]x100 [num_50s]x50) +[mods] [combo]x [misses]m scorev[scoring_version]`""" global last_calc_beatmap # This service is only supported on Linux as of yet assert platform.system() == "Linux", "This service is unsupported since the bot is not hosted using Linux." # Make sure the bot has access to "oppai" lib assert os.path.exists(os.path.join(oppai_path, "oppai")), \ "This service is unavailable until the owner sets up the `oppai` lib." # Only download and request when the id is different from the last check if last_calc_beatmap["beatmap_id"] not in beatmap_url and last_calc_beatmap["beatmapset_id"] not in beatmap_url: # Parse beatmap URL and download the beatmap .osu try: beatmap = yield from api.beatmap_from_url(beatmap_url) except Exception as e: yield from client.say(message, e) return # Download and save the beatmap pp_map.osu beatmap_file, _ = yield from utils.download_file(host + "osu/" + str(beatmap["beatmap_id"])) with open(os.path.join(oppai_path, "pp_map.osu"), "wb") as f: f.write(beatmap_file) else: beatmap = last_calc_beatmap last_calc_beatmap = beatmap command_args = [os.path.join(oppai_path, "oppai"), os.path.join(oppai_path, "pp_map.osu")] # Add additional options if options: command_args.extend(options) command_stream = Popen(command_args, universal_newlines=True, stdout=PIPE) output = command_stream.stdout.read() match = re.search(r"(?P<pp>[0-9.e+]+)pp", output) # Something went wrong with our service assert match, "A problem occurred when parsing the beatmap." # We're done! Tell the user how much this score is worth. yield from client.say(message, "*{artist} - {title}* **[{version}] {1}** would be worth `{0:,}pp`.".format( float(match.group("pp")), " ".join(options), **beatmap))
def define(client: discord.Client, message: discord.Message, term: Annotate.LowerCleanContent): """ Defines a term using Urban Dictionary. """ json = yield from utils.download_json("http://api.urbandictionary.com/v0/define", term=term) definitions = json["list"] if "list" in json else [] # Make sure we have something to define assert definitions, "Could not define `{}`.".format(term) # Send any valid definition (length of message < 2000 characters) msg = "" for definition in definitions: # Format example in code if there is one if "example" in definition and definition["example"]: definition["example"] = "```{}```".format(definition["example"]) # Format definition msg = "**{}**:\n{}\n{}".format( definition["word"], definition["definition"], definition["example"] ) # If this definition fits in a message, break the loop so that we can send it if len(msg) <= 2000: break # Cancel if the message is too long assert len(msg) <= 2000, "Defining this word would be a bad idea." # Send the definition yield from client.say(message, msg)
def filter_type(client: discord.Client, message: discord.Message, slot_1: str.lower, slot_2: str.lower=None): matched_pokemon = [] assert_type(slot_1) # Find all pokemon with the matched criteria if slot_2: assert_type(slot_2) # If two slots are provided, search for pokemon with both types matching for pokemon in pokedex.values(): if pokemon["types"] == [slot_1, slot_2]: matched_pokemon.append(pokemon["locale_name"]) else: # All pokemon have a type in their first slot, so check if these are equal for pokemon in pokedex.values(): if pokemon["types"][0] == slot_1: matched_pokemon.append(pokemon["locale_name"]) types = [slot_1] if slot_2 is None else [slot_1, slot_2] # There might not be any pokemon with the specified types assert matched_pokemon, "Looks like there are no pokemon of type **{}**!".format(format_type(types)) yield from client.say(message, "**Pokemon with type {}**: ```\n{}```".format( format_type(types), ", ".join(sorted(matched_pokemon))))
def debug(client: discord.Client, message: discord.Message): """ Display some debug info. """ yield from client.say(message, "Sent `{}` requests since the bot started (`{}`).\n" "Members registered for update: {}".format( api.requests_sent, client.time_started.ctime(), utils.format_members(*[d["member"] for d in osu_tracking.values()]) ))
def url(client: discord.Client, message: discord.Message, member: Annotate.Member=Annotate.Self): """ Display the member's osu! profile URL. """ # Member might not be registered assert member.id in osu_config.data["profiles"], "No osu! profile assigned to **{}**!".format(member.name) # Send the URL since the member is registered yield from client.say(message, "**{0.display_name}'s profile:** <https://osu.ppy.sh/u/{1}>".format( member, osu_config.data["profiles"][member.id]))
def unload(client: discord.Client, message: discord.Message, name: str.lower): """ Unloads a plugin. """ assert plugins.get_plugin(name), "`{}` is not a loaded plugin.".format(name) # The plugin is loaded so we unload it yield from plugins.save_plugin(name) plugins.unload_plugin(name) yield from client.say(message, "Plugin `{}` unloaded.".format(name))
def lambda_(client: discord.Client, message: discord.Message): """ Create commands. See `{pre}help do` for information on how the code works. **In addition**, there's the `arg(i, default=0)` function for getting arguments in positions, where the default argument is what to return when the argument does not exist. **Owner command unless no argument is specified.**""" yield from client.say(message, "**Lambdas:** ```\n" "{}```".format(", ".join(sorted(lambdas.data.keys()))))
def remove(client: discord.Client, message: discord.Message, trigger: str.lower): """ Remove a command. """ assert trigger in lambdas.data, "Command `{}` does not exist.".format(trigger) # The command specified exists and we remove it del lambdas.data[trigger] lambdas.save() yield from client.say(message, "Command `{}` removed.".format(trigger))
def reload(client: discord.Client, message: discord.Message, name: str.lower=None): """ Reloads all plugins or the specified plugin. """ if name: assert plugins.get_plugin(name), "`{}` is not a plugin".format(name) # The plugin entered is valid so we reload it yield from plugins.save_plugin(name) plugins.reload_plugin(name) yield from client.say(message, "Reloaded plugin `{}`.".format(name)) else: # Reload all plugins yield from plugins.save_plugins() for plugin_name in plugins.all_keys(): plugins.reload_plugin(plugin_name) yield from client.say(message, "All plugins reloaded.")
def load(client: discord.Client, message: discord.Message, name: str.lower): """ Loads a plugin. """ assert not plugins.get_plugin(name), "Plugin `{}` is already loaded.".format(name) # The plugin isn't loaded so we'll try to load it assert plugins.load_plugin(name), "Plugin `{}` could not be loaded.".format(name) # The plugin was loaded successfully yield from client.say(message, "Plugin `{}` loaded.".format(name))
def resize(client: discord.Client, message: discord.Message, url: str, resolution: parse_resolution, *options, extension: str=None): """ Resize an image with the given resolution formatted as `<width>x<height>` with an optional extension. """ # Make sure the URL is valid try: image_bytes, headers = yield from utils.download_file(url) except ValueError: yield from client.say(message, "The given URL is invalid.") return match = extension_regex.search(headers["CONTENT-TYPE"]) assert match, "The given url is not an image." # Create some metadata image_format = extension or match.group("ext") # Set the image upload extension extension = image_format.lower() if extension.lower() == "jpeg": extension = "jpg" if image_format.lower() == "jpg": image_format = "JPEG" filename = "{}.{}".format(message.author.display_name, extension) # Open the image in Pillow image = Image.open(BytesIO(image_bytes)) image = image.resize(resolution, Image.NEAREST if "-nearest" in options else Image.ANTIALIAS) # Upload the image buffer = BytesIO() try: image.save(buffer, image_format) except KeyError as e: yield from client.say(message, "Image format `{}` is unsupported.".format(e)) return except Exception as e: yield from client.say(message, str(e) + ".") return buffer.seek(0) yield from client.send_file(message.channel, buffer, filename=filename)
def eval_(client: discord.Client, message: discord.Message, python_code: Annotate.Code): """ Evaluate a python expression. Can be any python code on one line that returns something. """ code_globals.update(dict(message=message, client=client)) try: result = eval(python_code, code_globals) except Exception as e: result = utils.format_exception(e) yield from client.say(message, "**Result:** \n```{}\n```".format(result))
def setowner(client: discord.Client, message: discord.Message): """ Set the bot owner. Only works in private messages. """ if not message.channel.is_private: return assert not utils.owner_cfg.data, "An owner is already set." owner_code = str(random.randint(100, 999)) logging.critical("Owner code for assignment: {}".format(owner_code)) yield from client.say(message, "A code has been printed in the console for you to repeat within 60 seconds.") user_code = yield from client.wait_for_message(timeout=60, channel=message.channel, content=owner_code) assert user_code, "You failed to send the desired code." if user_code: yield from client.say(message, "You have been assigned bot owner.") utils.owner_cfg.data = message.author.id utils.owner_cfg.save()
def ping(client: discord.Client, message: discord.Message): """ Tracks the time spent parsing the command and sending a message. """ # Track the time it took to receive a message and send it. start_time = datetime.now() first_message = yield from client.say(message, "Pong!") stop_time = datetime.now() # Edit our message with the tracked time (in ms) time_elapsed = (stop_time - start_time).microseconds / 1000 yield from client.edit_message(first_message, "Pong! `{elapsed:.4f}ms`".format(elapsed=time_elapsed))
def unlink(client: discord.Client, message: discord.Message, member: Annotate.Member=Annotate.Self): """ Unlink your osu! account or unlink the member specified (**Owner only**). """ # The message author is allowed to unlink himself # If a member is specified and the member is not the owner, set member to the author if not utils.is_owner(message.author): member = message.author # The member might not be linked to any profile assert member.id in osu_config.data["profiles"], "No osu! profile assigned to **{}**!".format(member.name) # Unlink the given member (usually the message author) del osu_config.data["profiles"][member.id] osu_config.save() yield from client.say(message, "Unlinked **{}'s** osu! profile.".format(member.name))
def egg(client: discord.Client, message: discord.Message, egg_type: Annotate.LowerCleanContent): """ Get the pokemon hatched from the specified egg_type (in distance, e.g. 2 or 5km) """ # Strip any km suffix (or prefix, whatever) egg_type = egg_type.replace("km", "") try: distance = int(float(egg_type)) # Using float for anyone willing to type 2.0km except ValueError: yield from client.say(message, "The egg type **{}** is invalid.".format(egg_type)) return pokemon_criteria = [] egg_types = [] # Find all pokemon with the specified distance for pokemon in sorted(pokedex.values(), key=itemgetter("id")): # We've exceeded the generation and no longer need to search if pokemon["generation"] not in pokemon_go_gen: break if "hatches_from" not in pokemon: continue if pokemon["hatches_from"] not in egg_types: egg_types.append(pokemon["hatches_from"]) if pokemon["hatches_from"] == distance: pokemon_criteria.append(pokemon["locale_name"]) # The list might be empty assert pokemon_criteria, "No pokemon hatch from a **{}km** egg. **Valid distances are** ```\n{}```".format( distance, ", ".join("{}km".format(s) for s in sorted(egg_types))) # Respond with the list of matching criteria yield from client.say(message, "**The following Pokémon may hatch from a {}km egg**:```\n{}```".format( distance, ", ".join(sorted(pokemon_criteria))))
def help_(client: discord.Client, message: discord.Message, command: str.lower=None, *args): """ Display commands or their usage and description. """ # Display the specific command if command: if command.startswith(config.command_prefix): command = command[1:] for plugin in plugins.all_values(): cmd = plugins.get_command(plugin, command) if not cmd: continue # Get the specific command with arguments and send the help cmd = plugins.get_sub_command(cmd, args) yield from client.say(message, utils.format_help(cmd)) break # Display every command else: commands = [] for plugin in plugins.all_values(): if getattr(plugin, "__commands", False): # Massive pile of shit that works (so sorry) commands.extend( cmd.name_prefix.split()[0] for cmd in plugin.__commands if not cmd.hidden and (not getattr(getattr(cmd, "function"), "__owner__", False) or utils.is_owner(message.author)) ) commands = ", ".join(sorted(commands)) m = "**Commands**:```{0}```Use `{1}help <command>`, `{1}<command> {2}` or " \ "`{1}<command> {3}` for command specific help.".format( commands, config.command_prefix, *config.help_arg) yield from client.say(message, m)
def gamemode(client: discord.Client, message: discord.Message, mode: api.GameMode.get_mode): """ Set the gamemode for the specified member. Gamemodes are `Standard`, `Taiko`, `CTB` and `Mania`. """ assert message.author.id in osu_config.data["profiles"], \ "No osu! profile assigned to **{}**!".format(message.author.name) osu_config.data["mode"][message.author.id] = mode.value osu_config.save() # Clear the scores when changing mode if message.author.id in osu_tracking: del osu_tracking[message.author.id] yield from client.say(message, "Set your gamemode to **{}**.".format(mode.name))
def link(client: discord.Client, message: discord.Message, name: Annotate.LowerContent): """ Tell the bot who you are on osu!. """ osu_user = yield from api.get_user(u=name) # Check if the osu! user exists assert osu_user, "osu! user `{}` does not exist.".format(name) # Clear the scores when changing user if message.author.id in osu_tracking: del osu_tracking[message.author.id] # Assign the user using their unique user_id osu_config.data["profiles"][message.author.id] = osu_user["user_id"] osu_config.data["mode"][message.author.id] = api.GameMode.Standard.value osu_config.save() yield from client.say(message, "Set your osu! profile to `{}`.".format(osu_user["username"]))
def bot_info(client: discord.Client, message: discord.Message): """ Display basic information and changelog. """ # Grab the latest commit # changelog = yield from get_changelog(1) yield from client.say(message, "**{ver}** - **{name}**\n" "__Github repo:__ <{repo}>\n" "__Owner (host):__ `{host}`\n" "__Up since:__ `{up}`\n" "__Messages since up date:__ `{mes}`\n" "__Servers connected to:__ `{servers}`".format( ver=config.version, name=client.user.name, up=client.time_started.strftime("%d-%m-%Y %H:%M:%S"), mes=len(client.messages), host=getattr(utils.get_member(client, utils.owner_cfg.data), "name", None) or "Not in this server.", servers=len(client.servers), repo="https://github.com/{}".format(config.github_repo), # changelog=changelog ))
def osu(client: discord.Client, message: discord.Message, member: Annotate.Member=Annotate.Self): """ Handle osu! commands. When your user is linked, this plugin will check if you are playing osu! (your profile would have `playing osu!`), and send updates whenever you set a new top score. """ # Make sure the member is assigned assert member.id in osu_config.data["profiles"], "No osu! profile assigned to **{}**!".format(member.name) user_id = osu_config.data["profiles"][member.id] # Set the signature color to that of the role color color = "pink" if member.color == discord.Color.default() \ else "#{0:02x}{1:02x}{2:02x}".format(*member.color.to_tuple()) # Download and upload the signature signature, _ = yield from utils.download_file("http://lemmmy.pw/osusig/sig.php", colour=color, uname=user_id, pp=True, countryrank=True, xpbar=True, mode=get_mode(member.id).value) yield from client.send_file(message.channel, signature, filename="sig.png") yield from client.say(message, "<https://osu.ppy.sh/u/{}>".format(user_id))
def scalefactor(client: discord.Client, message: discord.Message, factor: float=default_scale_factor): """ Set the image scaling factor for your server. If no factor is given, the default is set. / **This command requires the `Manage Server` permission.**""" assert factor <= max_scale_factor, "The factor **{}** is too high **(max={})**.".format( factor, max_scale_factor) assert min_scale_factor <= factor, "The factor **{}** is too low **(min={})**.".format( factor, min_scale_factor) if message.server.id not in pokedex_config.data: pokedex_config.data[message.server.id] = {} # Handle specific scenarios if factor == default_scale_factor: if "scale-factor" in pokedex_config.data[message.server.id]: del pokedex_config.data[message.server.id]["scale-factor"] reply = "Pokédex image scale factor reset to **{factor}**." else: reply = "Pokédex image scale factor is **{factor}**." else: pokedex_config.data[message.server.id]["scale-factor"] = factor reply = "Pokédex image scale factor set to **{factor}**." pokedex_config.save() yield from client.say(message, reply.format(factor=factor))
def plugin_(client: discord.Client, message: discord.Message): """ Manage plugins. **Owner command unless no argument is specified.** """ yield from client.say(message, "**Plugins:** ```{}```".format(", ".join(plugins.all_keys())))
def stop(client: discord.Client, message: discord.Message): """ Stops the bot. """ yield from client.say(message, ":boom: :gun:") yield from plugins.save_plugins() yield from client.logout()
def add(client: discord.Client, message: discord.Message, trigger: str.lower, python_code: Annotate.Code): """ Add a command that runs the specified python code. """ lambdas.data[trigger] = python_code lambdas.save() yield from client.say(message, "Command `{}` set.".format(trigger))
def changelog_(client: discord.Client, message: discord.Message, num: utils.int_range(f=1)=3): """ Get `num` requests from the changelog. Defaults to 3. """ changelog = yield from get_changelog(num) yield from client.say(message, changelog)
def source(client: discord.Client, message: discord.Message, trigger: str.lower): """ Disable source of a command """ assert trigger in lambdas.data, "Command `{}` does not exist.".format(trigger) # The command exists so we display the source yield from client.say(message, "Source for `{}`:\n{}".format(trigger, lambdas.data[trigger]))