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 } })
{
Log.Error(@"
Login failed.
*** Please enable privileged 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.
How to enable privileged intents:
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
2. Select your Application.
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
4. Enable all intents.
5. Restart your bot.
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.
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");
Log.Error("""
Login failed.
*** Please enable privileged 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.
How to enable privileged intents:
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
2. Select your Application.
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
4. Enable all intents.
5. Restart your bot.
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.
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
""");
return Task.CompletedTask;
}

View File

@@ -11,74 +11,89 @@ namespace NadekoBot.Common.Configs;
[Cloneable]
public sealed partial class BotConfig : ICloneable<BotConfig>
{
[Comment(@"DO NOT CHANGE")]
[Comment("""DO NOT CHANGE""")]
public int Version { get; set; } = 5;
[Comment(@"Most commands, when executed, have a small colored line
next to the response. The color depends whether the command
is completed, errored or in progress (pending)
Color settings below are for the color of those lines.
To get color's hex, you can go here https://htmlcolorcodes.com/
and copy the hex code fo your selected color (marked as #)")]
[Comment("""
Most commands, when executed, have a small colored line
next to the response. The color depends whether the command
is completed, errored or in progress (pending)
Color settings below are for the color of those lines.
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; }
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
public CultureInfo DefaultLocale { get; set; }
[Comment(@"Style in which executed commands will show up in the console.
Allowed values: Simple, Normal, None")]
[Comment("""
Style in which executed commands will show up in the console.
Allowed values: Simple, Normal, None
""")]
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;
[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; }
[Comment(
@"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)")]
[Comment("""
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)
""")]
public bool ForwardToAllOwners { get; set; }
[Comment(@"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")]
[Comment("""
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; }
[Comment(@"When a user DMs the bot with a message which is not a command
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")]
[Comment("""
When a user DMs the bot with a message which is not a command
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)]
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.
Case insensitive.
Leave empty to reply with DmHelpText to every DM.")]
[Comment("""
Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
Case insensitive.
Leave empty to reply with DmHelpText to every DM.
""")]
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)]
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; }
[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; }
[Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
1st user who joins will get greeted immediately
If more users join within the next 5 seconds, they will be greeted in groups of 5.
This will cause %user.mention% and other placeholders to be replaced with multiple users.
Keep in mind this might break some of your embeds - for example if you have %user.avatar% in the thumbnail,
it will become invalid, as it will resolve to a list of avatars of grouped users.
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
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.")]
[Comment("""
Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
1st user who joins will get greeted immediately
If more users join within the next 5 seconds, they will be greeted in groups of 5.
This will cause %user.mention% and other placeholders to be replaced with multiple users.
Keep in mind this might break some of your embeds - for example if you have %user.avatar% in the thumbnail,
it will become invalid, as it will resolve to a list of avatars of grouped users.
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
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; }
[Comment(@"Whether the bot will rotate through all specified statuses.
This setting can be changed via .ropl command.
See RotatingStatuses submodule in Administration.")]
[Comment("""
Whether the bot will rotate through all specified statuses.
This setting can be changed via .ropl command.
See RotatingStatuses submodule in Administration.
""")]
public bool RotateStatuses { get; set; }
public BotConfig()
@@ -89,32 +104,34 @@ See RotatingStatuses submodule in Administration.")]
ConsoleOutputType = ConsoleOutputType.Normal;
ForwardMessages = false;
ForwardToAllOwners = false;
DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
HelpText = @"{
""title"": ""To invite me to your server, use this link"",
""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
""color"": 53380,
""thumbnail"": ""https://i.imgur.com/nKYyqMK.png"",
""fields"": [
{
""name"": ""Useful help commands"",
""value"": ""`%bot.prefix%modules` Lists all bot modules.
`%prefix%h CommandName` Shows some help about a specific command.
`%prefix%commands ModuleName` Lists all commands in a module."",
""inline"": false
},
{
""name"": ""List of all Commands"",
""value"": ""https://nadeko.bot/commands"",
""inline"": false
},
{
""name"": ""Nadeko Support Server"",
""value"": ""https://discord.nadeko.bot/ "",
""inline"": true
}
]
}";
DmHelpText = """{"description": "Type `%prefix%h` for help."}""";
HelpText = """
{
"title": "To invite me to your server, use this link",
"description": "https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303",
"color": 53380,
"thumbnail": "https://i.imgur.com/nKYyqMK.png",
"fields": [
{
"name": "Useful help commands",
"value": "`%bot.prefix%modules` Lists all bot modules.
`%prefix%h CommandName` Shows some help about a specific command.
`%prefix%commands ModuleName` Lists all commands in a module.",
"inline": false
},
{
"name": "List of all Commands",
"value": "https://nadeko.bot/commands",
"inline": false
},
{
"name": "Nadeko Support Server",
"value": "https://discord.nadeko.bot/ ",
"inline": true
}
]
}
""";
var blocked = new BlockedConfig();
Blocked = blocked;
Prefix = ".";
@@ -160,13 +177,13 @@ public sealed partial class BlockedConfig
[Cloneable]
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; }
[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; }
[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 ColorConfig()

View File

@@ -5,115 +5,141 @@ namespace NadekoBot.Common;
public sealed class Creds : IBotCredentials
{
[Comment(@"DO NOT CHANGE")]
[Comment("""DO NOT CHANGE""")]
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; }
[Comment(@"List of Ids of the users who have bot owner permissions
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
[Comment("""
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; }
[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; }
[Comment(@"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.")]
[Comment("""
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.
""")]
public int TotalShards { get; set; }
[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.
Used only for Youtube Data Api (at the moment).")]
"""
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.
Used only for Youtube Data Api (at the moment).
""")]
public string GoogleApiKey { get; set; }
[Comment(
@"Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
Enable SafeSearch
Remove all Sites to Search
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")]
"""
Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
Enable SafeSearch
Remove all Sites to Search
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
""")]
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; }
[Comment(@"Patreon auto reward system settings.
go to https://www.patreon.com/portal -> my clients -> create client")]
[Comment("""
Patreon auto reward system settings.
go to https://www.patreon.com/portal -> my clients -> create client
""")]
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; }
[Comment(@"Official cleverbot api key.")]
[Comment("""Official cleverbot api key.""")]
public string CleverbotApiKey { get; set; }
[Comment(@"Which cache implementation should bot use.
'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")]
[Comment("""
Which cache implementation should bot use.
'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; }
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.
Only used if botCache is set to 'redis'")]
[Comment("""
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; }
[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; }
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
Change only if you've changed the coordinator address or port.")]
[Comment("""
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; }
[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; }
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
Used only for .time command.")]
[Comment("""
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; }
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
Used only for .time command")]
[Comment("""
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; }
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
Used for cryptocurrency related commands.")]
[Comment("""
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; }
// [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
// Used for stocks related commands.")]
// 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; }
[Comment(@"Optional Trovo client id.
You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.")]
[Comment("""
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; }
[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; }
[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; }
[Comment(@"Command and args which will be used to restart the bot.
Only used if bot is executed directly (NOT through the coordinator)
placeholders:
{0} -> shard id
{1} -> total shards
Linux default
cmd: dotnet
args: ""NadekoBot.dll -- {0}""
Windows default
cmd: NadekoBot.exe
args: ""{0}""")]
[Comment("""
Command and args which will be used to restart the bot.
Only used if bot is executed directly (NOT through the coordinator)
placeholders:
{0} -> shard id
{1} -> total shards
Linux default
cmd: dotnet
args: "NadekoBot.dll -- {0}"
Windows default
cmd: NadekoBot.exe
args: "{0}"
""")]
public RestartConfig RestartCommand { get; set; }
public Creds()
@@ -145,15 +171,19 @@ Windows default
public class DbOptions
{
[Comment(@"Database type. ""sqlite"", ""mysql"" and ""postgresql"" are supported.
Default is ""sqlite""")]
[Comment("""
Database type. "sqlite", "mysql" and "postgresql" are supported.
Default is "sqlite"
""")]
public string Type { get; set; }
[Comment(@"Database connection string.
You MUST change this if you're not using ""sqlite"" type.
Default is ""Data Source=data/NadekoBot.db""
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;""")]
[Comment("""
Database connection string.
You MUST change this if you're not using "sqlite" type.
Default is "Data Source=data/NadekoBot.db"
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; }
}
@@ -165,7 +195,7 @@ Example for postgresql: ""Server=localhost;Port=5432;User Id=postgres;Password=m
public string ClientSecret { get; set; }
[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 PatreonSettings(
@@ -187,22 +217,30 @@ Example for postgresql: ""Server=localhost;Port=5432;User Id=postgres;Password=m
public sealed record VotesSettings
{
[Comment(@"top.gg votes service url
This is the url of your instance of the NadekoBot.Votes api
Example: https://votes.my.cool.bot.com")]
[Comment("""
top.gg votes service url
This is the url of your instance of the NadekoBot.Votes api
Example: https://votes.my.cool.bot.com
""")]
public string TopggServiceUrl { get; set; }
[Comment(@"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")]
[Comment("""
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; }
[Comment(@"discords.com votes service url
This is the url of your instance of the NadekoBot.Votes api
Example: https://votes.my.cool.bot.com")]
[Comment("""
discords.com votes service url
This is the url of your instance of the NadekoBot.Votes api
Example: https://votes.my.cool.bot.com
""")]
public string DiscordsServiceUrl { get; set; }
[Comment(@"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")]
[Comment("""
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 VotesSettings()

View File

@@ -7,10 +7,10 @@ namespace Nadeko.Medusa;
[Cloneable]
public sealed partial class MedusaConfig : ICloneable<MedusaConfig>
{
[Comment(@"DO NOT CHANGE")]
[Comment("""DO NOT CHANGE""")]
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 MedusaConfig()

View File

@@ -10,29 +10,35 @@ public static class MigrationQueries
if (migrationBuilder.IsMySql())
{
migrationBuilder.Sql(
@"INSERT IGNORE into reactionroles(guildid, channelid, messageid, emote, roleid, `group`, levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
"""
INSERT IGNORE into reactionroles(guildid, channelid, messageid, emote, roleid, `group`, levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;
""");
}
else if (migrationBuilder.IsSqlite())
{
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
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
"""
insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;
""");
}
else if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql(@"insert into reactionroles(guildid, channelid, messageid, emote, roleid, ""group"", levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id
ON CONFLICT DO NOTHING;");
migrationBuilder.Sql("""
insert into reactionroles(guildid, channelid, messageid, emote, roleid, "group", levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id
ON CONFLICT DO NOTHING;
""");
}
else
{

View File

@@ -28,14 +28,16 @@ namespace NadekoBot.Migrations
table: "NsfwBlacklistedTags",
column: "GuildId");
migrationBuilder.Sql(@"INSERT INTO NsfwBlacklistedTags(Id, GuildId, Tag, DateAdded)
SELECT
Id,
(SELECT GuildId From GuildConfigs WHERE Id=GuildConfigId),
Tag,
DateAdded
FROM NsfwBlacklitedTag
WHERE GuildConfigId in (SELECT Id from GuildConfigs);");
migrationBuilder.Sql("""
INSERT INTO NsfwBlacklistedTags(Id, GuildId, Tag, DateAdded)
SELECT
Id,
(SELECT GuildId From GuildConfigs WHERE Id=GuildConfigId),
Tag,
DateAdded
FROM NsfwBlacklitedTag
WHERE GuildConfigId in (SELECT Id from GuildConfigs);
""");
migrationBuilder.DropTable(
name: "NsfwBlacklitedTag");

View File

@@ -50,24 +50,30 @@ namespace NadekoBot.Migrations
principalTable: "GuildConfigs",
principalColumn: "Id");
migrationBuilder.Sql(@"UPDATE Permissions
SET SecondaryTargetName='ACTUALEXPRESSIONS'
WHERE SecondaryTargetName='ActualCustomReactions' COLLATE NOCASE;");
migrationBuilder.Sql("""
UPDATE Permissions
SET SecondaryTargetName='ACTUALEXPRESSIONS'
WHERE SecondaryTargetName='ActualCustomReactions' COLLATE NOCASE;
""");
migrationBuilder.Sql(@"UPDATE Permissions
SET SecondaryTargetName='EXPRESSIONS'
WHERE SecondaryTargetName='CustomReactions' COLLATE NOCASE;");
migrationBuilder.Sql("""
UPDATE Permissions
SET SecondaryTargetName='EXPRESSIONS'
WHERE SecondaryTargetName='CustomReactions' COLLATE NOCASE;
""");
migrationBuilder.Sql(@"UPDATE Permissions
SET SecondaryTargetName= case lower(SecondaryTargetName)
WHEN 'editcustreact' THEN 'expredit'
WHEN 'delcustreact' THEN 'exprdel'
WHEN 'listcustreact' THEN 'exprlist'
WHEN 'addcustreact' THEN 'expradd'
WHEN 'showcustreact' THEN 'exprshow'
ELSE SecondaryTargetName
END
WHERE lower(SecondaryTargetName) in ('editcustreact', 'delcustreact', 'listcustreact', 'addcustreact', 'showcustreact');");
migrationBuilder.Sql("""
UPDATE Permissions
SET SecondaryTargetName= case lower(SecondaryTargetName)
WHEN 'editcustreact' THEN 'expredit'
WHEN 'delcustreact' THEN 'exprdel'
WHEN 'listcustreact' THEN 'exprlist'
WHEN 'addcustreact' THEN 'expradd'
WHEN 'showcustreact' THEN 'exprshow'
ELSE SecondaryTargetName
END
WHERE lower(SecondaryTargetName) in ('editcustreact', 'delcustreact', 'listcustreact', 'addcustreact', 'showcustreact');
""");
}
protected override void Down(MigrationBuilder migrationBuilder)

View File

@@ -42,13 +42,15 @@ namespace NadekoBot.Migrations
table: "FilterWordsChannelId",
column: "GuildConfigId");
migrationBuilder.Sql(@"INSERT INTO FilterWordsChannelId(Id, ChannelId, GuildConfigId, DateAdded)
SELECT Id, ChannelId, GuildConfigId1, DateAdded
FROM FilterChannelId
WHERE GuildConfigId1 is not null;
-- Remove them after moving them to a different table
DELETE FROM FilterChannelId
WHERE GuildConfigId is null;");
migrationBuilder.Sql("""
INSERT INTO FilterWordsChannelId(Id, ChannelId, GuildConfigId, DateAdded)
SELECT Id, ChannelId, GuildConfigId1, DateAdded
FROM FilterChannelId
WHERE GuildConfigId1 is not null;
-- Remove them after moving them to a different table
DELETE FROM FilterChannelId
WHERE GuildConfigId is null;
""");
migrationBuilder.DropColumn(
name: "GuildConfigId1",

View File

@@ -9,19 +9,21 @@ namespace NadekoBot.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"UPDATE Clubs
SET Name = Name || '#' || Discrim
WHERE Discrim <> 1;
UPDATE Clubs as co
SET Name =
CASE (select count(*) from Clubs as ci where co.Name == ci.Name) = 1
WHEN true
THEN Name
ELSE
Name || '#' || Discrim
END
WHERE Discrim = 1;");
migrationBuilder.Sql("""
UPDATE Clubs
SET Name = Name || '#' || Discrim
WHERE Discrim <> 1;
UPDATE Clubs as co
SET Name =
CASE (select count(*) from Clubs as ci where co.Name == ci.Name) = 1
WHEN true
THEN Name
ELSE
Name || '#' || Discrim
END
WHERE Discrim = 1;
""");
migrationBuilder.DropForeignKey(
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 PREPEND_EXPORT =
@"# Keys are triggers, Each key has a LIST of expressions in the following format:
# - res: Response string
# id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.)
# react:
# - <List
# - of
# - reactions>
# at: Whether expression allows targets (see .h .exprat)
# ca: Whether expression expects trigger anywhere (see .h .exprca)
# dm: Whether expression DMs the response (see .h .exprdm)
# ad: Whether expression automatically deletes triggering message (see .h .exprad)
"""
# Keys are triggers, Each key has a LIST of expressions in the following format:
# - res: Response string
# id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.)
# react:
# - <List
# - of
# - reactions>
# at: Whether expression allows targets (see .h .exprat)
# ca: Whether expression expects trigger anywhere (see .h .exprca)
# 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()
.WithEventEmitter(args

View File

@@ -10,50 +10,58 @@ namespace NadekoBot.Modules.Gambling.Common;
[Cloneable]
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
{
[Comment(@"DO NOT CHANGE")]
[Comment("""DO NOT CHANGE""")]
public int Version { get; set; } = 2;
[Comment(@"Currency settings")]
[Comment("""Currency settings""")]
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;
[Comment(@"Maximum amount users can bet
Set 0 for unlimited")]
[Comment("""
Maximum amount users can bet
Set 0 for unlimited
""")]
public int MaxBet { get; set; } = 0;
[Comment(@"Settings for betflip command")]
[Comment("""Settings for betflip command""")]
public BetFlipConfig BetFlip { get; set; }
[Comment(@"Settings for betroll command")]
[Comment("""Settings for betroll command""")]
public BetRollConfig BetRoll { get; set; }
[Comment(@"Automatic currency generation settings.")]
[Comment("""Automatic currency generation settings.""")]
public GenerationConfig Generation { get; set; }
[Comment(@"Settings for timely command
(letting people claim X amount of currency every Y hours)")]
[Comment("""
Settings for timely command
(letting people claim X amount of currency every Y hours)
""")]
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; }
[Comment(@"Settings for LuckyLadder command")]
[Comment("""Settings for LuckyLadder command""")]
public LuckyLadderSettings LuckyLadder { get; set; }
[Comment(@"Settings related to waifus")]
[Comment("""Settings related to waifus""")]
public WaifuConfig Waifu { get; set; }
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
[Comment("""
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;
[Comment(@"Currency reward per vote.
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
[Comment("""
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;
[Comment(@"Slot config")]
[Comment("""Slot config""")]
public SlotsConfig Slots { get; set; }
public GamblingConfig()
@@ -72,42 +80,50 @@ This will work only if you've set up VotesApi and correct credentials for topgg
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; } = "🌸";
[Comment(@"What is the name of the currency")]
[Comment("""What is the name of the currency""")]
public string Name { get; set; } = "Nadeko Flower";
[Comment(@"For how long (in days) will the transactions be kept in the database (curtrs)
Set 0 to disable cleanup (keep transactions forever)")]
[Comment("""
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;
}
[Cloneable]
public partial class TimelyConfig
{
[Comment(@"How much currency will the users get every time they run .timely command
setting to 0 or less will disable this feature")]
[Comment("""
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;
[Comment(@"How often (in hours) can users claim currency with .timely command
setting to 0 or less will disable this feature")]
[Comment("""
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;
}
[Cloneable]
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;
}
[Cloneable]
public partial class BetRollConfig
{
[Comment(@"When betroll is played, user will roll a number 0-100.
This setting will describe which multiplier is used for when the roll is higher than the given number.
Doesn't have to be ordered.")]
[Comment("""
When betroll is played, user will roll a number 0-100.
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 BetRollConfig()
@@ -134,48 +150,56 @@ Doesn't have to be ordered.")]
[Cloneable]
public partial class GenerationConfig
{
[Comment(@"when currency is generated, should it also have a random password
associated with it which users have to type after the .pick command
in order to get it")]
[Comment("""
when currency is generated, should it also have a random password
associated with it which users have to type after the .pick command
in order to get it
""")]
public bool HasPassword { get; set; } = true;
[Comment(@"Every message sent has a certain % chance to generate the currency
specify the percentage here (1 being 100%, 0 being 0% - for example
default is 0.02, which is 2%")]
[Comment("""
Every message sent has a certain % chance to generate the currency
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;
[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;
[Comment(@"Minimum amount of currency that can spawn")]
[Comment("""Minimum amount of currency that can spawn""")]
public int MinAmount { get; set; } = 1;
[Comment(@"Maximum amount of currency that can spawn.
Set to the same value as MinAmount to always spawn the same amount")]
[Comment("""
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;
}
[Cloneable]
public partial class DecayConfig
{
[Comment(@"Percentage of user's current currency which will be deducted every 24h.
0 - 1 (1 is 100%, 0.5 50%, 0 disabled)")]
[Comment("""
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;
[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;
[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;
[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;
}
[Cloneable]
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 LuckyLadderSettings()
@@ -185,17 +209,21 @@ public partial class LuckyLadderSettings
[Cloneable]
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 MultipliersData Multipliers { get; set; } = new();
[Comment(@"Settings for periodic waifu price decay.
Waifu price decays only if the waifu has no claimer.")]
[Comment("""
Settings for periodic waifu price decay.
Waifu price decays only if the waifu has no claimer.
""")]
public WaifuDecayConfig Decay { get; set; } = new();
[Comment(@"List of items available for gifting.
If negative is true, gift will instead reduce waifu value.")]
[Comment("""
List of items available for gifting.
If negative is true, gift will instead reduce waifu value.
""")]
public List<WaifuItemModel> Items { get; set; } = new();
public WaifuConfig()
@@ -241,16 +269,20 @@ If negative is true, gift will instead reduce waifu value.")]
public class WaifuDecayConfig
{
[Comment(@"Percentage (0 - 100) of the waifu value to reduce.
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$)")]
[Comment("""
Percentage (0 - 100) of the waifu value to reduce.
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;
[Comment(@"How often to decay waifu values, in hours")]
[Comment("""How often to decay waifu values, in hours""")]
public int HourInterval { get; set; } = 24;
[Comment(@"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.")]
[Comment("""
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;
}
}
@@ -258,47 +290,61 @@ For example if this value is set to 300, any waifu with the price 300 or less wi
[Cloneable]
public sealed partial class MultipliersData
{
[Comment(@"Multiplier for waifureset. Default 150.
Formula (at the time of writing this):
price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up")]
[Comment("""
Multiplier for waifureset. Default 150.
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;
[Comment(@"The minimum amount of currency that you have to pay
in order to buy a waifu who doesn't have a crush on you.
Default is 1.1
Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
(100 * 1.1 = 110)")]
[Comment("""
The minimum amount of currency that you have to pay
in order to buy a waifu who doesn't have a crush on you.
Default is 1.1
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;
[Comment(@"The minimum amount of currency that you have to pay
in order to buy a waifu that has a crush on you.
Default is 0.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)")]
[Comment("""
The minimum amount of currency that you have to pay
in order to buy a waifu that has a crush on you.
Default is 0.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;
[Comment(@"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)")]
[Comment("""
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;
[Comment(@"All gift prices will be multiplied by this number.
Default 1 (meaning no effect)")]
[Comment("""
All gift prices will be multiplied by this number.
Default 1 (meaning no effect)
""")]
public decimal AllGiftPrices { get; set; } = 1.0M;
[Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
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)")]
[Comment("""
What percentage of the value of the gift will a waifu gain when she's gifted.
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;
[Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
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)")]
[Comment("""
What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
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 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;
}

View File

@@ -96,9 +96,12 @@ public class GamblingService : INService, IReadyExecutor
continue;
}
Log.Information(@"Decaying users' currency - decay: {ConfigDecayPercent}%
| max: {MaxDecay}
| threshold: {DecayMinTreshold}",
Log.Information("""
--- Decaying users' currency ---
| decay: {ConfigDecayPercent}%
| max: {MaxDecay}
| threshold: {DecayMinTreshold}
""",
config.Decay.Percent * 100,
maxDecay,
config.Decay.MinThreshold);

View File

@@ -187,11 +187,13 @@ public class ChatterBotService : IExecOnMessage
// , footer: counter > 0 ? counter.ToString() : null
);
Log.Information(@"CleverBot Executed
Server: {GuildName} [{GuildId}]
Channel: {ChannelName} [{ChannelId}]
UserId: {Author} [{AuthorId}]
Message: {Content}",
Log.Information("""
CleverBot Executed
Server: {GuildName} [{GuildId}]
Channel: {ChannelName} [{ChannelId}]
UserId: {Author} [{AuthorId}]
Message: {Content}
""",
guild.Name,
guild.Id,
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.")]
public long CurrencyReward { get; set; }
[Comment(@"Users won't be able to start trivia games which have
a smaller win requirement than the one specified by this setting.")]
[Comment("""
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;
}

View File

@@ -14,13 +14,15 @@ public partial class Games
=> await SendConfirmAsync(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n'));
private static string Draw(HangmanGame.State state)
=> $@". ┌─────┐
.┃...............┋
.┃...............┋
.┃{(state.Errors > 0 ? ".............😲" : "")}
.{(state.Errors > 1 ? "............./" : "")} {(state.Errors > 2 ? "|" : "")} {(state.Errors > 3 ? "\\" : "")}
.{(state.Errors > 4 ? "............../" : "")} {(state.Errors > 5 ? "\\" : "")}
/-\";
=> $"""
. ┌─────┐
.┃...............┋
.┃...............┋
.┃{(state.Errors > 0 ? ".............😲" : "")}
.{(state.Errors > 1 ? "............./" : "")} {(state.Errors > 2 ? "|" : "")} {(state.Errors > 3 ? "\\" : "")}
.{(state.Errors > 4 ? "............../" : "")} {(state.Errors > 5 ? "\\" : "")}
/-\
""";
public static IEmbedBuilder GetEmbed(IEmbedBuilderService eb, HangmanGame.State state)
{

View File

@@ -69,7 +69,7 @@ public class TypingGame
try
{
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;

View File

@@ -78,13 +78,13 @@ public class TriviaQuestion
private static string Clean(string str)
{
str = " " + str.ToLowerInvariant() + " ";
str = Regex.Replace(str, "\\s+", " ");
str = Regex.Replace(str, "[^\\w\\d\\s]", "");
str = Regex.Replace(str, @"\s+", " ");
str = Regex.Replace(str, @"[^\w\d\s]", "");
//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
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
str = str.Length <= MAX_STRING_LENGTH ? str : str[..MAX_STRING_LENGTH];
return str;

View File

@@ -490,13 +490,15 @@ public partial class Help : NadekoModule<HelpService>
private Task SelfhostAction(SocketMessageComponent smc, object _)
=> 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.
*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 :)",
"""
- 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*
- 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);
[Cmd]
@@ -541,12 +543,14 @@ Nadeko will DM you the welcome instructions, and you may start using the patron-
🎉 **Enjoy!** 🎉
")
.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.
`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.
`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");
"""
*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.
`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.
`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
{

View File

@@ -39,9 +39,9 @@ public static class MusicExtensions
if (trackInfo.Duration == TimeSpan.MaxValue)
return "∞";
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)

View File

@@ -5,10 +5,10 @@ namespace NadekoBot.Modules.Music.Resolvers;
public class RadioResolver : IRadioResolver
{
private readonly Regex _plsRegex = new("File1=(?<url>.*?)\\n", RegexOptions.Compiled);
private readonly Regex _m3URegex = new("(?<url>^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline);
private readonly Regex _asxRegex = new("<ref href=\"(?<url>.*?)\"", RegexOptions.Compiled);
private readonly Regex _xspfRegex = new("<location>(?<url>.*?)</location>", 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 _asxRegex = new(@"<ref href=""(?<url>.*?)""", RegexOptions.Compiled);
private readonly Regex _xspfRegex = new(@"<location>(?<url>.*?)</location>", RegexOptions.Compiled);
public async Task<ITrackInfo> ResolveByQueryAsync(string query)
{

View File

@@ -24,7 +24,7 @@ public partial class Searches
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]

View File

@@ -304,7 +304,7 @@ public partial class Searches
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;
}

View File

@@ -9,51 +9,61 @@ public partial class SearchesConfig : ICloneable<SearchesConfig>
[Comment("DO NOT CHANGE")]
public int Version { get; set; } = 0;
[Comment(@"Which engine should .search command
'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys.
'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")]
[Comment("""
Which engine should .search command
'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys.
'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;
[Comment(@"Which engine should .image command use
'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")]
[Comment("""
Which engine should .image command use
'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;
[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
- `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
- `invidious` - recommended advanced, uses invidious api. Requires at least one invidious instance specified in the `invidiousInstances` property")]
[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
- `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
- `invidious` - recommended advanced, uses invidious api. Requires at least one invidious instance specified in the `invidiousInstances` property
""")]
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.
Nadeko will use a random one for each request.
Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com`
Instances specified must support 'format=json' query parameter.
- In case you're running your own searx instance, set
search:
formats:
- json
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)")]
[Comment("""
Set the searx instance urls in case you want to use 'searx' for either img or web search.
Nadeko will use a random one for each request.
Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com`
Instances specified must support 'format=json' query parameter.
- In case you're running your own searx instance, set
search:
formats:
- json
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)
""")]
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
Nadeko will use a random one for each request.
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")]
[Comment("""
Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search
Nadeko will use a random one for each request.
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
""")]
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))
{
Log.Warning(@"Trovo streams are using a default clientId.
If you are experiencing ratelimits, you should create your own application at: https://developer.trovo.live/");
Log.Warning("""
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
? "∞"
: (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 usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "" : inv.MaxUses?.ToString())}";

View File

@@ -782,13 +782,15 @@ public sealed class PatronageService
patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(),
true)
.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>*
*- You can use the `.patron` command in this chat to check your current quota usage for the Patron-only commands*
*- **ALL** of the servers that you **own** will enjoy your Patron benefits.*
*- You can use any of the commands available in your tier on any server (assuming you have sufficient permissions to run those commands)*
*- 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/>*",
"""
*- 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>*
*- You can use the `.patron` command in this chat to check your current quota usage for the Patron-only commands*
*- **ALL** of the servers that you **own** will enjoy your Patron benefits.*
*- You can use any of the commands available in your tier on any server (assuming you have sufficient permissions to run those commands)*
*- 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)
.WithFooter($"platform id: {patron.UniquePlatformUserId}");

View File

@@ -13,12 +13,14 @@ public partial class Utility
public partial class QuoteCommands : NadekoModule
{
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.)
# an: Author name
# aid: Author id
# txt: Quote text
";
"""
# 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.)
# an: Author name
# aid: Author id
# txt: Quote text
""";
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
.WithEventEmitter(args

View File

@@ -9,58 +9,68 @@ namespace NadekoBot.Modules.Xp;
[Cloneable]
public sealed partial class XpConfig : ICloneable<XpConfig>
{
[Comment(@"DO NOT CHANGE")]
[Comment("""DO NOT CHANGE""")]
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;
[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;
[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;
[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;
[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;
[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;
[Comment(@"Xp Shop config")]
[Comment("""Xp Shop config""")]
public ShopConfig Shop { get; set; } = new();
public sealed class ShopConfig
{
[Comment(@"Whether the xp shop is enabled
True -> Users can access the xp shop using .xpshop command
False -> Users can't access the xp shop")]
[Comment("""
Whether the xp shop is enabled
True -> Users can access the xp shop using .xpshop command
False -> Users can't access the xp shop
""")]
public bool IsEnabled { get; set; } = false;
[Comment(@"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")]
[Comment("""
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;
[Comment(@"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")]
[Comment("""
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;
[Comment(@"Frames available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
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")]
[Comment("""
Frames available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
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()
{
{"default", new() {Name = "No frame", Price = 0, Url = string.Empty}}
};
[Comment(@"Backgrounds available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
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")]
[Comment("""
Backgrounds available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
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()
{
{"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
{
[Comment(@"Visible name of the item")]
[Comment("""Visible name of the item""")]
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; }
[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; }
[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; }
[Comment(@"Optional description of the item")]
[Comment("""Optional description of the item""")]
public string Desc { get; set; }
}
}

View File

@@ -43,7 +43,7 @@ public partial class Xp
.OrderBy(x => x.Level)
.Select(x =>
{
var sign = !x.Remove ? @"✅ " : @"❌ ";
var sign = !x.Remove ? "✅ " : "❌ ";
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)
{
Log.Information(@"Command Executed after {ExecTime}s
User: {User}
Server: {Server}
Channel: {Channel}
Message: {Message}",
Log.Information("""
Command Executed after {ExecTime}s
User: {User}
Server: {Server}
Channel: {Channel}
Message: {Message}
""",
string.Join("/", execPoints.Select(x => (x * ONE_THOUSANDTH).ToString("F3"))),
usrMsg.Author + " [" + usrMsg.Author.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)
{
Log.Warning(@"Command Errored after {ExecTime}s
User: {User}
Server: {Guild}
Channel: {Channel}
Message: {Message}
Error: {ErrorMessage}",
Log.Warning("""
Command Errored after {ExecTime}s
User: {User}
Server: {Guild}
Channel: {Channel}
Message: {Message}
Error: {ErrorMessage}
""",
string.Join("/", execPoints.Select(x => (x * ONE_THOUSANDTH).ToString("F3"))),
usrMsg.Author + " [" + usrMsg.Author.Id + "]",
channel is null ? "DM" : channel.Guild.Name + " [" + channel.Guild.Id + "]",
@@ -193,8 +197,10 @@ public class CommandHandler : INService, IReadyExecutor
}
else
{
Log.Warning(@"Err | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}
Err: {ErrorMessage}",
Log.Warning("""
Err | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}
Err: {ErrorMessage}
""",
channel?.Guild.Id.ToString() ?? "-",
channel?.Id.ToString() ?? "-",
usrMsg.Author.Id,

View File

@@ -13,7 +13,7 @@ namespace NadekoBot.Services;
public sealed partial class GoogleApiService : IGoogleApiService, INService
{
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;

View File

@@ -2,140 +2,142 @@
public sealed partial class GoogleApiService
{
private const string SUPPORTED = @"afrikaans af
albanian sq
amharic am
arabic ar
armenian hy
assamese as
aymara ay
azerbaijani az
bambara bm
basque eu
belarusian be
bengali bn
bhojpuri bho
bosnian bs
bulgarian bg
catalan ca
cebuano ceb
chinese zh-CN
chinese-trad zh-TW
corsican co
croatian hr
czech cs
danish da
dhivehi dv
dogri doi
dutch nl
english en
esperanto eo
estonian et
ewe ee
filipino fil
finnish fi
french fr
frisian fy
galician gl
georgian ka
german de
greek el
guarani gn
gujarati gu
haitian ht
hausa ha
hawaiian haw
hebrew he
hindi hi
hmong hmn
hungarian hu
icelandic is
igbo ig
ilocano ilo
indonesian id
irish ga
italian it
japanese ja
javanese jv
kannada kn
kazakh kk
khmer km
kinyarwanda rw
konkani gom
korean ko
krio kri
kurdish ku
kurdish-sor ckb
kyrgyz ky
lao lo
latin la
latvian lv
lingala ln
lithuanian lt
luganda lg
luxembourgish lb
macedonian mk
maithili mai
malagasy mg
malay ms
malayalam ml
maltese mt
maori mi
marathi mr
meiteilon mni-Mtei
mizo lus
mongolian mn
myanmar my
nepali ne
norwegian no
nyanja ny
odia or
oromo om
pashto ps
persian fa
polish pl
portuguese pt
punjabi pa
quechua qu
romanian ro
russian ru
samoan sm
sanskrit sa
scots gd
sepedi nso
serbian sr
sesotho st
shona sn
sindhi sd
sinhala si
slovak sk
slovenian sl
somali so
spanish es
sundanese su
swahili sw
swedish sv
tagalog tl
tajik tg
tamil ta
tatar tt
telugu te
thai th
tigrinya ti
tsonga ts
turkish tr
turkmen tk
twi ak
ukrainian uk
urdu ur
uyghur ug
uzbek uz
vietnamese vi
welsh cy
xhosa xh
yiddish yi
yoruba yo
zulu zu";
private const string SUPPORTED = """
afrikaans af
albanian sq
amharic am
arabic ar
armenian hy
assamese as
aymara ay
azerbaijani az
bambara bm
basque eu
belarusian be
bengali bn
bhojpuri bho
bosnian bs
bulgarian bg
catalan ca
cebuano ceb
chinese zh-CN
chinese-trad zh-TW
corsican co
croatian hr
czech cs
danish da
dhivehi dv
dogri doi
dutch nl
english en
esperanto eo
estonian et
ewe ee
filipino fil
finnish fi
french fr
frisian fy
galician gl
georgian ka
german de
greek el
guarani gn
gujarati gu
haitian ht
hausa ha
hawaiian haw
hebrew he
hindi hi
hmong hmn
hungarian hu
icelandic is
igbo ig
ilocano ilo
indonesian id
irish ga
italian it
japanese ja
javanese jv
kannada kn
kazakh kk
khmer km
kinyarwanda rw
konkani gom
korean ko
krio kri
kurdish ku
kurdish-sor ckb
kyrgyz ky
lao lo
latin la
latvian lv
lingala ln
lithuanian lt
luganda lg
luxembourgish lb
macedonian mk
maithili mai
malagasy mg
malay ms
malayalam ml
maltese mt
maori mi
marathi mr
meiteilon mni-Mtei
mizo lus
mongolian mn
myanmar my
nepali ne
norwegian no
nyanja ny
odia or
oromo om
pashto ps
persian fa
polish pl
portuguese pt
punjabi pa
quechua qu
romanian ro
russian ru
samoan sm
sanskrit sa
scots gd
sepedi nso
serbian sr
sesotho st
shona sn
sindhi sd
sinhala si
slovak sk
slovenian sl
somali so
spanish es
sundanese su
swahili sw
swedish sv
tagalog tl
tajik tg
tamil ta
tatar tt
telugu te
thai th
tigrinya ti
tsonga ts
turkish tr
turkmen tk
twi ak
ukrainian uk
urdu ur
uyghur ug
uzbek uz
vietnamese vi
welsh cy
xhosa xh
yiddish yi
yoruba yo
zulu zu
""";
public IReadOnlyDictionary<string, string> Languages { get; }