1616from dateutil import parser
1717
1818from core import checks
19- from core .models import DMDisabled , PermissionLevel , SimilarCategoryConverter , getLogger
19+ from core .models import DMDisabled , PermissionLevel , SimilarCategoryConverter , UnseenFormatter , getLogger
2020from core .paginator import EmbedPaginatorSession
2121from core .thread import Thread
2222from core .time import UserFriendlyTime , human_timedelta
2525logger = getLogger (__name__ )
2626
2727
28+ # Arg names reserved by formatreply commands (channel, recipient, author).
29+ RESERVED_ARG_NAMES = {"channel" , "recipient" , "author" }
30+
31+
2832class Modmail (commands .Cog ):
2933 """Commands directly related to Modmail functionality."""
3034
@@ -209,7 +213,7 @@ async def snippet(self, ctx, *, name: str.lower = None):
209213
210214 When `{prefix}snippet` is used by itself, this will retrieve
211215 a list of snippets that are currently set. `{prefix}snippet-name` will show what the
212- snippet point to.
216+ snippet points to.
213217
214218 To create a snippet:
215219 - `{prefix}snippet add snippet-name A pre-defined text.`
@@ -535,6 +539,214 @@ async def snippet_rename(self, ctx, name: str.lower, *, value):
535539 embed = create_not_found_embed (name , self .bot .snippets .keys (), "Snippet" )
536540 await ctx .send (embed = embed )
537541
542+ @commands .group (invoke_without_command = True )
543+ @checks .has_permissions (PermissionLevel .SUPPORTER )
544+ async def args (self , ctx , * , name : str .lower = None ):
545+ """
546+ Create dynamic args for use in replies.
547+
548+ When `{prefix}args` is used by itself, this will retrieve
549+ a list of args that are currently set. `{prefix}args name` will show what the
550+ arg points to.
551+
552+ To create an arg:
553+ - `{prefix}args add arg-name A value.`
554+
555+ You can use your arg in a reply with `{arg-name}`.
556+ """
557+
558+ if name is not None :
559+ if name == "compact" :
560+ embeds = []
561+
562+ for i , names in enumerate (zip_longest (* (iter (sorted (self .bot .args )),) * 15 )):
563+ description = format_description (i , names )
564+ embed = discord .Embed (color = self .bot .main_color , description = description )
565+ embed .set_author (name = "Args" , icon_url = self .bot .get_guild_icon (guild = ctx .guild , size = 128 ))
566+ embeds .append (embed )
567+
568+ session = EmbedPaginatorSession (ctx , * embeds )
569+ await session .run ()
570+ return
571+
572+ if name not in self .bot .args :
573+ embed = create_not_found_embed (name , self .bot .args .keys (), "Arg" )
574+ else :
575+ val = self .bot .args [name ]
576+ embed = discord .Embed (
577+ title = f'Arg - "{ name } ":' ,
578+ description = val ,
579+ color = self .bot .main_color ,
580+ )
581+ return await ctx .send (embed = embed )
582+
583+ if not self .bot .args :
584+ embed = discord .Embed (
585+ color = self .bot .error_color ,
586+ description = "You don't have any args at the moment." ,
587+ )
588+ embed .set_footer (text = f'See "{ self .bot .prefix } help args add" for how to add an arg.' )
589+ embed .set_author (
590+ name = "Args" ,
591+ icon_url = self .bot .get_guild_icon (guild = ctx .guild , size = 128 ),
592+ )
593+ return await ctx .send (embed = embed )
594+
595+ embeds = [discord .Embed (color = self .bot .main_color ) for _ in range ((len (self .bot .args ) + 9 ) // 10 )]
596+ for embed in embeds :
597+ embed .set_author (name = "Args" , icon_url = self .bot .get_guild_icon (guild = ctx .guild , size = 128 ))
598+
599+ for i , arg in enumerate (sorted (self .bot .args .items ())):
600+ embeds [i // 10 ].add_field (name = arg [0 ], value = return_or_truncate (arg [1 ], 350 ), inline = False )
601+
602+ session = EmbedPaginatorSession (ctx , * embeds )
603+ await session .run ()
604+
605+ @args .command (name = "raw" )
606+ @checks .has_permissions (PermissionLevel .SUPPORTER )
607+ async def args_raw (self , ctx , * , name : str .lower ):
608+ """
609+ View the raw content of an arg.
610+ """
611+ if name not in self .bot .args :
612+ embed = create_not_found_embed (name , self .bot .args .keys (), "Arg" )
613+ else :
614+ val = truncate (escape_code_block (self .bot .args [name ]), 2048 - 7 )
615+ embed = discord .Embed (
616+ title = f'Raw arg - "{ name } ":' ,
617+ description = f"```\n { val } ```" ,
618+ color = self .bot .main_color ,
619+ )
620+
621+ return await ctx .send (embed = embed )
622+
623+ @args .command (name = "add" , aliases = ["create" , "make" ])
624+ @checks .has_permissions (PermissionLevel .SUPPORTER )
625+ async def args_add (self , ctx , name : str .lower , * , value : commands .clean_content ):
626+ """
627+ Add an arg.
628+
629+ To add an arg, simply do: ```
630+ {prefix}args add name value
631+ ```
632+ """
633+ if name in self .bot .args :
634+ embed = discord .Embed (
635+ title = "Error" ,
636+ color = self .bot .error_color ,
637+ description = f"Arg `{ name } ` already exists." ,
638+ )
639+ return await ctx .send (embed = embed )
640+
641+ if name in RESERVED_ARG_NAMES :
642+ embed = discord .Embed (
643+ title = "Error" ,
644+ color = self .bot .error_color ,
645+ description = f"Arg name `{ name } ` is reserved (used by formatreply commands). "
646+ f"Reserved names: { ', ' .join (f'`{ n } `' for n in sorted (RESERVED_ARG_NAMES ))} ." ,
647+ )
648+ return await ctx .send (embed = embed )
649+
650+ if len (name ) > 120 :
651+ embed = discord .Embed (
652+ title = "Error" ,
653+ color = self .bot .error_color ,
654+ description = "Arg names cannot be longer than 120 characters." ,
655+ )
656+ return await ctx .send (embed = embed )
657+
658+ self .bot .args [name ] = value
659+ await self .bot .config .update ()
660+
661+ embed = discord .Embed (
662+ title = "Added arg" ,
663+ color = self .bot .main_color ,
664+ description = "Successfully created arg." ,
665+ )
666+ return await ctx .send (embed = embed )
667+
668+ @args .command (name = "remove" , aliases = ["del" , "delete" ])
669+ @checks .has_permissions (PermissionLevel .SUPPORTER )
670+ async def args_remove (self , ctx , * , name : str .lower ):
671+ """Remove an arg."""
672+ if name in self .bot .args :
673+ self .bot .args .pop (name )
674+ await self .bot .config .update ()
675+ embed = discord .Embed (
676+ title = "Removed arg" ,
677+ color = self .bot .main_color ,
678+ description = f"Arg `{ name } ` is now deleted." ,
679+ )
680+ else :
681+ embed = create_not_found_embed (name , self .bot .args .keys (), "Arg" )
682+ await ctx .send (embed = embed )
683+
684+ @args .command (name = "edit" )
685+ @checks .has_permissions (PermissionLevel .SUPPORTER )
686+ async def args_edit (self , ctx , name : str .lower , * , value ):
687+ """
688+ Edit an arg.
689+ """
690+ if name in self .bot .args :
691+ self .bot .args [name ] = value
692+ await self .bot .config .update ()
693+
694+ embed = discord .Embed (
695+ title = "Edited arg" ,
696+ color = self .bot .main_color ,
697+ description = f'`{ name } ` will now be replaced with "{ value } ".' ,
698+ )
699+ else :
700+ embed = create_not_found_embed (name , self .bot .args .keys (), "Arg" )
701+ await ctx .send (embed = embed )
702+
703+ @args .command (name = "rename" )
704+ @checks .has_permissions (PermissionLevel .SUPPORTER )
705+ async def args_rename (self , ctx , name : str .lower , * , value : commands .clean_content ):
706+ """
707+ Rename an arg.
708+ """
709+ if name not in self .bot .args :
710+ embed = create_not_found_embed (name , self .bot .args .keys (), "Arg" )
711+ return await ctx .send (embed = embed )
712+
713+ if value in self .bot .args :
714+ embed = discord .Embed (
715+ title = "Error" ,
716+ color = self .bot .error_color ,
717+ description = f"Arg `{ value } ` already exists." ,
718+ )
719+ return await ctx .send (embed = embed )
720+
721+ if value in RESERVED_ARG_NAMES :
722+ embed = discord .Embed (
723+ title = "Error" ,
724+ color = self .bot .error_color ,
725+ description = f"Arg name `{ value } ` is reserved (used by formatreply commands). "
726+ f"Reserved names: { ', ' .join (f'`{ n } `' for n in sorted (RESERVED_ARG_NAMES ))} ." ,
727+ )
728+ return await ctx .send (embed = embed )
729+
730+ if len (value ) > 120 :
731+ embed = discord .Embed (
732+ title = "Error" ,
733+ color = self .bot .error_color ,
734+ description = "Arg names cannot be longer than 120 characters." ,
735+ )
736+ return await ctx .send (embed = embed )
737+
738+ old_arg_value = self .bot .args [name ]
739+ self .bot .args .pop (name )
740+ self .bot .args [value ] = old_arg_value
741+ await self .bot .config .update ()
742+
743+ embed = discord .Embed (
744+ title = "Renamed arg" ,
745+ color = self .bot .main_color ,
746+ description = f'`{ name } ` has been renamed to "{ value } ".' ,
747+ )
748+ await ctx .send (embed = embed )
749+
538750 @commands .command (usage = "<category> [options]" )
539751 @checks .has_permissions (PermissionLevel .MODERATOR )
540752 @checks .thread_only ()
@@ -1510,6 +1722,19 @@ async def reply(self, ctx, *, msg: str = ""):
15101722 automatically embedding image URLs.
15111723 """
15121724
1725+ if self .bot .args :
1726+ msg = UnseenFormatter ().format (msg , ** self .bot .args )
1727+
1728+ if len (msg ) > 4096 :
1729+ return await ctx .send (
1730+ embed = discord .Embed (
1731+ title = "Error" ,
1732+ color = self .bot .error_color ,
1733+ description = "The resulting message is too long to fit in an embed description "
1734+ f"({ len (msg )} /4096 characters). Please shorten your message or args." ,
1735+ )
1736+ )
1737+
15131738 # Ensure logs record only the reply text, not the command.
15141739 ctx .message .content = msg
15151740 async with safe_typing (ctx ):
@@ -1532,10 +1757,22 @@ async def freply(self, ctx, *, msg: str = ""):
15321757 """
15331758 msg = self .bot .formatter .format (
15341759 msg ,
1760+ ** self .bot .args ,
15351761 channel = ctx .channel ,
15361762 recipient = ctx .thread .recipient ,
15371763 author = ctx .message .author ,
15381764 )
1765+
1766+ if len (msg ) > 4096 :
1767+ return await ctx .send (
1768+ embed = discord .Embed (
1769+ title = "Error" ,
1770+ color = self .bot .error_color ,
1771+ description = "The resulting message is too long to fit in an embed description "
1772+ f"({ len (msg )} /4096 characters). Please shorten your message or args." ,
1773+ )
1774+ )
1775+
15391776 # Ensure logs record only the reply text, not the command.
15401777 ctx .message .content = msg
15411778 async with safe_typing (ctx ):
@@ -1558,10 +1795,22 @@ async def fareply(self, ctx, *, msg: str = ""):
15581795 """
15591796 msg = self .bot .formatter .format (
15601797 msg ,
1798+ ** self .bot .args ,
15611799 channel = ctx .channel ,
15621800 recipient = ctx .thread .recipient ,
15631801 author = ctx .message .author ,
15641802 )
1803+
1804+ if len (msg ) > 4096 :
1805+ return await ctx .send (
1806+ embed = discord .Embed (
1807+ title = "Error" ,
1808+ color = self .bot .error_color ,
1809+ description = "The resulting message is too long to fit in an embed description "
1810+ f"({ len (msg )} /4096 characters). Please shorten your message or args." ,
1811+ )
1812+ )
1813+
15651814 # Ensure logs record only the reply text, not the command.
15661815 ctx .message .content = msg
15671816 async with safe_typing (ctx ):
@@ -1584,10 +1833,22 @@ async def fpreply(self, ctx, *, msg: str = ""):
15841833 """
15851834 msg = self .bot .formatter .format (
15861835 msg ,
1836+ ** self .bot .args ,
15871837 channel = ctx .channel ,
15881838 recipient = ctx .thread .recipient ,
15891839 author = ctx .message .author ,
15901840 )
1841+
1842+ if len (msg ) > 4096 :
1843+ return await ctx .send (
1844+ embed = discord .Embed (
1845+ title = "Error" ,
1846+ color = self .bot .error_color ,
1847+ description = "The resulting message is too long to fit in an embed description "
1848+ f"({ len (msg )} /4096 characters). Please shorten your message or args." ,
1849+ )
1850+ )
1851+
15911852 # Ensure logs record only the reply text, not the command.
15921853 ctx .message .content = msg
15931854 async with safe_typing (ctx ):
@@ -1610,10 +1871,22 @@ async def fpareply(self, ctx, *, msg: str = ""):
16101871 """
16111872 msg = self .bot .formatter .format (
16121873 msg ,
1874+ ** self .bot .args ,
16131875 channel = ctx .channel ,
16141876 recipient = ctx .thread .recipient ,
16151877 author = ctx .message .author ,
16161878 )
1879+
1880+ if len (msg ) > 4096 :
1881+ return await ctx .send (
1882+ embed = discord .Embed (
1883+ title = "Error" ,
1884+ color = self .bot .error_color ,
1885+ description = "The resulting message is too long to fit in an embed description "
1886+ f"({ len (msg )} /4096 characters). Please shorten your message or args." ,
1887+ )
1888+ )
1889+
16171890 # Ensure logs record only the reply text, not the command.
16181891 ctx .message .content = msg
16191892 async with safe_typing (ctx ):
0 commit comments