mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-03 16:24:27 -05:00 
			
		
		
		
	Make extensive use of raw string literals C#11 feature
This commit is contained in:
		@@ -364,26 +364,27 @@ public sealed class Bot
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (arg.Exception is { InnerException: WebSocketClosedException { CloseCode: 4014 } })
 | 
					        if (arg.Exception is { InnerException: WebSocketClosedException { CloseCode: 4014 } })
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Log.Error(@"
 | 
					            Log.Error("""
 | 
				
			||||||
Login failed.
 | 
					                Login failed.
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
*** Please enable privileged intents ***
 | 
					                *** Please enable privileged intents ***
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
Certain Nadeko features require Discord's privileged gateway intents.
 | 
					                Certain Nadeko features require Discord's privileged gateway intents.
 | 
				
			||||||
These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding.
 | 
					                These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding.
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
How to enable privileged intents:
 | 
					                How to enable privileged intents:
 | 
				
			||||||
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
 | 
					                1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
 | 
				
			||||||
2. Select your Application.
 | 
					                2. Select your Application.
 | 
				
			||||||
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
 | 
					                3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
 | 
				
			||||||
4. Enable all intents.
 | 
					                4. Enable all intents.
 | 
				
			||||||
5. Restart your bot.
 | 
					                5. Restart your bot.
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
Read this only if your bot is in 100 or more servers:
 | 
					                Read this only if your bot is in 100 or more servers:
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal.
 | 
					                You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal.
 | 
				
			||||||
Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before.
 | 
					                Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before.
 | 
				
			||||||
While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the nadeko's features");
 | 
					                While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the nadeko's features
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
            return Task.CompletedTask;
 | 
					            return Task.CompletedTask;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,74 +11,89 @@ namespace NadekoBot.Common.Configs;
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public sealed partial class BotConfig : ICloneable<BotConfig>
 | 
					public sealed partial class BotConfig : ICloneable<BotConfig>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"DO NOT CHANGE")]
 | 
					    [Comment("""DO NOT CHANGE""")]
 | 
				
			||||||
    public int Version { get; set; } = 5;
 | 
					    public int Version { get; set; } = 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Most commands, when executed, have a small colored line
 | 
					    [Comment("""
 | 
				
			||||||
next to the response. The color depends whether the command
 | 
					        Most commands, when executed, have a small colored line
 | 
				
			||||||
is completed, errored or in progress (pending)
 | 
					        next to the response. The color depends whether the command
 | 
				
			||||||
Color settings below are for the color of those lines.
 | 
					        is completed, errored or in progress (pending)
 | 
				
			||||||
To get color's hex, you can go here https://htmlcolorcodes.com/
 | 
					        Color settings below are for the color of those lines.
 | 
				
			||||||
and copy the hex code fo your selected color (marked as #)")]
 | 
					        To get color's hex, you can go here https://htmlcolorcodes.com/
 | 
				
			||||||
 | 
					        and copy the hex code fo your selected color (marked as #)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public ColorConfig Color { get; set; }
 | 
					    public ColorConfig Color { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
 | 
					    [Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
 | 
				
			||||||
    public CultureInfo DefaultLocale { get; set; }
 | 
					    public CultureInfo DefaultLocale { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Style in which executed commands will show up in the console.
 | 
					    [Comment("""
 | 
				
			||||||
Allowed values: Simple, Normal, None")]
 | 
					        Style in which executed commands will show up in the console.
 | 
				
			||||||
 | 
					        Allowed values: Simple, Normal, None
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public ConsoleOutputType ConsoleOutputType { get; set; }
 | 
					    public ConsoleOutputType ConsoleOutputType { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Whether the bot will check for new releases every hour")]
 | 
					    [Comment("""Whether the bot will check for new releases every hour""")]
 | 
				
			||||||
    public bool CheckForUpdates { get; set; } = true;
 | 
					    public bool CheckForUpdates { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
 | 
					    [Comment("""Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?""")]
 | 
				
			||||||
    public bool ForwardMessages { get; set; }
 | 
					    public bool ForwardMessages { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(
 | 
					    [Comment("""
 | 
				
			||||||
        @"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
 | 
					            Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
 | 
				
			||||||
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
 | 
					            or all owners? (this might cause the bot to lag if there's a lot of owners specified)
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
    public bool ForwardToAllOwners { get; set; }
 | 
					    public bool ForwardToAllOwners { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(@"Any messages sent by users in Bot's DM to be forwarded to the specified channel.
 | 
					    [Comment("""
 | 
				
			||||||
This option will only work when ForwardToAllOwners is set to false")]
 | 
					        Any messages sent by users in Bot's DM to be forwarded to the specified channel.
 | 
				
			||||||
 | 
					        This option will only work when ForwardToAllOwners is set to false
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public ulong? ForwardToChannel { get; set; }
 | 
					    public ulong? ForwardToChannel { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"When a user DMs the bot with a message which is not a command
 | 
					    [Comment("""
 | 
				
			||||||
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
 | 
					        When a user DMs the bot with a message which is not a command
 | 
				
			||||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
 | 
					        they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
 | 
				
			||||||
 | 
					        Supports embeds. How it looks: https://puu.sh/B0BLV.png
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
					    [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
				
			||||||
    public string DmHelpText { get; set; }
 | 
					    public string DmHelpText { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
 | 
					    [Comment("""
 | 
				
			||||||
Case insensitive.
 | 
					        Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
 | 
				
			||||||
Leave empty to reply with DmHelpText to every DM.")]
 | 
					        Case insensitive.
 | 
				
			||||||
 | 
					        Leave empty to reply with DmHelpText to every DM.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public List<string> DmHelpTextKeywords { get; set; }
 | 
					    public List<string> DmHelpTextKeywords { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"This is the response for the .h command")]
 | 
					    [Comment("""This is the response for the .h command""")]
 | 
				
			||||||
    [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
					    [YamlMember(ScalarStyle = ScalarStyle.Literal)]
 | 
				
			||||||
    public string HelpText { get; set; }
 | 
					    public string HelpText { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"List of modules and commands completely blocked on the bot")]
 | 
					    [Comment("""List of modules and commands completely blocked on the bot""")]
 | 
				
			||||||
    public BlockedConfig Blocked { get; set; }
 | 
					    public BlockedConfig Blocked { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Which string will be used to recognize the commands")]
 | 
					    [Comment("""Which string will be used to recognize the commands""")]
 | 
				
			||||||
    public string Prefix { get; set; }
 | 
					    public string Prefix { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
 | 
					    [Comment("""
 | 
				
			||||||
1st user who joins will get greeted immediately
 | 
					        Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
 | 
				
			||||||
If more users join within the next 5 seconds, they will be greeted in groups of 5.
 | 
					        1st user who joins will get greeted immediately
 | 
				
			||||||
This will cause %user.mention% and other placeholders to be replaced with multiple users. 
 | 
					        If more users join within the next 5 seconds, they will be greeted in groups of 5.
 | 
				
			||||||
Keep in mind this might break some of your embeds - for example if you have %user.avatar% in the thumbnail,
 | 
					        This will cause %user.mention% and other placeholders to be replaced with multiple users. 
 | 
				
			||||||
it will become invalid, as it will resolve to a list of avatars of grouped users.
 | 
					        Keep in mind this might break some of your embeds - for example if you have %user.avatar% in the thumbnail,
 | 
				
			||||||
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
 | 
					        it will become invalid, as it will resolve to a list of avatars of grouped users.
 | 
				
			||||||
      servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
 | 
					        note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
 | 
				
			||||||
      and (slightly) reduce the greet spam in those servers.")]
 | 
					              servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
 | 
				
			||||||
 | 
					              and (slightly) reduce the greet spam in those servers.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public bool GroupGreets { get; set; }
 | 
					    public bool GroupGreets { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Whether the bot will rotate through all specified statuses.
 | 
					    [Comment("""
 | 
				
			||||||
This setting can be changed via .ropl command.
 | 
					        Whether the bot will rotate through all specified statuses.
 | 
				
			||||||
See RotatingStatuses submodule in Administration.")]
 | 
					        This setting can be changed via .ropl command.
 | 
				
			||||||
 | 
					        See RotatingStatuses submodule in Administration.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public bool RotateStatuses { get; set; }
 | 
					    public bool RotateStatuses { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public BotConfig()
 | 
					    public BotConfig()
 | 
				
			||||||
@@ -89,32 +104,34 @@ See RotatingStatuses submodule in Administration.")]
 | 
				
			|||||||
        ConsoleOutputType = ConsoleOutputType.Normal;
 | 
					        ConsoleOutputType = ConsoleOutputType.Normal;
 | 
				
			||||||
        ForwardMessages = false;
 | 
					        ForwardMessages = false;
 | 
				
			||||||
        ForwardToAllOwners = false;
 | 
					        ForwardToAllOwners = false;
 | 
				
			||||||
        DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
 | 
					        DmHelpText = """{"description": "Type `%prefix%h` for help."}""";
 | 
				
			||||||
        HelpText = @"{
 | 
					        HelpText = """
 | 
				
			||||||
  ""title"": ""To invite me to your server, use this link"",
 | 
					            {
 | 
				
			||||||
  ""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
 | 
					              "title": "To invite me to your server, use this link",
 | 
				
			||||||
  ""color"": 53380,
 | 
					              "description": "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303",
 | 
				
			||||||
  ""thumbnail"": ""https://i.imgur.com/nKYyqMK.png"",
 | 
					              "color": 53380,
 | 
				
			||||||
  ""fields"": [
 | 
					              "thumbnail": "https://i.imgur.com/nKYyqMK.png",
 | 
				
			||||||
    {
 | 
					              "fields": [
 | 
				
			||||||
      ""name"": ""Useful help commands"",
 | 
					                {
 | 
				
			||||||
      ""value"": ""`%bot.prefix%modules` Lists all bot modules.
 | 
					                  "name": "Useful help commands",
 | 
				
			||||||
`%prefix%h CommandName` Shows some help about a specific command.
 | 
					                  "value": "`%bot.prefix%modules` Lists all bot modules.
 | 
				
			||||||
`%prefix%commands ModuleName` Lists all commands in a module."",
 | 
					            `%prefix%h CommandName` Shows some help about a specific command.
 | 
				
			||||||
      ""inline"": false
 | 
					            `%prefix%commands ModuleName` Lists all commands in a module.",
 | 
				
			||||||
    },
 | 
					                  "inline": false
 | 
				
			||||||
    {
 | 
					                },
 | 
				
			||||||
      ""name"": ""List of all Commands"",
 | 
					                {
 | 
				
			||||||
      ""value"": ""https://nadeko.bot/commands"",
 | 
					                  "name": "List of all Commands",
 | 
				
			||||||
      ""inline"": false
 | 
					                  "value": "https://nadeko.bot/commands",
 | 
				
			||||||
    },
 | 
					                  "inline": false
 | 
				
			||||||
    {
 | 
					                },
 | 
				
			||||||
      ""name"": ""Nadeko Support Server"",
 | 
					                {
 | 
				
			||||||
      ""value"": ""https://discord.nadeko.bot/ "",
 | 
					                  "name": "Nadeko Support Server",
 | 
				
			||||||
      ""inline"": true
 | 
					                  "value": "https://discord.nadeko.bot/ ",
 | 
				
			||||||
    }
 | 
					                  "inline": true
 | 
				
			||||||
  ]
 | 
					                }
 | 
				
			||||||
}";
 | 
					              ]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            """;
 | 
				
			||||||
        var blocked = new BlockedConfig();
 | 
					        var blocked = new BlockedConfig();
 | 
				
			||||||
        Blocked = blocked;
 | 
					        Blocked = blocked;
 | 
				
			||||||
        Prefix = ".";
 | 
					        Prefix = ".";
 | 
				
			||||||
@@ -160,13 +177,13 @@ public sealed partial class BlockedConfig
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class ColorConfig
 | 
					public partial class ColorConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Color used for embed responses when command successfully executes")]
 | 
					    [Comment("""Color used for embed responses when command successfully executes""")]
 | 
				
			||||||
    public Rgba32 Ok { get; set; }
 | 
					    public Rgba32 Ok { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Color used for embed responses when command has an error")]
 | 
					    [Comment("""Color used for embed responses when command has an error""")]
 | 
				
			||||||
    public Rgba32 Error { get; set; }
 | 
					    public Rgba32 Error { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Color used for embed responses while command is doing work or is in progress")]
 | 
					    [Comment("""Color used for embed responses while command is doing work or is in progress""")]
 | 
				
			||||||
    public Rgba32 Pending { get; set; }
 | 
					    public Rgba32 Pending { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ColorConfig()
 | 
					    public ColorConfig()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,115 +5,141 @@ namespace NadekoBot.Common;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public sealed class Creds : IBotCredentials
 | 
					public sealed class Creds : IBotCredentials
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"DO NOT CHANGE")]
 | 
					    [Comment("""DO NOT CHANGE""")]
 | 
				
			||||||
    public int Version { get; set; }
 | 
					    public int Version { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
 | 
					    [Comment("""Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/""")]
 | 
				
			||||||
    public string Token { get; set; }
 | 
					    public string Token { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"List of Ids of the users who have bot owner permissions
 | 
					    [Comment("""
 | 
				
			||||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
 | 
					        List of Ids of the users who have bot owner permissions
 | 
				
			||||||
 | 
					        **DO NOT ADD PEOPLE YOU DON'T TRUST**
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public ICollection<ulong> OwnerIds { get; set; }
 | 
					    public ICollection<ulong> OwnerIds { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment("Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")]
 | 
					    [Comment("Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")]
 | 
				
			||||||
    public bool UsePrivilegedIntents { get; set; }
 | 
					    public bool UsePrivilegedIntents { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"The number of shards that the bot will be running on.
 | 
					    [Comment("""
 | 
				
			||||||
Leave at 1 if you don't know what you're doing.
 | 
					        The number of shards that the bot will be running on.
 | 
				
			||||||
 | 
					        Leave at 1 if you don't know what you're doing.
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
 | 
					        note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
 | 
				
			||||||
      Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value.")]
 | 
					              Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int TotalShards { get; set; }
 | 
					    public int TotalShards { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(   
 | 
					    [Comment(   
 | 
				
			||||||
        @"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
 | 
					        """
 | 
				
			||||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
 | 
					            Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
 | 
				
			||||||
Used only for Youtube Data Api (at the moment).")]
 | 
					            Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
 | 
				
			||||||
 | 
					            Used only for Youtube Data Api (at the moment).
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
    public string GoogleApiKey { get; set; }
 | 
					    public string GoogleApiKey { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(   
 | 
					    [Comment(   
 | 
				
			||||||
        @"Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
 | 
					        """
 | 
				
			||||||
Enable SafeSearch
 | 
					            Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
 | 
				
			||||||
Remove all Sites to Search
 | 
					            Enable SafeSearch
 | 
				
			||||||
Enable Search the entire web
 | 
					            Remove all Sites to Search
 | 
				
			||||||
Copy the 'Search Engine ID' to the SearchId field
 | 
					            Enable Search the entire web
 | 
				
			||||||
 | 
					            Copy the 'Search Engine ID' to the SearchId field
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
Do all steps again but enable image search for the ImageSearchId")]
 | 
					            Do all steps again but enable image search for the ImageSearchId
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
    public GoogleApiConfig Google { get; set; }
 | 
					    public GoogleApiConfig Google { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
 | 
					    [Comment("""Settings for voting system for discordbots. Meant for use on global Nadeko.""")]
 | 
				
			||||||
    public VotesSettings Votes { get; set; }
 | 
					    public VotesSettings Votes { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Patreon auto reward system settings.
 | 
					    [Comment("""
 | 
				
			||||||
go to https://www.patreon.com/portal -> my clients -> create client")]
 | 
					        Patreon auto reward system settings.
 | 
				
			||||||
 | 
					        go to https://www.patreon.com/portal -> my clients -> create client
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public PatreonSettings Patreon { get; set; }
 | 
					    public PatreonSettings Patreon { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Api key for sending stats to DiscordBotList.")]
 | 
					    [Comment("""Api key for sending stats to DiscordBotList.""")]
 | 
				
			||||||
    public string BotListToken { get; set; }
 | 
					    public string BotListToken { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Official cleverbot api key.")]
 | 
					    [Comment("""Official cleverbot api key.""")]
 | 
				
			||||||
    public string CleverbotApiKey { get; set; }
 | 
					    public string CleverbotApiKey { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(@"Which cache implementation should bot use.
 | 
					    [Comment("""
 | 
				
			||||||
'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset. 
 | 
					        Which cache implementation should bot use.
 | 
				
			||||||
'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml")]
 | 
					        'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset. 
 | 
				
			||||||
 | 
					        'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public BotCacheImplemenation BotCache { get; set; }
 | 
					    public BotCacheImplemenation BotCache { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(@"Redis connection string. Don't change if you don't know what you're doing.
 | 
					    [Comment("""
 | 
				
			||||||
Only used if botCache is set to 'redis'")]
 | 
					        Redis connection string. Don't change if you don't know what you're doing.
 | 
				
			||||||
 | 
					        Only used if botCache is set to 'redis'
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public string RedisOptions { get; set; }
 | 
					    public string RedisOptions { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
 | 
					    [Comment("""Database options. Don't change if you don't know what you're doing. Leave null for default values""")]
 | 
				
			||||||
    public DbOptions Db { get; set; }
 | 
					    public DbOptions Db { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
 | 
					    [Comment("""
 | 
				
			||||||
Change only if you've changed the coordinator address or port.")]
 | 
					        Address and port of the coordinator endpoint. Leave empty for default.
 | 
				
			||||||
 | 
					        Change only if you've changed the coordinator address or port.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public string CoordinatorUrl { get; set; }
 | 
					    public string CoordinatorUrl { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(
 | 
					    [Comment(
 | 
				
			||||||
        @"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
 | 
					        """Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)""")]
 | 
				
			||||||
    public string RapidApiKey { get; set; }
 | 
					    public string RapidApiKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
 | 
					    [Comment("""
 | 
				
			||||||
Used only for .time command.")]
 | 
					        https://locationiq.com api key (register and you will receive the token in the email).
 | 
				
			||||||
 | 
					        Used only for .time command.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public string LocationIqApiKey { get; set; }
 | 
					    public string LocationIqApiKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
 | 
					    [Comment("""
 | 
				
			||||||
Used only for .time command")]
 | 
					        https://timezonedb.com api key (register and you will receive the token in the email).
 | 
				
			||||||
 | 
					        Used only for .time command
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public string TimezoneDbApiKey { get; set; }
 | 
					    public string TimezoneDbApiKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
 | 
					    [Comment("""
 | 
				
			||||||
Used for cryptocurrency related commands.")]
 | 
					        https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
 | 
				
			||||||
 | 
					        Used for cryptocurrency related commands.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public string CoinmarketcapApiKey { get; set; }
 | 
					    public string CoinmarketcapApiKey { get; set; }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
//     [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
 | 
					//     [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
 | 
				
			||||||
// Used for stocks related commands.")]
 | 
					// Used for stocks related commands.")]
 | 
				
			||||||
//     public string PolygonIoApiKey { get; set; }
 | 
					//     public string PolygonIoApiKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
 | 
					    [Comment("""Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api""")]
 | 
				
			||||||
    public string OsuApiKey { get; set; }
 | 
					    public string OsuApiKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Optional Trovo client id.
 | 
					    [Comment("""
 | 
				
			||||||
You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.")]
 | 
					        Optional Trovo client id.
 | 
				
			||||||
 | 
					        You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public string TrovoClientId { get; set; }
 | 
					    public string TrovoClientId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Obtain by creating an application at https://dev.twitch.tv/console/apps")]
 | 
					    [Comment("""Obtain by creating an application at https://dev.twitch.tv/console/apps""")]
 | 
				
			||||||
    public string TwitchClientId { get; set; }
 | 
					    public string TwitchClientId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Obtain by creating an application at https://dev.twitch.tv/console/apps")]
 | 
					    [Comment("""Obtain by creating an application at https://dev.twitch.tv/console/apps""")]
 | 
				
			||||||
    public string TwitchClientSecret { get; set; }
 | 
					    public string TwitchClientSecret { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Command and args which will be used to restart the bot.
 | 
					    [Comment("""
 | 
				
			||||||
Only used if bot is executed directly (NOT through the coordinator)
 | 
					        Command and args which will be used to restart the bot.
 | 
				
			||||||
placeholders: 
 | 
					        Only used if bot is executed directly (NOT through the coordinator)
 | 
				
			||||||
    {0} -> shard id 
 | 
					        placeholders: 
 | 
				
			||||||
    {1} -> total shards
 | 
					            {0} -> shard id 
 | 
				
			||||||
Linux default
 | 
					            {1} -> total shards
 | 
				
			||||||
    cmd: dotnet
 | 
					        Linux default
 | 
				
			||||||
    args: ""NadekoBot.dll -- {0}""
 | 
					            cmd: dotnet
 | 
				
			||||||
Windows default
 | 
					            args: "NadekoBot.dll -- {0}"
 | 
				
			||||||
    cmd: NadekoBot.exe
 | 
					        Windows default
 | 
				
			||||||
    args: ""{0}""")]
 | 
					            cmd: NadekoBot.exe
 | 
				
			||||||
 | 
					            args: "{0}"
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public RestartConfig RestartCommand { get; set; }
 | 
					    public RestartConfig RestartCommand { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Creds()
 | 
					    public Creds()
 | 
				
			||||||
@@ -145,15 +171,19 @@ Windows default
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public class DbOptions
 | 
					    public class DbOptions
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Comment(@"Database type. ""sqlite"", ""mysql"" and ""postgresql"" are supported.
 | 
					        [Comment("""
 | 
				
			||||||
Default is ""sqlite""")]
 | 
					            Database type. "sqlite", "mysql" and "postgresql" are supported.
 | 
				
			||||||
 | 
					            Default is "sqlite"
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public string Type { get; set; }
 | 
					        public string Type { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"Database connection string.
 | 
					        [Comment("""
 | 
				
			||||||
You MUST change this if you're not using ""sqlite"" type.
 | 
					            Database connection string.
 | 
				
			||||||
Default is ""Data Source=data/NadekoBot.db""
 | 
					            You MUST change this if you're not using "sqlite" type.
 | 
				
			||||||
Example for mysql: ""Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=nadeko""
 | 
					            Default is "Data Source=data/NadekoBot.db"
 | 
				
			||||||
Example for postgresql: ""Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=nadeko;""")]
 | 
					            Example for mysql: "Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=nadeko"
 | 
				
			||||||
 | 
					            Example for postgresql: "Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=nadeko;"
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public string ConnectionString { get; set; }
 | 
					        public string ConnectionString { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,7 +195,7 @@ Example for postgresql: ""Server=localhost;Port=5432;User Id=postgres;Password=m
 | 
				
			|||||||
        public string ClientSecret { get; set; }
 | 
					        public string ClientSecret { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(
 | 
					        [Comment(
 | 
				
			||||||
            @"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
 | 
					            """Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type "prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);" in the console. (ctrl + shift + i)""")]
 | 
				
			||||||
        public string CampaignId { get; set; }
 | 
					        public string CampaignId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public PatreonSettings(
 | 
					        public PatreonSettings(
 | 
				
			||||||
@@ -187,22 +217,30 @@ Example for postgresql: ""Server=localhost;Port=5432;User Id=postgres;Password=m
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public sealed record VotesSettings
 | 
					    public sealed record VotesSettings
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Comment(@"top.gg votes service url
 | 
					        [Comment("""
 | 
				
			||||||
This is the url of your instance of the NadekoBot.Votes api
 | 
					            top.gg votes service url
 | 
				
			||||||
Example: https://votes.my.cool.bot.com")]
 | 
					            This is the url of your instance of the NadekoBot.Votes api
 | 
				
			||||||
 | 
					            Example: https://votes.my.cool.bot.com
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public string TopggServiceUrl { get; set; }
 | 
					        public string TopggServiceUrl { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"Authorization header value sent to the TopGG service url with each request
 | 
					        [Comment("""
 | 
				
			||||||
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
 | 
					            Authorization header value sent to the TopGG service url with each request
 | 
				
			||||||
 | 
					            This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public string TopggKey { get; set; }
 | 
					        public string TopggKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"discords.com votes service url
 | 
					        [Comment("""
 | 
				
			||||||
This is the url of your instance of the NadekoBot.Votes api
 | 
					            discords.com votes service url
 | 
				
			||||||
Example: https://votes.my.cool.bot.com")]
 | 
					            This is the url of your instance of the NadekoBot.Votes api
 | 
				
			||||||
 | 
					            Example: https://votes.my.cool.bot.com
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public string DiscordsServiceUrl { get; set; }
 | 
					        public string DiscordsServiceUrl { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"Authorization header value sent to the Discords service url with each request
 | 
					        [Comment("""
 | 
				
			||||||
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
 | 
					            Authorization header value sent to the Discords service url with each request
 | 
				
			||||||
 | 
					            This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public string DiscordsKey { get; set; }
 | 
					        public string DiscordsKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public VotesSettings()
 | 
					        public VotesSettings()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,10 +7,10 @@ namespace Nadeko.Medusa;
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public sealed partial class MedusaConfig : ICloneable<MedusaConfig>
 | 
					public sealed partial class MedusaConfig : ICloneable<MedusaConfig>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"DO NOT CHANGE")]
 | 
					    [Comment("""DO NOT CHANGE""")]
 | 
				
			||||||
    public int Version { get; set; } = 1;
 | 
					    public int Version { get; set; } = 1;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment("List of medusae automatically loaded at startup")]
 | 
					    [Comment("""List of medusae automatically loaded at startup""")]
 | 
				
			||||||
    public List<string>? Loaded { get; set; }
 | 
					    public List<string>? Loaded { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MedusaConfig()
 | 
					    public MedusaConfig()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,29 +10,35 @@ public static class MigrationQueries
 | 
				
			|||||||
        if (migrationBuilder.IsMySql())
 | 
					        if (migrationBuilder.IsMySql())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            migrationBuilder.Sql(
 | 
					            migrationBuilder.Sql(
 | 
				
			||||||
                @"INSERT IGNORE into reactionroles(guildid, channelid, messageid, emote, roleid, `group`, levelreq, dateadded)
 | 
					                """
 | 
				
			||||||
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
 | 
					                    INSERT IGNORE into reactionroles(guildid, channelid, messageid, emote, roleid, `group`, levelreq, dateadded)
 | 
				
			||||||
from reactionrole
 | 
					                    select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
 | 
				
			||||||
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
 | 
					                    from reactionrole
 | 
				
			||||||
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
 | 
					                    left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
 | 
				
			||||||
 | 
					                    left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;
 | 
				
			||||||
 | 
					                    """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (migrationBuilder.IsSqlite())
 | 
					        else if (migrationBuilder.IsSqlite())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            migrationBuilder.Sql(
 | 
					            migrationBuilder.Sql(
 | 
				
			||||||
                @"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
 | 
					                """
 | 
				
			||||||
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
 | 
					                    insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
 | 
				
			||||||
from reactionrole
 | 
					                    select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
 | 
				
			||||||
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
 | 
					                    from reactionrole
 | 
				
			||||||
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
 | 
					                    left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
 | 
				
			||||||
 | 
					                    left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;
 | 
				
			||||||
 | 
					                    """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (migrationBuilder.IsNpgsql())
 | 
					        else if (migrationBuilder.IsNpgsql())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            migrationBuilder.Sql(@"insert into reactionroles(guildid, channelid, messageid, emote, roleid, ""group"", levelreq, dateadded)
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
            select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded
 | 
					                insert into reactionroles(guildid, channelid, messageid, emote, roleid, "group", levelreq, dateadded)
 | 
				
			||||||
                from reactionrole
 | 
					                            select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded
 | 
				
			||||||
                left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
 | 
					                                from reactionrole
 | 
				
			||||||
            left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id
 | 
					                                left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
 | 
				
			||||||
            ON CONFLICT DO NOTHING;");
 | 
					                            left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id
 | 
				
			||||||
 | 
					                            ON CONFLICT DO NOTHING;
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,14 +28,16 @@ namespace NadekoBot.Migrations
 | 
				
			|||||||
                table: "NsfwBlacklistedTags",
 | 
					                table: "NsfwBlacklistedTags",
 | 
				
			||||||
                column: "GuildId");
 | 
					                column: "GuildId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            migrationBuilder.Sql(@"INSERT INTO NsfwBlacklistedTags(Id, GuildId, Tag, DateAdded)
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
SELECT 
 | 
					                INSERT INTO NsfwBlacklistedTags(Id, GuildId, Tag, DateAdded)
 | 
				
			||||||
    Id,
 | 
					                SELECT 
 | 
				
			||||||
    (SELECT GuildId From GuildConfigs WHERE Id=GuildConfigId),
 | 
					                    Id,
 | 
				
			||||||
    Tag,
 | 
					                    (SELECT GuildId From GuildConfigs WHERE Id=GuildConfigId),
 | 
				
			||||||
    DateAdded
 | 
					                    Tag,
 | 
				
			||||||
FROM NsfwBlacklitedTag
 | 
					                    DateAdded
 | 
				
			||||||
WHERE GuildConfigId in (SELECT Id from GuildConfigs);");
 | 
					                FROM NsfwBlacklitedTag
 | 
				
			||||||
 | 
					                WHERE GuildConfigId in (SELECT Id from GuildConfigs);
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            migrationBuilder.DropTable(
 | 
					            migrationBuilder.DropTable(
 | 
				
			||||||
                name: "NsfwBlacklitedTag");
 | 
					                name: "NsfwBlacklitedTag");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,24 +50,30 @@ namespace NadekoBot.Migrations
 | 
				
			|||||||
                principalTable: "GuildConfigs",
 | 
					                principalTable: "GuildConfigs",
 | 
				
			||||||
                principalColumn: "Id");
 | 
					                principalColumn: "Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            migrationBuilder.Sql(@"UPDATE Permissions
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
SET SecondaryTargetName='ACTUALEXPRESSIONS'
 | 
					                UPDATE Permissions
 | 
				
			||||||
WHERE SecondaryTargetName='ActualCustomReactions' COLLATE NOCASE;");
 | 
					                SET SecondaryTargetName='ACTUALEXPRESSIONS'
 | 
				
			||||||
 | 
					                WHERE SecondaryTargetName='ActualCustomReactions' COLLATE NOCASE;
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            migrationBuilder.Sql(@"UPDATE Permissions
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
SET SecondaryTargetName='EXPRESSIONS'
 | 
					                UPDATE Permissions
 | 
				
			||||||
WHERE SecondaryTargetName='CustomReactions' COLLATE NOCASE;");
 | 
					                SET SecondaryTargetName='EXPRESSIONS'
 | 
				
			||||||
 | 
					                WHERE SecondaryTargetName='CustomReactions' COLLATE NOCASE;
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            migrationBuilder.Sql(@"UPDATE Permissions
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
SET SecondaryTargetName= case lower(SecondaryTargetName) 
 | 
					                UPDATE Permissions
 | 
				
			||||||
    WHEN 'editcustreact' THEN 'expredit'
 | 
					                SET SecondaryTargetName= case lower(SecondaryTargetName) 
 | 
				
			||||||
    WHEN 'delcustreact' THEN 'exprdel'
 | 
					                    WHEN 'editcustreact' THEN 'expredit'
 | 
				
			||||||
    WHEN 'listcustreact' THEN 'exprlist'
 | 
					                    WHEN 'delcustreact' THEN 'exprdel'
 | 
				
			||||||
    WHEN 'addcustreact' THEN 'expradd'
 | 
					                    WHEN 'listcustreact' THEN 'exprlist'
 | 
				
			||||||
    WHEN 'showcustreact' THEN 'exprshow'
 | 
					                    WHEN 'addcustreact' THEN 'expradd'
 | 
				
			||||||
ELSE SecondaryTargetName
 | 
					                    WHEN 'showcustreact' THEN 'exprshow'
 | 
				
			||||||
END
 | 
					                ELSE SecondaryTargetName
 | 
				
			||||||
WHERE lower(SecondaryTargetName) in ('editcustreact', 'delcustreact', 'listcustreact', 'addcustreact', 'showcustreact');");
 | 
					                END
 | 
				
			||||||
 | 
					                WHERE lower(SecondaryTargetName) in ('editcustreact', 'delcustreact', 'listcustreact', 'addcustreact', 'showcustreact');
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,13 +42,15 @@ namespace NadekoBot.Migrations
 | 
				
			|||||||
                table: "FilterWordsChannelId",
 | 
					                table: "FilterWordsChannelId",
 | 
				
			||||||
                column: "GuildConfigId");
 | 
					                column: "GuildConfigId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            migrationBuilder.Sql(@"INSERT INTO FilterWordsChannelId(Id, ChannelId, GuildConfigId, DateAdded)
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
SELECT Id, ChannelId, GuildConfigId1, DateAdded 
 | 
					                INSERT INTO FilterWordsChannelId(Id, ChannelId, GuildConfigId, DateAdded)
 | 
				
			||||||
FROM FilterChannelId
 | 
					                SELECT Id, ChannelId, GuildConfigId1, DateAdded 
 | 
				
			||||||
WHERE GuildConfigId1 is not null;
 | 
					                FROM FilterChannelId
 | 
				
			||||||
-- Remove them after moving them to a different table
 | 
					                WHERE GuildConfigId1 is not null;
 | 
				
			||||||
DELETE FROM FilterChannelId
 | 
					                -- Remove them after moving them to a different table
 | 
				
			||||||
WHERE GuildConfigId is null;");
 | 
					                DELETE FROM FilterChannelId
 | 
				
			||||||
 | 
					                WHERE GuildConfigId is null;
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            migrationBuilder.DropColumn(
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
                name: "GuildConfigId1",
 | 
					                name: "GuildConfigId1",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,19 +9,21 @@ namespace NadekoBot.Migrations
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            migrationBuilder.Sql(@"UPDATE Clubs
 | 
					            migrationBuilder.Sql("""
 | 
				
			||||||
SET Name = Name || '#' || Discrim
 | 
					                UPDATE Clubs
 | 
				
			||||||
WHERE Discrim <> 1;
 | 
					                SET Name = Name || '#' || Discrim
 | 
				
			||||||
 | 
					                WHERE Discrim <> 1;
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
UPDATE Clubs as co
 | 
					                UPDATE Clubs as co
 | 
				
			||||||
SET Name =
 | 
					                SET Name =
 | 
				
			||||||
	CASE (select count(*) from Clubs as ci where co.Name == ci.Name) = 1
 | 
					                	CASE (select count(*) from Clubs as ci where co.Name == ci.Name) = 1
 | 
				
			||||||
		WHEN true
 | 
					                		WHEN true
 | 
				
			||||||
			THEN Name
 | 
					                			THEN Name
 | 
				
			||||||
		ELSE
 | 
					                		ELSE
 | 
				
			||||||
			Name || '#' || Discrim
 | 
					                			Name || '#' || Discrim
 | 
				
			||||||
    END
 | 
					                    END
 | 
				
			||||||
 WHERE Discrim = 1;");
 | 
					                 WHERE Discrim = 1;
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            migrationBuilder.DropForeignKey(
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
                name: "FK_Clubs_DiscordUser_OwnerId",
 | 
					                name: "FK_Clubs_DiscordUser_OwnerId",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,19 +19,21 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
 | 
				
			|||||||
    private const string MENTION_PH = "%bot.mention%";
 | 
					    private const string MENTION_PH = "%bot.mention%";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private const string PREPEND_EXPORT =
 | 
					    private const string PREPEND_EXPORT =
 | 
				
			||||||
        @"# Keys are triggers, Each key has a LIST of expressions in the following format:
 | 
					        """
 | 
				
			||||||
# - res: Response string
 | 
					            # Keys are triggers, Each key has a LIST of expressions in the following format:
 | 
				
			||||||
#   id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.)
 | 
					            # - res: Response string
 | 
				
			||||||
#   react: 
 | 
					            #   id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.)
 | 
				
			||||||
#     - <List
 | 
					            #   react: 
 | 
				
			||||||
#     -  of
 | 
					            #     - <List
 | 
				
			||||||
#     - reactions>
 | 
					            #     -  of
 | 
				
			||||||
#   at: Whether expression allows targets (see .h .exprat) 
 | 
					            #     - reactions>
 | 
				
			||||||
#   ca: Whether expression expects trigger anywhere (see .h .exprca) 
 | 
					            #   at: Whether expression allows targets (see .h .exprat) 
 | 
				
			||||||
#   dm: Whether expression DMs the response (see .h .exprdm) 
 | 
					            #   ca: Whether expression expects trigger anywhere (see .h .exprca) 
 | 
				
			||||||
#   ad: Whether expression automatically deletes triggering message (see .h .exprad) 
 | 
					            #   dm: Whether expression DMs the response (see .h .exprdm) 
 | 
				
			||||||
 | 
					            #   ad: Whether expression automatically deletes triggering message (see .h .exprad) 
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
";
 | 
					
 | 
				
			||||||
 | 
					            """;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static readonly ISerializer _exportSerializer = new SerializerBuilder()
 | 
					    private static readonly ISerializer _exportSerializer = new SerializerBuilder()
 | 
				
			||||||
                                                            .WithEventEmitter(args
 | 
					                                                            .WithEventEmitter(args
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,50 +10,58 @@ namespace NadekoBot.Modules.Gambling.Common;
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
 | 
					public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"DO NOT CHANGE")]
 | 
					    [Comment("""DO NOT CHANGE""")]
 | 
				
			||||||
    public int Version { get; set; } = 2;
 | 
					    public int Version { get; set; } = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Currency settings")]
 | 
					    [Comment("""Currency settings""")]
 | 
				
			||||||
    public CurrencyConfig Currency { get; set; }
 | 
					    public CurrencyConfig Currency { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Minimum amount users can bet (>=0)")]
 | 
					    [Comment("""Minimum amount users can bet (>=0)""")]
 | 
				
			||||||
    public int MinBet { get; set; } = 0;
 | 
					    public int MinBet { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Maximum amount users can bet
 | 
					    [Comment("""
 | 
				
			||||||
Set 0 for unlimited")]
 | 
					        Maximum amount users can bet
 | 
				
			||||||
 | 
					        Set 0 for unlimited
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int MaxBet { get; set; } = 0;
 | 
					    public int MaxBet { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings for betflip command")]
 | 
					    [Comment("""Settings for betflip command""")]
 | 
				
			||||||
    public BetFlipConfig BetFlip { get; set; }
 | 
					    public BetFlipConfig BetFlip { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings for betroll command")]
 | 
					    [Comment("""Settings for betroll command""")]
 | 
				
			||||||
    public BetRollConfig BetRoll { get; set; }
 | 
					    public BetRollConfig BetRoll { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Automatic currency generation settings.")]
 | 
					    [Comment("""Automatic currency generation settings.""")]
 | 
				
			||||||
    public GenerationConfig Generation { get; set; }
 | 
					    public GenerationConfig Generation { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings for timely command 
 | 
					    [Comment("""
 | 
				
			||||||
(letting people claim X amount of currency every Y hours)")]
 | 
					        Settings for timely command 
 | 
				
			||||||
 | 
					        (letting people claim X amount of currency every Y hours)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public TimelyConfig Timely { get; set; }
 | 
					    public TimelyConfig Timely { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"How much will each user's owned currency decay over time.")]
 | 
					    [Comment("""How much will each user's owned currency decay over time.""")]
 | 
				
			||||||
    public DecayConfig Decay { get; set; }
 | 
					    public DecayConfig Decay { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings for LuckyLadder command")]
 | 
					    [Comment("""Settings for LuckyLadder command""")]
 | 
				
			||||||
    public LuckyLadderSettings LuckyLadder { get; set; }
 | 
					    public LuckyLadderSettings LuckyLadder { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings related to waifus")]
 | 
					    [Comment("""Settings related to waifus""")]
 | 
				
			||||||
    public WaifuConfig Waifu { get; set; }
 | 
					    public WaifuConfig Waifu { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
 | 
					    [Comment("""
 | 
				
			||||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
 | 
					        Amount of currency selfhosters will get PER pledged dollar CENT.
 | 
				
			||||||
 | 
					        1 = 100 currency per $. Used almost exclusively on public nadeko.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal PatreonCurrencyPerCent { get; set; } = 1;
 | 
					    public decimal PatreonCurrencyPerCent { get; set; } = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Currency reward per vote.
 | 
					    [Comment("""
 | 
				
			||||||
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
 | 
					        Currency reward per vote.
 | 
				
			||||||
 | 
					        This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public long VoteReward { get; set; } = 100;
 | 
					    public long VoteReward { get; set; } = 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Slot config")]
 | 
					    [Comment("""Slot config""")]
 | 
				
			||||||
    public SlotsConfig Slots { get; set; }
 | 
					    public SlotsConfig Slots { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GamblingConfig()
 | 
					    public GamblingConfig()
 | 
				
			||||||
@@ -72,42 +80,50 @@ This will work only if you've set up VotesApi and correct credentials for topgg
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public class CurrencyConfig
 | 
					public class CurrencyConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"What is the emoji/character which represents the currency")]
 | 
					    [Comment("""What is the emoji/character which represents the currency""")]
 | 
				
			||||||
    public string Sign { get; set; } = "🌸";
 | 
					    public string Sign { get; set; } = "🌸";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"What is the name of the currency")]
 | 
					    [Comment("""What is the name of the currency""")]
 | 
				
			||||||
    public string Name { get; set; } = "Nadeko Flower";
 | 
					    public string Name { get; set; } = "Nadeko Flower";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"For how long (in days) will the transactions be kept in the database (curtrs)
 | 
					    [Comment("""
 | 
				
			||||||
Set 0 to disable cleanup (keep transactions forever)")]
 | 
					        For how long (in days) will the transactions be kept in the database (curtrs)
 | 
				
			||||||
 | 
					        Set 0 to disable cleanup (keep transactions forever)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int TransactionsLifetime { get; set; } = 0;
 | 
					    public int TransactionsLifetime { get; set; } = 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class TimelyConfig
 | 
					public partial class TimelyConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"How much currency will the users get every time they run .timely command
 | 
					    [Comment("""
 | 
				
			||||||
setting to 0 or less will disable this feature")]
 | 
					        How much currency will the users get every time they run .timely command
 | 
				
			||||||
 | 
					        setting to 0 or less will disable this feature
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int Amount { get; set; } = 0;
 | 
					    public int Amount { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"How often (in hours) can users claim currency with .timely command
 | 
					    [Comment("""
 | 
				
			||||||
setting to 0 or less will disable this feature")]
 | 
					        How often (in hours) can users claim currency with .timely command
 | 
				
			||||||
 | 
					        setting to 0 or less will disable this feature
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int Cooldown { get; set; } = 24;
 | 
					    public int Cooldown { get; set; } = 24;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class BetFlipConfig
 | 
					public partial class BetFlipConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Bet multiplier if user guesses correctly")]
 | 
					    [Comment("""Bet multiplier if user guesses correctly""")]
 | 
				
			||||||
    public decimal Multiplier { get; set; } = 1.95M;
 | 
					    public decimal Multiplier { get; set; } = 1.95M;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class BetRollConfig
 | 
					public partial class BetRollConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"When betroll is played, user will roll a number 0-100.
 | 
					    [Comment("""
 | 
				
			||||||
This setting will describe which multiplier is used for when the roll is higher than the given number.
 | 
					        When betroll is played, user will roll a number 0-100.
 | 
				
			||||||
Doesn't have to be ordered.")]
 | 
					        This setting will describe which multiplier is used for when the roll is higher than the given number.
 | 
				
			||||||
 | 
					        Doesn't have to be ordered.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
 | 
					    public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public BetRollConfig()
 | 
					    public BetRollConfig()
 | 
				
			||||||
@@ -134,48 +150,56 @@ Doesn't have to be ordered.")]
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class GenerationConfig
 | 
					public partial class GenerationConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"when currency is generated, should it also have a random password
 | 
					    [Comment("""
 | 
				
			||||||
associated with it which users have to type after the .pick command
 | 
					        when currency is generated, should it also have a random password
 | 
				
			||||||
in order to get it")]
 | 
					        associated with it which users have to type after the .pick command
 | 
				
			||||||
 | 
					        in order to get it
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public bool HasPassword { get; set; } = true;
 | 
					    public bool HasPassword { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Every message sent has a certain % chance to generate the currency
 | 
					    [Comment("""
 | 
				
			||||||
specify the percentage here (1 being 100%, 0 being 0% - for example
 | 
					        Every message sent has a certain % chance to generate the currency
 | 
				
			||||||
default is 0.02, which is 2%")]
 | 
					        specify the percentage here (1 being 100%, 0 being 0% - for example
 | 
				
			||||||
 | 
					        default is 0.02, which is 2%
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal Chance { get; set; } = 0.02M;
 | 
					    public decimal Chance { get; set; } = 0.02M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"How many seconds have to pass for the next message to have a chance to spawn currency")]
 | 
					    [Comment("""How many seconds have to pass for the next message to have a chance to spawn currency""")]
 | 
				
			||||||
    public int GenCooldown { get; set; } = 10;
 | 
					    public int GenCooldown { get; set; } = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Minimum amount of currency that can spawn")]
 | 
					    [Comment("""Minimum amount of currency that can spawn""")]
 | 
				
			||||||
    public int MinAmount { get; set; } = 1;
 | 
					    public int MinAmount { get; set; } = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Maximum amount of currency that can spawn.
 | 
					    [Comment("""
 | 
				
			||||||
 Set to the same value as MinAmount to always spawn the same amount")]
 | 
					        Maximum amount of currency that can spawn.
 | 
				
			||||||
 | 
					         Set to the same value as MinAmount to always spawn the same amount
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int MaxAmount { get; set; } = 1;
 | 
					    public int MaxAmount { get; set; } = 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class DecayConfig
 | 
					public partial class DecayConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Percentage of user's current currency which will be deducted every 24h. 
 | 
					    [Comment("""
 | 
				
			||||||
0 - 1 (1 is 100%, 0.5 50%, 0 disabled)")]
 | 
					        Percentage of user's current currency which will be deducted every 24h. 
 | 
				
			||||||
 | 
					        0 - 1 (1 is 100%, 0.5 50%, 0 disabled)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal Percent { get; set; } = 0;
 | 
					    public decimal Percent { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Maximum amount of user's currency that can decay at each interval. 0 for unlimited.")]
 | 
					    [Comment("""Maximum amount of user's currency that can decay at each interval. 0 for unlimited.""")]
 | 
				
			||||||
    public int MaxDecay { get; set; } = 0;
 | 
					    public int MaxDecay { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Only users who have more than this amount will have their currency decay.")]
 | 
					    [Comment("""Only users who have more than this amount will have their currency decay.""")]
 | 
				
			||||||
    public int MinThreshold { get; set; } = 99;
 | 
					    public int MinThreshold { get; set; } = 99;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"How often, in hours, does the decay run. Default is 24 hours")]
 | 
					    [Comment("""How often, in hours, does the decay run. Default is 24 hours""")]
 | 
				
			||||||
    public int HourInterval { get; set; } = 24;
 | 
					    public int HourInterval { get; set; } = 24;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public partial class LuckyLadderSettings
 | 
					public partial class LuckyLadderSettings
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
 | 
					    [Comment("""Self-Explanatory. Has to have 8 values, otherwise the command won't work.""")]
 | 
				
			||||||
    public decimal[] Multipliers { get; set; }
 | 
					    public decimal[] Multipliers { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public LuckyLadderSettings()
 | 
					    public LuckyLadderSettings()
 | 
				
			||||||
@@ -185,17 +209,21 @@ public partial class LuckyLadderSettings
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public sealed partial class WaifuConfig
 | 
					public sealed partial class WaifuConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Minimum price a waifu can have")]
 | 
					    [Comment("""Minimum price a waifu can have""")]
 | 
				
			||||||
    public long MinPrice { get; set; } = 50;
 | 
					    public long MinPrice { get; set; } = 50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MultipliersData Multipliers { get; set; } = new();
 | 
					    public MultipliersData Multipliers { get; set; } = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Settings for periodic waifu price decay.
 | 
					    [Comment("""
 | 
				
			||||||
Waifu price decays only if the waifu has no claimer.")]
 | 
					        Settings for periodic waifu price decay.
 | 
				
			||||||
 | 
					        Waifu price decays only if the waifu has no claimer.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public WaifuDecayConfig Decay { get; set; } = new();
 | 
					    public WaifuDecayConfig Decay { get; set; } = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"List of items available for gifting.
 | 
					    [Comment("""
 | 
				
			||||||
If negative is true, gift will instead reduce waifu value.")]
 | 
					        List of items available for gifting.
 | 
				
			||||||
 | 
					        If negative is true, gift will instead reduce waifu value.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public List<WaifuItemModel> Items { get; set; } = new();
 | 
					    public List<WaifuItemModel> Items { get; set; } = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public WaifuConfig()
 | 
					    public WaifuConfig()
 | 
				
			||||||
@@ -241,16 +269,20 @@ If negative is true, gift will instead reduce waifu value.")]
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public class WaifuDecayConfig
 | 
					    public class WaifuDecayConfig
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Comment(@"Percentage (0 - 100) of the waifu value to reduce.
 | 
					        [Comment("""
 | 
				
			||||||
Set 0 to disable
 | 
					            Percentage (0 - 100) of the waifu value to reduce.
 | 
				
			||||||
For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)")]
 | 
					            Set 0 to disable
 | 
				
			||||||
 | 
					            For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public int Percent { get; set; } = 0;
 | 
					        public int Percent { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"How often to decay waifu values, in hours")]
 | 
					        [Comment("""How often to decay waifu values, in hours""")]
 | 
				
			||||||
        public int HourInterval { get; set; } = 24;
 | 
					        public int HourInterval { get; set; } = 24;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"Minimum waifu price required for the decay to be applied.
 | 
					        [Comment("""
 | 
				
			||||||
For example if this value is set to 300, any waifu with the price 300 or less will not experience decay.")]
 | 
					            Minimum waifu price required for the decay to be applied.
 | 
				
			||||||
 | 
					            For example if this value is set to 300, any waifu with the price 300 or less will not experience decay.
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public long MinPrice { get; set; } = 300;
 | 
					        public long MinPrice { get; set; } = 300;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -258,47 +290,61 @@ For example if this value is set to 300, any waifu with the price 300 or less wi
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public sealed partial class MultipliersData
 | 
					public sealed partial class MultipliersData
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Multiplier for waifureset. Default 150.
 | 
					    [Comment("""
 | 
				
			||||||
Formula (at the time of writing this): 
 | 
					        Multiplier for waifureset. Default 150.
 | 
				
			||||||
price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up")]
 | 
					        Formula (at the time of writing this): 
 | 
				
			||||||
 | 
					        price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int WaifuReset { get; set; } = 150;
 | 
					    public int WaifuReset { get; set; } = 150;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"The minimum amount of currency that you have to pay 
 | 
					    [Comment("""
 | 
				
			||||||
in order to buy a waifu who doesn't have a crush on you.
 | 
					        The minimum amount of currency that you have to pay 
 | 
				
			||||||
Default is 1.1
 | 
					        in order to buy a waifu who doesn't have a crush on you.
 | 
				
			||||||
Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
 | 
					        Default is 1.1
 | 
				
			||||||
(100 * 1.1 = 110)")]
 | 
					        Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
 | 
				
			||||||
 | 
					        (100 * 1.1 = 110)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal NormalClaim { get; set; } = 1.1m;
 | 
					    public decimal NormalClaim { get; set; } = 1.1m;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"The minimum amount of currency that you have to pay 
 | 
					    [Comment("""
 | 
				
			||||||
in order to buy a waifu that has a crush on you.
 | 
					        The minimum amount of currency that you have to pay 
 | 
				
			||||||
Default is 0.88
 | 
					        in order to buy a waifu that has a crush on you.
 | 
				
			||||||
Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her.
 | 
					        Default is 0.88
 | 
				
			||||||
(100 * 0.88 = 88)")]
 | 
					        Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her.
 | 
				
			||||||
 | 
					        (100 * 0.88 = 88)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal CrushClaim { get; set; } = 0.88M;
 | 
					    public decimal CrushClaim { get; set; } = 0.88M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"When divorcing a waifu, her new value will be her current value multiplied by this number.
 | 
					    [Comment("""
 | 
				
			||||||
Default 0.75 (meaning will lose 25% of her value)")]
 | 
					        When divorcing a waifu, her new value will be her current value multiplied by this number.
 | 
				
			||||||
 | 
					        Default 0.75 (meaning will lose 25% of her value)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal DivorceNewValue { get; set; } = 0.75M;
 | 
					    public decimal DivorceNewValue { get; set; } = 0.75M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"All gift prices will be multiplied by this number.
 | 
					    [Comment("""
 | 
				
			||||||
Default 1 (meaning no effect)")]
 | 
					        All gift prices will be multiplied by this number.
 | 
				
			||||||
 | 
					        Default 1 (meaning no effect)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal AllGiftPrices { get; set; } = 1.0M;
 | 
					    public decimal AllGiftPrices { get; set; } = 1.0M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
 | 
					    [Comment("""
 | 
				
			||||||
Default 0.95 (meaning 95%)
 | 
					        What percentage of the value of the gift will a waifu gain when she's gifted.
 | 
				
			||||||
Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")]
 | 
					        Default 0.95 (meaning 95%)
 | 
				
			||||||
 | 
					        Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal GiftEffect { get; set; } = 0.95M;
 | 
					    public decimal GiftEffect { get; set; } = 0.95M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
 | 
					    [Comment("""
 | 
				
			||||||
Default 0.5 (meaning 50%)
 | 
					        What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
 | 
				
			||||||
Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)")]
 | 
					        Default 0.5 (meaning 50%)
 | 
				
			||||||
 | 
					        Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public decimal NegativeGiftEffect { get; set; } = 0.50M;
 | 
					    public decimal NegativeGiftEffect { get; set; } = 0.50M;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public sealed class SlotsConfig
 | 
					public sealed class SlotsConfig
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"Hex value of the color which the numbers on the slot image will have.")]
 | 
					    [Comment("""Hex value of the color which the numbers on the slot image will have.""")]
 | 
				
			||||||
    public Rgba32 CurrencyFontColor { get; set; } = Color.Red;
 | 
					    public Rgba32 CurrencyFontColor { get; set; } = Color.Red;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,9 +96,12 @@ public class GamblingService : INService, IReadyExecutor
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Log.Information(@"Decaying users' currency - decay: {ConfigDecayPercent}% 
 | 
					                Log.Information("""
 | 
				
			||||||
                                    | max: {MaxDecay} 
 | 
					                    --- Decaying users' currency ---
 | 
				
			||||||
                                    | threshold: {DecayMinTreshold}",
 | 
					                    | decay: {ConfigDecayPercent}% 
 | 
				
			||||||
 | 
					                    | max: {MaxDecay} 
 | 
				
			||||||
 | 
					                    | threshold: {DecayMinTreshold}
 | 
				
			||||||
 | 
					                    """,
 | 
				
			||||||
                    config.Decay.Percent * 100,
 | 
					                    config.Decay.Percent * 100,
 | 
				
			||||||
                    maxDecay,
 | 
					                    maxDecay,
 | 
				
			||||||
                    config.Decay.MinThreshold);
 | 
					                    config.Decay.MinThreshold);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -187,11 +187,13 @@ public class ChatterBotService : IExecOnMessage
 | 
				
			|||||||
                // , footer: counter > 0 ? counter.ToString() : null
 | 
					                // , footer: counter > 0 ? counter.ToString() : null
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Log.Information(@"CleverBot Executed
 | 
					            Log.Information("""
 | 
				
			||||||
Server: {GuildName} [{GuildId}]
 | 
					                CleverBot Executed
 | 
				
			||||||
Channel: {ChannelName} [{ChannelId}]
 | 
					                Server: {GuildName} [{GuildId}]
 | 
				
			||||||
UserId: {Author} [{AuthorId}]
 | 
					                Channel: {ChannelName} [{ChannelId}]
 | 
				
			||||||
Message: {Content}",
 | 
					                UserId: {Author} [{AuthorId}]
 | 
				
			||||||
 | 
					                Message: {Content}
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
                guild.Name,
 | 
					                guild.Name,
 | 
				
			||||||
                guild.Id,
 | 
					                guild.Id,
 | 
				
			||||||
                usrMsg.Channel?.Name,
 | 
					                usrMsg.Channel?.Name,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -110,8 +110,10 @@ public sealed partial class TriviaConfig
 | 
				
			|||||||
    [Comment("The amount of currency awarded to the winner of the trivia game.")]
 | 
					    [Comment("The amount of currency awarded to the winner of the trivia game.")]
 | 
				
			||||||
    public long CurrencyReward { get; set; }
 | 
					    public long CurrencyReward { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Users won't be able to start trivia games which have 
 | 
					    [Comment("""
 | 
				
			||||||
a smaller win requirement than the one specified by this setting.")]
 | 
					        Users won't be able to start trivia games which have 
 | 
				
			||||||
 | 
					        a smaller win requirement than the one specified by this setting.
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public int MinimumWinReq { get; set; } = 1;
 | 
					    public int MinimumWinReq { get; set; } = 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,13 +14,15 @@ public partial class Games
 | 
				
			|||||||
            => await SendConfirmAsync(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n'));
 | 
					            => await SendConfirmAsync(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static string Draw(HangmanGame.State state)
 | 
					        private static string Draw(HangmanGame.State state)
 | 
				
			||||||
            => $@". ┌─────┐
 | 
					            => $"""
 | 
				
			||||||
.┃...............┋
 | 
					            . ┌─────┐
 | 
				
			||||||
.┃...............┋
 | 
					            .┃...............┋
 | 
				
			||||||
.┃{(state.Errors > 0 ? ".............😲" : "")}
 | 
					            .┃...............┋
 | 
				
			||||||
.┃{(state.Errors > 1 ? "............./" : "")} {(state.Errors > 2 ? "|" : "")} {(state.Errors > 3 ? "\\" : "")}
 | 
					            .┃{(state.Errors > 0 ? ".............😲" : "")}
 | 
				
			||||||
.┃{(state.Errors > 4 ? "............../" : "")} {(state.Errors > 5 ? "\\" : "")}
 | 
					            .┃{(state.Errors > 1 ? "............./" : "")} {(state.Errors > 2 ? "|" : "")} {(state.Errors > 3 ? "\\" : "")}
 | 
				
			||||||
/-\";
 | 
					            .┃{(state.Errors > 4 ? "............../" : "")} {(state.Errors > 5 ? "\\" : "")}
 | 
				
			||||||
 | 
					            /-\
 | 
				
			||||||
 | 
					            """;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static IEmbedBuilder GetEmbed(IEmbedBuilderService eb, HangmanGame.State state)
 | 
					        public static IEmbedBuilder GetEmbed(IEmbedBuilderService eb, HangmanGame.State state)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,7 +69,7 @@ public class TypingGame
 | 
				
			|||||||
        try
 | 
					        try
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await Channel.SendConfirmAsync(_eb,
 | 
					            await Channel.SendConfirmAsync(_eb,
 | 
				
			||||||
                $@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.");
 | 
					                $":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var time = _options.StartTime;
 | 
					            var time = _options.StartTime;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,13 +78,13 @@ public class TriviaQuestion
 | 
				
			|||||||
    private static string Clean(string str)
 | 
					    private static string Clean(string str)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        str = " " + str.ToLowerInvariant() + " ";
 | 
					        str = " " + str.ToLowerInvariant() + " ";
 | 
				
			||||||
        str = Regex.Replace(str, "\\s+", " ");
 | 
					        str = Regex.Replace(str, @"\s+", " ");
 | 
				
			||||||
        str = Regex.Replace(str, "[^\\w\\d\\s]", "");
 | 
					        str = Regex.Replace(str, @"[^\w\d\s]", "");
 | 
				
			||||||
        //Here's where custom modification can be done
 | 
					        //Here's where custom modification can be done
 | 
				
			||||||
        str = Regex.Replace(str, "\\s(a|an|the|of|in|for|to|as|at|be)\\s", " ");
 | 
					        str = Regex.Replace(str, @"\s(a|an|the|of|in|for|to|as|at|be)\s", " ");
 | 
				
			||||||
        //End custom mod and cleanup whitespace
 | 
					        //End custom mod and cleanup whitespace
 | 
				
			||||||
        str = Regex.Replace(str, "^\\s+", "");
 | 
					        str = Regex.Replace(str, @"^\s+", "");
 | 
				
			||||||
        str = Regex.Replace(str, "\\s+$", "");
 | 
					        str = Regex.Replace(str, @"\s+$", "");
 | 
				
			||||||
        //Trim the really long answers
 | 
					        //Trim the really long answers
 | 
				
			||||||
        str = str.Length <= MAX_STRING_LENGTH ? str : str[..MAX_STRING_LENGTH];
 | 
					        str = str.Length <= MAX_STRING_LENGTH ? str : str[..MAX_STRING_LENGTH];
 | 
				
			||||||
        return str;
 | 
					        return str;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -490,13 +490,15 @@ public partial class Help : NadekoModule<HelpService>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private Task SelfhostAction(SocketMessageComponent smc, object _)
 | 
					    private Task SelfhostAction(SocketMessageComponent smc, object _)
 | 
				
			||||||
        => smc.RespondConfirmAsync(_eb,
 | 
					        => smc.RespondConfirmAsync(_eb,
 | 
				
			||||||
            @"- In case you don't want or cannot Donate to NadekoBot project, but you 
 | 
					            """
 | 
				
			||||||
- NadekoBot is a completely free and fully [open source](https://gitlab.com/kwoth/nadekobot) project which means you can run your own ""selfhosted"" instance on your computer or server for free.
 | 
					                - In case you don't want or cannot Donate to NadekoBot project, but you 
 | 
				
			||||||
 | 
					                - NadekoBot is a completely free and fully [open source](https://gitlab.com/kwoth/nadekobot) project which means you can run your own "selfhosted" instance on your computer or server for free.
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
*Keep in mind that running the bot on your computer means that the bot will be offline when you turn off your computer*
 | 
					                *Keep in mind that running the bot on your computer means that the bot will be offline when you turn off your computer*
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
- You can find the selfhosting guides by using the `.guide` command and clicking on the second link that pops up.
 | 
					                - You can find the selfhosting guides by using the `.guide` command and clicking on the second link that pops up.
 | 
				
			||||||
- If you decide to selfhost the bot, still consider [supporting the project](https://patreon.com/join/nadekobot) to keep the development going :)",
 | 
					                - If you decide to selfhost the bot, still consider [supporting the project](https://patreon.com/join/nadekobot) to keep the development going :)
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
            true);
 | 
					            true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Cmd]
 | 
					    [Cmd]
 | 
				
			||||||
@@ -541,12 +543,14 @@ Nadeko will DM you the welcome instructions, and you may start using the patron-
 | 
				
			|||||||
🎉 **Enjoy!** 🎉
 | 
					🎉 **Enjoy!** 🎉
 | 
				
			||||||
")
 | 
					")
 | 
				
			||||||
            .AddField("Troubleshooting",
 | 
					            .AddField("Troubleshooting",
 | 
				
			||||||
                @"
 | 
					                """
 | 
				
			||||||
*In case you didn't receive the rewards within 5 minutes:*
 | 
					                    
 | 
				
			||||||
`1.` Make sure your DMs are open to everyone. Maybe your pledge was processed successfully but the bot was unable to DM you. Use the `.patron` command to check your status.
 | 
					                    *In case you didn't receive the rewards within 5 minutes:*
 | 
				
			||||||
`2.` Make sure you've connected the CORRECT Discord account. Quite often users log in to different Discord accounts in their browser. You may also try disconnecting and reconnecting your account.
 | 
					                    `1.` Make sure your DMs are open to everyone. Maybe your pledge was processed successfully but the bot was unable to DM you. Use the `.patron` command to check your status.
 | 
				
			||||||
`3.` Make sure your payment has been processed and not declined by Patreon.
 | 
					                    `2.` Make sure you've connected the CORRECT Discord account. Quite often users log in to different Discord accounts in their browser. You may also try disconnecting and reconnecting your account.
 | 
				
			||||||
`4.` If any of the previous steps don't help, you can join the nadeko support server <https://discord.nadeko.bot> and ask for help in the #help channel");
 | 
					                    `3.` Make sure your payment has been processed and not declined by Patreon.
 | 
				
			||||||
 | 
					                    `4.` If any of the previous steps don't help, you can join the nadeko support server <https://discord.nadeko.bot> and ask for help in the #help channel
 | 
				
			||||||
 | 
					                    """);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,9 +39,9 @@ public static class MusicExtensions
 | 
				
			|||||||
        if (trackInfo.Duration == TimeSpan.MaxValue)
 | 
					        if (trackInfo.Duration == TimeSpan.MaxValue)
 | 
				
			||||||
            return "∞";
 | 
					            return "∞";
 | 
				
			||||||
        if (trackInfo.Duration.TotalHours >= 1)
 | 
					        if (trackInfo.Duration.TotalHours >= 1)
 | 
				
			||||||
            return trackInfo.Duration.ToString(@"hh\:mm\:ss");
 | 
					            return trackInfo.Duration.ToString("""hh\:mm\:ss""");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return trackInfo.Duration.ToString(@"mm\:ss");
 | 
					        return trackInfo.Duration.ToString("""mm\:ss""");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static ICachableTrackData ToCachedData(this ITrackInfo trackInfo, string id)
 | 
					    public static ICachableTrackData ToCachedData(this ITrackInfo trackInfo, string id)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,10 @@ namespace NadekoBot.Modules.Music.Resolvers;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public class RadioResolver : IRadioResolver
 | 
					public class RadioResolver : IRadioResolver
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private readonly Regex _plsRegex = new("File1=(?<url>.*?)\\n", RegexOptions.Compiled);
 | 
					    private readonly Regex _plsRegex = new(@"File1=(?<url>.*?)\n", RegexOptions.Compiled);
 | 
				
			||||||
    private readonly Regex _m3URegex = new("(?<url>^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline);
 | 
					    private readonly Regex _m3URegex = new(@"(?<url>^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline);
 | 
				
			||||||
    private readonly Regex _asxRegex = new("<ref href=\"(?<url>.*?)\"", RegexOptions.Compiled);
 | 
					    private readonly Regex _asxRegex = new(@"<ref href=""(?<url>.*?)""", RegexOptions.Compiled);
 | 
				
			||||||
    private readonly Regex _xspfRegex = new("<location>(?<url>.*?)</location>", RegexOptions.Compiled);
 | 
					    private readonly Regex _xspfRegex = new(@"<location>(?<url>.*?)</location>", RegexOptions.Compiled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async Task<ITrackInfo> ResolveByQueryAsync(string query)
 | 
					    public async Task<ITrackInfo> ResolveByQueryAsync(string query)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ public partial class Searches
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var channelId = m.Groups["channelid"].Value;
 | 
					            var channelId = m.Groups["channelid"].Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Feed("https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId, channel, message);
 | 
					            return Feed($"https://www.youtube.com/feeds/videos.xml?channel_id={channelId}", channel, message);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Cmd]
 | 
					        [Cmd]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -304,7 +304,7 @@ public partial class Searches
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private static string ShortLeagueName(string str)
 | 
					        private static string ShortLeagueName(string str)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var league = Regex.Replace(str, "Hardcore", "HC", RegexOptions.IgnoreCase);
 | 
					            var league = str.Replace("Hardcore", "HC", StringComparison.InvariantCultureIgnoreCase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return league;
 | 
					            return league;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,51 +9,61 @@ public partial class SearchesConfig : ICloneable<SearchesConfig>
 | 
				
			|||||||
    [Comment("DO NOT CHANGE")]
 | 
					    [Comment("DO NOT CHANGE")]
 | 
				
			||||||
    public int Version { get; set; } = 0;
 | 
					    public int Version { get; set; } = 0;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(@"Which engine should .search command
 | 
					    [Comment("""
 | 
				
			||||||
'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys.
 | 
					        Which engine should .search command
 | 
				
			||||||
'google' - official google api. Requires googleApiKey and google.searchId set in creds.yml
 | 
					        'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys.
 | 
				
			||||||
'searx' - requires at least one searx instance specified in the 'searxInstances' property below")]
 | 
					        'google' - official google api. Requires googleApiKey and google.searchId set in creds.yml
 | 
				
			||||||
 | 
					        'searx' - requires at least one searx instance specified in the 'searxInstances' property below
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public WebSearchEngine WebSearchEngine { get; set; } = WebSearchEngine.Google_Scrape;
 | 
					    public WebSearchEngine WebSearchEngine { get; set; } = WebSearchEngine.Google_Scrape;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(@"Which engine should .image command use
 | 
					    [Comment("""
 | 
				
			||||||
'google'- official google api. googleApiKey and google.imageSearchId set in creds.yml
 | 
					        Which engine should .image command use
 | 
				
			||||||
'searx' requires at least one searx instance specified in the 'searxInstances' property below")]
 | 
					        'google'- official google api. googleApiKey and google.imageSearchId set in creds.yml
 | 
				
			||||||
 | 
					        'searx' requires at least one searx instance specified in the 'searxInstances' property below
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public ImgSearchEngine ImgSearchEngine { get; set; } = ImgSearchEngine.Google;
 | 
					    public ImgSearchEngine ImgSearchEngine { get; set; } = ImgSearchEngine.Google;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Which search provider will be used for the `.youtube` command.
 | 
					    [Comment("""
 | 
				
			||||||
 | 
					        Which search provider will be used for the `.youtube` command.
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
- `ytDataApiv3` - uses google's official youtube data api. Requires `GoogleApiKey` set in creds and youtube data api enabled in developers console
 | 
					        - `ytDataApiv3` - uses google's official youtube data api. Requires `GoogleApiKey` set in creds and youtube data api enabled in developers console
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
- `ytdl` - default, uses youtube-dl. Requires `youtube-dl` to be installed and it's path added to env variables. Slow.
 | 
					        - `ytdl` - default, uses youtube-dl. Requires `youtube-dl` to be installed and it's path added to env variables. Slow.
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
- `ytdlp` - recommended easy, uses `yt-dlp`. Requires `yt-dlp` to be installed and it's path added to env variables
 | 
					        - `ytdlp` - recommended easy, uses `yt-dlp`. Requires `yt-dlp` to be installed and it's path added to env variables
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
- `invidious` - recommended advanced, uses invidious api. Requires at least one invidious instance specified in the `invidiousInstances` property")]
 | 
					        - `invidious` - recommended advanced, uses invidious api. Requires at least one invidious instance specified in the `invidiousInstances` property
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public YoutubeSearcher YtProvider { get; set; } = YoutubeSearcher.Ytdl;
 | 
					    public YoutubeSearcher YtProvider { get; set; } = YoutubeSearcher.Ytdl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Set the searx instance urls in case you want to use 'searx' for either img or web search.
 | 
					    [Comment("""
 | 
				
			||||||
Nadeko will use a random one for each request.
 | 
					        Set the searx instance urls in case you want to use 'searx' for either img or web search.
 | 
				
			||||||
Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com`
 | 
					        Nadeko will use a random one for each request.
 | 
				
			||||||
Instances specified must support 'format=json' query parameter.
 | 
					        Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com`
 | 
				
			||||||
- In case you're running your own searx instance, set 
 | 
					        Instances specified must support 'format=json' query parameter.
 | 
				
			||||||
 | 
					        - In case you're running your own searx instance, set 
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
search:
 | 
					        search:
 | 
				
			||||||
  formats:
 | 
					          formats:
 | 
				
			||||||
    - json
 | 
					            - json
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
in 'searxng/settings.yml' on your server 
 | 
					        in 'searxng/settings.yml' on your server 
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
- If you're using a public instance, make sure that the instance you're using supports it (they usually don't)")]
 | 
					        - If you're using a public instance, make sure that the instance you're using supports it (they usually don't)
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public List<string> SearxInstances { get; set; } = new List<string>();
 | 
					    public List<string> SearxInstances { get; set; } = new List<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search
 | 
					    [Comment("""
 | 
				
			||||||
Nadeko will use a random one for each request.
 | 
					        Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search
 | 
				
			||||||
These instances may be used for music queue functionality in the future.
 | 
					        Nadeko will use a random one for each request.
 | 
				
			||||||
Use a fully qualified url. Example: https://my-invidious-instance.mydomain.com
 | 
					        These instances may be used for music queue functionality in the future.
 | 
				
			||||||
 | 
					        Use a fully qualified url. Example: https://my-invidious-instance.mydomain.com
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
Instances specified must have api available.
 | 
					        Instances specified must have api available.
 | 
				
			||||||
You check that by opening an api endpoint in your browser. For example: https://my-invidious-instance.mydomain.com/api/v1/trending")]
 | 
					        You check that by opening an api endpoint in your browser. For example: https://my-invidious-instance.mydomain.com/api/v1/trending
 | 
				
			||||||
 | 
					        """)]
 | 
				
			||||||
    public List<string> InvidiousInstances { get; set; } = new List<string>();
 | 
					    public List<string> InvidiousInstances { get; set; } = new List<string>();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,8 +23,10 @@ public class TrovoProvider : Provider
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (string.IsNullOrWhiteSpace(creds.GetCreds().TrovoClientId))
 | 
					        if (string.IsNullOrWhiteSpace(creds.GetCreds().TrovoClientId))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Log.Warning(@"Trovo streams are using a default clientId.
 | 
					            Log.Warning("""
 | 
				
			||||||
If you are experiencing ratelimits, you should create your own application at: https://developer.trovo.live/");
 | 
					                Trovo streams are using a default clientId.
 | 
				
			||||||
 | 
					                If you are experiencing ratelimits, you should create your own application at: https://developer.trovo.live/
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,7 +52,7 @@ public partial class Utility
 | 
				
			|||||||
                        var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
 | 
					                        var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
 | 
				
			||||||
                            ? "∞"
 | 
					                            ? "∞"
 | 
				
			||||||
                            : (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow).ToString(
 | 
					                            : (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow).ToString(
 | 
				
			||||||
                                @"d\.hh\:mm\:ss");
 | 
					                                """d\.hh\:mm\:ss""");
 | 
				
			||||||
                        var creator = inv.Inviter.ToString().TrimTo(25);
 | 
					                        var creator = inv.Inviter.ToString().TrimTo(25);
 | 
				
			||||||
                        var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "∞" : inv.MaxUses?.ToString())}";
 | 
					                        var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "∞" : inv.MaxUses?.ToString())}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -782,13 +782,15 @@ public sealed class PatronageService
 | 
				
			|||||||
                            patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(),
 | 
					                            patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(),
 | 
				
			||||||
                            true)
 | 
					                            true)
 | 
				
			||||||
                        .AddField("Instructions",
 | 
					                        .AddField("Instructions",
 | 
				
			||||||
                            @"*- Within the next **1-2 minutes** you will have all of the benefits of the Tier you've subscribed to.*
 | 
					                            """
 | 
				
			||||||
*- You can check your benefits on <https://www.patreon.com/join/nadekobot>*
 | 
					                                *- Within the next **1-2 minutes** you will have all of the benefits of the Tier you've subscribed to.*
 | 
				
			||||||
*- You can use the `.patron` command in this chat to check your current quota usage for the Patron-only commands*
 | 
					                                *- You can check your benefits on <https://www.patreon.com/join/nadekobot>*
 | 
				
			||||||
*- **ALL** of the servers that you **own** will enjoy your Patron benefits.*
 | 
					                                *- You can use the `.patron` command in this chat to check your current quota usage for the Patron-only commands*
 | 
				
			||||||
*- You can use any of the commands available in your tier on any server (assuming you have sufficient permissions to run those commands)*
 | 
					                                *- **ALL** of the servers that you **own** will enjoy your Patron benefits.*
 | 
				
			||||||
*- Any user in any of your servers can use Patron-only commands, but they will spend **your quota**, which is why it's recommended to use Nadeko's command cooldown system (.h .cmdcd) or permission system to limit the command usage for your server members.*
 | 
					                                *- You can use any of the commands available in your tier on any server (assuming you have sufficient permissions to run those commands)*
 | 
				
			||||||
*- Permission guide can be found here if you're not familiar with it: <https://nadekobot.readthedocs.io/en/latest/permissions-system/>*",
 | 
					                                *- Any user in any of your servers can use Patron-only commands, but they will spend **your quota**, which is why it's recommended to use Nadeko's command cooldown system (.h .cmdcd) or permission system to limit the command usage for your server members.*
 | 
				
			||||||
 | 
					                                *- Permission guide can be found here if you're not familiar with it: <https://nadekobot.readthedocs.io/en/latest/permissions-system/>*
 | 
				
			||||||
 | 
					                                """,
 | 
				
			||||||
                            isInline: false)
 | 
					                            isInline: false)
 | 
				
			||||||
                        .WithFooter($"platform id: {patron.UniquePlatformUserId}");
 | 
					                        .WithFooter($"platform id: {patron.UniquePlatformUserId}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,12 +13,14 @@ public partial class Utility
 | 
				
			|||||||
    public partial class QuoteCommands : NadekoModule
 | 
					    public partial class QuoteCommands : NadekoModule
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private const string PREPEND_EXPORT =
 | 
					        private const string PREPEND_EXPORT =
 | 
				
			||||||
            @"# Keys are keywords, Each key has a LIST of quotes in the following format:
 | 
					            """
 | 
				
			||||||
# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.) 
 | 
					                # Keys are keywords, Each key has a LIST of quotes in the following format:
 | 
				
			||||||
#   an: Author name 
 | 
					                # - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.) 
 | 
				
			||||||
#   aid: Author id 
 | 
					                #   an: Author name 
 | 
				
			||||||
#   txt: Quote text
 | 
					                #   aid: Author id 
 | 
				
			||||||
";
 | 
					                #   txt: Quote text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                """;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly ISerializer _exportSerializer = new SerializerBuilder()
 | 
					        private static readonly ISerializer _exportSerializer = new SerializerBuilder()
 | 
				
			||||||
            .WithEventEmitter(args
 | 
					            .WithEventEmitter(args
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,58 +9,68 @@ namespace NadekoBot.Modules.Xp;
 | 
				
			|||||||
[Cloneable]
 | 
					[Cloneable]
 | 
				
			||||||
public sealed partial class XpConfig : ICloneable<XpConfig>
 | 
					public sealed partial class XpConfig : ICloneable<XpConfig>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Comment(@"DO NOT CHANGE")]
 | 
					    [Comment("""DO NOT CHANGE""")]
 | 
				
			||||||
    public int Version { get; set; } = 5;
 | 
					    public int Version { get; set; } = 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"How much XP will the users receive per message")]
 | 
					    [Comment("""How much XP will the users receive per message""")]
 | 
				
			||||||
    public int XpPerMessage { get; set; } = 3;
 | 
					    public int XpPerMessage { get; set; } = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"How often can the users receive XP in minutes")]
 | 
					    [Comment("""How often can the users receive XP in minutes""")]
 | 
				
			||||||
    public int MessageXpCooldown { get; set; } = 5;
 | 
					    public int MessageXpCooldown { get; set; } = 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Amount of xp users gain from posting an image")]
 | 
					    [Comment("""Amount of xp users gain from posting an image""")]
 | 
				
			||||||
    public int XpFromImage { get; set; } = 0;
 | 
					    public int XpFromImage { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Average amount of xp earned per minute in VC")]
 | 
					    [Comment("""Average amount of xp earned per minute in VC""")]
 | 
				
			||||||
    public double VoiceXpPerMinute { get; set; } = 0;
 | 
					    public double VoiceXpPerMinute { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"The maximum amount of minutes the bot will keep track of a user in a voice channel")]
 | 
					    [Comment("""The maximum amount of minutes the bot will keep track of a user in a voice channel""")]
 | 
				
			||||||
    public int VoiceMaxMinutes { get; set; } = 720;
 | 
					    public int VoiceMaxMinutes { get; set; } = 720;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    [Comment(@"The amount of currency users will receive for each point of global xp that they earn")]
 | 
					    [Comment("""The amount of currency users will receive for each point of global xp that they earn""")]
 | 
				
			||||||
    public float CurrencyPerXp { get; set; } = 0;
 | 
					    public float CurrencyPerXp { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Comment(@"Xp Shop config")]
 | 
					    [Comment("""Xp Shop config""")]
 | 
				
			||||||
    public ShopConfig Shop { get; set; } = new();
 | 
					    public ShopConfig Shop { get; set; } = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public sealed class ShopConfig
 | 
					    public sealed class ShopConfig
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Comment(@"Whether the xp shop is enabled
 | 
					        [Comment("""
 | 
				
			||||||
True -> Users can access the xp shop using .xpshop command
 | 
					            Whether the xp shop is enabled
 | 
				
			||||||
False -> Users can't access the xp shop")]
 | 
					            True -> Users can access the xp shop using .xpshop command
 | 
				
			||||||
 | 
					            False -> Users can't access the xp shop
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public bool IsEnabled { get; set; } = false;
 | 
					        public bool IsEnabled { get; set; } = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"Which patron tier do users need in order to use the .xpshop bgs command
 | 
					        [Comment("""
 | 
				
			||||||
Leave at 'None' if patron system is disabled or you don't want any restrictions")]
 | 
					            Which patron tier do users need in order to use the .xpshop bgs command
 | 
				
			||||||
 | 
					            Leave at 'None' if patron system is disabled or you don't want any restrictions
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public PatronTier BgsTierRequirement { get; set; } = PatronTier.None;
 | 
					        public PatronTier BgsTierRequirement { get; set; } = PatronTier.None;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Comment(@"Which patron tier do users need in order to use the .xpshop frames command
 | 
					        [Comment("""
 | 
				
			||||||
Leave at 'None' if patron system is disabled or you don't want any restrictions")]
 | 
					            Which patron tier do users need in order to use the .xpshop frames command
 | 
				
			||||||
 | 
					            Leave at 'None' if patron system is disabled or you don't want any restrictions
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public PatronTier FramesTierRequirement { get; set; } = PatronTier.None;
 | 
					        public PatronTier FramesTierRequirement { get; set; } = PatronTier.None;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Comment(@"Frames available for sale. Keys are unique IDs.
 | 
					        [Comment("""
 | 
				
			||||||
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
 | 
					            Frames available for sale. Keys are unique IDs.
 | 
				
			||||||
Removing a key which previously existed means that all previous purchases will also be unusable.
 | 
					            Do not change keys as they are not publicly visible. Only change properties (name, price, id)
 | 
				
			||||||
To remove an item from the shop, but keep previous purchases, set the price to -1")]
 | 
					            Removing a key which previously existed means that all previous purchases will also be unusable.
 | 
				
			||||||
 | 
					            To remove an item from the shop, but keep previous purchases, set the price to -1
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public Dictionary<string, ShopItemInfo>? Frames { get; set; } = new()
 | 
					        public Dictionary<string, ShopItemInfo>? Frames { get; set; } = new()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            {"default", new() {Name = "No frame", Price = 0, Url = string.Empty}}
 | 
					            {"default", new() {Name = "No frame", Price = 0, Url = string.Empty}}
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Comment(@"Backgrounds available for sale. Keys are unique IDs. 
 | 
					        [Comment("""
 | 
				
			||||||
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
 | 
					            Backgrounds available for sale. Keys are unique IDs. 
 | 
				
			||||||
Removing a key which previously existed means that all previous purchases will also be unusable.
 | 
					            Do not change keys as they are not publicly visible. Only change properties (name, price, id)
 | 
				
			||||||
To remove an item from the shop, but keep previous purchases, set the price to -1")]
 | 
					            Removing a key which previously existed means that all previous purchases will also be unusable.
 | 
				
			||||||
 | 
					            To remove an item from the shop, but keep previous purchases, set the price to -1
 | 
				
			||||||
 | 
					            """)]
 | 
				
			||||||
        public Dictionary<string, ShopItemInfo>? Bgs { get; set; } = new()
 | 
					        public Dictionary<string, ShopItemInfo>? Bgs { get; set; } = new()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            {"default", new() {Name = "Default Background", Price = 0, Url = string.Empty}}
 | 
					            {"default", new() {Name = "Default Background", Price = 0, Url = string.Empty}}
 | 
				
			||||||
@@ -69,19 +79,19 @@ To remove an item from the shop, but keep previous purchases, set the price to -
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public sealed class ShopItemInfo
 | 
					    public sealed class ShopItemInfo
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Comment(@"Visible name of the item")]
 | 
					        [Comment("""Visible name of the item""")]
 | 
				
			||||||
        public string Name { get; set; }
 | 
					        public string Name { get; set; }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Comment(@"Price of the item. Set to -1 if you no longer want to sell the item but want the users to be able to keep their old purchase")]
 | 
					        [Comment("""Price of the item. Set to -1 if you no longer want to sell the item but want the users to be able to keep their old purchase""")]
 | 
				
			||||||
        public int Price { get; set; }
 | 
					        public int Price { get; set; }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Comment(@"Direct url to the .png image which will be applied to the user's XP card")]
 | 
					        [Comment("""Direct url to the .png image which will be applied to the user's XP card""")]
 | 
				
			||||||
        public string Url { get; set; }
 | 
					        public string Url { get; set; }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Comment(@"Optional preview url which will show instead of the real URL in the shop ")]
 | 
					        [Comment("""Optional preview url which will show instead of the real URL in the shop """)]
 | 
				
			||||||
        public string Preview { get; set; }
 | 
					        public string Preview { get; set; }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Comment(@"Optional description of the item")]
 | 
					        [Comment("""Optional description of the item""")]
 | 
				
			||||||
        public string Desc { get; set; }
 | 
					        public string Desc { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ public partial class Xp
 | 
				
			|||||||
                                     .OrderBy(x => x.Level)
 | 
					                                     .OrderBy(x => x.Level)
 | 
				
			||||||
                                     .Select(x =>
 | 
					                                     .Select(x =>
 | 
				
			||||||
                                     {
 | 
					                                     {
 | 
				
			||||||
                                         var sign = !x.Remove ? @"✅ " : @"❌ ";
 | 
					                                         var sign = !x.Remove ? "✅ " : "❌ ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                         var str = ctx.Guild.GetRole(x.RoleId)?.ToString();
 | 
					                                         var str = ctx.Guild.GetRole(x.RoleId)?.ToString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -147,11 +147,13 @@ public class CommandHandler : INService, IReadyExecutor
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        if (_bss.Data.ConsoleOutputType == ConsoleOutputType.Normal)
 | 
					        if (_bss.Data.ConsoleOutputType == ConsoleOutputType.Normal)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Log.Information(@"Command Executed after {ExecTime}s
 | 
					            Log.Information("""
 | 
				
			||||||
	User: {User}
 | 
					                Command Executed after {ExecTime}s
 | 
				
			||||||
	Server: {Server}
 | 
					                	User: {User}
 | 
				
			||||||
	Channel: {Channel}
 | 
					                	Server: {Server}
 | 
				
			||||||
	Message: {Message}",
 | 
					                	Channel: {Channel}
 | 
				
			||||||
 | 
					                	Message: {Message}
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
                string.Join("/", execPoints.Select(x => (x * ONE_THOUSANDTH).ToString("F3"))),
 | 
					                string.Join("/", execPoints.Select(x => (x * ONE_THOUSANDTH).ToString("F3"))),
 | 
				
			||||||
                usrMsg.Author + " [" + usrMsg.Author.Id + "]",
 | 
					                usrMsg.Author + " [" + usrMsg.Author.Id + "]",
 | 
				
			||||||
                channel is null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]",
 | 
					                channel is null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]",
 | 
				
			||||||
@@ -178,12 +180,14 @@ public class CommandHandler : INService, IReadyExecutor
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        if (_bss.Data.ConsoleOutputType == ConsoleOutputType.Normal)
 | 
					        if (_bss.Data.ConsoleOutputType == ConsoleOutputType.Normal)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Log.Warning(@"Command Errored after {ExecTime}s
 | 
					            Log.Warning("""
 | 
				
			||||||
	User: {User}
 | 
					                Command Errored after {ExecTime}s
 | 
				
			||||||
	Server: {Guild}
 | 
					                	User: {User}
 | 
				
			||||||
	Channel: {Channel}
 | 
					                	Server: {Guild}
 | 
				
			||||||
	Message: {Message}
 | 
					                	Channel: {Channel}
 | 
				
			||||||
	Error: {ErrorMessage}",
 | 
					                	Message: {Message}
 | 
				
			||||||
 | 
					                	Error: {ErrorMessage}
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
                string.Join("/", execPoints.Select(x => (x * ONE_THOUSANDTH).ToString("F3"))),
 | 
					                string.Join("/", execPoints.Select(x => (x * ONE_THOUSANDTH).ToString("F3"))),
 | 
				
			||||||
                usrMsg.Author + " [" + usrMsg.Author.Id + "]",
 | 
					                usrMsg.Author + " [" + usrMsg.Author.Id + "]",
 | 
				
			||||||
                channel is null ? "DM" : channel.Guild.Name + " [" + channel.Guild.Id + "]",
 | 
					                channel is null ? "DM" : channel.Guild.Name + " [" + channel.Guild.Id + "]",
 | 
				
			||||||
@@ -193,8 +197,10 @@ public class CommandHandler : INService, IReadyExecutor
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Log.Warning(@"Err | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}
 | 
					            Log.Warning("""
 | 
				
			||||||
	Err: {ErrorMessage}",
 | 
					                Err | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}
 | 
				
			||||||
 | 
					                	Err: {ErrorMessage}
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
                channel?.Guild.Id.ToString() ?? "-",
 | 
					                channel?.Guild.Id.ToString() ?? "-",
 | 
				
			||||||
                channel?.Id.ToString() ?? "-",
 | 
					                channel?.Id.ToString() ?? "-",
 | 
				
			||||||
                usrMsg.Author.Id,
 | 
					                usrMsg.Author.Id,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ namespace NadekoBot.Services;
 | 
				
			|||||||
public sealed partial class GoogleApiService : IGoogleApiService, INService
 | 
					public sealed partial class GoogleApiService : IGoogleApiService, INService
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private static readonly Regex
 | 
					    private static readonly Regex
 | 
				
			||||||
        _plRegex = new("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)", RegexOptions.Compiled);
 | 
					        _plRegex = new(@"(?:youtu\.be\/|list=)(?<id>[\da-zA-Z\-_]*)", RegexOptions.Compiled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private readonly YouTubeService _yt;
 | 
					    private readonly YouTubeService _yt;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,140 +2,142 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public sealed partial class GoogleApiService
 | 
					public sealed partial class GoogleApiService
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private const string SUPPORTED = @"afrikaans af
 | 
					    private const string SUPPORTED = """
 | 
				
			||||||
albanian sq
 | 
					        afrikaans af
 | 
				
			||||||
amharic am
 | 
					        albanian sq
 | 
				
			||||||
arabic ar
 | 
					        amharic am
 | 
				
			||||||
armenian hy
 | 
					        arabic ar
 | 
				
			||||||
assamese as
 | 
					        armenian hy
 | 
				
			||||||
aymara ay
 | 
					        assamese as
 | 
				
			||||||
azerbaijani az
 | 
					        aymara ay
 | 
				
			||||||
bambara bm
 | 
					        azerbaijani az
 | 
				
			||||||
basque eu
 | 
					        bambara bm
 | 
				
			||||||
belarusian be
 | 
					        basque eu
 | 
				
			||||||
bengali bn
 | 
					        belarusian be
 | 
				
			||||||
bhojpuri bho
 | 
					        bengali bn
 | 
				
			||||||
bosnian bs
 | 
					        bhojpuri bho
 | 
				
			||||||
bulgarian bg
 | 
					        bosnian bs
 | 
				
			||||||
catalan ca
 | 
					        bulgarian bg
 | 
				
			||||||
cebuano ceb
 | 
					        catalan ca
 | 
				
			||||||
chinese zh-CN
 | 
					        cebuano ceb
 | 
				
			||||||
chinese-trad zh-TW
 | 
					        chinese zh-CN
 | 
				
			||||||
corsican co
 | 
					        chinese-trad zh-TW
 | 
				
			||||||
croatian hr
 | 
					        corsican co
 | 
				
			||||||
czech cs
 | 
					        croatian hr
 | 
				
			||||||
danish da
 | 
					        czech cs
 | 
				
			||||||
dhivehi dv
 | 
					        danish da
 | 
				
			||||||
dogri doi
 | 
					        dhivehi dv
 | 
				
			||||||
dutch nl
 | 
					        dogri doi
 | 
				
			||||||
english en
 | 
					        dutch nl
 | 
				
			||||||
esperanto eo
 | 
					        english en
 | 
				
			||||||
estonian et
 | 
					        esperanto eo
 | 
				
			||||||
ewe ee
 | 
					        estonian et
 | 
				
			||||||
filipino fil
 | 
					        ewe ee
 | 
				
			||||||
finnish fi
 | 
					        filipino fil
 | 
				
			||||||
french fr
 | 
					        finnish fi
 | 
				
			||||||
frisian fy
 | 
					        french fr
 | 
				
			||||||
galician gl
 | 
					        frisian fy
 | 
				
			||||||
georgian ka
 | 
					        galician gl
 | 
				
			||||||
german de
 | 
					        georgian ka
 | 
				
			||||||
greek el
 | 
					        german de
 | 
				
			||||||
guarani gn
 | 
					        greek el
 | 
				
			||||||
gujarati gu
 | 
					        guarani gn
 | 
				
			||||||
haitian ht
 | 
					        gujarati gu
 | 
				
			||||||
hausa ha
 | 
					        haitian ht
 | 
				
			||||||
hawaiian haw
 | 
					        hausa ha
 | 
				
			||||||
hebrew he 
 | 
					        hawaiian haw
 | 
				
			||||||
hindi hi
 | 
					        hebrew he 
 | 
				
			||||||
hmong hmn
 | 
					        hindi hi
 | 
				
			||||||
hungarian hu
 | 
					        hmong hmn
 | 
				
			||||||
icelandic is
 | 
					        hungarian hu
 | 
				
			||||||
igbo ig
 | 
					        icelandic is
 | 
				
			||||||
ilocano ilo
 | 
					        igbo ig
 | 
				
			||||||
indonesian id
 | 
					        ilocano ilo
 | 
				
			||||||
irish ga
 | 
					        indonesian id
 | 
				
			||||||
italian it
 | 
					        irish ga
 | 
				
			||||||
japanese ja
 | 
					        italian it
 | 
				
			||||||
javanese jv 
 | 
					        japanese ja
 | 
				
			||||||
kannada kn
 | 
					        javanese jv 
 | 
				
			||||||
kazakh kk
 | 
					        kannada kn
 | 
				
			||||||
khmer km
 | 
					        kazakh kk
 | 
				
			||||||
kinyarwanda rw
 | 
					        khmer km
 | 
				
			||||||
konkani gom
 | 
					        kinyarwanda rw
 | 
				
			||||||
korean ko
 | 
					        konkani gom
 | 
				
			||||||
krio kri
 | 
					        korean ko
 | 
				
			||||||
kurdish ku
 | 
					        krio kri
 | 
				
			||||||
kurdish-sor ckb
 | 
					        kurdish ku
 | 
				
			||||||
kyrgyz ky
 | 
					        kurdish-sor ckb
 | 
				
			||||||
lao lo
 | 
					        kyrgyz ky
 | 
				
			||||||
latin la
 | 
					        lao lo
 | 
				
			||||||
latvian lv
 | 
					        latin la
 | 
				
			||||||
lingala ln
 | 
					        latvian lv
 | 
				
			||||||
lithuanian lt
 | 
					        lingala ln
 | 
				
			||||||
luganda lg
 | 
					        lithuanian lt
 | 
				
			||||||
luxembourgish lb
 | 
					        luganda lg
 | 
				
			||||||
macedonian mk
 | 
					        luxembourgish lb
 | 
				
			||||||
maithili mai
 | 
					        macedonian mk
 | 
				
			||||||
malagasy mg
 | 
					        maithili mai
 | 
				
			||||||
malay ms
 | 
					        malagasy mg
 | 
				
			||||||
malayalam ml
 | 
					        malay ms
 | 
				
			||||||
maltese mt
 | 
					        malayalam ml
 | 
				
			||||||
maori mi
 | 
					        maltese mt
 | 
				
			||||||
marathi mr
 | 
					        maori mi
 | 
				
			||||||
meiteilon mni-Mtei
 | 
					        marathi mr
 | 
				
			||||||
mizo lus
 | 
					        meiteilon mni-Mtei
 | 
				
			||||||
mongolian mn
 | 
					        mizo lus
 | 
				
			||||||
myanmar my
 | 
					        mongolian mn
 | 
				
			||||||
nepali ne
 | 
					        myanmar my
 | 
				
			||||||
norwegian no
 | 
					        nepali ne
 | 
				
			||||||
nyanja ny
 | 
					        norwegian no
 | 
				
			||||||
odia or
 | 
					        nyanja ny
 | 
				
			||||||
oromo om
 | 
					        odia or
 | 
				
			||||||
pashto ps
 | 
					        oromo om
 | 
				
			||||||
persian fa
 | 
					        pashto ps
 | 
				
			||||||
polish pl
 | 
					        persian fa
 | 
				
			||||||
portuguese pt
 | 
					        polish pl
 | 
				
			||||||
punjabi pa
 | 
					        portuguese pt
 | 
				
			||||||
quechua qu
 | 
					        punjabi pa
 | 
				
			||||||
romanian ro
 | 
					        quechua qu
 | 
				
			||||||
russian ru
 | 
					        romanian ro
 | 
				
			||||||
samoan sm
 | 
					        russian ru
 | 
				
			||||||
sanskrit sa
 | 
					        samoan sm
 | 
				
			||||||
scots gd
 | 
					        sanskrit sa
 | 
				
			||||||
sepedi nso
 | 
					        scots gd
 | 
				
			||||||
serbian sr
 | 
					        sepedi nso
 | 
				
			||||||
sesotho st
 | 
					        serbian sr
 | 
				
			||||||
shona sn
 | 
					        sesotho st
 | 
				
			||||||
sindhi sd
 | 
					        shona sn
 | 
				
			||||||
sinhala si
 | 
					        sindhi sd
 | 
				
			||||||
slovak sk
 | 
					        sinhala si
 | 
				
			||||||
slovenian sl
 | 
					        slovak sk
 | 
				
			||||||
somali so
 | 
					        slovenian sl
 | 
				
			||||||
spanish es
 | 
					        somali so
 | 
				
			||||||
sundanese su
 | 
					        spanish es
 | 
				
			||||||
swahili sw
 | 
					        sundanese su
 | 
				
			||||||
swedish sv
 | 
					        swahili sw
 | 
				
			||||||
tagalog tl
 | 
					        swedish sv
 | 
				
			||||||
tajik tg
 | 
					        tagalog tl
 | 
				
			||||||
tamil ta
 | 
					        tajik tg
 | 
				
			||||||
tatar tt
 | 
					        tamil ta
 | 
				
			||||||
telugu te
 | 
					        tatar tt
 | 
				
			||||||
thai th
 | 
					        telugu te
 | 
				
			||||||
tigrinya ti
 | 
					        thai th
 | 
				
			||||||
tsonga ts
 | 
					        tigrinya ti
 | 
				
			||||||
turkish tr
 | 
					        tsonga ts
 | 
				
			||||||
turkmen tk
 | 
					        turkish tr
 | 
				
			||||||
twi ak
 | 
					        turkmen tk
 | 
				
			||||||
ukrainian uk
 | 
					        twi ak
 | 
				
			||||||
urdu ur
 | 
					        ukrainian uk
 | 
				
			||||||
uyghur ug
 | 
					        urdu ur
 | 
				
			||||||
uzbek uz
 | 
					        uyghur ug
 | 
				
			||||||
vietnamese vi
 | 
					        uzbek uz
 | 
				
			||||||
welsh cy
 | 
					        vietnamese vi
 | 
				
			||||||
xhosa xh
 | 
					        welsh cy
 | 
				
			||||||
yiddish yi
 | 
					        xhosa xh
 | 
				
			||||||
yoruba yo
 | 
					        yiddish yi
 | 
				
			||||||
zulu zu";
 | 
					        yoruba yo
 | 
				
			||||||
 | 
					        zulu zu
 | 
				
			||||||
 | 
					        """;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public IReadOnlyDictionary<string, string> Languages { get; }
 | 
					    public IReadOnlyDictionary<string, string> Languages { get; }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user