def __init__(self, bot: Red) -> None: self.bot = bot self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("message"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks)
def __init__(self, bot: Red) -> None: self.bot = bot cog = self.bot.get_cog("CustomCommands") if cog: raise RuntimeError( "This cog conflicts with CustomCommands and cannot be loaded with both at the same time." ) self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = stable_blocks + [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("message"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks)
def __init__(self, bot: Red) -> None: self.bot = bot self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = stable_blocks + [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("args"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks) self.role_converter = commands.RoleConverter() self.channel_converter = commands.TextChannelConverter() self.member_converter = commands.MemberConverter() self.emoji_converter = commands.EmojiConverter() self.guild_tag_cache = defaultdict(dict) self.task = asyncio.create_task(self.cache_tags())
def __init__(self, bot): self.bot = bot blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), ] self.engine = Interpreter(blocks)
def __init__(self, bot): self.bot = bot blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), ] self.engine = Interpreter(blocks) # Initialize a session self.session = aiohttp.ClientSession()
def run(self, interpreter: tse.Interpreter, seed_variables: dict = {}, **kwargs) -> tse.Response: self.uses += 1 seed_variables.update(uses=tse.IntAdapter(self.uses)) return interpreter.process(self.tagscript, seed_variables, **kwargs)
class Calculator(commands.Cog): """ Do math """ def __init__(self, bot): self.bot = bot blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), ] self.engine = Interpreter(blocks) async def red_delete_data_for_user(self, **kwargs): return @commands.command(aliases=["calc"]) async def calculate(self, ctx, *, query): """Math""" query = query.replace(",", "") engine_input = "{m:" + query + "}" start = time.monotonic() output = self.engine.process(engine_input) end = time.monotonic() output_string = output.body.replace("{m:", "").replace("}", "") e = discord.Embed( color=await ctx.embed_color(), title=f"Input: `{query}`", description=f"Output: `{output_string}`", ) e.set_footer(text=f"Calculated in {round((end - start) * 1000, 3)} ms") await ctx.send(embed=e)
def init_tagscript( self, blocks: list = None, member: discord.Member = None, guild: discord.Guild = None, context: commands.Context = None, ): if not blocks: blocks = self.blocks if member: blocks += [DiscordMemberBlock(member, context)] if guild: blocks += [DiscordGuildBlock(guild, context)] return Interpreter(blocks)
class Math(BaseCog): def __init__(self, bot): self.bot = bot blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), ] self.engine = Interpreter(blocks) async def red_delete_data_for_user(self, **kwargs): return @commands.command() async def math(self, ctx, *expression): """ Solve a math expression. Supports very complex problems too. For eg. `luci math sin(pi/4)` """ log = logging.getLogger("red.cogsbylucifer.math") if (expression == ""): embed = discord.Embed(color=0xea1010, title="Input A Expression") await ctx.send(embed=embed) return start = time.monotonic() api = "http://api.mathjs.org/v4/" params = {"expr": "".join(expression)} response = requests.get(url=api, params=params) end = time.monotonic() if (str(response.status_code)[:2] == "40"): log.info(expression) log.error(response.text) embed = discord.Embed(color=await ctx.embed_color(), title=response.text) embed.add_field(name="Your Input:", value=f'`{"".join(expression)}`', inline=True) embed.add_field(name="Answer:", value=response.text, inline=True) embed.set_footer( text=f"Calculated in {round((end - start) * 1000, 3)} ms") await ctx.send(embed=embed) @commands.command(aliases=["calc"]) async def calculate(self, ctx, *, query): """ Faster but sometimes does not work. """ query = query.replace(",", "") engine_input = "{m:" + query + "}" start = time.monotonic() output = self.engine.process(engine_input) end = time.monotonic() output_string = output.body.replace("{m:", "").replace("}", "") embed = discord.Embed( color=await ctx.embed_color(), title=f"Input: `{query}`", description=f"Output: `{output_string}`", ) embed.set_footer( text=f"Calculated in {round((end - start) * 1000, 3)} ms") await ctx.send(embed=embed)
class Tags(commands.Cog): """ Create and use tags. The TagScript documentation can be found [here](https://github.com/phenom4n4n/phen-cogs/blob/master/tags/README.md). """ __version__ = "1.2.13" def format_help_for_context(self, ctx): pre_processed = super().format_help_for_context(ctx) n = "\n" if "\n\n" not in pre_processed else "" return f"{pre_processed}{n}\nCog Version: {self.__version__}" def __init__(self, bot: Red) -> None: self.bot = bot self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = stable_blocks + [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("message"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks) self.role_converter = commands.RoleConverter() self.channel_converter = commands.TextChannelConverter() self.member_converter = commands.MemberConverter() self.emoji_converter = commands.EmojiConverter() self.tag_cache = {} self.guild_data_cache = {} self.task = asyncio.create_task(self.cache_tags()) def cog_unload(self): if self.task: self.task.cancel() async def red_delete_data_for_user(self, *, requester: str, user_id: int): if requester not in ("discord_deleted_user", "user"): return guilds_data = await self.config.all_guilds() for guild_id, data in guilds_data.items(): guild = self.bot.get_guild(guild_id) if guild and data["tags"]: for name, tag in data["tags"].items(): if str(user_id) in str(tag["author"]): async with self.config.guild(guild).tags() as t: del t[name] async def cache_tags(self): guilds_data = await self.config.all_guilds() self.guild_data_cache = guilds_data for guild_id, data in guilds_data.items(): self.tag_cache[guild_id] = list(data.get("tags", {}).keys()) @commands.guild_only() @commands.group(invoke_without_command=True, usage="<tag_name> [args]", aliases=["customcom"]) async def tag(self, ctx, response: Optional[bool], tag_name: str, *, args: Optional[str] = ""): """Tag management with TagScript. These commands use TagScriptEngine. [This site](https://github.com/phenom4n4n/phen-cogs/blob/master/tags/README.md) has documentation on how to use TagScript blocks.""" if response is None: response = True try: _tag = await TagConverter().convert(ctx, tag_name) except commands.BadArgument as e: if response: await ctx.send(e) return async with self.config.guild(ctx.guild).tags() as t: t[tag_name]["uses"] += 1 seed = {"args": adapter.StringAdapter(args)} log.info( f"Processing tag for {tag_name} on {ctx.guild} ({ctx.guild.id})") await self.process_tag(ctx, _tag, seed_variables=seed) @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["create", "+"]) async def add(self, ctx, tag_name: TagName, *, tagscript): """Add a tag with TagScript.""" tag = await self.get_stored_tag(ctx, tag_name, False) if tag: msg = await ctx.send( f"`{tag_name}` is already registered tag. Would you like to overwrite it?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) await ctx.bot.wait_for("reaction_add", check=pred) if pred.result is False: await ctx.send("Action cancelled.") return await self.store_tag(ctx, tag_name, tagscript) await self.cache_tags() @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["e"]) async def edit(self, ctx, tag: TagConverter, *, tagscript): """Edit a tag with TagScript.""" async with self.config.guild(ctx.guild).tags() as t: t[str(tag)]["tag"] = tagscript await ctx.send(f"Tag `{tag}` edited.") @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["delete", "-"]) async def remove(self, ctx, tag: TagConverter): """Delete a tag.""" async with self.config.guild(ctx.guild).tags() as e: del e[str(tag)] await ctx.send("Tag deleted.") await self.cache_tags() @tag.command(name="info") async def tag_info(self, ctx, tag: TagConverter): """Get info about an tag that is stored on this server.""" e = discord.Embed( color=await ctx.embed_color(), title=f"`{tag}` Info", description= f"Author: {tag.author.mention if tag.author else tag.author_id}\nUses: {tag.uses}\nLength: {len(tag)}", ) e.add_field(name="TagScript", value=box(str(tag))) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @tag.command(name="raw") async def tag_raw(self, ctx, tag: TagConverter): """Get a tag's raw content.""" await ctx.send( escape_markdown(tag.tagscript[:2000]), allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False), ) @tag.command(name="list") async def tag_list(self, ctx): """View stored tags.""" tags = await self.config.guild(ctx.guild).tags() if not tags: return await ctx.send("There are no stored tags on this server.") description = [] for name, tag in tags.items(): description.append(f"`{name}` - Created by <@!{tag['author']}>") description = "\n".join(description) color = await self.bot.get_embed_colour(ctx) e = discord.Embed(color=color, title=f"Stored Tags") e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) if len(description) > 2048: embeds = [] pages = list(pagify(description, page_length=1024)) for index, page in enumerate(pages, start=1): embed = e.copy() embed.description = page embed.set_footer(text=f"{index}/{len(pages)}") embeds.append(embed) await menu(ctx, embeds, DEFAULT_CONTROLS) else: e.description = description emoji = self.bot.get_emoji(736038541364297738) or "❌" controls = {emoji: close_menu} await menu(ctx, [e], controls) @commands.is_owner() @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["execute"]) async def run(self, ctx: commands.Context, *, tagscript: str): """Execute TagScript without storing.""" start = time.monotonic() author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = TextChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } output = self.engine.process(tagscript, seed_variables=seed) end = time.monotonic() e = discord.Embed( color=await ctx.embed_color(), title="TagScriptEngine", description=f"Executed in **{round((end - start) * 1000, 3)}** ms", ) e.add_field(name="Input", value=tagscript, inline=False) if output.actions: e.add_field(name="Actions", value=output.actions, inline=False) if output.variables: vars = "\n".join([ f"{name}: {type(obj).__name__}" for name, obj in output.variables.items() ]) e.add_field(name="Variables", value=vars, inline=False) e.add_field(name="Output", value=output.body or "NO OUTPUT", inline=False) await ctx.send(embed=e) @commands.is_owner() @tag.command() async def process(self, ctx: commands.Context, *, tagscript: str): """Process TagScript without storing.""" tag = Tag( "processed_tag", tagscript, invoker=ctx.author, author=ctx.author, author_id=ctx.author.id, uses=1, ctx=ctx, ) await self.process_tag(ctx, tag) await ctx.tick() async def store_tag(self, ctx: commands.Context, name: str, tagscript: str): async with self.config.guild(ctx.guild).tags() as t: t[name] = {"author": ctx.author.id, "uses": 0, "tag": tagscript} await ctx.send(f"Tag stored under the name `{name}`.") # thanks trusty, https://github.com/TrustyJAID/Trusty-cogs/blob/master/retrigger/retrigger.py#L1065 @tag.command() async def explain(self, ctx: commands.Context): """View Tag block documentation.""" with open(Path(__file__).parent / "README.md", "r", encoding="utf8") as infile: data = infile.read() pages = list( pagify(data, ["\n\n\n", "\n\n"], page_length=500, priority=True)) embeds = [] e = discord.Embed( title="Tags", color=await ctx.embed_color(), url= "https://github.com/phenom4n4n/phen-cogs/blob/master/tags/README.md", ) for index, page in enumerate(pages, start=1): embed = e.copy() embed.description = page embed.set_footer(text=f"{index}/{len(pages)}") embeds.append(embed) await menu(ctx, embeds, DEFAULT_CONTROLS) async def get_stored_tag(self, ctx: commands.Context, name: TagName, response: bool = True) -> Optional[Tag]: tags = await self.config.guild(ctx.guild).tags() tag = tags.get(name) if tag: tag = Tag.from_dict(name, tag, ctx=ctx) return tag return None @commands.Cog.listener() async def on_message_without_command(self, message: discord.Message): if (message.author.bot or not isinstance(message.author, discord.Member) or not message.guild): return if message.guild.id not in self.tag_cache.keys(): return if not await self.bot.message_eligible_as_command(message): return ctx = await self.bot.get_context(message) if ctx.prefix is None: return tag_command = message.content[len(ctx.prefix):] tag_split = tag_command.split(" ") if not tag_split: return tag_name = tag_split[0] if tag_name in self.tag_cache.get(message.guild.id, []): new_message = copy(message) new_message.content = f"{ctx.prefix}tag False {tag_command}" ctx = await self.bot.get_context(new_message) await self.bot.invoke(ctx) async def process_tag(self, ctx: commands.Context, tag: Tag, *, seed_variables: dict = {}, **kwargs) -> str: author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = TextChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) destination = ctx.channel seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } seed_variables.update(seed) output = tag.run(self.engine, seed_variables=seed_variables, **kwargs) to_gather = [] command_messages = [] content = output.body[:2000] if output.body else None actions = output.actions embed = actions.get("embed") if actions: if actions.get("requires") or actions.get("blacklist"): check, response = await self.validate_checks(ctx, actions) if not check: if response: await ctx.send(response[:2000]) else: start_adding_reactions(ctx.message, ["❌"]) return if delete := actions.get("delete"): if ctx.channel.permissions_for(ctx.me).manage_messages: to_gather.append(delete_quietly(ctx.message)) if not delete and (reactu := actions.get("reactu")): to_gather.append(self.do_reactu(ctx, reactu)) if actions.get("commands"): for command in actions["commands"]: if command.startswith("tag"): await ctx.send("Looping isn't allowed.") return new = copy(ctx.message) new.content = ctx.prefix + command command_messages.append(new) if target := actions.get("target"): if target == "dm": destination = await ctx.author.create_dm() else: try: chan = await self.channel_converter.convert( ctx, target) except commands.BadArgument: pass else: if chan.permissions_for(ctx.me).send_messages: destination = chan
class Utilities(commands.Cog): def __init__(self, bot): self.bot = bot blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), ] self.engine = Interpreter(blocks) @commands.command(name="iplookup", aliases=["ip", "ipinfo"]) async def iplookup(self, ctx, arg): suffix = (arg) lookup = ("http://ip-api.com/json/" + suffix + "?fields=66846719") values = requests.get(lookup).json() if values['status'] == "fail": return await ctx.send("Please provide a valid argument") embed = discord.Embed( colour=await self.bot.get_embed_color(ctx.channel)) embed.set_author(name=f"🌐 IP Lookup Details: {arg}") embed.add_field(name="IP", value=values['query'] or "N/A", inline=False) embed.add_field(name="Mobile", value=values['mobile'] or "N/A", inline=True) embed.add_field(name="Proxy", value=values['proxy'] or "N/A", inline=True) embed.add_field(name="Hosting", value=values['hosting'] or "N/A", inline=True) embed.add_field(name="Continent", value=values['continent'] or "N/A", inline=True) embed.add_field(name="Country", value=values['country'] or "N/A", inline=True) embed.add_field(name="Region", value=values['regionName'] or "N/A", inline=True) embed.add_field(name="City", value=values['city'] or "N/A", inline=True) embed.add_field(name="District", value=values['district'] or "N/A", inline=True) embed.add_field(name="Zip", value=values['zip'] or "N/A", inline=True) embed.add_field(name="Latitude", value=values['lat'] or "N/A", inline=True) embed.add_field(name="Longitude", value=values['lon'] or "N/A", inline=True) embed.add_field(name="💸 Currency", value=values['currency'] or "N/A", inline=True) embed.add_field(name="⏲️ Timezone", value=values['timezone'] or "N/A", inline=True) embed.add_field(name="🌐 ISP/Organization", value=values['isp'] or values['org'] or "Not available", inline=False) embed.set_footer(text=f"Requested by: {ctx.message.author}", icon_url=ctx.message.author.avatar_url) await ctx.send(embed=embed) @commands.command(aliases=["calc"]) async def calculate(self, ctx, *, query): """Math""" query = query.replace(",", "") engine_input = "{m:" + query + "}" start = time.monotonic() output = self.engine.process(engine_input) end = time.monotonic() output_string = output.body.replace("{m:", "").replace("}", "") e = discord.Embed( color=await ctx.embed_color(), title=f"Input: `{query}`", description=f"Output: `{output_string}`", ) e.set_footer(text=f"Calculated in {round((end - start) * 1000, 3)} ms") await ctx.send(embed=e)
block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("message"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] x = Interpreter(blocks) def press(button): o = x.process(app.getTextArea("input")).body app.clearTextArea("output") app.setTextArea("output", o) app = gui("TSE Playground", "750x450") app.setPadding([2, 2]) app.setInPadding([2, 2]) app.addTextArea("input", text="I see {rand:1,2,3,4} new items!", row=0, column=0) app.addTextArea("output", text="Press process to continue", row=0, column=1) app.addButton("process", press, row=1, column=0, colspan=2) app.go()
def run(self, interpreter: Interpreter, **kwargs) -> Interpreter.Response: return interpreter.process(self.tagscript, **kwargs)
class Tags(commands.Cog): """ Create and use tags. """ __version__ = "0.1.1" def format_help_for_context(self, ctx): pre_processed = super().format_help_for_context(ctx) n = "\n" if "\n\n" not in pre_processed else "" return f"{pre_processed}{n}\nCog Version: {self.__version__}" def __init__(self, bot: Red) -> None: self.bot = bot self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("message"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks) async def red_delete_data_for_user(self, *, requester: str, user_id: int): guilds_data = await self.config.all_guilds() for guild_id, data in guilds_data.items(): guild = self.bot.get_guild(guild_id) if guild and data["tags"]: for name, tag in data["tags"].items(): if str(user_id) in str(tag["author"]): async with self.config.guild(guild).tags() as t: del t[name] @commands.guild_only() @commands.group(invoke_without_command=True, usage="<tag_name> [args]") async def tag(self, ctx, response: Optional[bool], tag_name: tag_name, *, args: str = ""): """Tag management with TagScript. These commands use TagScriptEngine. [This site](https://github.com/JonSnowbd/TagScript/blob/v2/Documentation/Using%20TSE.md) has documentation on how to use TagScript blocks.""" if response is None: response = True tag_data = await self.get_stored_tag(ctx, tag_name, response) if tag_data: tag = tag_data["tag"] async with self.config.guild(ctx.guild).tags() as t: t[tag_name]["uses"] += 1 await self.process_tag(ctx, tag, args) @commands.mod_or_permissions(manage_guild=True) @tag.command() async def add(self, ctx, tag_name: tag_name, *, tagscript): """Add a tag with TagScript.""" command = self.bot.get_command(tag_name) if command: await ctx.send(f"`{tag_name}` is already a registered command.") return tag = await self.get_stored_tag(ctx, tag_name, False) if tag: msg = await ctx.send( f"`{tag_name}` is already registered tag. Would you like to overwrite it?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) await ctx.bot.wait_for("reaction_add", check=pred) if pred.result is False: await ctx.send("Action cancelled.") return await self.store_tag(ctx, tag_name, tagscript) @commands.mod_or_permissions(manage_guild=True) @tag.command() async def edit(self, ctx, tag_name: tag_name, *, tagscript): """Edit a tag with TagScript.""" tag = await self.get_stored_tag(ctx, tag_name, False) if not tag: return async with self.config.guild(ctx.guild).tags() as t: t[tag_name]["tag"] = tagscript await ctx.send(f"Tag `{tag_name}` edited.") @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["delete"]) async def remove(self, ctx, tag_name: tag_name): """Delete a tag.""" tag = await self.get_stored_tag(ctx, tag_name) if tag: async with self.config.guild(ctx.guild).tags() as e: del e[tag_name] await ctx.send("Tag deleted.") @tag.command(name="info") async def tag_info(self, ctx, name: str): """Get info about an tag that is stored on this server.""" tag = await self.get_stored_tag(ctx, name) if tag: e = discord.Embed( color=await ctx.embed_color(), title=f"`{name}` Info", description= f"Author: <@!{tag['author']}>\nUses: {tag['uses']}\nLength: {len(tag['tag'])}", ) e.add_field(name="TagScript", value=box(tag["tag"])) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @tag.command(name="raw") async def tag_raw(self, ctx, name: str): """Get a tag's raw content.""" tag = await self.get_stored_tag(ctx, name) if tag: await ctx.send( escape_markdown(tag["tag"]), allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False), ) @tag.command(name="list") async def tag_list(self, ctx): """View stored tags.""" tags = await self.config.guild(ctx.guild).tags() description = [] for name, tag in tags.items(): description.append(f"`{name}` - Created by <@!{tag['author']}>") description = "\n".join(description) color = await self.bot.get_embed_colour(ctx) e = discord.Embed(color=color, title=f"Stored Tags", description=description) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @commands.is_owner() @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["execute"]) async def run(self, ctx, *, tagscript): """Execute TagScript without storing.""" start = time.monotonic() output = self.engine.process(tagscript) end = time.monotonic() e = discord.Embed( color=await ctx.embed_color(), title="TagScriptEngine", description=f"Executed in **{round((end - start) * 1000, 3)}** ms", ) e.add_field(name="Input", value=tagscript, inline=False) if output.actions: e.add_field(name="Actions", value=output.actions) if output.variables: e.add_field(name="Variables", value=output.variables) e.add_field(name="Output", value=output.body or discord.Embed.Empty, inline=False) m = await ctx.send(embed=e) @commands.is_owner() @tag.command() async def process(self, ctx: commands.Context, *, tagscript: str): """Process TagScript without storing.""" await self.process_tag(ctx, tagscript, "") async def store_tag(self, ctx: commands.Context, name: str, tagscript: str): async with self.config.guild(ctx.guild).tags() as t: t[name] = {"author": ctx.author.id, "uses": 0, "tag": tagscript} await ctx.send(f"Tag stored under the name `{name}`.") async def get_stored_tag(self, ctx: commands.Context, name: tag_name, response: bool = True): tags = await self.config.guild(ctx.guild).tags() try: tag = tags[name] except KeyError: if response: await ctx.send(f'Tag "{name}" not found.') return return tag @commands.Cog.listener() async def on_message_without_command(self, message: discord.Message): if message.author.bot or not ( message.guild and await self.bot.message_eligible_as_command(message)): return ctx = await self.bot.get_context(message) if ctx.prefix is None: return tag_command = message.content[len(ctx.prefix):] tag_split = tag_command.split(" ") if not tag_split: return tag_name = tag_split[0] tag = await self.get_stored_tag(ctx, tag_name, False) if tag: new_message = copy(message) new_message.content = f"{ctx.prefix}tag False {tag_command}" await self.bot.process_commands(new_message) async def process_tag(self, ctx: commands.Context, tagscript: str, args: str) -> str: output = self.engine.process(tagscript) if output.body: o = output.body.replace("{args}", args) commands = COM_RE.findall(o) to_process = [] if commands: o = re.sub(COM_RE, "", o) for index, command in enumerate(commands): if index > 2: break if command.startswith("tag"): await ctx.send("Looping isn't allowed.") return new = copy(ctx.message) new.content = ctx.prefix + command to_process.append(self.bot.process_commands(new)) if "".join(o.strip()): await ctx.send(o) if to_process: await asyncio.gather(*to_process)
class Roles(MixinMeta): """ Useful role commands. """ def __init__(self): self.interpreter = Interpreter([LooseVariableGetterBlock()]) super().__init__() async def initialize(self): log.debug("Roles Initialize") await super().initialize() @commands.guild_only() @commands.group(invoke_without_command=True) async def role(self, ctx: commands.Context, member: TouchableMember(False), *, role: StrictRole(False)): """Base command for modifying roles. Invoking this command will add or remove the given role from the member, depending on whether they already had it.""" if role in member.roles and await can_run_command(ctx, "role remove"): com = self.bot.get_command("role remove") await ctx.invoke( com, member=member, role=role, ) elif role not in member.roles and await can_run_command( ctx, "role add"): com = self.bot.get_command("role add") await ctx.invoke( com, member=member, role=role, ) else: await ctx.send_help() @commands.bot_has_permissions(embed_links=True) @role.command("info") async def role_info(self, ctx: commands.Context, *, role: FuzzyRole): """Get information about a role.""" await ctx.send(embed=await self.get_info(role)) async def get_info(self, role: discord.Role) -> discord.Embed: if guild_roughly_chunked( role.guild) is False and self.bot.intents.members: await role.guild.chunk() description = [ f"{role.mention}", f"Members: {len(role.members)} | Position: {role.position}", f"Color: {role.color}", f"Hoisted: {role.hoist}", f"Mentionable: {role.mentionable}", ] if role.managed: description.append(f"Managed: {role.managed}") if role in await self.bot.get_mod_roles(role.guild): description.append(f"Mod Role: True") if role in await self.bot.get_admin_roles(role.guild): description.append(f"Admin Role: True") e = discord.Embed( color=role.color, title=role.name, description="\n".join(description), timestamp=role.created_at, ) e.set_footer(text=role.id) return e def format_member(self, member: discord.Member, formatting: str) -> str: output = self.interpreter.process(formatting, {"member": MemberAdapter(member)}) return output.body @commands.bot_has_permissions(attach_files=True) @commands.admin_or_permissions(manage_roles=True) @role.command("members", aliases=["dump"]) async def role_members( self, ctx: commands.Context, role: FuzzyRole, *, formatting: str = "{member} - {member(id)}", ): """ Sends a list of members in a role. You can supply a custom formatting tagscript for each member. The [member](https://phen-cogs.readthedocs.io/en/latest/tags/default_variables.html#author-block) block is available to use, found on the [TagScript documentation](https://phen-cogs.readthedocs.io/en/latest/index.html). **Example:** `[p]role dump @admin <t:{member(timestamp)}> - {member(mention)}` """ if guild_roughly_chunked( ctx.guild) is False and self.bot.intents.members: await ctx.guild.chunk() if not role.members: return await ctx.send(f"**{role}** has no members.") members = "\n".join( self.format_member(member, formatting) for member in role.members) if len(members) > 2000: await ctx.send(file=text_to_file(members, f"members.txt")) else: await ctx.send(members, allowed_mentions=discord.AllowedMentions.none()) @staticmethod def get_hsv(role: discord.Role): return rgb_to_hsv(*role.color.to_rgb()) @commands.bot_has_permissions(embed_links=True) @commands.admin_or_permissions(manage_roles=True) @role.command("colors") async def role_colors(self, ctx: commands.Context): """Sends the server's roles, ordered by color.""" roles = defaultdict(list) for r in ctx.guild.roles: roles[str(r.color)].append(r) roles = dict(sorted(roles.items(), key=lambda v: self.get_hsv(v[1][0]))) lines = [ f"**{color}**\n{' '.join(r.mention for r in rs)}" for color, rs in roles.items() ] for page in pagify("\n".join(lines)): e = discord.Embed(description=page) await ctx.send(embed=e) @commands.bot_has_permissions(manage_roles=True) @commands.admin_or_permissions(manage_roles=True) @role.command("create") async def role_create( self, ctx: commands.Context, color: Optional[discord.Color] = discord.Color.default(), hoist: Optional[bool] = False, *, name: str = None, ): """ Creates a role. Color and whether it is hoisted can be specified. """ if len(ctx.guild.roles) >= 250: return await ctx.send( "This server has reached the maximum role limit (250).") role = await ctx.guild.create_role(name=name, colour=color, hoist=hoist) await ctx.send(f"**{role}** created!", embed=await self.get_info(role)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("color", aliases=["colour"]) async def role_color(self, ctx: commands.Context, role: StrictRole(check_integrated=False), color: discord.Color): """Change a role's color.""" await role.edit(color=color) await ctx.send(f"**{role}** color changed to **{color}**.", embed=await self.get_info(role)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("hoist") async def role_hoist( self, ctx: commands.Context, role: StrictRole(check_integrated=False), hoisted: bool = None, ): """Toggle whether a role should appear seperate from other roles.""" hoisted = hoisted if hoisted is not None else not role.hoist await role.edit(hoist=hoisted) now = "now" if hoisted else "no longer" await ctx.send(f"**{role}** is {now} hoisted.", embed=await self.get_info(role)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("name") async def role_name(self, ctx: commands.Context, role: StrictRole(check_integrated=False), *, name: str): """Change a role's name.""" old_name = role.name await role.edit(name=name) await ctx.send(f"Changed **{old_name}** to **{name}**.", embed=await self.get_info(role)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("add") async def role_add(self, ctx: commands.Context, member: TouchableMember, *, role: StrictRole): """Add a role to a member.""" if role in member.roles: await ctx.send( f"**{member}** already has the role **{role}**. Maybe try removing it instead." ) return reason = get_audit_reason(ctx.author) await member.add_roles(role, reason=reason) await ctx.send(f"Added **{role.name}** to **{member}**.") @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("remove") async def role_remove(self, ctx: commands.Context, member: TouchableMember, *, role: StrictRole): """Remove a role from a member.""" if role not in member.roles: await ctx.send( f"**{member}** doesn't have the role **{role}**. Maybe try adding it instead." ) return reason = get_audit_reason(ctx.author) await member.remove_roles(role, reason=reason) await ctx.send(f"Removed **{role.name}** from **{member}**.") @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command(require_var_positional=True) async def addmulti(self, ctx: commands.Context, role: StrictRole, *members: TouchableMember): """Add a role to multiple members.""" reason = get_audit_reason(ctx.author) already_members = [] success_members = [] for member in members: if role not in member.roles: await member.add_roles(role, reason=reason) success_members.append(member) else: already_members.append(member) msg = [] if success_members: msg.append( f"Added **{role}** to {humanize_roles(success_members)}.") if already_members: msg.append( f"{humanize_roles(already_members)} already had **{role}**.") await ctx.send("\n".join(msg)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command(require_var_positional=True) async def removemulti(self, ctx: commands.Context, role: StrictRole, *members: TouchableMember): """Remove a role from multiple members.""" reason = get_audit_reason(ctx.author) already_members = [] success_members = [] for member in members: if role in member.roles: await member.remove_roles(role, reason=reason) success_members.append(member) else: already_members.append(member) msg = [] if success_members: msg.append( f"Removed **{role}** from {humanize_roles(success_members)}.") if already_members: msg.append( f"{humanize_roles(already_members)} didn't have **{role}**.") await ctx.send("\n".join(msg)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @commands.group(invoke_without_command=True, require_var_positional=True) async def multirole(self, ctx: commands.Context, member: TouchableMember, *roles: StrictRole): """Add multiple roles to a member.""" not_allowed = [] already_added = [] to_add = [] for role in roles: allowed = await is_allowed_by_role_hierarchy( self.bot, ctx.me, ctx.author, role) if not allowed[0]: not_allowed.append(role) elif role in member.roles: already_added.append(role) else: to_add.append(role) reason = get_audit_reason(ctx.author) msg = [] if to_add: await member.add_roles(*to_add, reason=reason) msg.append(f"Added {humanize_roles(to_add)} to **{member}**.") if already_added: msg.append( f"**{member}** already had {humanize_roles(already_added)}.") if not_allowed: msg.append( f"You do not have permission to assign the roles {humanize_roles(not_allowed)}." ) await ctx.send("\n".join(msg)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @multirole.command("remove", require_var_positional=True) async def multirole_remove(self, ctx: commands.Context, member: TouchableMember, *roles: StrictRole): """Remove multiple roles from a member.""" not_allowed = [] not_added = [] to_rm = [] for role in roles: allowed = await is_allowed_by_role_hierarchy( self.bot, ctx.me, ctx.author, role) if not allowed[0]: not_allowed.append(role) elif role not in member.roles: not_added.append(role) else: to_rm.append(role) reason = get_audit_reason(ctx.author) msg = [] if to_rm: await member.remove_roles(*to_rm, reason=reason) msg.append(f"Removed {humanize_roles(to_rm)} from **{member}**.") if not_added: msg.append( f"**{member}** didn't have {humanize_roles(not_added)}.") if not_allowed: msg.append( f"You do not have permission to assign the roles {humanize_roles(not_allowed)}." ) await ctx.send("\n".join(msg)) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command() async def all(self, ctx: commands.Context, *, role: StrictRole): """Add a role to all members of the server.""" await self.super_massrole(ctx, ctx.guild.members, role) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command(aliases=["removeall"]) async def rall(self, ctx: commands.Context, *, role: StrictRole): """Remove a role from all members of the server.""" member_list = self.get_member_list(ctx.guild.members, role, False) await self.super_massrole(ctx, member_list, role, "No one on the server has this role.", False) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command() async def humans(self, ctx: commands.Context, *, role: StrictRole): """Add a role to all humans (non-bots) in the server.""" await self.super_massrole( ctx, [member for member in ctx.guild.members if not member.bot], role, "Every human in the server has this role.", ) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command() async def rhumans(self, ctx: commands.Context, *, role: StrictRole): """Remove a role from all humans (non-bots) in the server.""" await self.super_massrole( ctx, [member for member in ctx.guild.members if not member.bot], role, "None of the humans in the server have this role.", False, ) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command() async def bots(self, ctx: commands.Context, *, role: StrictRole): """Add a role to all bots in the server.""" await self.super_massrole( ctx, [member for member in ctx.guild.members if member.bot], role, "Every bot in the server has this role.", ) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command() async def rbots(self, ctx: commands.Context, *, role: StrictRole): """Remove a role from all bots in the server.""" await self.super_massrole( ctx, [member for member in ctx.guild.members if member.bot], role, "None of the bots in the server have this role.", False, ) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("in") async def role_in(self, ctx: commands.Context, target_role: FuzzyRole, *, add_role: StrictRole): """Add a role to all members of a another role.""" await self.super_massrole( ctx, [member for member in target_role.members], add_role, f"Every member of **{target_role}** has this role.", ) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.command("rin") async def role_rin(self, ctx: commands.Context, target_role: FuzzyRole, *, remove_role: StrictRole): """Remove a role from all members of a another role.""" await self.super_massrole( ctx, [member for member in target_role.members], remove_role, f"No one in **{target_role}** has this role.", False, ) @commands.check(targeter_cog) @commands.admin_or_permissions(manage_roles=True) @commands.bot_has_permissions(manage_roles=True) @role.group() async def target(self, ctx: commands.Context): """ Modify roles using 'targeting' args. An explanation of Targeter and test commands to preview the members affected can be found with `[p]target`. """ @target.command("add") async def target_add(self, ctx: commands.Context, role: StrictRole, *, args: TargeterArgs): """ Add a role to members using targeting args. An explanation of Targeter and test commands to preview the members affected can be found with `[p]target`. """ await self.super_massrole( ctx, args, role, f"No one was found with the given args that was eligible to recieve **{role}**.", ) @target.command("remove") async def target_remove(self, ctx: commands.Context, role: StrictRole, *, args: TargeterArgs): """ Remove a role from members using targeting args. An explanation of Targeter and test commands to preview the members affected can be found with `[p]target`. """ await self.super_massrole( ctx, args, role, f"No one was found with the given args that was eligible have **{role}** removed from them.", False, ) async def super_massrole( self, ctx: commands.Context, members: list, role: discord.Role, fail_message: str = "Everyone in the server has this role.", adding: bool = True, ): if guild_roughly_chunked( ctx.guild) is False and self.bot.intents.members: await ctx.guild.chunk() member_list = self.get_member_list(members, role, adding) if not member_list: await ctx.send(fail_message) return verb = "add" if adding else "remove" word = "to" if adding else "from" await ctx.send( f"Beginning to {verb} **{role.name}** {word} **{len(member_list)}** members." ) async with ctx.typing(): result = await self.massrole(member_list, [role], get_audit_reason(ctx.author), adding) result_text = f"{verb.title()[:5]}ed **{role.name}** {word} **{len(result['completed'])}** members." if result["skipped"]: result_text += ( f"\nSkipped {verb[:5]}ing roles for **{len(result['skipped'])}** members." ) if result["failed"]: result_text += ( f"\nFailed {verb[:5]}ing roles for **{len(result['failed'])}** members." ) await ctx.send(result_text) def get_member_list(self, members: list, role: discord.Role, adding: bool = True): if adding: members = [ member for member in members if role not in member.roles ] else: members = [member for member in members if role in member.roles] return members async def massrole(self, members: list, roles: list, reason: str, adding: bool = True): completed = [] skipped = [] failed = [] for member in members: if adding: to_add = [role for role in roles if role not in member.roles] if to_add: try: await member.add_roles(*to_add, reason=reason) except Exception as e: failed.append(member) log.exception(f"Failed to add roles to {member}", exc_info=e) else: completed.append(member) else: skipped.append(member) else: to_remove = [role for role in roles if role in member.roles] if to_remove: try: await member.remove_roles(*to_remove, reason=reason) except Exception as e: failed.append(member) log.exception(f"Failed to remove roles from {member}", exc_info=e) else: completed.append(member) else: skipped.append(member) return {"completed": completed, "skipped": skipped, "failed": failed} @staticmethod def format_members(members: List[discord.Member]): length = len(members) s = "" if length == 1 else "s" return f"**{hn(length)}** member{s}" @role.command("uniquemembers", aliases=["um"], require_var_positional=True) async def role_uniquemembers(self, ctx: commands.Context, *roles: FuzzyRole): """ View the total unique members between multiple roles. """ roles_length = len(roles) if roles_length == 1: raise commands.UserFeedbackCheckFailure( "You must provide at least 2 roles.") if not ctx.guild.chunked: await ctx.guild.chunk() color = roles[0].color unique_members = set() description = [] for role in roles: unique_members.update(role.members) description.append( f"{role.mention}: {self.format_members(role.members)}") description.insert( 0, f"**Unique members**: {self.format_members(unique_members)}") e = discord.Embed( color=color, title=f"Unique members between {roles_length} roles", description="\n".join(description), ) ref = ctx.message.to_reference(fail_if_not_exists=False) await ctx.send(embed=e, reference=ref)
class Math(commands.Cog): """Do math""" def __init__(self, bot): self.bot = bot blocks = [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), ] self.engine = Interpreter(blocks) # Initialize a session self.session = aiohttp.ClientSession() @commands.command() async def math(self, ctx, *expression): """ Solve a math expression. Supports very complex problems too. For eg. `luci math sin(pi/4)` """ log = logging.getLogger("math") if (expression == ""): embed = discord.Embed( color=0xf34949, # Red title="Input A Expression") await ctx.send(embed=embed) return start = time.monotonic() api = "http://api.mathjs.org/v4/" params = {"expr": "".join(expression)} async with self.session.get(api, params=params) as response: end = time.monotonic() if (response.status != 200): log.info(expression) log.error(await response.text()) return embed = discord.Embed( color=0xf34949, # Red title=await response.text()) embed.add_field(name="Your Input:", value=f'`{"".join(expression)}`', inline=True) embed.add_field(name="Answer:", value=f"`{await response.text()}`", inline=True) embed.set_footer( text=f"Calculated in {round((end - start) * 1000, 3)} ms") await ctx.send(embed=embed) @commands.command(aliases=["calc"]) async def calculate(self, ctx, *, query): """ Faster but sometimes does not work.""" query = query.replace(",", "") engine_input = "{m:" + query + "}" start = time.monotonic() output = self.engine.process(engine_input) end = time.monotonic() output_string = output.body.replace("{m:", "").replace("}", "") embed = discord.Embed( color=0xf34949, title=f"Input: `{query}`", description=f"Output: `{output_string}`", ) embed.set_footer( text=f"Calculated in {round((end - start) * 1000, 3)} ms") await ctx.send(embed=embed) async def get_result(self, ctx, operation, expression): # First properly encode expression # Change "/" to (over) because the api says so if ("/" in expression): expression = expression.replace("/", "(over)") # Encode the url now encoded_expression = parse.quote(expression) api = "https://newton.now.sh/api/v2" start = time.monotonic() async with self.session.get( f"{api}/{operation}/{encoded_expression}") as response: data = await response.json() if ("error" in data.keys()): # Get coolcry emoji coolcry = self.bot.get_emoji(780445565476798475) embed = discord.Embed( title= f"Sorry! But the server was unable to solve the expression {coolcry}", color=0xf34949) return embed result = data["result"] end = time.monotonic() embed = discord.Embed( color=0xf34949, # Red title=result) embed.add_field(name="Your Input:", value=f'`{expression.replace("(over)", "/")}`', inline=True) embed.add_field(name="Answer:", value=f"`{result}`", inline=True) embed.set_footer( text=f"Calculated in {round((end - start) * 1000, 3)} ms") return embed @commands.command(aliases=["factor"]) async def factorise(self, ctx, *expression): """Factorise a polynomial equation. Please put tan(x) instead of tanx and so on for all trigonametric functions. Usage: `luci factorise x^2 + 2x`""" expression = "".join(expression) loading = await ctx.send("Calculating...") await ctx.trigger_typing() embed = await self.get_result(ctx=ctx, operation="factor", expression=expression) # First delete the calculating message await loading.delete() await ctx.send(embed=embed) @commands.command(aliases=["derivation", "differentiate"]) async def derive(self, ctx, *expression): """Differentiate a polynomial. Please put tan(x) instead of tanx and so on for all trigonametric functions. Usage: `luci derive x^2 + 2x`""" expression = "".join(expression) loading = await ctx.send("Calculating...") await ctx.trigger_typing() embed = await self.get_result(ctx=ctx, operation="derive", expression=expression) # First delete the calculating message await loading.delete() await ctx.send(embed=embed) @commands.command(aliases=["integration"]) async def integrate(self, ctx, *expression): """Integrate a polynomial. Please put tan(x) instead of tanx and so on for all trigonametric functions. Usage: `luci integrate x^2 + 2x`""" expression = "".join(expression) loading = await ctx.send("Calculating...") await ctx.trigger_typing() embed = await self.get_result(ctx=ctx, operation="integrate", expression=expression) # First delete the calculating message await loading.delete() await ctx.send(embed=embed) @commands.command(aliases=["solution", "zeroes", "roots"]) async def solve(self, ctx, *expression): """Find roots of a polynomial. Please put tan(x) instead of tanx and so on for all trigonametric functions. Usage: `luci roots x^2 + 2x`""" expression = "".join(expression) loading = await ctx.send("Calculating...") await ctx.trigger_typing() embed = await self.get_result(ctx=ctx, operation="zeroes", expression=expression) # First delete the calculating message await loading.delete() await ctx.send(embed=embed) @commands.command() async def tangent(self, ctx, *expression): """Find tangent of a curve at a point [Eg: `luci tangent 2|x^3`. See `luci help tangent`]. Please put tan(x) instead of tanx and so on for all trigonametric functions. Usage: `luci tangent 2|x^3` # Here 2 is the point where tangent is to be find""" expression = "".join(expression) loading = await ctx.send("Calculating...") await ctx.trigger_typing() embed = await self.get_result(ctx=ctx, operation="tangent", expression=expression) # First delete the calculating message await loading.delete() await ctx.send(embed=embed) @commands.command(name="area", aliases=["area under the curve", "definite integration"]) async def definite_integral(self, ctx, *expression): """Do definite integration on a polynomial [Eg: `luci area 2:4|x^3`. See `luci help area`]. Please put tan(x) instead of tanx and so on for all trigonametric functions. Usage: `luci area 2:4|x^3` # Here area is to be calulated from 2 to 4""" expression = "".join(expression) loading = await ctx.send("Calculating...") await ctx.trigger_typing() embed = await self.get_result(ctx=ctx, operation="area", expression=expression) # First delete the calculating message await loading.delete() await ctx.send(embed=embed)
class Tags(commands.Cog): """ Create and use tags. The TagScript documentation can be found [here](https://phen-cogs.readthedocs.io/en/latest/index.html). """ __version__ = "2.0.5" def format_help_for_context(self, ctx: commands.Context): pre_processed = super().format_help_for_context(ctx) n = "\n" if "\n\n" not in pre_processed else "" return f"{pre_processed}{n}\nCog Version: {self.__version__}" def __init__(self, bot: Red) -> None: self.bot = bot self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} default_global = {"tags": {}} self.config.register_guild(**default_guild) self.config.register_global(**default_global) blocks = stable_blocks + [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("args"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks) self.role_converter = commands.RoleConverter() self.channel_converter = commands.TextChannelConverter() self.member_converter = commands.MemberConverter() self.emoji_converter = commands.EmojiConverter() self.guild_tag_cache = defaultdict(dict) self.global_tag_cache = {} self.task = asyncio.create_task(self.cache_tags()) def cog_unload(self): if self.task: self.task.cancel() async def red_delete_data_for_user(self, *, requester: str, user_id: int): if requester not in ("discord_deleted_user", "user"): return guilds_data = await self.config.all_guilds() for guild_id, data in guilds_data.items(): guild = self.bot.get_guild(guild_id) if guild and data["tags"]: for name, tag in data["tags"].items(): if str(user_id) in str(tag["author"]): async with self.config.guild(guild).tags() as t: del t[name] async def cache_tags(self): guilds_data = await self.config.all_guilds() async for guild_id, guild_data in AsyncIter(guilds_data.items(), steps=100): async for tag_name, tag_data in AsyncIter( guild_data["tags"].items(), steps=50): tag_object = Tag.from_dict(self, tag_name, tag_data, guild_id=guild_id) self.guild_tag_cache[guild_id][tag_name] = tag_object global_tags = await self.config.tags() async for global_tag_name, global_tag_data in AsyncIter( global_tags.items(), steps=50): global_tag = Tag.from_dict(self, global_tag_name, global_tag_data) self.global_tag_cache[global_tag_name] = global_tag log.debug("tag cache built") def get_tag( self, guild: Optional[discord.Guild], tag_name: str, *, check_global: bool = True, global_priority: bool = False, ) -> Optional[Tag]: tag = None if global_priority is True and check_global is True: return self.global_tag_cache.get(tag_name) if guild is not None: tag = self.guild_tag_cache[guild.id].get(tag_name) if tag is None and check_global is True: tag = self.global_tag_cache.get(tag_name) return tag async def validate_tagscript(self, ctx: commands.Context, tagscript: str): output = self.engine.process(tagscript) is_owner = await self.bot.is_owner(ctx.author) if is_owner: return True author_perms = ctx.channel.permissions_for(ctx.author) if output.actions.get("overrides"): if not author_perms.manage_guild: raise MissingTagPermissions( "You must have **Manage Server** permissions to use the `override` block." ) if output.actions.get("allowed_mentions"): # if not author_perms.mention_everyone: if not is_owner: raise MissingTagPermissions( "You must have **Mention Everyone** permissions to use the `allowedmentions` block." ) return True @commands.command(usage="<tag_name> [args]") async def invoketag(self, ctx, response: Optional[bool], tag_name: str, *, args: Optional[str] = ""): """ Manually invoke a tag with its name and arguments. Restricting this command with permissions in servers will restrict all members from invoking tags. """ response = response or True try: _tag = await TagConverter(check_global=True).convert(ctx, tag_name) except commands.BadArgument as e: if response is True: await ctx.send(e) else: seed = {"args": adapter.StringAdapter(args)} await self.process_tag(ctx, _tag, seed_variables=seed) @commands.guild_only() @commands.group(aliases=["customcom"]) async def tag(self, ctx: commands.Context): """ Tag management with TagScript. These commands use TagScriptEngine. [This site](https://phen-cogs.readthedocs.io/en/latest/index.html) has documentation on how to use TagScript blocks. """ @commands.mod_or_permissions(manage_guild=True) @tag.command(name="add", aliases=["create", "+"]) async def tag_add(self, ctx: commands.Context, tag_name: TagName, *, tagscript: TagScriptConverter): """ Add a tag with TagScript. [Tag usage guide](https://phen-cogs.readthedocs.io/en/latest/blocks.html#usage) """ tag = self.get_tag(ctx.guild, tag_name) if tag: msg = await ctx.send( f"`{tag_name}` is already registered tag. Would you like to overwrite it?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: return await ctx.send("Tag edit cancelled.") if pred.result is False: return await ctx.send("Tag edit cancelled.") tag.tagscript = tagscript await tag.update_config() await ctx.send(f"Tag `{tag}` edited.") return tag = Tag(self, tag_name, tagscript, author_id=ctx.author.id, guild_id=ctx.guild.id) self.guild_tag_cache[ctx.guild.id][tag_name] = tag await tag.update_config() await ctx.send(f"Tag `{tag}` added.") @commands.mod_or_permissions(manage_guild=True) @tag.command(name="edit", aliases=["e"]) async def tag_edit(self, ctx: commands.Context, tag: TagConverter, *, tagscript: TagScriptConverter): """Edit a tag with TagScript.""" tag.tagscript = tagscript await tag.update_config() await ctx.send(f"Tag `{tag}` edited.") @commands.mod_or_permissions(manage_guild=True) @tag.command(name="remove", aliases=["delete", "-"]) async def tag_remove(self, ctx: commands.Context, tag: TagConverter): """Delete a tag.""" await tag.delete() await ctx.send(f"Tag `{tag}` deleted.") @tag.command(name="info") async def tag_info(self, ctx: commands.Context, tag: TagConverter): """Get info about an tag that is stored on this server.""" desc = [ f"Author: {tag.author.mention if tag.author else tag.author_id}", f"Uses: {tag.uses}", f"Length: {len(tag)}", ] e = discord.Embed( color=await ctx.embed_color(), title=f"Tag `{tag}` Info", description="\n".join(desc), ) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @tag.command(name="raw") async def tag_raw(self, ctx: commands.Context, tag: TagConverter): """Get a tag's raw content.""" for page in pagify(tag.tagscript, shorten_by=100): await ctx.send( escape_markdown(page), allowed_mentions=discord.AllowedMentions.none(), ) @tag.command(name="list") async def tag_list(self, ctx: commands.Context): """View stored tags.""" tags = self.guild_tag_cache[ctx.guild.id] if not tags: return await ctx.send("There are no stored tags on this server.") description = [] for name, tag in tags.items(): tagscript = tag.tagscript if len(tagscript) > 23: tagscript = tagscript[:20] + "..." tagscript = tagscript.replace("\n", " ") description.append(f"`{name}` - {escape_markdown(tagscript)}") description = "\n".join(description) e = discord.Embed(color=await ctx.embed_color()) e.set_author(name="Stored Tags", icon_url=ctx.guild.icon_url) embeds = [] pages = list(pagify(description)) for index, page in enumerate(pages, 1): embed = e.copy() embed.description = page embed.set_footer(text=f"{index}/{len(pages)} | {len(tags)} tags") embeds.append(embed) await menu(ctx, embeds, DEFAULT_CONTROLS) @commands.is_owner() @tag.command(name="run", aliases=["execute"]) async def tag_run(self, ctx: commands.Context, *, tagscript: str): """Execute TagScript without storing.""" start = time.monotonic() author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = ChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } output = self.engine.process(tagscript, seed_variables=seed) end = time.monotonic() e = discord.Embed( color=await ctx.embed_color(), title="TagScriptEngine", description=f"Executed in **{round((end - start) * 1000, 3)}** ms", ) e.add_field(name="Input", value=tagscript, inline=False) if output.actions: e.add_field(name="Actions", value=output.actions, inline=False) if output.variables: vars = "\n".join([ f"{name}: {type(obj).__name__}" for name, obj in output.variables.items() ]) e.add_field(name="Variables", value=vars, inline=False) e.add_field(name="Output", value=output.body or "NO OUTPUT", inline=False) await ctx.send(embed=e) @commands.is_owner() @tag.command(name="process") async def tag_process(self, ctx: commands.Context, *, tagscript: str): """Process TagScript without storing.""" tag = Tag( self, "processed_tag", tagscript, author_id=ctx.author.id, real=False, ) await self.process_tag(ctx, tag) await ctx.tick() @commands.is_owner() @tag.group(name="global") async def tag_global(self, ctx: commands.Context): """Manage global tags.""" @tag_global.command(name="add", aliases=["create", "+"]) async def tag_global_add(self, ctx: commands.Context, tag_name: TagName, *, tagscript: TagScriptConverter): """ Add a global tag with TagScript. [Tag usage guide](https://phen-cogs.readthedocs.io/en/latest/blocks.html#usage) """ tag = self.get_tag(None, tag_name, check_global=True) if tag: msg = await ctx.send( f"`{tag_name}` is already registered global tag. Would you like to overwrite it?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: return await ctx.send("Global tag edit cancelled.") if pred.result is False: return await ctx.send("Global tag edit cancelled.") tag.tagscript = tagscript await tag.update_config() await ctx.send(f"Global tag `{tag}` edited.") return tag = Tag(self, tag_name, tagscript, author_id=ctx.author.id) self.global_tag_cache[tag_name] = tag await tag.update_config() await ctx.send(f"Global tag `{tag}` added.") @tag_global.command(name="edit", aliases=["e"]) async def tag_global_edit( self, ctx: commands.Context, tag: TagConverter(check_global=True, global_priority=True), *, tagscript: TagScriptConverter, ): """Edit a global tag with TagScript.""" tag.tagscript = tagscript await tag.update_config() await ctx.send(f"Global tag `{tag}` edited.") @tag_global.command(name="remove", aliases=["delete", "-"]) async def tag_global_remove(self, ctx: commands.Context, tag: TagConverter(check_global=True, global_priority=True)): """Delete a global tag.""" await tag.delete() await ctx.send(f"Global tag `{tag}` deleted.") @tag_global.command(name="info") async def tag_global_info(self, ctx: commands.Context, tag: TagConverter(check_global=True, global_priority=True)): """Get info about a global tag.""" desc = [ f"Author: {tag.author.mention if tag.author else tag.author_id}", f"Uses: {tag.uses}", f"Length: {len(tag)}", ] e = discord.Embed( color=await ctx.embed_color(), title=f"Tag `{tag}` Info", description="\n".join(desc), ) e.set_author(name=ctx.me, icon_url=ctx.me.avatar_url) await ctx.send(embed=e) @tag_global.command(name="raw") async def tag_global_raw(self, ctx: commands.Context, tag: TagConverter(check_global=True, global_priority=True)): """Get a tag's raw content.""" for page in pagify(tag.tagscript, shorten_by=100): await ctx.send( escape_markdown(page), allowed_mentions=discord.AllowedMentions.none(), ) @tag_global.command(name="list") async def tag_global_list(self, ctx: commands.Context): """View stored tags.""" tags = self.global_tag_cache if not tags: return await ctx.send("There are no global tags.") description = [] for name, tag in tags.items(): tagscript = tag.tagscript if len(tagscript) > 23: tagscript = tagscript[:20] + "..." tagscript = tagscript.replace("\n", " ") description.append(f"`{name}` - {escape_markdown(tagscript)}") description = "\n".join(description) e = discord.Embed(color=await ctx.embed_color()) e.set_author(name="Global Tags", icon_url=ctx.me.avatar_url) embeds = [] pages = list(pagify(description)) for index, page in enumerate(pages, 1): embed = e.copy() embed.description = page embed.set_footer(text=f"{index}/{len(pages)} | {len(tags)} tags") embeds.append(embed) await menu(ctx, embeds, DEFAULT_CONTROLS) @commands.is_owner() @commands.command() async def migratealias(self, ctx: commands.Context): """Migrate alias global and guild configs to tags.""" alias_cog = self.bot.get_cog("Alias") if not alias_cog: return await ctx.send("Alias cog must be loaded to migrate data.") query = await ctx.send( f"Are you sure you want to migrate alias data to tags? (Y/n)") pred = MessagePredicate.yes_or_no(ctx) try: response = await self.bot.wait_for("message", check=pred, timeout=30) except asyncio.TimeoutError: return await ctx.send( "Query timed out, not migrating alias to tags.") if pred.result is False: return await ctx.send("Migration cancelled.") migrated_guilds = 0 migrated_guild_alias = 0 all_guild_data: dict = await alias_cog.config.all_guilds() async for guild_id, guild_data in AsyncIter(all_guild_data.items(), steps=100): if not guild_data["entries"]: continue migrated_guilds += 1 for alias in guild_data["entries"]: tagscript = "{c:" + alias["command"] + " {args}}" tag = Tag( self, alias["name"], tagscript, author_id=alias["creator"], guild_id=alias["guild"], uses=alias["uses"], ) self.guild_tag_cache[guild_id][alias["name"]] = tag await tag.update_config() migrated_guild_alias += 1 await ctx.send( f"Migrated {migrated_guild_alias} aliases from {migrated_guilds} " "servers to tags. Moving on to global aliases..") migrated_global_alias = 0 async for entry in AsyncIter(await alias_cog.config.entries(), steps=50): tagscript = "{c:" + entry["command"] + " {args}}" global_tag = Tag( self, entry["name"], tagscript, author_id=entry["creator"], uses=entry["uses"], ) self.global_tag_cache[entry["name"]] = global_tag await global_tag.update_config() migrated_global_alias += 1 await ctx.send( f"Migrated {migrated_global_alias} global aliases to tags. " "Migration completed, unload the alias cog to prevent command " f"duplication with `{ctx.clean_prefix}unload alias`.") @commands.Cog.listener() async def on_message_without_command(self, message: discord.Message): if message.author.bot: return if message.guild: if not isinstance(message.author, discord.Member): return if not await self.bot.message_eligible_as_command(message): return else: if not (await self.bot.allowed_by_whitelist_blacklist( message.author)): return await self.handle_message(message) async def handle_message(self, message: discord.Message): try: prefix = await Alias.get_prefix(self, message) except ValueError: return tag_command = message.content[len(prefix):] tag_split = tag_command.split(" ", 1) if self.get_tag(message.guild, tag_split[0], check_global=True): await self.invoke_tag_message(message, prefix, tag_command) async def invoke_tag_message(self, message: discord.Message, prefix: str, tag_command: str): new_message = copy(message) new_message.content = f"{prefix}invoketag False {tag_command}" ctx = await self.bot.get_context(new_message) await self.bot.invoke(ctx) async def process_tag(self, ctx: commands.Context, tag: Tag, *, seed_variables: dict = {}, **kwargs) -> str: author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = ChannelAdapter(ctx.channel) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, } if ctx.guild: guild = GuildAdapter(ctx.guild) seed.update(guild=guild, server=guild) seed_variables.update(seed) output = tag.run(self.engine, seed_variables=seed_variables, **kwargs) await tag.update_config() to_gather = [] command_messages = [] content = output.body[:2000] if output.body else None actions = output.actions embed = actions.get("embed") destination = ctx.channel replying = False if actions: try: await self.validate_checks(ctx, actions) except RequireCheckFailure as error: response = error.response if response is not None: if response.strip(): await ctx.send(response[:2000]) else: start_adding_reactions(ctx.message, ["❌"]) return if delete := actions.get("delete", False): to_gather.append(self.delete_quietly(ctx)) if delete is False and (reactu := actions.get("reactu")): to_gather.append(self.do_reactu(ctx, reactu)) if actions.get("commands"): for command in actions["commands"]: if command.startswith("tag") or command == "invoketag": await ctx.send("Tag looping isn't allowed.") return new = copy(ctx.message) new.content = ctx.prefix + command command_messages.append(new) if target := actions.get("target"): if target == "dm": destination = await ctx.author.create_dm() elif target == "reply": replying = True else: try: chan = await self.channel_converter.convert( ctx, target) except commands.BadArgument: pass else: if chan.permissions_for(ctx.me).send_messages: destination = chan
class Tags(commands.Cog): """ Create and use tags. The TagScript documentation can be found [here](https://github.com/phenom4n4n/phen-cogs/blob/master/tags/README.md). """ __version__ = "1.2.1" def format_help_for_context(self, ctx): pre_processed = super().format_help_for_context(ctx) n = "\n" if "\n\n" not in pre_processed else "" return f"{pre_processed}{n}\nCog Version: {self.__version__}" def __init__(self, bot: Red) -> None: self.bot = bot cog = self.bot.get_cog("CustomCommands") if cog: raise RuntimeError( "This cog conflicts with CustomCommands and cannot be loaded with both at the same time." ) self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = stable_blocks + [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("message"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks) async def red_delete_data_for_user(self, *, requester: str, user_id: int): guilds_data = await self.config.all_guilds() for guild_id, data in guilds_data.items(): guild = self.bot.get_guild(guild_id) if guild and data["tags"]: for name, tag in data["tags"].items(): if str(user_id) in str(tag["author"]): async with self.config.guild(guild).tags() as t: del t[name] @commands.guild_only() @commands.group(invoke_without_command=True, usage="<tag_name> [args]") async def tag(self, ctx, response: Optional[bool], tag_name: str, *, args: Optional[str] = ""): """Tag management with TagScript. These commands use TagScriptEngine. [This site](https://github.com/phenom4n4n/phen-cogs/blob/master/tags/README.md) has documentation on how to use TagScript blocks.""" if response is None: response = True try: tag = await TagConverter().convert(ctx, tag_name) except commands.BadArgument as e: if response: await ctx.send(e) return async with self.config.guild(ctx.guild).tags() as t: t[tag_name]["uses"] += 1 seed = {"args": adapter.StringAdapter(args)} await self.process_tag(ctx, tag, seed_variables=seed) @commands.mod_or_permissions(manage_guild=True) @tag.command() async def add(self, ctx, tag_name: TagName, *, tagscript): """Add a tag with TagScript.""" tag = await self.get_stored_tag(ctx, tag_name, False) if tag: msg = await ctx.send( f"`{tag_name}` is already registered tag. Would you like to overwrite it?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) await ctx.bot.wait_for("reaction_add", check=pred) if pred.result is False: await ctx.send("Action cancelled.") return await self.store_tag(ctx, tag_name, tagscript) @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["e"]) async def edit(self, ctx, tag: TagConverter, *, tagscript): """Edit a tag with TagScript.""" async with self.config.guild(ctx.guild).tags() as t: t[str(tag)]["tag"] = tagscript await ctx.send(f"Tag `{tag}` edited.") @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["delete"]) async def remove(self, ctx, tag: TagConverter): """Delete a tag.""" async with self.config.guild(ctx.guild).tags() as e: del e[str(tag)] await ctx.send("Tag deleted.") @tag.command(name="info") async def tag_info(self, ctx, tag: TagConverter): """Get info about an tag that is stored on this server.""" e = discord.Embed( color=await ctx.embed_color(), title=f"`{tag}` Info", description= f"Author: {tag.author.mention if tag.author else tag.author_id}\nUses: {tag.uses}\nLength: {len(tag)}", ) e.add_field(name="TagScript", value=box(str(tag))) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @tag.command(name="raw") async def tag_raw(self, ctx, tag: TagConverter): """Get a tag's raw content.""" await ctx.send( escape_markdown(tag.tagscript[:2000]), allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False), ) @tag.command(name="list") async def tag_list(self, ctx): """View stored tags.""" tags = await self.config.guild(ctx.guild).tags() description = [] for name, tag in tags.items(): description.append(f"`{name}` - Created by <@!{tag['author']}>") description = "\n".join(description) color = await self.bot.get_embed_colour(ctx) e = discord.Embed(color=color, title=f"Stored Tags", description=description) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @commands.is_owner() @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["execute"]) async def run(self, ctx: commands.Context, *, tagscript: str): """Execute TagScript without storing.""" start = time.monotonic() author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = TextChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } output = self.engine.process(tagscript, seed_variables=seed) end = time.monotonic() e = discord.Embed( color=await ctx.embed_color(), title="TagScriptEngine", description=f"Executed in **{round((end - start) * 1000, 3)}** ms", ) e.add_field(name="Input", value=tagscript, inline=False) if output.actions: e.add_field(name="Actions", value=output.actions, inline=False) if output.variables: vars = "\n".join([ f"{name}: {type(obj).__name__}" for name, obj in output.variables.items() ]) e.add_field(name="Variables", value=vars, inline=False) e.add_field(name="Output", value=output.body or "NO OUTPUT", inline=False) await ctx.send(embed=e) @commands.is_owner() @tag.command() async def process(self, ctx: commands.Context, *, tagscript: str): """Process TagScript without storing.""" tag = Tag( "processed_tag", tagscript, invoker=ctx.author, author=ctx.author, author_id=ctx.author.id, uses=1, ctx=ctx, ) await self.process_tag(ctx, tag) async def store_tag(self, ctx: commands.Context, name: str, tagscript: str): async with self.config.guild(ctx.guild).tags() as t: t[name] = {"author": ctx.author.id, "uses": 0, "tag": tagscript} await ctx.send(f"Tag stored under the name `{name}`.") async def get_stored_tag(self, ctx: commands.Context, name: TagName, response: bool = True): tags = await self.config.guild(ctx.guild).tags() tag = tags.get(name) if tag: tag = Tag.from_dict(name, tag, ctx=ctx) return tag return None @commands.Cog.listener() async def on_message_without_command(self, message: discord.Message): if message.author.bot or not ( message.guild and await self.bot.message_eligible_as_command(message)): return ctx = await self.bot.get_context(message) if ctx.prefix is None: return tag_command = message.content[len(ctx.prefix):] tag_split = tag_command.split(" ") if not tag_split: return tag_name = tag_split[0] tag = await self.get_stored_tag(ctx, tag_name, False) if tag: new_message = copy(message) new_message.content = f"{ctx.prefix}tag False {tag_command}" await self.bot.process_commands(new_message) async def process_tag(self, ctx: commands.Context, tag: Tag, *, seed_variables: dict = {}, **kwargs) -> str: author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = TextChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } seed_variables.update(seed) output = tag.run(self.engine, seed_variables=seed_variables, **kwargs) to_gather = [] commands_to_process = [] content = output.body[:2000] if output.body else None actions = output.actions embed = actions.get("embed") if actions: if actions.get("delete"): if ctx.channel.permissions_for(ctx.me).manage_messages: await delete_quietly(ctx.message) if actions.get("commands"): for command in actions["commands"]: if command.startswith("tag"): await ctx.send("Looping isn't allowed.") return new = copy(ctx.message) new.content = ctx.prefix + command commands_to_process.append(self.bot.process_commands(new)) if content or embed: try: await ctx.send(content, embed=embed) except discord.HTTPException: return await ctx.send( "I failed to send that embed. The tag has stopped processing." ) if to_gather: await asyncio.gather(*to_gather) if commands_to_process: await asyncio.gather(*commands_to_process)
class Tags(commands.Cog): """ Create and use tags. The TagScript documentation can be found [here](https://phen-cogs.readthedocs.io/en/latest/index.html). """ __version__ = "1.5.0" def format_help_for_context(self, ctx: commands.Context): pre_processed = super().format_help_for_context(ctx) n = "\n" if "\n\n" not in pre_processed else "" return f"{pre_processed}{n}\nCog Version: {self.__version__}" def __init__(self, bot: Red) -> None: self.bot = bot self.config = Config.get_conf( self, identifier=567234895692346562369, force_registration=True, ) default_guild = {"tags": {}} self.config.register_guild(**default_guild) blocks = stable_blocks + [ block.MathBlock(), block.RandomBlock(), block.RangeBlock(), block.AnyBlock(), block.IfBlock(), block.AllBlock(), block.BreakBlock(), block.StrfBlock(), block.StopBlock(), block.AssignmentBlock(), block.FiftyFiftyBlock(), block.ShortCutRedirectBlock("args"), block.LooseVariableGetterBlock(), block.SubstringBlock(), ] self.engine = Interpreter(blocks) self.role_converter = commands.RoleConverter() self.channel_converter = commands.TextChannelConverter() self.member_converter = commands.MemberConverter() self.emoji_converter = commands.EmojiConverter() self.guild_tag_cache = defaultdict(dict) self.task = asyncio.create_task(self.cache_tags()) def cog_unload(self): if self.task: self.task.cancel() async def red_delete_data_for_user(self, *, requester: str, user_id: int): if requester not in ("discord_deleted_user", "user"): return guilds_data = await self.config.all_guilds() for guild_id, data in guilds_data.items(): guild = self.bot.get_guild(guild_id) if guild and data["tags"]: for name, tag in data["tags"].items(): if str(user_id) in str(tag["author"]): async with self.config.guild(guild).tags() as t: del t[name] async def cache_tags(self): await self.bot.wait_until_ready() guilds_data = await self.config.all_guilds() for guild_id, data in guilds_data.items(): tags = data.get("tags", {}) guild = self.bot.get_guild(guild_id) or discord.Object(guild_id) for tag_name, tag_data in tags.items(): tag_object = Tag.from_dict(self, guild, tag_name, tag_data) self.guild_tag_cache[guild_id][tag_name] = tag_object log.debug("tag cache built") def get_tag(self, guild: discord.Guild, tag_name: str): return self.guild_tag_cache[guild.id].get(tag_name) async def validate_tagscript(self, ctx: commands.Context, tagscript: str): output = self.engine.process(tagscript) is_owner = await self.bot.is_owner(ctx.author) if is_owner: return True author_perms = ctx.channel.permissions_for(ctx.author) if output.actions.get("overrides"): if not author_perms.manage_guild: raise MissingTagPermissions( "You must have **Manage Server** permissions to use the `override` block." ) if output.actions.get("allowed_mentions"): # if not author_perms.mention_everyone: if not is_owner: raise MissingTagPermissions( "You must have **Mention Everyone** permissions to use the `allowedmentions` block." ) return True @commands.guild_only() @commands.group(invoke_without_command=True, usage="<tag_name> [args]", aliases=["customcom"]) async def tag(self, ctx, response: Optional[bool], tag_name: str, *, args: Optional[str] = ""): """ Tag management with TagScript. These commands use TagScriptEngine. [This site](https://phen-cogs.readthedocs.io/en/latest/index.html) has documentation on how to use TagScript blocks. """ if response is None: response = True try: _tag = await TagConverter().convert(ctx, tag_name) except commands.BadArgument as e: if response: await ctx.send(e) return seed = {"args": adapter.StringAdapter(args)} log.info( f"Processing tag for {tag_name} on {ctx.guild} ({ctx.guild.id})") await self.process_tag(ctx, _tag, seed_variables=seed) @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["create", "+"]) async def add(self, ctx: commands.Context, tag_name: TagName, *, tagscript: TagScriptConverter): """ Add a tag with TagScript. [Tag usage guide](https://phen-cogs.readthedocs.io/en/latest/blocks.html#usage) """ tag = self.get_tag(ctx.guild, tag_name) if tag: msg = await ctx.send( f"`{tag_name}` is already registered tag. Would you like to overwrite it?" ) start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS) pred = ReactionPredicate.yes_or_no(msg, ctx.author) try: await ctx.bot.wait_for("reaction_add", check=pred, timeout=30) except asyncio.TimeoutError: return await ctx.send("Tag edit cancelled.") if pred.result is False: return await ctx.send("Tag edit cancelled.") tag.tagscript = tagscript await tag.update_config() await ctx.send(f"Tag `{tag}` edited.") return tag = Tag(self, ctx.guild, tag_name, tagscript, author=ctx.author) self.guild_tag_cache[ctx.guild.id][tag_name] = tag async with self.config.guild(ctx.guild).tags() as t: t[tag_name] = tag.to_dict() await ctx.send(f"Tag `{tag}` added.") @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["e"]) async def edit(self, ctx: commands.Context, tag: TagConverter, *, tagscript: TagScriptConverter): """Edit a tag with TagScript.""" tag.tagscript = tagscript await tag.update_config() await ctx.send(f"Tag `{tag}` edited.") # await self.cache_tags() @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["delete", "-"]) async def remove(self, ctx: commands.Context, tag: TagConverter): """Delete a tag.""" async with self.config.guild(ctx.guild).tags() as e: del e[str(tag)] del self.guild_tag_cache[ctx.guild.id][str(tag)] await ctx.send("Tag deleted.") # await self.cache_tags() @tag.command(name="info") async def tag_info(self, ctx: commands.Context, tag: TagConverter): """Get info about an tag that is stored on this server.""" desc = [ f"Author: {tag.author.mention if tag.author else tag.author_id}", f"Uses: {tag.uses}", f"Length: {len(tag)}", ] e = discord.Embed( color=await ctx.embed_color(), title=f"Tag `{tag}` Info", description="\n".join(desc), ) e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) await ctx.send(embed=e) @tag.command(name="raw") async def tag_raw(self, ctx: commands.Context, tag: TagConverter): """Get a tag's raw content.""" await ctx.send( escape_markdown(tag.tagscript[:2000]), allowed_mentions=discord.AllowedMentions.none(), ) @tag.command(name="list") async def tag_list(self, ctx: commands.Context): """View stored tags.""" tags = self.guild_tag_cache[ctx.guild.id] if not tags: return await ctx.send("There are no stored tags on this server.") description = [] for name, tag in tags.items(): tagscript = tag.tagscript if len(tagscript) > 23: tagscript = tagscript[:20] + "..." tagscript = tagscript.replace("\n", " ") description.append(f"`{name}` - {escape_markdown(tagscript)}") description = "\n".join(description) e = discord.Embed(color=await ctx.embed_color(), title=f"Stored Tags") e.set_author(name=ctx.guild, icon_url=ctx.guild.icon_url) embeds = [] pages = list(pagify(description)) for index, page in enumerate(pages, 1): embed = e.copy() embed.description = page embed.set_footer(text=f"{index}/{len(pages)}") embeds.append(embed) await menu(ctx, embeds, DEFAULT_CONTROLS) @commands.is_owner() @commands.mod_or_permissions(manage_guild=True) @tag.command(aliases=["execute"]) async def run(self, ctx: commands.Context, *, tagscript: str): """Execute TagScript without storing.""" start = time.monotonic() author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = TextChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } output = self.engine.process(tagscript, seed_variables=seed) end = time.monotonic() e = discord.Embed( color=await ctx.embed_color(), title="TagScriptEngine", description=f"Executed in **{round((end - start) * 1000, 3)}** ms", ) e.add_field(name="Input", value=tagscript, inline=False) if output.actions: e.add_field(name="Actions", value=output.actions, inline=False) if output.variables: vars = "\n".join([ f"{name}: {type(obj).__name__}" for name, obj in output.variables.items() ]) e.add_field(name="Variables", value=vars, inline=False) e.add_field(name="Output", value=output.body or "NO OUTPUT", inline=False) await ctx.send(embed=e) @commands.is_owner() @tag.command() async def process(self, ctx: commands.Context, *, tagscript: str): """Process TagScript without storing.""" tag = Tag( self, ctx.guild, "processed_tag", tagscript, author=ctx.author, real=False, ) await self.process_tag(ctx, tag) await ctx.tick() @commands.Cog.listener() async def on_message_without_command(self, message: discord.Message): if (message.author.bot or not isinstance(message.author, discord.Member) or not message.guild): return if not self.guild_tag_cache[message.guild.id]: return if not await self.bot.message_eligible_as_command(message): return ctx = await self.bot.get_context(message) if ctx.prefix is None: return tag_command = message.content[len(ctx.prefix):] tag_split = tag_command.split(" ", 1) if self.get_tag(message.guild, tag_split[0]): new_message = copy(message) new_message.content = f"{ctx.prefix}tag False {tag_command}" ctx = await self.bot.get_context(new_message) await self.bot.invoke(ctx) async def process_tag(self, ctx: commands.Context, tag: Tag, *, seed_variables: dict = {}, **kwargs) -> str: author = MemberAdapter(ctx.author) target = MemberAdapter( ctx.message.mentions[0]) if ctx.message.mentions else author channel = TextChannelAdapter(ctx.channel) guild = GuildAdapter(ctx.guild) seed = { "author": author, "user": author, "target": target, "member": target, "channel": channel, "guild": guild, "server": guild, } seed_variables.update(seed) output = tag.run(self.engine, seed_variables=seed_variables, **kwargs) await tag.update_config() to_gather = [] command_messages = [] content = output.body[:2000] if output.body else None actions = output.actions embed = actions.get("embed") destination = ctx.channel replying = False if actions: try: await self.validate_checks(ctx, actions) except RequireCheckFailure as error: response = error.response if response is not None: if response: await ctx.send(response[:2000]) else: start_adding_reactions(ctx.message, ["❌"]) return if delete := actions.get("delete", False): to_gather.append(self.delete_quietly(ctx)) if delete is False and (reactu := actions.get("reactu")): to_gather.append(self.do_reactu(ctx, reactu)) if actions.get("commands"): for command in actions["commands"]: if command.startswith("tag"): await ctx.send("Looping isn't allowed.") return new = copy(ctx.message) new.content = ctx.prefix + command command_messages.append(new) if target := actions.get("target"): if target == "dm": destination = await ctx.author.create_dm() elif target == "reply": replying = True else: try: chan = await self.channel_converter.convert( ctx, target) except commands.BadArgument: pass else: if chan.permissions_for(ctx.me).send_messages: destination = chan
def __init__(self): self.interpreter = Interpreter([LooseVariableGetterBlock()]) super().__init__()