Make extensive use of raw string literals C#11 feature

This commit is contained in:
Kwoth
2023-01-21 01:12:11 +01:00
parent 0fe4f14d96
commit 63a9ae2dac
32 changed files with 756 additions and 587 deletions

View File

@@ -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;
} }

View File

@@ -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()

View File

@@ -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'.
Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value.")] 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.
""")]
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()

View File

@@ -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()

View File

@@ -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
{ {

View File

@@ -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");

View File

@@ -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)

View File

@@ -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",

View File

@@ -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
SET Name = UPDATE Clubs as co
CASE (select count(*) from Clubs as ci where co.Name == ci.Name) = 1 SET Name =
WHEN true CASE (select count(*) from Clubs as ci where co.Name == ci.Name) = 1
THEN Name WHEN true
ELSE THEN Name
Name || '#' || Discrim ELSE
END Name || '#' || Discrim
WHERE Discrim = 1;"); END
WHERE Discrim = 1;
""");
migrationBuilder.DropForeignKey( migrationBuilder.DropForeignKey(
name: "FK_Clubs_DiscordUser_OwnerId", name: "FK_Clubs_DiscordUser_OwnerId",

View File

@@ -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

View File

@@ -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;
} }

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
} }

View File

@@ -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)
{ {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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.
- If you decide to selfhost the bot, still consider [supporting the project](https://patreon.com/join/nadekobot) to keep the development going :)", - 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 :)
""",
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
{ {

View File

@@ -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)

View File

@@ -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)
{ {

View File

@@ -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]

View File

@@ -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;
} }

View File

@@ -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:
formats: search:
- json formats:
- 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.
You check that by opening an api endpoint in your browser. For example: https://my-invidious-instance.mydomain.com/api/v1/trending")] 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
""")]
public List<string> InvidiousInstances { get; set; } = new List<string>(); public List<string> InvidiousInstances { get; set; } = new List<string>();
} }

View File

@@ -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/
""");
} }
} }

View File

@@ -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())}";

View File

@@ -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}");

View File

@@ -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

View File

@@ -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; }
} }
} }

View File

@@ -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();

View File

@@ -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,

View File

@@ -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;

View File

@@ -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; }