Compare commits

..

60 Commits

Author SHA1 Message Date
Kwoth
c473669cbc docs: Version upped to 5.1.10, updated changelog 2024-09-24 02:00:02 +00:00
Kwoth
b97c486b80 dev: added some logs in greet service 2024-09-24 01:57:19 +00:00
Kwoth
716e092fd0 fix: Fixed claimed waifu decay that was introduced in a recent patch
dev: Cleaned up a little bit in medusa loading. Clean medusa unloading will be broken for a while probably
2024-09-23 18:18:38 +00:00
Kwoth
a362ee90fc dev: forgot to update the version in csproj, again 2024-09-22 01:51:42 +00:00
Kwoth
1de6cdb8dc dev: updatd migration script as mysql no longer exists 2024-09-21 20:12:09 +00:00
Kwoth
f473014fe9 fix: Fixed medusa dependency loading. In case your medusa has other dependencies they will be correctly loaded now. Note: Make sure to not publish any other DLLs besides the ones you are sure you will need, as there can be version conflicts which didn't happen before. For example if you have a NadekoMedusa.dll which is a different version in the data/medusa/mymedusa folder, your medusa will now break, as this fix will now (correctly) try to load it and there will be a version mismatch between the attributes. In a future patch i'll try to mitigate this by not loading dlls which are already loaded by the bot (even if their versions are different) but this might cause new issues as sometimes you do need different version of libraries for medusa... The best option is to just keep what you need, and make sure to remove any other dlls 2024-09-21 20:11:36 +00:00
Kwoth
2c3e5fe507 fix: Fixed .greettest byetest greetdmtest and boosttest command if you didn't have them enabled. Also fixed greetdmtest sending messages twice. 2024-09-21 19:05:59 +00:00
Kwoth
ecc192c6a9 fix: possible fix for docker 2024-09-19 14:52:07 +00:00
Kwoth
f7bd181034 docs: Updated changelog, version upped to 5.1.8 2024-09-19 02:08:34 +00:00
Kwoth
664a4b3604 dev: comment cleanup 2024-09-19 01:51:36 +00:00
Kwoth
0326e88910 add: Added .q support for invidious. If you have ytProvider set to invidious in data/searches.yml, invidious will be used to queue up songs and play them."work 2024-09-19 01:40:42 +00:00
Kwoth
e4202b33f5 Merge branch 'greet-rework' into 'v5'
Greet reworked under the hood

See merge request Kwoth/nadekobot!333
2024-09-15 22:44:37 +00:00
Kwoth
021e7978da * dev: Greet stuff moved to its own table in the database. GreetSettings
* fix: Fixed placeholders not working
* fix: Fixed some countries in countries.yml for hangman game
* add: Added custom status overload for \`.adpl\`
* dev: Removed some unused strings
* fix: Fixed postgres support in Nadeko
* remove: Removed mysql support, it was broken for a while and some queries weren't compiling.
* dev: Updated image library
* fix: Some command strings fixed and clarified
2024-09-15 22:44:37 +00:00
Kwoth
28ad6db2de change: .qimport will is no longer owner only on the public bot
dev: Creds issues should now be properly caught and logged, instead of showing unhandled exceptions
2024-09-10 20:26:33 +00:00
Kwoth
fb62df7aa2 dev: some more cleanup/attempts to fix a weird mysql error 2024-09-08 14:30:39 +00:00
Kwoth
33663d7efc dev: Some packages updated, and small cleanup 2024-09-08 14:16:35 +00:00
Kwoth
6d1edc07cb fix: Fixed voice and text channel counting
docs: Updated changelog
2024-09-05 06:58:54 +00:00
Kwoth
c36ab34c4f fix: Fixed voice and text channel counting 2024-09-05 06:04:47 +00:00
Kwoth
e85e7c49cb dev: Updated image library 2024-09-04 06:55:59 +00:00
Kwoth
e56190e9da fix: fixed quoteshow and quoteid commands not working 2024-08-30 05:18:10 +00:00
Kwoth
2d16ecf6de change: increased delay to 3k on leaveunkeptservers 2024-08-30 02:06:56 +00:00
Kwoth
2b12269917 fix: increased delay to 2500, renamed method 2024-08-29 11:50:42 +00:00
Kwoth
79c2dfec2d Fix: fixed .leaveunkeptservers 2024-08-29 08:33:02 +00:00
Kwoth
73356b6beb change: Changed .leaveunkeptservers again to only accept startShardId, it will loop through the shards and execute clean on them every 2250 seconds (assuming shards are almost full). Delay is fixed at 1 second as that is the discord ratelimit 2024-08-29 00:44:28 +00:00
Kwoth
bc22987330 change: updated gambling.yml to the newest version
dev: fixed pubsub not supporting tuples
2024-08-28 05:04:52 +00:00
Kwoth
c033c0e3c8 change: added some logging to .leaveunkeptservers 2024-08-28 04:37:03 +00:00
Kwoth
c9ed2cf4b5 change: unkept leave now has a configurable delay 2024-08-28 02:22:58 +00:00
Kwoth
52b87c7776 change: Changed how leaving unkept servers work. It only works per-shard now 2024-08-27 01:38:36 +00:00
Kwoth
8b2ed0dbdc fix: fix for .leaveunkeptservers 2024-08-27 00:39:42 +00:00
Kwoth
9424d4d5f9 add: Implemented .leaveunkeptservers which will cause the bot to leave all servers unmarked by .keep. Extremely dangerous and irreversible. Meant for use on public bot. 2024-08-26 23:09:33 +00:00
Kwoth
67b186a1a5 add: Added unclaimed waifu decay to gambling.yml
fix: Fixed regular decay. It was doing the opposite of what the comment says. All waifu decays will be reset
2024-08-24 23:53:03 +00:00
Kwoth
436f9ed074 docs: CLarified .anti* command help 2024-08-22 17:57:08 +00:00
Kwoth
c1e51329be fix: Fixed some .waifu related strings 2024-08-22 17:01:30 +00:00
Kwoth
ae1193c1c5 dev: fixed a file name 2024-08-22 03:11:02 +00:00
Kwoth
9601a4d1a9 docs: Clarified some quote command strings
change: Changed .delallq to be .qdall as all quote related commands start with .q<verb> now
2024-08-21 23:45:40 +00:00
Kwoth
bdfde1205a docs: Updated CHANGELOG.md 2024-08-21 00:10:01 +00:00
Kwoth
5992628f80 change: Quote commands slightly changed and some of them renamed. Added a lot of new aliases. Notable rename is .liqu to .qli
change: Quotes now follow the same naming pattern as Expression commands
dev: Code vastly improved
2024-08-20 23:42:10 +00:00
Kwoth
d24e6fd8e7 dev: .qid cleaned up 2024-08-19 23:58:41 +00:00
Kwoth
c31c2e8d8e dev: Most cleanup logic moved to the service, improved some commands, possible bugs 2024-08-19 23:55:35 +00:00
Kwoth
9aaf062d78 dev: Started cleanup of quote commands. Moving logic to the service 2024-08-18 23:52:32 +00:00
Kwoth
0b9e812d59 dev: some cleanup of remind command. Moved some logic to the service 2024-08-17 23:08:36 +00:00
Kwoth
dc63e46852 change: .setgame renamed to .setactivity and now supports custom activities (with no playing in front of the text) 2024-08-17 00:16:09 +00:00
Kwoth
e314686a03 dev: small cleanup of utility.cs 2024-08-16 23:58:25 +00:00
Kwoth
f764a650da change: .whosplaying is now properly paginated 2024-08-16 23:56:05 +00:00
Kwoth
67616deb79 dev: moved xpnotificationlocation to its own file 2024-08-16 23:47:21 +00:00
Kwoth
d0aa80a004 dev: .whosplaying code cleanup 2024-08-16 23:46:16 +00:00
Kwoth
f66c105cc0 dev: Small cleanup of gamestatusevent 2024-08-16 23:43:02 +00:00
Kwoth
2a528cb3d6 dev: cleaed up inrole and whosplaying commands a little 2024-08-14 00:32:36 +00:00
Kwoth
8b40f97a3d fix: fixed an issue in .gatari
dev: fully cleaned osu related commands and moved to a service
2024-08-14 00:17:24 +00:00
Kwoth
fa9263ed32 dev: further cleanup of osu commands 2024-08-12 22:51:19 +00:00
Kwoth
88c42b74c7 fix: Fixed xpcurrew breaking xp gain if user gains 0 xp from being in a voice channel while voice xp is enabled 2024-08-12 12:28:11 +00:00
Kwoth
f7406ec90b dev: started cleaning the .osu command 2024-08-11 21:26:17 +00:00
Kwoth
e446c8ee8b dev: Moved streamlist logic to the service 2024-08-10 21:10:06 +00:00
Kwoth
5839e944e1 docs: updated commandlist 2024-08-09 10:59:46 +00:00
Kwoth
15e41c10db docs: simplified some command strings 2024-08-09 08:50:40 +00:00
Kwoth
99a8ea18bb docs: updated commandlist 2024-08-08 15:34:42 +00:00
Kwoth
5453f8acfa docs: Added embed links in command descriptions where missing 2024-08-08 14:32:34 +00:00
Kwoth
c95d1421c6 fix: fixed some command groups incorrectly showing up as modules 2024-08-08 12:15:53 +00:00
Kwoth
f631f16690 fix: fixed one of the migrations which was preventing some bots from starting 2024-08-07 17:45:25 +00:00
Kwoth
d397c2dce8 docs: fixed changelog 2024-08-07 16:46:33 +00:00
154 changed files with 6998 additions and 84082 deletions

View File

@@ -2,6 +2,70 @@
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [5.1.10] - 24.09.2024
### Fixed
- Fixed claimed waifu decay in `games.yml`
### Changed
- Added some logs for greet service in case there are unforeseen issues, for easier debugging
## [5.1.9] - 21.09.2024
### Fixed
- Fixed `.greettest`, and other `.*test` commands if you didn't have them enabled.
- Fixed `.greetdmtest` sending messages twice.
- Fixed a serious bug which caused greet messages to be jumbled up, and wrong ones to be sent for the wrong events.
- There is no database issue, all greet messages are safe, the cache was caching any setting every 3 seconds with no regard for the type of the event
- This also caused `.greetdm` messages to not be sent if `.greet` is enabled
- This bug was introduced in 5.1.8. PLEASE UPDATE if you are on 5.1.8
- Selfhosters only: Fixed medusa dependency loading
- Note: Make sure to not publish any other DLLs besides the ones you are sure you will need, as there can be version conflicts which didn't happen before.
## [5.1.8] - 19.09.2024
### Added
- Added `.leaveunkeptservers` which will make the bot leave all servers on all shards whose owners didn't run `.keep` command.
- This is a dangerous and irreversible command, don't use it. Meant for use on the public bot.
- `.adpl` now supports custom statuses (you no longer need to specify Playing, Watching, etc...)
### Changed
- `.quote` commands cleaned up and improved
- All quote commands now start with `.q<whatever>` and follow the same naming pattern as Expression commands
- `.liqu` renamed to `.qli`
- `.quotesearch` / `.qse` is now paginated for easier searching
- `.whosplaying` is now paginated
- `.img` is now paginated
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing, listening etc before the activity
- Clarified and added some embed / placeholder links to command help where needed
- dev: A lot of code cleanup and internal improvements
### Fixed
- Fixed `.xpcurrew` breaking xp gain if user gains 0 xp from being in a voice channel
- Fixed a bug in `.gatari` command
- Fixed some waifu related strings
- Fixed `.quoteshow` and `.quoteid` commands
- Fixed some placeholders not working in `.greetdm`
- Fixed postgres support
- Fixed and clarified some command strings/parameter descriptions
### Removed
- Removed mysql support as it didn't work for a while, and requires some special handling/maintenance
- Sqlite and Postgres support stays
## [5.1.7] - 08.08.2024
### Fixed
- Fixed some command groups incorrectly showing up as modules
## [5.1.6] - 07.08.2024 ## [5.1.6] - 07.08.2024
### Added ### Added
@@ -14,7 +78,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
### Fixed ### Fixed
- `.afk` messages can no longer ping, and the response is moved to DMs to avoid - `.afk` messages can no longer ping, and the response is moved to DMs to avoid abuse
- Possible fix for `.remind` timestamp - Possible fix for `.remind` timestamp
### Removed ### Removed

View File

@@ -5,6 +5,5 @@ else {
$migrationName = $args[0] $migrationName = $args[0]
dotnet ef migrations add $migrationName -c SqliteContext -p src/NadekoBot/NadekoBot.csproj dotnet ef migrations add $migrationName -c SqliteContext -p src/NadekoBot/NadekoBot.csproj
dotnet ef migrations add $migrationName -c PostgreSqlContext -p src/NadekoBot/NadekoBot.csproj dotnet ef migrations add $migrationName -c PostgreSqlContext -p src/NadekoBot/NadekoBot.csproj
dotnet ef migrations add $migrationName -c MysqlContext -p src/NadekoBot/NadekoBot.csproj
} }

View File

@@ -1,4 +1,3 @@
dotnet ef migrations remove -c SqliteContext -f -p src/NadekoBot/NadekoBot.csproj dotnet ef migrations remove -c SqliteContext -f -p src/NadekoBot/NadekoBot.csproj
dotnet ef migrations remove -c PostgreSqlContext -f -p src/NadekoBot/NadekoBot.csproj dotnet ef migrations remove -c PostgreSqlContext -f -p src/NadekoBot/NadekoBot.csproj
dotnet ef migrations remove -c MysqlContext -f -p src/NadekoBot/NadekoBot.csproj

View File

@@ -1,76 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using Nadeko.Common;
using NadekoBot.Services;
using NUnit.Framework;
namespace NadekoBot.Tests
{
public class GroupGreetTests
{
private GreetGrouper<int> _grouper;
[SetUp]
public void Setup()
=> _grouper = new GreetGrouper<int>();
[Test]
public void CreateTest()
{
var created = _grouper.CreateOrAdd(0, 5);
Assert.True(created);
}
[Test]
public void CreateClearTest()
{
_grouper.CreateOrAdd(0, 5);
_grouper.ClearGroup(0, 5, out var items);
Assert.AreEqual(0, items.Count());
}
[Test]
public void NotCreatedTest()
{
_grouper.CreateOrAdd(0, 5);
var created = _grouper.CreateOrAdd(0, 4);
Assert.False(created);
}
[Test]
public void ClearAddedTest()
{
_grouper.CreateOrAdd(0, 5);
_grouper.CreateOrAdd(0, 4);
_grouper.ClearGroup(0, 5, out var items);
var list = items.ToList();
Assert.AreEqual(1, list.Count, $"Count was {list.Count}");
Assert.AreEqual(4, list[0]);
}
[Test]
public async Task ClearManyTest()
{
_grouper.CreateOrAdd(0, 5);
// add 15 items
await Enumerable.Range(10, 15)
.Select(x => Task.Run(() => _grouper.CreateOrAdd(0, x))).WhenAll();
// get 5 at most
_grouper.ClearGroup(0, 5, out var items);
var list = items.ToList();
Assert.AreEqual(5, list.Count, $"Count was {list.Count}");
// try to get 15, but there should be 10 left
_grouper.ClearGroup(0, 15, out items);
list = items.ToList();
Assert.AreEqual(10, list.Count, $"Count was {list.Count}");
}
}
}

View File

@@ -1,5 +1,6 @@
#nullable disable #nullable disable
using DryIoc; using DryIoc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.Configs; using NadekoBot.Common.Configs;
@@ -88,18 +89,18 @@ public sealed class Bot : IBot
public IReadOnlyList<ulong> GetCurrentGuildIds() public IReadOnlyList<ulong> GetCurrentGuildIds()
=> Client.Guilds.Select(x => x.Id).ToList().ToList(); => Client.Guilds.Select(x => x.Id).ToList().AsReadOnly();
private void AddServices() private async Task AddServices()
{ {
var startingGuildIdList = GetCurrentGuildIds(); var startingGuildIdList = GetCurrentGuildIds().ToList();
var startTime = Stopwatch.GetTimestamp(); var startTime = Stopwatch.GetTimestamp();
var bot = Client.CurrentUser; var bot = Client.CurrentUser;
using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
AllGuildConfigs = await uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList);
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId); uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
AllGuildConfigs = uow.Set<GuildConfig>().GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
} }
// var svcs = new StandardKernel(new NinjectSettings() // var svcs = new StandardKernel(new NinjectSettings()
@@ -109,7 +110,7 @@ public sealed class Bot : IBot
// }); // });
var svcs = new Container(); var svcs = new Container();
// this is required in order for medusa unloading to work // this is required in order for medusa unloading to work
// svcs.Components.Remove<IPlanner, Planner>(); // svcs.Components.Remove<IPlanner, Planner>();
// svcs.Components.Add<IPlanner, RemovablePlanner>(); // svcs.Components.Add<IPlanner, RemovablePlanner>();
@@ -160,8 +161,9 @@ public sealed class Bot : IBot
{ {
LoadTypeReaders(a); LoadTypeReaders(a);
} }
Log.Information("All services loaded in {ServiceLoadTime:F2}s", Stopwatch.GetElapsedTime(startTime) .TotalSeconds); Log.Information("All services loaded in {ServiceLoadTime:F2}s",
Stopwatch.GetElapsedTime(startTime).TotalSeconds);
} }
private void LoadTypeReaders(Assembly assembly) private void LoadTypeReaders(Assembly assembly)
@@ -261,11 +263,11 @@ public sealed class Bot : IBot
var startTime = Stopwatch.GetTimestamp(); var startTime = Stopwatch.GetTimestamp();
await LoginAsync(_creds.Token); await LoginAsync(_creds.Token);
Log.Information("Shard {ShardId} loading services...", Client.ShardId); Log.Information("Shard {ShardId} loading services...", Client.ShardId);
try try
{ {
AddServices(); await AddServices();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -273,7 +275,9 @@ public sealed class Bot : IBot
Helpers.ReadErrorAndExit(9); Helpers.ReadErrorAndExit(9);
} }
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, Stopwatch.GetElapsedTime(startTime).TotalSeconds); Log.Information("Shard {ShardId} connected in {Elapsed:F2}s",
Client.ShardId,
Stopwatch.GetElapsedTime(startTime).TotalSeconds);
var commandHandler = Services.GetRequiredService<CommandHandler>(); var commandHandler = Services.GetRequiredService<CommandHandler>();
// start handling messages received in commandhandler // start handling messages received in commandhandler
@@ -338,26 +342,26 @@ public sealed class Bot : IBot
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

@@ -1,4 +1,5 @@
#nullable disable #nullable disable
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
@@ -32,8 +33,8 @@ public static class GuildConfigExtensions
{ {
var conf = ctx.GuildConfigsForId(guildId, var conf = ctx.GuildConfigsForId(guildId,
set => set.Include(y => y.StreamRole) set => set.Include(y => y.StreamRole)
.Include(y => y.StreamRole.Whitelist) .Include(y => y.StreamRole.Whitelist)
.Include(y => y.StreamRole.Blacklist)); .Include(y => y.StreamRole.Blacklist));
if (conf.StreamRole is null) if (conf.StreamRole is null)
conf.StreamRole = new(); conf.StreamRole = new();
@@ -42,19 +43,28 @@ public static class GuildConfigExtensions
} }
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs) private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
=> configs.AsQueryable() => configs
.AsSplitQuery() .AsSplitQuery()
.Include(gc => gc.CommandCooldowns) .Include(gc => gc.CommandCooldowns)
.Include(gc => gc.FollowedStreams) .Include(gc => gc.FollowedStreams)
.Include(gc => gc.StreamRole) .Include(gc => gc.StreamRole)
.Include(gc => gc.XpSettings) .Include(gc => gc.DelMsgOnCmdChannels)
.ThenInclude(x => x.ExclusionList) .Include(gc => gc.XpSettings)
.Include(gc => gc.DelMsgOnCmdChannels); .ThenInclude(x => x.ExclusionList);
public static IEnumerable<GuildConfig> GetAllGuildConfigs( public static async Task<GuildConfig[]> GetAllGuildConfigs(
this DbSet<GuildConfig> configs, this DbSet<GuildConfig> configs,
IReadOnlyList<ulong> availableGuilds) List<ulong> availableGuilds)
=> configs.IncludeEverything().AsNoTracking().Where(x => availableGuilds.Contains(x.GuildId)).ToList(); {
var result = await configs
.AsQueryable()
.Include(x => x.CommandCooldowns)
.Where(x => availableGuilds.Contains(x.GuildId))
.AsNoTracking()
.ToArrayAsync();
return result;
}
/// <summary> /// <summary>
/// Gets and creates if it doesn't exist a config for a guild. /// Gets and creates if it doesn't exist a config for a guild.
@@ -80,13 +90,14 @@ public static class GuildConfigExtensions
if (config is null) if (config is null)
{ {
ctx.Set<GuildConfig>().Add(config = new() ctx.Set<GuildConfig>()
{ .Add(config = new()
GuildId = guildId, {
Permissions = Permissionv2.GetDefaultPermlist, GuildId = guildId,
WarningsInitialized = true, Permissions = Permissionv2.GetDefaultPermlist,
WarnPunishments = DefaultWarnPunishments WarningsInitialized = true,
}); WarnPunishments = DefaultWarnPunishments
});
ctx.SaveChanges(); ctx.SaveChanges();
} }
@@ -122,18 +133,18 @@ public static class GuildConfigExtensions
public static LogSetting LogSettingsFor(this DbContext ctx, ulong guildId) public static LogSetting LogSettingsFor(this DbContext ctx, ulong guildId)
{ {
var logSetting = ctx.Set<LogSetting>() var logSetting = ctx.Set<LogSetting>()
.AsQueryable() .AsQueryable()
.Include(x => x.LogIgnores) .Include(x => x.LogIgnores)
.Where(x => x.GuildId == guildId) .Where(x => x.GuildId == guildId)
.FirstOrDefault(); .FirstOrDefault();
if (logSetting is null) if (logSetting is null)
{ {
ctx.Set<LogSetting>() ctx.Set<LogSetting>()
.Add(logSetting = new() .Add(logSetting = new()
{ {
GuildId = guildId GuildId = guildId
}); });
ctx.SaveChanges(); ctx.SaveChanges();
} }
@@ -149,18 +160,20 @@ public static class GuildConfigExtensions
public static GuildConfig GcWithPermissionsFor(this DbContext ctx, ulong guildId) public static GuildConfig GcWithPermissionsFor(this DbContext ctx, ulong guildId)
{ {
var config = ctx.Set<GuildConfig>().AsQueryable() var config = ctx.Set<GuildConfig>()
.Where(gc => gc.GuildId == guildId) .AsQueryable()
.Include(gc => gc.Permissions) .Where(gc => gc.GuildId == guildId)
.FirstOrDefault(); .Include(gc => gc.Permissions)
.FirstOrDefault();
if (config is null) // if there is no guildconfig, create new one if (config is null) // if there is no guildconfig, create new one
{ {
ctx.Set<GuildConfig>().Add(config = new() ctx.Set<GuildConfig>()
{ .Add(config = new()
GuildId = guildId, {
Permissions = Permissionv2.GetDefaultPermlist GuildId = guildId,
}); Permissions = Permissionv2.GetDefaultPermlist
});
ctx.SaveChanges(); ctx.SaveChanges();
} }
else if (config.Permissions is null || !config.Permissions.Any()) // if no perms, add default ones else if (config.Permissions is null || !config.Permissions.Any()) // if no perms, add default ones
@@ -177,21 +190,21 @@ public static class GuildConfigExtensions
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs, List<ulong> included) public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs, List<ulong> included)
=> configs.AsQueryable() => configs.AsQueryable()
.Where(gc => included.Contains(gc.GuildId)) .Where(gc => included.Contains(gc.GuildId))
.Include(gc => gc.FollowedStreams) .Include(gc => gc.FollowedStreams)
.SelectMany(gc => gc.FollowedStreams) .SelectMany(gc => gc.FollowedStreams)
.ToList(); .ToList();
public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId) public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId)
{ {
var gc = ctx.GuildConfigsForId(guildId, var gc = ctx.GuildConfigsForId(guildId,
set => set.Include(x => x.XpSettings) set => set.Include(x => x.XpSettings)
.ThenInclude(x => x.RoleRewards) .ThenInclude(x => x.RoleRewards)
.Include(x => x.XpSettings) .Include(x => x.XpSettings)
.ThenInclude(x => x.CurrencyRewards) .ThenInclude(x => x.CurrencyRewards)
.Include(x => x.XpSettings) .Include(x => x.XpSettings)
.ThenInclude(x => x.ExclusionList)); .ThenInclude(x => x.ExclusionList));
if (gc.XpSettings is null) if (gc.XpSettings is null)
gc.XpSettings = new(); gc.XpSettings = new();
@@ -201,15 +214,15 @@ public static class GuildConfigExtensions
public static IEnumerable<GeneratingChannel> GetGeneratingChannels(this DbSet<GuildConfig> configs) public static IEnumerable<GeneratingChannel> GetGeneratingChannels(this DbSet<GuildConfig> configs)
=> configs.AsQueryable() => configs.AsQueryable()
.Include(x => x.GenerateCurrencyChannelIds) .Include(x => x.GenerateCurrencyChannelIds)
.Where(x => x.GenerateCurrencyChannelIds.Any()) .Where(x => x.GenerateCurrencyChannelIds.Any())
.SelectMany(x => x.GenerateCurrencyChannelIds) .SelectMany(x => x.GenerateCurrencyChannelIds)
.Select(x => new GeneratingChannel .Select(x => new GeneratingChannel
{ {
ChannelId = x.ChannelId, ChannelId = x.ChannelId,
GuildId = x.GuildConfig.GuildId GuildId = x.GuildConfig.GuildId
}) })
.ToArray(); .ToArray();
public class GeneratingChannel public class GeneratingChannel
{ {

View File

@@ -1,53 +0,0 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
namespace NadekoBot.Db;
public static class QuoteExtensions
{
public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
=> quotes.AsQueryable().Where(x => x.GuildId == guildId);
public static IReadOnlyCollection<Quote> GetGroup(
this DbSet<Quote> quotes,
ulong guildId,
int page,
OrderType order)
{
var q = quotes.AsQueryable().Where(x => x.GuildId == guildId);
if (order == OrderType.Keyword)
q = q.OrderBy(x => x.Keyword);
else
q = q.OrderBy(x => x.Id);
return q.Skip(15 * page).Take(15).ToArray();
}
public static async Task<Quote> GetRandomQuoteByKeywordAsync(
this DbSet<Quote> quotes,
ulong guildId,
string keyword)
{
return (await quotes.AsQueryable().Where(q => q.GuildId == guildId && q.Keyword == keyword).ToArrayAsync())
.RandomOrDefault();
}
public static async Task<Quote> SearchQuoteKeywordTextAsync(
this DbSet<Quote> quotes,
ulong guildId,
string keyword,
string text)
{
return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId
&& (keyword == null || q.Keyword == keyword)
&& (EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
|| EF.Functions.Like(q.AuthorName, text)))
.ToArrayAsync())
.RandomOrDefault();
}
public static void RemoveAllByKeyword(this DbSet<Quote> quotes, ulong guildId, string keyword)
=> quotes.RemoveRange(quotes.AsQueryable().Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword));
}

View File

@@ -13,21 +13,23 @@ public class GuildConfig : DbEntity
public string AutoAssignRoleIds { get; set; } public string AutoAssignRoleIds { get; set; }
//greet stuff // //greet stuff
public int AutoDeleteGreetMessagesTimer { get; set; } = 30; // public int AutoDeleteGreetMessagesTimer { get; set; } = 30;
public int AutoDeleteByeMessagesTimer { get; set; } = 30; // public int AutoDeleteByeMessagesTimer { get; set; } = 30;
//
public ulong GreetMessageChannelId { get; set; } // public ulong GreetMessageChannelId { get; set; }
public ulong ByeMessageChannelId { get; set; } // public ulong ByeMessageChannelId { get; set; }
//
public bool SendDmGreetMessage { get; set; } // public bool SendDmGreetMessage { get; set; }
public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; // public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
//
public bool SendChannelGreetMessage { get; set; } // public bool SendChannelGreetMessage { get; set; }
public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; // public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
//
public bool SendChannelByeMessage { get; set; } // public bool SendChannelByeMessage { get; set; }
public string ChannelByeMessageText { get; set; } = "%user% has left!"; // public string ChannelByeMessageText { get; set; } = "%user% has left!";
// public bool SendBoostMessage { get; set; }
// pulic int BoostMessageDeleteAfter { get; set; }
//self assignable roles //self assignable roles
public bool ExclusiveSelfAssignedRoles { get; set; } public bool ExclusiveSelfAssignedRoles { get; set; }
@@ -98,10 +100,6 @@ public class GuildConfig : DbEntity
#region Boost Message #region Boost Message
public bool SendBoostMessage { get; set; }
public string BoostMessage { get; set; } = "%user% just boosted this server!";
public ulong BoostMessageChannelId { get; set; }
public int BoostMessageDeleteAfter { get; set; }
public bool StickyRoles { get; set; } public bool StickyRoles { get; set; }
#endregion #endregion

View File

@@ -8,6 +8,4 @@ public class UserXpStats : DbEntity
public long Xp { get; set; } public long Xp { get; set; }
public long AwardedXp { get; set; } public long AwardedXp { get; set; }
public XpNotificationLocation NotifyOnLevelUp { get; set; } public XpNotificationLocation NotifyOnLevelUp { get; set; }
} }
public enum XpNotificationLocation { None, Dm, Channel }

View File

@@ -0,0 +1,8 @@
namespace NadekoBot.Db.Models;
public enum XpNotificationLocation
{
None,
Dm,
Channel
}

View File

@@ -1,38 +0,0 @@
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
namespace NadekoBot.Db;
public sealed class MysqlContext : NadekoContext
{
private readonly string _connStr;
private readonly string _version;
protected override string CurrencyTransactionOtherIdDefaultValue
=> "NULL";
public MysqlContext(string connStr = "Server=localhost", string version = "8.0")
{
_connStr = connStr;
_version = version;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder
.UseLowerCaseNamingConvention()
.UseMySql(_connStr, ServerVersion.Parse(_version));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// mysql is case insensitive by default
// we can set binary collation to change that
modelBuilder.Entity<ClubInfo>()
.Property(x => x.Name)
.UseCollation("utf8mb4_bin");
}
}

View File

@@ -10,6 +10,7 @@ namespace NadekoBot.Db;
public abstract class NadekoContext : DbContext public abstract class NadekoContext : DbContext
{ {
public DbSet<GuildConfig> GuildConfigs { get; set; } public DbSet<GuildConfig> GuildConfigs { get; set; }
public DbSet<GreetSettings> GreetSettings { get; set; }
public DbSet<Quote> Quotes { get; set; } public DbSet<Quote> Quotes { get; set; }
public DbSet<Reminder> Reminders { get; set; } public DbSet<Reminder> Reminders { get; set; }
@@ -149,7 +150,7 @@ public abstract class NadekoContext : DbContext
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
// start antispam // start antispam
modelBuilder.Entity<GuildConfig>() modelBuilder.Entity<GuildConfig>()
.HasOne(x => x.AntiSpamSetting) .HasOne(x => x.AntiSpamSetting)
.WithOne() .WithOne()
@@ -678,6 +679,29 @@ public abstract class NadekoContext : DbContext
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
#endregion #endregion
#region GreetSettings
modelBuilder
.Entity<GreetSettings>(gs => gs.HasIndex(x => new
{
x.GuildId,
x.GreetType
})
.IsUnique());
modelBuilder.Entity<GreetSettings>(gs =>
{
gs
.Property(x => x.IsEnabled)
.HasDefaultValue(false);
gs
.Property(x => x.AutoDeleteTimer)
.HasDefaultValue(0);
});
#endregion
} }
#if DEBUG #if DEBUG

View File

@@ -44,8 +44,6 @@ public sealed class NadekoDbService : DbService
case "postgres": case "postgres":
case "pgsql": case "pgsql":
return new PostgreSqlContext(connString); return new PostgreSqlContext(connString);
case "mysql":
return new MysqlContext(connString);
case "sqlite": case "sqlite":
return new SqliteContext(connString); return new SqliteContext(connString);
default: default:

View File

@@ -7,16 +7,7 @@ public static class MigrationQueries
{ {
public static void MigrateRero(MigrationBuilder migrationBuilder) public static void MigrateRero(MigrationBuilder migrationBuilder)
{ {
if (migrationBuilder.IsMySql()) if (migrationBuilder.IsSqlite())
{
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;");
}
else if (migrationBuilder.IsSqlite())
{ {
migrationBuilder.Sql( migrationBuilder.Sql(
@"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded) @"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
@@ -27,7 +18,8 @@ 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(
@"insert into reactionroles(guildid, channelid, messageid, emote, roleid, ""group"", levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded
from reactionrole from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
@@ -43,11 +35,34 @@ left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;")
public static void GuildConfigCleanup(MigrationBuilder builder) public static void GuildConfigCleanup(MigrationBuilder builder)
{ {
builder.Sql($""" builder.Sql($"""
DELETE FROM "DelMsgOnCmdChannel" WHERE "GuildConfigId" is NULL;
DELETE FROM "WarningPunishment" WHERE "GuildConfigId" NOT IN (SELECT "Id" from "GuildConfigs");
DELETE FROM "StreamRoleBlacklistedUser" WHERE "StreamRoleSettingsId" is NULL; DELETE FROM "StreamRoleBlacklistedUser" WHERE "StreamRoleSettingsId" is NULL;
"""); """);
}
builder.Sql($""" public static void GreetSettingsCopy(MigrationBuilder builder)
DELETE FROM "DelMsgOnCmdChannel" WHERE "GuildConfigId" is NULL; {
"""); builder.Sql("""
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 0, ChannelGreetMessageText, SendChannelGreetMessage, GreetMessageChannelId, AutoDeleteGreetMessagesTimer
FROM GuildConfigs
WHERE SendChannelGreetMessage = TRUE;
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 1, DmGreetMessageText, SendDmGreetMessage, GreetMessageChannelId, 0
FROM GuildConfigs
WHERE SendDmGreetMessage = TRUE;
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 2, ChannelByeMessageText, SendChannelByeMessage, ByeMessageChannelId, AutoDeleteByeMessagesTimer
FROM GuildConfigs
WHERE SendChannelByeMessage = TRUE;
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 3, BoostMessage, SendBoostMessage, BoostMessageChannelId, BoostMessageDeleteAfter
FROM GuildConfigs
WHERE SendBoostMessage = TRUE;
""");
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class stondel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "deletestreamonlinemessage",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "deletestreamonlinemessage",
table: "guildconfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class bank : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "bankusers",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
balance = table.Column<long>(type: "bigint", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_bankusers", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_bankusers_userid",
table: "bankusers",
column: "userid",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "bankusers");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,120 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class newrero : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "reactionroles",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
emote = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
roleid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
group = table.Column<int>(type: "int", nullable: false),
levelreq = table.Column<int>(type: "int", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_reactionroles", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_reactionroles_guildid",
table: "reactionroles",
column: "guildid");
migrationBuilder.CreateIndex(
name: "ix_reactionroles_messageid_emote",
table: "reactionroles",
columns: new[] { "messageid", "emote" },
unique: true);
MigrationQueries.MigrateRero(migrationBuilder);
migrationBuilder.DropTable(
name: "reactionrole");
migrationBuilder.DropTable(
name: "reactionrolemessage");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "reactionroles");
migrationBuilder.CreateTable(
name: "reactionrolemessage",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
guildconfigid = table.Column<int>(type: "int", nullable: false),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true),
exclusive = table.Column<bool>(type: "tinyint(1)", nullable: false),
index = table.Column<int>(type: "int", nullable: false),
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_reactionrolemessage", x => x.id);
table.ForeignKey(
name: "fk_reactionrolemessage_guildconfigs_guildconfigid",
column: x => x.guildconfigid,
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "reactionrole",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true),
emotename = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
reactionrolemessageid = table.Column<int>(type: "int", nullable: true),
roleid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_reactionrole", x => x.id);
table.ForeignKey(
name: "fk_reactionrole_reactionrolemessage_reactionrolemessageid",
column: x => x.reactionrolemessageid,
principalTable: "reactionrolemessage",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_reactionrole_reactionrolemessageid",
table: "reactionrole",
column: "reactionrolemessageid");
migrationBuilder.CreateIndex(
name: "ix_reactionrolemessage_guildconfigid",
table: "reactionrolemessage",
column: "guildconfigid");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,175 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class patronagesystem : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "patreonuserid",
table: "rewardedusers",
newName: "platformuserid");
migrationBuilder.RenameIndex(
name: "ix_rewardedusers_patreonuserid",
table: "rewardedusers",
newName: "ix_rewardedusers_platformuserid");
migrationBuilder.AlterColumn<long>(
name: "xp",
table: "userxpstats",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<long>(
name: "awardedxp",
table: "userxpstats",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<long>(
name: "amountrewardedthismonth",
table: "rewardedusers",
type: "bigint",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<bool>(
name: "verboseerrors",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "tinyint(1)");
migrationBuilder.AlterColumn<long>(
name: "totalxp",
table: "discorduser",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(int),
oldType: "int",
oldDefaultValue: 0);
migrationBuilder.CreateTable(
name: "patronquotas",
columns: table => new
{
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
featuretype = table.Column<int>(type: "int", nullable: false),
feature = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
hourlycount = table.Column<uint>(type: "int unsigned", nullable: false),
dailycount = table.Column<uint>(type: "int unsigned", nullable: false),
monthlycount = table.Column<uint>(type: "int unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_patronquotas", x => new { x.userid, x.featuretype, x.feature });
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "patrons",
columns: table => new
{
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
uniqueplatformuserid = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
amountcents = table.Column<int>(type: "int", nullable: false),
lastcharge = table.Column<DateTime>(type: "datetime(6)", nullable: false),
validthru = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_patrons", x => x.userid);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_patronquotas_userid",
table: "patronquotas",
column: "userid");
migrationBuilder.CreateIndex(
name: "ix_patrons_uniqueplatformuserid",
table: "patrons",
column: "uniqueplatformuserid",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "patronquotas");
migrationBuilder.DropTable(
name: "patrons");
migrationBuilder.RenameColumn(
name: "platformuserid",
table: "rewardedusers",
newName: "patreonuserid");
migrationBuilder.RenameIndex(
name: "ix_rewardedusers_platformuserid",
table: "rewardedusers",
newName: "ix_rewardedusers_patreonuserid");
migrationBuilder.AlterColumn<int>(
name: "xp",
table: "userxpstats",
type: "int",
nullable: false,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<int>(
name: "awardedxp",
table: "userxpstats",
type: "int",
nullable: false,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<int>(
name: "amountrewardedthismonth",
table: "rewardedusers",
type: "int",
nullable: false,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<bool>(
name: "verboseerrors",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
oldClrType: typeof(bool),
oldType: "tinyint(1)",
oldDefaultValue: true);
migrationBuilder.AlterColumn<int>(
name: "totalxp",
table: "discorduser",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(long),
oldType: "bigint",
oldDefaultValue: 0L);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class stondeldbcache : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "streamonlinemessages",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
type = table.Column<int>(type: "int", nullable: false),
name = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_streamonlinemessages", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "streamonlinemessages");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "logwarnsid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "logwarnsid",
table: "logsettings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class xpitemshop : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "xpshopowneditem",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
itemtype = table.Column<int>(type: "int", nullable: false),
isusing = table.Column<bool>(type: "tinyint(1)", nullable: false),
itemkey = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_xpshopowneditem", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_xpshopowneditem_userid_itemtype_itemkey",
table: "xpshopowneditem",
columns: new[] { "userid", "itemtype", "itemkey" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "xpshopowneditem");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class linkonlychannels : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "type",
table: "imageonlychannels",
type: "int",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "type",
table: "imageonlychannels");
}
}
}

View File

@@ -1,48 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class removeobsoletexpcolumns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "lastlevelup",
table: "userxpstats");
migrationBuilder.DropColumn(
name: "lastlevelup",
table: "discorduser");
migrationBuilder.DropColumn(
name: "lastxpgain",
table: "discorduser");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "lastlevelup",
table: "userxpstats",
type: "datetime(6)",
nullable: false,
defaultValueSql: "(UTC_TIMESTAMP)");
migrationBuilder.AddColumn<DateTime>(
name: "lastlevelup",
table: "discorduser",
type: "datetime(6)",
nullable: false,
defaultValueSql: "(UTC_TIMESTAMP)");
migrationBuilder.AddColumn<DateTime>(
name: "lastxpgain",
table: "discorduser",
type: "datetime(6)",
nullable: false,
defaultValueSql: "(UTC_TIMESTAMP - INTERVAL 1 year)");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class banprune : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "prunedays",
table: "bantemplates",
type: "int",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "prunedays",
table: "bantemplates");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class shoprolereq : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "rolerequirement",
table: "shopentry",
type: "bigint unsigned",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "rolerequirement",
table: "shopentry");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class autopub : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "autopublishchannel",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_autopublishchannel", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_autopublishchannel_guildid",
table: "autopublishchannel",
column: "guildid",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "autopublishchannel");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "gamblingstats",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
feature = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
bet = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
paidout = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_gamblingstats", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_gamblingstats_feature",
table: "gamblingstats",
column: "feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "gamblingstats");
}
}
}

View File

@@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "disableglobalexpressions",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "disableglobalexpressions",
table: "guildconfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,35 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class logthread : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "threadcreatedid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "threaddeletedid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "threadcreatedid",
table: "logsettings");
migrationBuilder.DropColumn(
name: "threaddeletedid",
table: "logsettings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class feedtext : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "message",
table: "feedsub",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "message",
table: "feedsub");
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,702 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
/// <inheritdoc />
public partial class guidlconfigcleanup : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_antiraidsetting_guildconfigs_guildconfigid",
table: "antiraidsetting");
migrationBuilder.DropForeignKey(
name: "fk_antispamignore_antispamsetting_antispamsettingid",
table: "antispamignore");
migrationBuilder.DropForeignKey(
name: "fk_antispamsetting_guildconfigs_guildconfigid",
table: "antispamsetting");
migrationBuilder.DropForeignKey(
name: "fk_commandalias_guildconfigs_guildconfigid",
table: "commandalias");
migrationBuilder.DropForeignKey(
name: "fk_commandcooldown_guildconfigs_guildconfigid",
table: "commandcooldown");
migrationBuilder.DropForeignKey(
name: "fk_delmsgoncmdchannel_guildconfigs_guildconfigid",
table: "delmsgoncmdchannel");
migrationBuilder.DropForeignKey(
name: "fk_excludeditem_xpsettings_xpsettingsid",
table: "excludeditem");
migrationBuilder.DropForeignKey(
name: "fk_filterchannelid_guildconfigs_guildconfigid",
table: "filterchannelid");
migrationBuilder.DropForeignKey(
name: "fk_filteredword_guildconfigs_guildconfigid",
table: "filteredword");
migrationBuilder.DropForeignKey(
name: "fk_filterlinkschannelid_guildconfigs_guildconfigid",
table: "filterlinkschannelid");
migrationBuilder.DropForeignKey(
name: "fk_filterwordschannelid_guildconfigs_guildconfigid",
table: "filterwordschannelid");
migrationBuilder.DropForeignKey(
name: "fk_followedstream_guildconfigs_guildconfigid",
table: "followedstream");
migrationBuilder.DropForeignKey(
name: "fk_gcchannelid_guildconfigs_guildconfigid",
table: "gcchannelid");
migrationBuilder.DropForeignKey(
name: "fk_muteduserid_guildconfigs_guildconfigid",
table: "muteduserid");
migrationBuilder.DropForeignKey(
name: "fk_permissions_guildconfigs_guildconfigid",
table: "permissions");
migrationBuilder.DropForeignKey(
name: "fk_shopentry_guildconfigs_guildconfigid",
table: "shopentry");
migrationBuilder.DropForeignKey(
name: "fk_shopentryitem_shopentry_shopentryid",
table: "shopentryitem");
migrationBuilder.DropForeignKey(
name: "fk_slowmodeignoredrole_guildconfigs_guildconfigid",
table: "slowmodeignoredrole");
migrationBuilder.DropForeignKey(
name: "fk_slowmodeignoreduser_guildconfigs_guildconfigid",
table: "slowmodeignoreduser");
migrationBuilder.DropForeignKey(
name: "fk_streamroleblacklisteduser_streamrolesettings_streamrolesetti~",
table: "streamroleblacklisteduser");
migrationBuilder.DropForeignKey(
name: "fk_streamrolewhitelisteduser_streamrolesettings_streamrolesetti~",
table: "streamrolewhitelisteduser");
migrationBuilder.DropForeignKey(
name: "fk_unbantimer_guildconfigs_guildconfigid",
table: "unbantimer");
migrationBuilder.DropForeignKey(
name: "fk_unmutetimer_guildconfigs_guildconfigid",
table: "unmutetimer");
migrationBuilder.DropForeignKey(
name: "fk_unroletimer_guildconfigs_guildconfigid",
table: "unroletimer");
migrationBuilder.DropForeignKey(
name: "fk_vcroleinfo_guildconfigs_guildconfigid",
table: "vcroleinfo");
migrationBuilder.DropForeignKey(
name: "fk_warningpunishment_guildconfigs_guildconfigid",
table: "warningpunishment");
migrationBuilder.DropTable(
name: "ignoredvoicepresencechannels");
migrationBuilder.AlterColumn<int>(
name: "streamrolesettingsid",
table: "streamrolewhitelisteduser",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "streamrolesettingsid",
table: "streamroleblacklisteduser",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "guildconfigid",
table: "delmsgoncmdchannel",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "fk_antiraidsetting_guildconfigs_guildconfigid",
table: "antiraidsetting",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_antispamignore_antispamsetting_antispamsettingid",
table: "antispamignore",
column: "antispamsettingid",
principalTable: "antispamsetting",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_antispamsetting_guildconfigs_guildconfigid",
table: "antispamsetting",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_commandalias_guildconfigs_guildconfigid",
table: "commandalias",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_commandcooldown_guildconfigs_guildconfigid",
table: "commandcooldown",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_delmsgoncmdchannel_guildconfigs_guildconfigid",
table: "delmsgoncmdchannel",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_excludeditem_xpsettings_xpsettingsid",
table: "excludeditem",
column: "xpsettingsid",
principalTable: "xpsettings",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_filterchannelid_guildconfigs_guildconfigid",
table: "filterchannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_filteredword_guildconfigs_guildconfigid",
table: "filteredword",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_filterlinkschannelid_guildconfigs_guildconfigid",
table: "filterlinkschannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_filterwordschannelid_guildconfigs_guildconfigid",
table: "filterwordschannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_followedstream_guildconfigs_guildconfigid",
table: "followedstream",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_gcchannelid_guildconfigs_guildconfigid",
table: "gcchannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_muteduserid_guildconfigs_guildconfigid",
table: "muteduserid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_permissions_guildconfigs_guildconfigid",
table: "permissions",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_shopentry_guildconfigs_guildconfigid",
table: "shopentry",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_shopentryitem_shopentry_shopentryid",
table: "shopentryitem",
column: "shopentryid",
principalTable: "shopentry",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_slowmodeignoredrole_guildconfigs_guildconfigid",
table: "slowmodeignoredrole",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_slowmodeignoreduser_guildconfigs_guildconfigid",
table: "slowmodeignoreduser",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_streamroleblacklisteduser_streamrolesettings_streamrolesetti~",
table: "streamroleblacklisteduser",
column: "streamrolesettingsid",
principalTable: "streamrolesettings",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_streamrolewhitelisteduser_streamrolesettings_streamrolesetti~",
table: "streamrolewhitelisteduser",
column: "streamrolesettingsid",
principalTable: "streamrolesettings",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_unbantimer_guildconfigs_guildconfigid",
table: "unbantimer",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_unmutetimer_guildconfigs_guildconfigid",
table: "unmutetimer",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_unroletimer_guildconfigs_guildconfigid",
table: "unroletimer",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_vcroleinfo_guildconfigs_guildconfigid",
table: "vcroleinfo",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_warningpunishment_guildconfigs_guildconfigid",
table: "warningpunishment",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_antiraidsetting_guildconfigs_guildconfigid",
table: "antiraidsetting");
migrationBuilder.DropForeignKey(
name: "fk_antispamignore_antispamsetting_antispamsettingid",
table: "antispamignore");
migrationBuilder.DropForeignKey(
name: "fk_antispamsetting_guildconfigs_guildconfigid",
table: "antispamsetting");
migrationBuilder.DropForeignKey(
name: "fk_commandalias_guildconfigs_guildconfigid",
table: "commandalias");
migrationBuilder.DropForeignKey(
name: "fk_commandcooldown_guildconfigs_guildconfigid",
table: "commandcooldown");
migrationBuilder.DropForeignKey(
name: "fk_delmsgoncmdchannel_guildconfigs_guildconfigid",
table: "delmsgoncmdchannel");
migrationBuilder.DropForeignKey(
name: "fk_excludeditem_xpsettings_xpsettingsid",
table: "excludeditem");
migrationBuilder.DropForeignKey(
name: "fk_filterchannelid_guildconfigs_guildconfigid",
table: "filterchannelid");
migrationBuilder.DropForeignKey(
name: "fk_filteredword_guildconfigs_guildconfigid",
table: "filteredword");
migrationBuilder.DropForeignKey(
name: "fk_filterlinkschannelid_guildconfigs_guildconfigid",
table: "filterlinkschannelid");
migrationBuilder.DropForeignKey(
name: "fk_filterwordschannelid_guildconfigs_guildconfigid",
table: "filterwordschannelid");
migrationBuilder.DropForeignKey(
name: "fk_followedstream_guildconfigs_guildconfigid",
table: "followedstream");
migrationBuilder.DropForeignKey(
name: "fk_gcchannelid_guildconfigs_guildconfigid",
table: "gcchannelid");
migrationBuilder.DropForeignKey(
name: "fk_muteduserid_guildconfigs_guildconfigid",
table: "muteduserid");
migrationBuilder.DropForeignKey(
name: "fk_permissions_guildconfigs_guildconfigid",
table: "permissions");
migrationBuilder.DropForeignKey(
name: "fk_shopentry_guildconfigs_guildconfigid",
table: "shopentry");
migrationBuilder.DropForeignKey(
name: "fk_shopentryitem_shopentry_shopentryid",
table: "shopentryitem");
migrationBuilder.DropForeignKey(
name: "fk_slowmodeignoredrole_guildconfigs_guildconfigid",
table: "slowmodeignoredrole");
migrationBuilder.DropForeignKey(
name: "fk_slowmodeignoreduser_guildconfigs_guildconfigid",
table: "slowmodeignoreduser");
migrationBuilder.DropForeignKey(
name: "fk_streamroleblacklisteduser_streamrolesettings_streamrolesetti~",
table: "streamroleblacklisteduser");
migrationBuilder.DropForeignKey(
name: "fk_streamrolewhitelisteduser_streamrolesettings_streamrolesetti~",
table: "streamrolewhitelisteduser");
migrationBuilder.DropForeignKey(
name: "fk_unbantimer_guildconfigs_guildconfigid",
table: "unbantimer");
migrationBuilder.DropForeignKey(
name: "fk_unmutetimer_guildconfigs_guildconfigid",
table: "unmutetimer");
migrationBuilder.DropForeignKey(
name: "fk_unroletimer_guildconfigs_guildconfigid",
table: "unroletimer");
migrationBuilder.DropForeignKey(
name: "fk_vcroleinfo_guildconfigs_guildconfigid",
table: "vcroleinfo");
migrationBuilder.DropForeignKey(
name: "fk_warningpunishment_guildconfigs_guildconfigid",
table: "warningpunishment");
migrationBuilder.AlterColumn<int>(
name: "streamrolesettingsid",
table: "streamrolewhitelisteduser",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<int>(
name: "streamrolesettingsid",
table: "streamroleblacklisteduser",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<int>(
name: "guildconfigid",
table: "delmsgoncmdchannel",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.CreateTable(
name: "ignoredvoicepresencechannels",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
logsettingid = table.Column<int>(type: "int", nullable: true),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_ignoredvoicepresencechannels", x => x.id);
table.ForeignKey(
name: "fk_ignoredvoicepresencechannels_logsettings_logsettingid",
column: x => x.logsettingid,
principalTable: "logsettings",
principalColumn: "id");
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_ignoredvoicepresencechannels_logsettingid",
table: "ignoredvoicepresencechannels",
column: "logsettingid");
migrationBuilder.AddForeignKey(
name: "fk_antiraidsetting_guildconfigs_guildconfigid",
table: "antiraidsetting",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_antispamignore_antispamsetting_antispamsettingid",
table: "antispamignore",
column: "antispamsettingid",
principalTable: "antispamsetting",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_antispamsetting_guildconfigs_guildconfigid",
table: "antispamsetting",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "fk_commandalias_guildconfigs_guildconfigid",
table: "commandalias",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_commandcooldown_guildconfigs_guildconfigid",
table: "commandcooldown",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_delmsgoncmdchannel_guildconfigs_guildconfigid",
table: "delmsgoncmdchannel",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_excludeditem_xpsettings_xpsettingsid",
table: "excludeditem",
column: "xpsettingsid",
principalTable: "xpsettings",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_filterchannelid_guildconfigs_guildconfigid",
table: "filterchannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_filteredword_guildconfigs_guildconfigid",
table: "filteredword",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_filterlinkschannelid_guildconfigs_guildconfigid",
table: "filterlinkschannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_filterwordschannelid_guildconfigs_guildconfigid",
table: "filterwordschannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_followedstream_guildconfigs_guildconfigid",
table: "followedstream",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_gcchannelid_guildconfigs_guildconfigid",
table: "gcchannelid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_muteduserid_guildconfigs_guildconfigid",
table: "muteduserid",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_permissions_guildconfigs_guildconfigid",
table: "permissions",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_shopentry_guildconfigs_guildconfigid",
table: "shopentry",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_shopentryitem_shopentry_shopentryid",
table: "shopentryitem",
column: "shopentryid",
principalTable: "shopentry",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_slowmodeignoredrole_guildconfigs_guildconfigid",
table: "slowmodeignoredrole",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_slowmodeignoreduser_guildconfigs_guildconfigid",
table: "slowmodeignoreduser",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_streamroleblacklisteduser_streamrolesettings_streamrolesetti~",
table: "streamroleblacklisteduser",
column: "streamrolesettingsid",
principalTable: "streamrolesettings",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_streamrolewhitelisteduser_streamrolesettings_streamrolesetti~",
table: "streamrolewhitelisteduser",
column: "streamrolesettingsid",
principalTable: "streamrolesettings",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_unbantimer_guildconfigs_guildconfigid",
table: "unbantimer",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_unmutetimer_guildconfigs_guildconfigid",
table: "unmutetimer",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_unroletimer_guildconfigs_guildconfigid",
table: "unroletimer",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_vcroleinfo_guildconfigs_guildconfigid",
table: "vcroleinfo",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
migrationBuilder.AddForeignKey(
name: "fk_warningpunishment_guildconfigs_guildconfigid",
table: "warningpunishment",
column: "guildconfigid",
principalTable: "guildconfigs",
principalColumn: "id");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
/// <inheritdoc />
public partial class removepatronlimits : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "patronquotas");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "patronquotas",
columns: table => new
{
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
featuretype = table.Column<int>(type: "int", nullable: false),
feature = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
dailycount = table.Column<uint>(type: "int unsigned", nullable: false),
hourlycount = table.Column<uint>(type: "int unsigned", nullable: false),
monthlycount = table.Column<uint>(type: "int unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_patronquotas", x => new { x.userid, x.featuretype, x.feature });
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_patronquotas_userid",
table: "patronquotas",
column: "userid");
}
}
}

View File

@@ -1,36 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
/// <inheritdoc />
public partial class honeypot : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "honeypotchannels",
columns: table => new
{
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_honeypotchannels", x => x.guildid);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "honeypotchannels");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,6 @@ namespace NadekoBot.Migrations.PostgreSql
{ {
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.AddColumn<decimal>(
name: "rolerequirement",
table: "shopentry",
type: "numeric(20,0)",
nullable: true);
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "autopublishchannel", name: "autopublishchannel",
columns: table => new columns: table => new
@@ -41,10 +35,6 @@ namespace NadekoBot.Migrations.PostgreSql
{ {
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "autopublishchannel"); name: "autopublishchannel");
migrationBuilder.DropColumn(
name: "rolerequirement",
table: "shopentry");
} }
} }
} }

View File

@@ -0,0 +1,199 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class greetsettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "greetsettings",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
greettype = table.Column<int>(type: "integer", nullable: false),
messagetext = table.Column<string>(type: "text", nullable: true),
isenabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
autodeletetimer = table.Column<int>(type: "integer", nullable: false, defaultValue: 0)
},
constraints: table =>
{
table.PrimaryKey("pk_greetsettings", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_greetsettings_guildid_greettype",
table: "greetsettings",
columns: new[] { "guildid", "greettype" },
unique: true);
MigrationQueries.GreetSettingsCopy(migrationBuilder);
migrationBuilder.DropColumn(
name: "autodeletebyemessagestimer",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "autodeletegreetmessagestimer",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessagedeleteafter",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "byemessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "channelbyemessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "channelgreetmessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "dmgreetmessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "greetmessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendboostmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendchannelbyemessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendchannelgreetmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "senddmgreetmessage",
table: "guildconfigs");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "greetsettings");
migrationBuilder.AddColumn<int>(
name: "autodeletebyemessagestimer",
table: "guildconfigs",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "autodeletegreetmessagestimer",
table: "guildconfigs",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "boostmessage",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "boostmessagechannelid",
table: "guildconfigs",
type: "numeric(20,0)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<int>(
name: "boostmessagedeleteafter",
table: "guildconfigs",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<decimal>(
name: "byemessagechannelid",
table: "guildconfigs",
type: "numeric(20,0)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<string>(
name: "channelbyemessagetext",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "channelgreetmessagetext",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "dmgreetmessagetext",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "greetmessagechannelid",
table: "guildconfigs",
type: "numeric(20,0)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<bool>(
name: "sendboostmessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "sendchannelbyemessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "sendchannelgreetmessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "senddmgreetmessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace NadekoBot.Migrations.PostgreSql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "8.0.4") .HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -1220,42 +1220,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("autoassignroleids"); .HasColumnName("autoassignroleids");
b.Property<int>("AutoDeleteByeMessagesTimer")
.HasColumnType("integer")
.HasColumnName("autodeletebyemessagestimer");
b.Property<int>("AutoDeleteGreetMessagesTimer")
.HasColumnType("integer")
.HasColumnName("autodeletegreetmessagestimer");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages") b.Property<bool>("AutoDeleteSelfAssignedRoleMessages")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("autodeleteselfassignedrolemessages"); .HasColumnName("autodeleteselfassignedrolemessages");
b.Property<string>("BoostMessage")
.HasColumnType("text")
.HasColumnName("boostmessage");
b.Property<decimal>("BoostMessageChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("boostmessagechannelid");
b.Property<int>("BoostMessageDeleteAfter")
.HasColumnType("integer")
.HasColumnName("boostmessagedeleteafter");
b.Property<decimal>("ByeMessageChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("byemessagechannelid");
b.Property<string>("ChannelByeMessageText")
.HasColumnType("text")
.HasColumnName("channelbyemessagetext");
b.Property<string>("ChannelGreetMessageText")
.HasColumnType("text")
.HasColumnName("channelgreetmessagetext");
b.Property<bool>("CleverbotEnabled") b.Property<bool>("CleverbotEnabled")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("cleverbotenabled"); .HasColumnName("cleverbotenabled");
@@ -1276,10 +1244,6 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("disableglobalexpressions"); .HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("text")
.HasColumnName("dmgreetmessagetext");
b.Property<bool>("ExclusiveSelfAssignedRoles") b.Property<bool>("ExclusiveSelfAssignedRoles")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("exclusiveselfassignedroles"); .HasColumnName("exclusiveselfassignedroles");
@@ -1300,10 +1264,6 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)") .HasColumnType("numeric(20,0)")
.HasColumnName("gamevoicechannel"); .HasColumnName("gamevoicechannel");
b.Property<decimal>("GreetMessageChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("greetmessagechannelid");
b.Property<decimal>("GuildId") b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)") .HasColumnType("numeric(20,0)")
.HasColumnName("guildid"); .HasColumnName("guildid");
@@ -1328,22 +1288,6 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("prefix"); .HasColumnName("prefix");
b.Property<bool>("SendBoostMessage")
.HasColumnType("boolean")
.HasColumnName("sendboostmessage");
b.Property<bool>("SendChannelByeMessage")
.HasColumnType("boolean")
.HasColumnName("sendchannelbyemessage");
b.Property<bool>("SendChannelGreetMessage")
.HasColumnType("boolean")
.HasColumnName("sendchannelgreetmessage");
b.Property<bool>("SendDmGreetMessage")
.HasColumnType("boolean")
.HasColumnName("senddmgreetmessage");
b.Property<bool>("StickyRoles") b.Property<bool>("StickyRoles")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("stickyroles"); .HasColumnName("stickyroles");
@@ -3163,6 +3107,53 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("xpshopowneditem", (string)null); b.ToTable("xpshopowneditem", (string)null);
}); });
modelBuilder.Entity("NadekoBot.Services.GreetSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AutoDeleteTimer")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("autodeletetimer");
b.Property<decimal?>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.Property<int>("GreetType")
.HasColumnType("integer")
.HasColumnName("greettype");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<bool>("IsEnabled")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("isenabled");
b.Property<string>("MessageText")
.HasColumnType("text")
.HasColumnName("messagetext");
b.HasKey("Id")
.HasName("pk_greetsettings");
b.HasIndex("GuildId", "GreetType")
.IsUnique()
.HasDatabaseName("ix_greetsettings_guildid_greettype");
b.ToTable("greetsettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
{ {
b.HasOne("NadekoBot.Db.Models.GuildConfig", null) b.HasOne("NadekoBot.Db.Models.GuildConfig", null)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class greetsettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GreetSettings",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
GreetType = table.Column<int>(type: "INTEGER", nullable: false),
MessageText = table.Column<string>(type: "TEXT", nullable: true),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: false),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: true),
AutoDeleteTimer = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0)
},
constraints: table =>
{
table.PrimaryKey("PK_GreetSettings", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_GreetSettings_GuildId_GreetType",
table: "GreetSettings",
columns: new[] { "GuildId", "GreetType" },
unique: true);
MigrationQueries.GreetSettingsCopy(migrationBuilder);
migrationBuilder.DropColumn(
name: "AutoDeleteByeMessagesTimer",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "AutoDeleteGreetMessagesTimer",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "BoostMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "BoostMessageChannelId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "BoostMessageDeleteAfter",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "ByeMessageChannelId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "ChannelByeMessageText",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "ChannelGreetMessageText",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "DmGreetMessageText",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "GreetMessageChannelId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendBoostMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendChannelByeMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendChannelGreetMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendDmGreetMessage",
table: "GuildConfigs");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GreetSettings");
migrationBuilder.AddColumn<int>(
name: "AutoDeleteByeMessagesTimer",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "AutoDeleteGreetMessagesTimer",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "BoostMessage",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "BoostMessageChannelId",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<int>(
name: "BoostMessageDeleteAfter",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<ulong>(
name: "ByeMessageChannelId",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<string>(
name: "ChannelByeMessageText",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ChannelGreetMessageText",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DmGreetMessageText",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "GreetMessageChannelId",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<bool>(
name: "SendBoostMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SendChannelByeMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SendChannelGreetMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SendDmGreetMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}
}

View File

@@ -15,7 +15,7 @@ namespace NadekoBot.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
{ {
@@ -907,33 +907,9 @@ namespace NadekoBot.Migrations
b.Property<string>("AutoAssignRoleIds") b.Property<string>("AutoAssignRoleIds")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("AutoDeleteByeMessagesTimer")
.HasColumnType("INTEGER");
b.Property<int>("AutoDeleteGreetMessagesTimer")
.HasColumnType("INTEGER");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages") b.Property<bool>("AutoDeleteSelfAssignedRoleMessages")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("BoostMessage")
.HasColumnType("TEXT");
b.Property<ulong>("BoostMessageChannelId")
.HasColumnType("INTEGER");
b.Property<int>("BoostMessageDeleteAfter")
.HasColumnType("INTEGER");
b.Property<ulong>("ByeMessageChannelId")
.HasColumnType("INTEGER");
b.Property<string>("ChannelByeMessageText")
.HasColumnType("TEXT");
b.Property<string>("ChannelGreetMessageText")
.HasColumnType("TEXT");
b.Property<bool>("CleverbotEnabled") b.Property<bool>("CleverbotEnabled")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -949,9 +925,6 @@ namespace NadekoBot.Migrations
b.Property<bool>("DisableGlobalExpressions") b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("DmGreetMessageText")
.HasColumnType("TEXT");
b.Property<bool>("ExclusiveSelfAssignedRoles") b.Property<bool>("ExclusiveSelfAssignedRoles")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -967,9 +940,6 @@ namespace NadekoBot.Migrations
b.Property<ulong?>("GameVoiceChannel") b.Property<ulong?>("GameVoiceChannel")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<ulong>("GreetMessageChannelId")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId") b.Property<ulong>("GuildId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -988,18 +958,6 @@ namespace NadekoBot.Migrations
b.Property<string>("Prefix") b.Property<string>("Prefix")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("SendBoostMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendChannelByeMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendChannelGreetMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendDmGreetMessage")
.HasColumnType("INTEGER");
b.Property<bool>("StickyRoles") b.Property<bool>("StickyRoles")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -2351,6 +2309,42 @@ namespace NadekoBot.Migrations
b.ToTable("XpShopOwnedItem"); b.ToTable("XpShopOwnedItem");
}); });
modelBuilder.Entity("NadekoBot.Services.GreetSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AutoDeleteTimer")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<ulong?>("ChannelId")
.HasColumnType("INTEGER");
b.Property<int>("GreetType")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false);
b.Property<string>("MessageText")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GuildId", "GreetType")
.IsUnique();
b.ToTable("GreetSettings");
});
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
{ {
b.HasOne("NadekoBot.Db.Models.GuildConfig", null) b.HasOne("NadekoBot.Db.Models.GuildConfig", null)

View File

@@ -36,6 +36,8 @@ public class AutoPublishService : IExecNoCommand, IReadyExecutor, INService
RetryMode = RetryMode.AlwaysFail RetryMode = RetryMode.AlwaysFail
}); });
} }
// todo GUILDS
public async Task OnReadyAsync() public async Task OnReadyAsync()
{ {

View File

@@ -1,14 +1,20 @@
namespace NadekoBot.Modules.Administration.DangerousCommands; using NadekoBot.Modules.Administration.DangerousCommands;
public partial class Administration namespace NadekoBot.Modules.Administration;
public partial class Administration
{ {
[Group] [Group]
public class CleanupCommands : CleanupModuleBase public partial class CleanupCommands : CleanupModuleBase
{ {
private readonly ICleanupService _svc; private readonly ICleanupService _svc;
private readonly IBotCredsProvider _creds;
public CleanupCommands(ICleanupService svc) public CleanupCommands(ICleanupService svc, IBotCredsProvider creds)
=> _svc = svc; {
_svc = svc;
_creds = creds;
}
[Cmd] [Cmd]
[OwnerOnly] [OwnerOnly]
@@ -37,5 +43,32 @@ public partial class Administration
await Response().Text("This guild's bot data will be saved.").SendAsync(); await Response().Text("This guild's bot data will be saved.").SendAsync();
} }
[Cmd]
[OwnerOnly]
public async Task LeaveUnkeptServers(int startShardId, int shardMultiplier = 3000)
{
var keptGuildCount = await _svc.GetKeptGuildCount();
var response = await PromptUserConfirmAsync(new EmbedBuilder()
.WithDescription($"""
Do you want the bot to leave all unkept servers?
There are currently {keptGuildCount} kept servers.
**This is a highly destructive and irreversible action.**
"""));
if (!response)
return;
for (var shardId = startShardId; shardId < _creds.GetCreds().TotalShards; shardId++)
{
await _svc.StartLeavingUnkeptServers(shardId);
await Task.Delay(shardMultiplier * 1000);
}
await ctx.OkAsync();
}
} }
} }

View File

@@ -9,9 +9,12 @@ namespace NadekoBot.Modules.Administration.DangerousCommands;
public sealed class CleanupService : ICleanupService, IReadyExecutor, INService public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
{ {
private TypedKey<KeepReport> _cleanupReportKey = new("cleanup:report");
private TypedKey<bool> _cleanupTriggerKey = new("cleanup:trigger");
private TypedKey<int> _keepTriggerKey = new("keep:trigger");
private readonly IPubSub _pubSub; private readonly IPubSub _pubSub;
private TypedKey<KeepReport> _keepReportKey = new("cleanup:report");
private TypedKey<bool> _keepTriggerKey = new("cleanup:trigger");
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private ConcurrentDictionary<int, ulong[]> guildIds = new(); private ConcurrentDictionary<int, ulong[]> guildIds = new();
private readonly IBotCredsProvider _creds; private readonly IBotCredsProvider _creds;
@@ -29,11 +32,90 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
_db = db; _db = db;
} }
public async Task OnReadyAsync()
{
await _pubSub.Sub(_cleanupTriggerKey, OnCleanupTrigger);
await _pubSub.Sub(_keepTriggerKey, InternalTriggerKeep);
_client.JoinedGuild += ClientOnJoinedGuild;
if (_client.ShardId == 0)
await _pubSub.Sub(_cleanupReportKey, OnKeepReport);
}
private bool keepTriggered = false;
private async ValueTask InternalTriggerKeep(int shardId)
{
if (_client.ShardId != shardId)
return;
if (keepTriggered)
return;
keepTriggered = true;
try
{
var allGuildIds = _client.Guilds.Select(x => x.Id).ToArray();
HashSet<ulong> dontDelete;
await using (var db = _db.GetDbContext())
{
await using var ctx = db.CreateLinqToDBContext();
var table = ctx.CreateTable<KeptGuilds>(tableOptions: TableOptions.CheckExistence);
var dontDeleteList = await table
.Where(x => allGuildIds.Contains(x.GuildId))
.Select(x => x.GuildId)
.ToListAsyncLinqToDB();
dontDelete = dontDeleteList.ToHashSet();
}
Log.Information("Leaving {RemainingCount} guilds, 1 every second. {DontDeleteCount} will remain",
allGuildIds.Length - dontDelete.Count,
dontDelete.Count);
foreach (var guildId in allGuildIds)
{
if (dontDelete.Contains(guildId))
continue;
await Task.Delay(1016);
SocketGuild? guild = null;
try
{
guild = _client.GetGuild(guildId);
if (guild is null)
{
Log.Warning("Unable to find guild {GuildId}", guildId);
continue;
}
await guild.LeaveAsync();
}
catch (Exception ex)
{
Log.Warning("Unable to leave guild {GuildName} [{GuildId}]: {ErrorMessage}",
guild?.Name,
guildId,
ex.Message);
}
}
}
finally
{
keepTriggered = false;
}
}
public async Task<KeepResult?> DeleteMissingGuildDataAsync() public async Task<KeepResult?> DeleteMissingGuildDataAsync()
{ {
guildIds = new(); guildIds = new();
var totalShards = _creds.GetCreds().TotalShards; var totalShards = _creds.GetCreds().TotalShards;
await _pubSub.Pub(_keepTriggerKey, true); await _pubSub.Pub(_cleanupTriggerKey, true);
var counter = 0; var counter = 0;
while (guildIds.Keys.Count < totalShards) while (guildIds.Keys.Count < totalShards)
{ {
@@ -135,9 +217,7 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
{ {
await using var db = _db.GetDbContext(); await using var db = _db.GetDbContext();
await using var ctx = db.CreateLinqToDBContext(); await using var ctx = db.CreateLinqToDBContext();
var table = ctx.CreateTable<KeptGuilds>(tableOptions: TableOptions.CheckExistence); var table = ctx.CreateTable<KeptGuilds>(tableOptions: TableOptions.CheckExistence);
if (await table.AnyAsyncLinqToDB(x => x.GuildId == guildId)) if (await table.AnyAsyncLinqToDB(x => x.GuildId == guildId))
return false; return false;
@@ -149,30 +229,31 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
return true; return true;
} }
public async Task<int> GetKeptGuildCount()
{
await using var db = _db.GetDbContext();
await using var ctx = db.CreateLinqToDBContext();
var table = ctx.CreateTable<KeptGuilds>(tableOptions: TableOptions.CheckExistence);
return await table.CountAsync();
}
public async Task StartLeavingUnkeptServers(int shardId)
=> await _pubSub.Pub(_keepTriggerKey, shardId);
private ValueTask OnKeepReport(KeepReport report) private ValueTask OnKeepReport(KeepReport report)
{ {
guildIds[report.ShardId] = report.GuildIds; guildIds[report.ShardId] = report.GuildIds;
return default; return default;
} }
public async Task OnReadyAsync()
{
await _pubSub.Sub(_keepTriggerKey, OnKeepTrigger);
_client.JoinedGuild += ClientOnJoinedGuild;
if (_client.ShardId == 0)
await _pubSub.Sub(_keepReportKey, OnKeepReport);
}
private async Task ClientOnJoinedGuild(SocketGuild arg) private async Task ClientOnJoinedGuild(SocketGuild arg)
{ {
await KeepGuild(arg.Id); await KeepGuild(arg.Id);
} }
private ValueTask OnKeepTrigger(bool arg) private ValueTask OnCleanupTrigger(bool arg)
{ {
_pubSub.Pub(_keepReportKey, _pubSub.Pub(_cleanupReportKey,
new KeepReport() new KeepReport()
{ {
ShardId = _client.ShardId, ShardId = _client.ShardId,

View File

@@ -4,4 +4,6 @@ public interface ICleanupService
{ {
Task<KeepResult?> DeleteMissingGuildDataAsync(); Task<KeepResult?> DeleteMissingGuildDataAsync();
Task<bool> KeepGuild(ulong guildId); Task<bool> KeepGuild(ulong guildId);
Task<int> GetKeptGuildCount();
Task StartLeavingUnkeptServers(int shardId);
} }

View File

@@ -8,236 +8,237 @@ public partial class Administration
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task Boost() public Task Boost()
{ => Toggle(GreetType.Boost);
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.boost_on).SendAsync();
else
await Response().Pending(strs.boost_off).SendAsync();
}
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task BoostDel(int timer = 30) public Task BoostDel(int timer = 30)
=> SetDel(GreetType.Boost, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Boost, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Greet()
=> Toggle(GreetType.Greet);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDel(int timer = 30)
=> SetDel(GreetType.Greet, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Greet, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDm()
=> Toggle(GreetType.GreetDm);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg([Leftover] string? text = null)
=> SetMsg(GreetType.GreetDm, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Bye()
=> Toggle(GreetType.Bye);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeDel(int timer = 30)
=> SetDel(GreetType.Bye, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Bye, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Greet, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.GreetDm, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task ByeTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Bye, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task BoostTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Boost, user);
public async Task Toggle(GreetType type)
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id, type);
if (enabled)
await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boost_on,
GreetType.Greet => strs.greet_on,
GreetType.Bye => strs.bye_on,
GreetType.GreetDm => strs.greetdm_on,
_ => strs.error
}
)
.SendAsync();
else
await Response()
.Pending(
type switch
{
GreetType.Boost => strs.boost_off,
GreetType.Greet => strs.greet_off,
GreetType.Bye => strs.bye_off,
GreetType.GreetDm => strs.greetdm_off,
_ => strs.error
}
)
.SendAsync();
}
public async Task SetDel(GreetType type, int timer)
{ {
if (timer is < 0 or > 600) if (timer is < 0 or > 600)
return; return;
await _service.SetBoostDel(ctx.Guild.Id, timer); await _service.SetDeleteTimer(ctx.Guild.Id, type, timer);
if (timer > 0) if (timer > 0)
await Response().Confirm(strs.boostdel_on(timer)).SendAsync(); await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boostdel_on(timer),
GreetType.Greet => strs.greetdel_on(timer),
GreetType.Bye => strs.byedel_on(timer),
_ => strs.error
}
)
.SendAsync();
else else
await Response().Pending(strs.boostdel_off).SendAsync(); await Response()
.Pending(
type switch
{
GreetType.Boost => strs.boostdel_off,
GreetType.Greet => strs.greetdel_off,
GreetType.Bye => strs.byedel_off,
_ => strs.error
})
.SendAsync();
} }
[Cmd]
[RequireContext(ContextType.Guild)] public async Task SetMsg(GreetType type, string? text = null)
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostMsg([Leftover] string? text = null)
{ {
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
var boostMessage = _service.GetBoostMessage(ctx.Guild.Id); var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
await Response().Confirm(strs.boostmsg_cur(boostMessage?.SanitizeMentions())).SendAsync(); var msg = conf?.MessageText ?? GreetService.GetDefaultGreet(type);
await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boostmsg_cur(msg),
GreetType.Greet => strs.greetmsg_cur(msg),
GreetType.Bye => strs.byemsg_cur(msg),
GreetType.GreetDm => strs.greetdmmsg_cur(msg),
_ => strs.error
})
.SendAsync();
return; return;
} }
var sendBoostEnabled = _service.SetBoostMessage(ctx.Guild.Id, ref text); var isEnabled = await _service.SetMessage(ctx.Guild.Id, type, text);
await Response().Confirm(strs.boostmsg_new).SendAsync(); await Response()
if (!sendBoostEnabled) .Confirm(type switch
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync(); {
} GreetType.Boost => strs.boostmsg_new,
GreetType.Greet => strs.greetmsg_new,
GreetType.Bye => strs.byemsg_new,
GreetType.GreetDm => strs.greetdmmsg_new,
_ => strs.error
})
.SendAsync();
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDel(int timer = 30)
{
if (timer is < 0 or > 600)
return;
await _service.SetGreetDel(ctx.Guild.Id, timer); if (!isEnabled)
if (timer > 0)
await Response().Confirm(strs.greetdel_on(timer)).SendAsync();
else
await Response().Pending(strs.greetdel_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Greet()
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.greet_on).SendAsync();
else
await Response().Pending(strs.greet_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{ {
var greetMsg = _service.GetGreetMsg(ctx.Guild.Id); var cmdName = GetCmdName(type);
await Response().Confirm(strs.greetmsg_cur(greetMsg?.SanitizeMentions())).SendAsync();
return; await Response().Pending(strs.boostmsg_enable($"`{prefix}{cmdName}`")).SendAsync();
} }
var sendGreetEnabled = _service.SetGreetMessage(ctx.Guild.Id, ref text);
await Response().Confirm(strs.greetmsg_new).SendAsync();
if (!sendGreetEnabled)
await Response().Pending(strs.greetmsg_enable($"`{prefix}greet`")).SendAsync();
} }
[Cmd] private static string GetCmdName(GreetType type)
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDm()
{ {
var enabled = await _service.SetGreetDm(ctx.Guild.Id); var cmdName = type switch
if (enabled)
await Response().Confirm(strs.greetdm_on).SendAsync();
else
await Response().Confirm(strs.greetdm_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDmMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{ {
var dmGreetMsg = _service.GetDmGreetMsg(ctx.Guild.Id); GreetType.Greet => "greet",
await Response().Confirm(strs.greetdmmsg_cur(dmGreetMsg?.SanitizeMentions())).SendAsync(); GreetType.Bye => "bye",
return; GreetType.Boost => "boost",
} GreetType.GreetDm => "greetdm",
_ => "unknown_command"
var sendGreetEnabled = _service.SetGreetDmMessage(ctx.Guild.Id, ref text); };
return cmdName;
await Response().Confirm(strs.greetdmmsg_new).SendAsync();
if (!sendGreetEnabled)
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
} }
[Cmd] public async Task Test(GreetType type, IGuildUser? user = null)
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Bye()
{ {
var enabled = await _service.SetBye(ctx.Guild.Id, ctx.Channel.Id); user ??= (IGuildUser)ctx.User;
if (enabled) await _service.Test(ctx.Guild.Id, type, (ITextChannel)ctx.Channel, user);
await Response().Confirm(strs.bye_on).SendAsync(); var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
else
await Response().Confirm(strs.bye_off).SendAsync();
}
[Cmd] var cmd = $"`{prefix}{GetCmdName(type)}`";
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] var str = type switch
public async Task ByeMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{ {
var byeMsg = _service.GetByeMessage(ctx.Guild.Id); GreetType.Greet => strs.boostmsg_enable(cmd),
await Response().Confirm(strs.byemsg_cur(byeMsg?.SanitizeMentions())).SendAsync(); GreetType.Bye => strs.greetmsg_enable(cmd),
return; GreetType.Boost => strs.byemsg_enable(cmd),
} GreetType.GreetDm => strs.greetdmmsg_enable(cmd),
_ => strs.error
};
var sendByeEnabled = _service.SetByeMessage(ctx.Guild.Id, ref text); if (conf?.IsEnabled is not true)
await Response().Pending(str).SendAsync();
await Response().Confirm(strs.byemsg_new).SendAsync();
if (!sendByeEnabled)
await Response().Pending(strs.byemsg_enable($"`{prefix}bye`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeDel(int timer = 30)
{
await _service.SetByeDel(ctx.Guild.Id, timer);
if (timer > 0)
await Response().Confirm(strs.byedel_on(timer)).SendAsync();
else
await Response().Pending(strs.byedel_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task ByeTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
await _service.ByeTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetByeEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.byemsg_enable($"`{prefix}bye`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
await _service.GreetTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetGreetEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.greetmsg_enable($"`{prefix}greet`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetDmTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
var success = await _service.GreetDmTest(user);
if (success)
await ctx.OkAsync();
else
await ctx.WarningAsync();
var enabled = _service.GetGreetDmEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task BoostTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
await _service.BoostTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetBoostEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync();
} }
} }
} }

View File

@@ -1,71 +0,0 @@
namespace NadekoBot.Services;
public class GreetGrouper<T>
{
private readonly Dictionary<ulong, HashSet<T>> _group;
private readonly object _locker = new();
public GreetGrouper()
=> _group = new();
/// <summary>
/// Creates a group, if group already exists, adds the specified user
/// </summary>
/// <param name="guildId">Id of the server for which to create group for</param>
/// <param name="toAddIfExists">User to add if group already exists</param>
/// <returns></returns>
public bool CreateOrAdd(ulong guildId, T toAddIfExists)
{
lock (_locker)
{
if (_group.TryGetValue(guildId, out var list))
{
list.Add(toAddIfExists);
return false;
}
_group[guildId] = new();
return true;
}
}
/// <summary>
/// Remove the specified amount of items from the group. If all items are removed, group will be removed.
/// </summary>
/// <param name="guildId">Id of the group</param>
/// <param name="count">Maximum number of items to retrieve</param>
/// <param name="items">Items retrieved</param>
/// <returns>Whether the group has no more items left and is deleted</returns>
public bool ClearGroup(ulong guildId, int count, out IReadOnlyCollection<T> items)
{
lock (_locker)
{
if (_group.TryGetValue(guildId, out var set))
{
// if we want more than there are, return everything
if (count >= set.Count)
{
items = set;
_group.Remove(guildId);
return true;
}
// if there are more in the group than what's needed
// take the requested number, remove them from the set
// and return them
var toReturn = set.TakeWhile(_ => count-- != 0).ToList();
foreach (var item in toReturn)
set.Remove(item);
items = toReturn;
// returning falsemeans group is not yet deleted
// because there are items left
return false;
}
items = Array.Empty<T>();
return true;
}
}
}

View File

@@ -1,58 +1,93 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using LinqToDB.Tools;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using System.Threading.Channels; using System.Threading.Channels;
namespace NadekoBot.Services; namespace NadekoBot.Services;
public class GreetService : INService, IReadyExecutor public class GreetService : INService, IReadyExecutor
{ {
public bool GroupGreets
=> _bss.Data.GroupGreets;
private readonly DbService _db; private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, GreetSettings> _guildConfigsCache; private ConcurrentDictionary<GreetType, ConcurrentHashSet<ulong>> _enabled = new();
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly GreetGrouper<IGuildUser> _greets = new();
private readonly GreetGrouper<IUser> _byes = new();
private readonly BotConfigService _bss;
private readonly IReplacementService _repSvc; private readonly IReplacementService _repSvc;
private readonly IBotCache _cache;
private readonly IMessageSenderService _sender; private readonly IMessageSenderService _sender;
private readonly Channel<(GreetSettings, IUser, ITextChannel?)> _greetQueue =
Channel.CreateBounded<(GreetSettings, IUser, ITextChannel?)>(
new BoundedChannelOptions(60)
{
FullMode = BoundedChannelFullMode.DropOldest
});
public GreetService( public GreetService(
DiscordSocketClient client, DiscordSocketClient client,
IBot bot,
DbService db, DbService db,
BotConfigService bss,
IMessageSenderService sender, IMessageSenderService sender,
IReplacementService repSvc) IReplacementService repSvc,
IBotCache cache
)
{ {
_db = db; _db = db;
_client = client; _client = client;
_bss = bss;
_repSvc = repSvc; _repSvc = repSvc;
_cache = cache;
_sender = sender; _sender = sender;
_guildConfigsCache = new(bot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
_client.UserJoined += OnUserJoined; foreach (var type in Enum.GetValues<GreetType>())
_client.UserLeft += OnUserLeft; {
_enabled[type] = new();
bot.JoinedGuild += OnBotJoinedGuild; }
_client.LeftGuild += OnClientLeftGuild;
_client.GuildMemberUpdated += ClientOnGuildMemberUpdated;
} }
public async Task OnReadyAsync() public async Task OnReadyAsync()
{ {
// cache all enabled guilds
await using (var uow = _db.GetDbContext())
{
var guilds = _client.Guilds.Select(x => x.Id).ToList();
var enabled = await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId.In(guilds))
.Where(x => x.IsEnabled)
.Select(x => new
{
x.GuildId,
x.GreetType
})
.ToListAsync();
foreach (var e in enabled)
{
_enabled[e.GreetType].Add(e.GuildId);
}
}
_client.UserJoined += OnUserJoined;
_client.UserLeft += OnUserLeft;
_client.LeftGuild += OnClientLeftGuild;
_client.GuildMemberUpdated += ClientOnGuildMemberUpdated;
while (true) while (true)
{ {
var (conf, user, compl) = await _greetDmQueue.Reader.ReadAsync(); try
var res = await GreetDmUserInternal(conf, user); {
compl.TrySetResult(res); var (conf, user, ch) = await _greetQueue.Reader.ReadAsync();
await Task.Delay(2000); await GreetUsers(conf, ch, user);
}
catch (Exception ex)
{
Log.Error(ex, "Greet Loop almost crashed. Please report this!");
}
await Task.Delay(2016);
} }
} }
@@ -65,61 +100,38 @@ public class GreetService : INService, IReadyExecutor
&& newUser.PremiumSince is { } newDate && newUser.PremiumSince is { } newDate
&& newDate > oldDate)) && newDate > oldDate))
{ {
var conf = GetOrAddSettingsForGuild(newUser.Guild.Id); _ = Task.Run(async () =>
if (!conf.SendBoostMessage) {
return Task.CompletedTask; var conf = await GetGreetSettingsAsync(newUser.Guild.Id, GreetType.Boost);
_ = Task.Run(TriggerBoostMessage(conf, newUser)); if (conf is null || !conf.IsEnabled)
return;
ITextChannel? channel = null;
if (conf.ChannelId is { } cid)
channel = newUser.Guild.GetTextChannel(cid);
if (channel is null)
return;
await GreetUsers(conf, channel, newUser);
});
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
private Func<Task> TriggerBoostMessage(GreetSettings conf, SocketGuildUser user) private async Task OnClientLeftGuild(SocketGuild guild)
=> async () =>
{
var channel = user.Guild.GetTextChannel(conf.BoostMessageChannelId);
if (channel is null)
return;
await SendBoostMessage(conf, user, channel);
};
private async Task<bool> SendBoostMessage(GreetSettings conf, IGuildUser user, ITextChannel channel)
{ {
if (string.IsNullOrWhiteSpace(conf.BoostMessage)) foreach (var gt in Enum.GetValues<GreetType>())
return false;
var toSend = SmartText.CreateFrom(conf.BoostMessage);
try
{ {
var newContent = await _repSvc.ReplaceAsync(toSend, _enabled[gt].TryRemove(guild.Id);
new(client: _client, guild: user.Guild, channel: channel, users: user));
var toDelete = await _sender.Response(channel).Text(newContent).Sanitize(false).SendAsync();
if (conf.BoostMessageDeleteAfter > 0)
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error sending boost message");
} }
return false; await using var uow = _db.GetDbContext();
} await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId == guild.Id)
private Task OnClientLeftGuild(SocketGuild arg) .DeleteAsync();
{
_guildConfigsCache.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
private Task OnBotJoinedGuild(GuildConfig gc)
{
_guildConfigsCache[gc.GuildId] = GreetSettings.Create(gc);
return Task.CompletedTask;
} }
private Task OnUserLeft(SocketGuild guild, SocketUser user) private Task OnUserLeft(SocketGuild guild, SocketUser user)
@@ -128,35 +140,20 @@ public class GreetService : INService, IReadyExecutor
{ {
try try
{ {
var conf = GetOrAddSettingsForGuild(guild.Id); var conf = await GetGreetSettingsAsync(guild.Id, GreetType.Bye);
if (!conf.SendChannelByeMessage) if (conf is null)
return; return;
var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId);
var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ChannelId);
if (channel is null) //maybe warn the server owner that the channel is missing if (channel is null) //maybe warn the server owner that the channel is missing
return;
if (GroupGreets)
{ {
// if group is newly created, greet that user right away, await SetGreet(guild.Id, null, GreetType.Bye, false);
// but any user which joins in the next 5 seconds will return;
// be greeted in a group greet
if (_byes.CreateOrAdd(guild.Id, user))
{
// greet single user
await ByeUsers(conf, channel, new[] { user });
var groupClear = false;
while (!groupClear)
{
await Task.Delay(5000);
groupClear = _byes.ClearGroup(guild.Id, 5, out var toBye);
await ByeUsers(conf, channel, toBye);
}
}
} }
else
await ByeUsers(conf, channel, new[] { user }); await _greetQueue.Writer.WriteAsync((conf, user, channel));
} }
catch catch
{ {
@@ -166,98 +163,62 @@ public class GreetService : INService, IReadyExecutor
return Task.CompletedTask; return Task.CompletedTask;
} }
public string? GetDmGreetMsg(ulong id) private TypedKey<GreetSettings?> GreetSettingsKey(GreetType type)
=> new($"greet_settings:{type}");
public async Task<GreetSettings?> GetGreetSettingsAsync(ulong gid, GreetType type)
=> await _cache.GetOrAddAsync<GreetSettings?>(GreetSettingsKey(type),
() => InternalGetGreetSettingsAsync(gid, type),
TimeSpan.FromSeconds(3));
private async Task<GreetSettings?> InternalGetGreetSettingsAsync(ulong gid, GreetType type)
{ {
using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(id, set => set).DmGreetMessageText; var res = await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId == gid && x.GreetType == type)
.FirstOrDefaultAsync();
if (res is not null)
res.MessageText ??= GetDefaultGreet(type);
return res;
} }
public string? GetGreetMsg(ulong gid) private async Task GreetUsers(GreetSettings conf, ITextChannel? channel, IUser user)
{ {
using var uow = _db.GetDbContext(); if (conf.GreetType == GreetType.GreetDm)
return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText; {
} if (user is not IGuildUser gu)
return;
public string? GetBoostMessage(ulong gid) await GreetDmUserInternal(conf, gu);
{ return;
using var uow = _db.GetDbContext(); }
return uow.GuildConfigsForId(gid, set => set).BoostMessage;
}
public GreetSettings GetGreetSettings(ulong gid) if (channel is null)
{
if (_guildConfigsCache.TryGetValue(gid, out var gs))
return gs;
using var uow = _db.GetDbContext();
return GreetSettings.Create(uow.GuildConfigsForId(gid, set => set));
}
private Task ByeUsers(GreetSettings conf, ITextChannel channel, IUser user)
=> ByeUsers(conf, channel, new[] { user });
private async Task ByeUsers(GreetSettings conf, ITextChannel channel, IReadOnlyCollection<IUser> users)
{
if (!users.Any())
return; return;
var repCtx = new ReplacementContext(client: _client, var repCtx = new ReplacementContext(client: _client,
guild: channel.Guild, guild: channel.Guild,
channel: channel, channel: channel,
users: users.ToArray()); user: user);
var text = SmartText.CreateFrom(conf.ChannelByeMessageText); var text = SmartText.CreateFrom(conf.MessageText);
text = await _repSvc.ReplaceAsync(text, repCtx); text = await _repSvc.ReplaceAsync(text, repCtx);
try try
{ {
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync(); var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
if (conf.AutoDeleteByeMessagesTimer > 0) if (conf.AutoDeleteTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer); toDelete.DeleteAfter(conf.AutoDeleteTimer);
} }
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions catch (HttpException ex) when (ex.DiscordCode is DiscordErrorCode.InsufficientPermissions
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions or DiscordErrorCode.MissingPermissions
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel) or DiscordErrorCode.UnknownChannel)
{
Log.Warning(ex,
"Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}",
channel.GuildId);
await SetBye(channel.GuildId, channel.Id, false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error embeding bye message");
}
}
private Task GreetUsers(GreetSettings conf, ITextChannel channel, IGuildUser user)
=> GreetUsers(conf, channel, new[] { user });
private async Task GreetUsers(GreetSettings conf, ITextChannel channel, IReadOnlyCollection<IGuildUser> users)
{
if (users.Count == 0)
return;
var repCtx = new ReplacementContext(client: _client,
guild: channel.Guild,
channel: channel,
users: users.ToArray());
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
text = await _repSvc.ReplaceAsync(text, repCtx);
try
{
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
if (conf.AutoDeleteGreetMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
{ {
Log.Warning(ex, Log.Warning(ex,
"Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}",
channel.GuildId); channel.GuildId);
await SetGreet(channel.GuildId, channel.Id, false); await SetGreet(channel.GuildId, channel.Id, GreetType.Greet, false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -265,33 +226,12 @@ public class GreetService : INService, IReadyExecutor
} }
} }
private readonly Channel<(GreetSettings, IGuildUser, TaskCompletionSource<bool>)> _greetDmQueue =
Channel.CreateBounded<(GreetSettings, IGuildUser, TaskCompletionSource<bool>)>(new BoundedChannelOptions(60)
{
// The limit of 60 users should be only hit when there's a raid. In that case
// probably the best thing to do is to drop newest (raiding) users
FullMode = BoundedChannelFullMode.DropNewest
});
private async Task<bool> GreetDmUser(GreetSettings conf, IGuildUser user)
{
var completionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
await _greetDmQueue.Writer.WriteAsync((conf, user, completionSource));
return await completionSource.Task;
}
private async Task<bool> GreetDmUserInternal(GreetSettings conf, IGuildUser user) private async Task<bool> GreetDmUserInternal(GreetSettings conf, IGuildUser user)
{ {
try try
{ {
// var rep = new ReplacementBuilder() var repCtx = new ReplacementContext(client: _client, guild: user.Guild, user: user);
// .WithUser(user) var smartText = SmartText.CreateFrom(conf.MessageText);
// .WithServer(_client, (SocketGuild)user.Guild)
// .Build();
var repCtx = new ReplacementContext(client: _client, guild: user.Guild, users: user);
var smartText = SmartText.CreateFrom(conf.DmGreetMessageText);
smartText = await _repSvc.ReplaceAsync(smartText, repCtx); smartText = await _repSvc.ReplaceAsync(smartText, repCtx);
if (smartText is SmartPlainText pt) if (smartText is SmartPlainText pt)
@@ -351,8 +291,9 @@ public class GreetService : INService, IReadyExecutor
await _sender.Response(user).Text(smartText).Sanitize(false).SendAsync(); await _sender.Response(user).Text(smartText).Sanitize(false).SendAsync();
} }
catch catch(Exception ex)
{ {
Log.Error(ex, "Error sending greet dm");
return false; return false;
} }
@@ -372,297 +313,187 @@ public class GreetService : INService, IReadyExecutor
{ {
try try
{ {
var conf = GetOrAddSettingsForGuild(user.GuildId); if (_enabled[GreetType.Greet].Contains(user.GuildId))
if (conf.SendChannelGreetMessage)
{ {
var channel = await user.Guild.GetTextChannelAsync(conf.GreetMessageChannelId); var conf = await GetGreetSettingsAsync(user.GuildId, GreetType.Greet);
if (channel is not null) if (conf?.ChannelId is ulong cid)
{ {
if (GroupGreets) var channel = await user.Guild.GetTextChannelAsync(cid);
if (channel is not null)
{ {
// if group is newly created, greet that user right away, await _greetQueue.Writer.WriteAsync((conf, user, channel));
// but any user which joins in the next 5 seconds will
// be greeted in a group greet
if (_greets.CreateOrAdd(user.GuildId, user))
{
// greet single user
await GreetUsers(conf, channel, new[] { user });
var groupClear = false;
while (!groupClear)
{
await Task.Delay(5000);
groupClear = _greets.ClearGroup(user.GuildId, 5, out var toGreet);
await GreetUsers(conf, channel, toGreet);
}
}
} }
else
await GreetUsers(conf, channel, new[] { user });
} }
} }
if (conf.SendDmGreetMessage)
await GreetDmUser(conf, user); if (_enabled[GreetType.GreetDm].Contains(user.GuildId))
{
var confDm = await GetGreetSettingsAsync(user.GuildId, GreetType.GreetDm);
if (confDm is not null)
{
await _greetQueue.Writer.WriteAsync((confDm, user, null));
}
}
} }
catch catch(Exception ex)
{ {
// ignored Log.Error(ex, "Error in GreetService.OnUserJoined. This should not happen. Please report it");
} }
}); });
return Task.CompletedTask; return Task.CompletedTask;
} }
public string? GetByeMessage(ulong gid)
{
using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText;
}
public GreetSettings GetOrAddSettingsForGuild(ulong guildId) public static string GetDefaultGreet(GreetType greetType)
{ => greetType switch
if (_guildConfigsCache.TryGetValue(guildId, out var settings))
return settings;
using (var uow = _db.GetDbContext())
{ {
var gc = uow.GuildConfigsForId(guildId, set => set); GreetType.Boost => "%user.mention% has boosted the server!",
settings = GreetSettings.Create(gc); GreetType.Greet => "%user.mention% has joined the server!",
GreetType.Bye => "%user.name% has left the server!",
GreetType.GreetDm => "Welcome to the server %user.name%",
_ => "%user.name% did something new!"
};
public async Task<bool> SetGreet(
ulong guildId,
ulong? channelId,
GreetType greetType,
bool? value = null)
{
await using var uow = _db.GetDbContext();
var q = uow.GetTable<GreetSettings>();
if (value is null)
value = !_enabled[greetType].Contains(guildId);
if (value is { } v)
{
await q
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GreetType = greetType,
IsEnabled = v,
ChannelId = channelId,
},
(old) => new()
{
IsEnabled = v,
ChannelId = channelId,
},
() => new()
{
GuildId = guildId,
GreetType = greetType,
});
} }
_guildConfigsCache.TryAdd(guildId, settings); if (value is true)
return settings; {
_enabled[greetType].Add(guildId);
return true;
}
_enabled[greetType].TryRemove(guildId);
return false;
} }
public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
public async Task<bool> SetMessage(ulong guildId, GreetType greetType, string? message)
{ {
await using var uow = _db.GetDbContext(); await using (var uow = _db.GetDbContext())
var conf = uow.GuildConfigsForId(guildId, set => set); {
var enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; await uow.GetTable<GreetSettings>()
conf.GreetMessageChannelId = channelId; .InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GreetType = greetType,
MessageText = message
},
x => new()
{
MessageText = message
},
() => new()
{
GuildId = guildId,
GreetType = greetType
});
}
var toAdd = GreetSettings.Create(conf); var conf = await GetGreetSettingsAsync(guildId, greetType);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync(); return conf?.IsEnabled ?? false;
return enabled;
} }
public bool SetGreetMessage(ulong guildId, ref string message) public async Task<bool> SetDeleteTimer(ulong guildId, GreetType greetType, int timer)
{ {
message = message.SanitizeMentions(); if (timer < 0 || timer > 3600)
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message));
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.ChannelGreetMessageText = message;
var greetMsgEnabled = conf.SendChannelGreetMessage;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache.AddOrUpdate(guildId, toAdd, (_, _) => toAdd);
uow.SaveChanges();
return greetMsgEnabled;
}
public async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
{
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
var enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
return enabled;
}
public bool SetGreetDmMessage(ulong guildId, ref string? message)
{
message = message?.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message));
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.DmGreetMessageText = message;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
uow.SaveChanges();
return conf.SendDmGreetMessage;
}
public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
{
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
var enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
conf.ByeMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
return enabled;
}
public bool SetByeMessage(ulong guildId, ref string? message)
{
message = message?.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message));
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.ChannelByeMessageText = message;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
uow.SaveChanges();
return conf.SendChannelByeMessage;
}
public async Task SetByeDel(ulong guildId, int timer)
{
if (timer is < 0 or > 600)
return;
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.AutoDeleteByeMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
}
public async Task SetGreetDel(ulong guildId, int timer)
{
if (timer is < 0 or > 600)
return;
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.AutoDeleteGreetMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
}
public bool SetBoostMessage(ulong guildId, ref string message)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.BoostMessage = message;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
uow.SaveChanges();
return conf.SendBoostMessage;
}
public async Task SetBoostDel(ulong guildId, int timer)
{
if (timer is < 0 or > 600)
throw new ArgumentOutOfRangeException(nameof(timer)); throw new ArgumentOutOfRangeException(nameof(timer));
await using var uow = _db.GetDbContext(); await using (var uow = _db.GetDbContext())
var conf = uow.GuildConfigsForId(guildId, set => set); {
conf.BoostMessageDeleteAfter = timer; await uow.GetTable<GreetSettings>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GreetType = greetType,
AutoDeleteTimer = timer,
},
x => new()
{
AutoDeleteTimer = timer
},
() => new()
{
GuildId = guildId,
GreetType = greetType
});
}
var toAdd = GreetSettings.Create(conf); var conf = await GetGreetSettingsAsync(guildId, greetType);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync(); return conf?.IsEnabled ?? false;
} }
public async Task<bool> ToggleBoost(ulong guildId, ulong channelId, bool? forceState = null)
public async Task<bool> Test(
ulong guildId,
GreetType type,
IMessageChannel channel,
IGuildUser user)
{ {
await using var uow = _db.GetDbContext(); var conf = await GetGreetSettingsAsync(guildId, type);
var conf = uow.GuildConfigsForId(guildId, set => set); if (conf is null)
{
conf = new GreetSettings()
{
ChannelId = channel.Id,
GreetType = type,
IsEnabled = false,
GuildId = guildId,
AutoDeleteTimer = 30,
MessageText = GetDefaultGreet(type)
};
}
if (forceState is not bool fs) await SendMessage(conf, channel, user);
conf.SendBoostMessage = !conf.SendBoostMessage; return true;
else
conf.SendBoostMessage = fs;
conf.BoostMessageChannelId = channelId;
await uow.SaveChangesAsync();
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
return conf.SendBoostMessage;
} }
#region Get Enabled Status public async Task<bool> SendMessage(GreetSettings conf, IMessageChannel channel, IGuildUser user)
public bool GetGreetDmEnabled(ulong guildId)
{ {
using var uow = _db.GetDbContext(); if (conf.GreetType == GreetType.GreetDm)
var conf = uow.GuildConfigsForId(guildId, set => set); {
return conf.SendDmGreetMessage; await _greetQueue.Writer.WriteAsync((conf, user, null));
return true;
}
if (channel is not ITextChannel ch)
return false;
await GreetUsers(conf, ch, user);
return true;
} }
public bool GetGreetEnabled(ulong guildId)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendChannelGreetMessage;
}
public bool GetByeEnabled(ulong guildId)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendChannelByeMessage;
}
public bool GetBoostEnabled(ulong guildId)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendBoostMessage;
}
#endregion
#region Test Messages
public Task ByeTest(ITextChannel channel, IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return ByeUsers(conf, channel, user);
}
public Task GreetTest(ITextChannel channel, IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return GreetUsers(conf, channel, user);
}
public Task<bool> GreetDmTest(IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return GreetDmUser(conf, user);
}
public Task<bool> BoostTest(ITextChannel channel, IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return SendBoostMessage(conf, user, channel);
}
#endregion
} }

View File

@@ -1,45 +1,21 @@
using NadekoBot.Db.Models;
namespace NadekoBot.Services; namespace NadekoBot.Services;
public enum GreetType
{
Greet,
GreetDm,
Bye,
Boost,
}
public class GreetSettings public class GreetSettings
{ {
public int AutoDeleteGreetMessagesTimer { get; set; } public int Id { get; set; }
public int AutoDeleteByeMessagesTimer { get; set; }
public ulong GuildId { get; set; }
public ulong GreetMessageChannelId { get; set; } public GreetType GreetType { get; set; }
public ulong ByeMessageChannelId { get; set; } public string? MessageText { get; set; }
public bool IsEnabled { get; set; }
public bool SendDmGreetMessage { get; set; } public ulong? ChannelId { get; set; }
public string? DmGreetMessageText { get; set; } public int AutoDeleteTimer { get; set; }
public bool SendChannelGreetMessage { get; set; }
public string? ChannelGreetMessageText { get; set; }
public bool SendChannelByeMessage { get; set; }
public string? ChannelByeMessageText { get; set; }
public bool SendBoostMessage { get; set; }
public string? BoostMessage { get; set; }
public int BoostMessageDeleteAfter { get; set; }
public ulong BoostMessageChannelId { get; set; }
public static GreetSettings Create(GuildConfig g)
=> new()
{
AutoDeleteByeMessagesTimer = g.AutoDeleteByeMessagesTimer,
AutoDeleteGreetMessagesTimer = g.AutoDeleteGreetMessagesTimer,
GreetMessageChannelId = g.GreetMessageChannelId,
ByeMessageChannelId = g.ByeMessageChannelId,
SendDmGreetMessage = g.SendDmGreetMessage,
DmGreetMessageText = g.DmGreetMessageText,
SendChannelGreetMessage = g.SendChannelGreetMessage,
ChannelGreetMessageText = g.ChannelGreetMessageText,
SendChannelByeMessage = g.SendChannelByeMessage,
ChannelByeMessageText = g.ChannelByeMessageText,
SendBoostMessage = g.SendBoostMessage,
BoostMessage = g.BoostMessage,
BoostMessageDeleteAfter = g.BoostMessageDeleteAfter,
BoostMessageChannelId = g.BoostMessageChannelId
};
} }

View File

@@ -18,11 +18,17 @@ public partial class Administration
await Response().Confirm(strs.ropl_disabled).SendAsync(); await Response().Confirm(strs.ropl_disabled).SendAsync();
} }
[Cmd] [Cmd]
[OwnerOnly] [OwnerOnly]
public async Task AddPlaying(ActivityType t, [Leftover] string status) public Task AddPlaying([Leftover] string status)
=> AddPlaying(ActivityType.CustomStatus, status);
[Cmd]
[OwnerOnly]
public async Task AddPlaying(ActivityType statusType, [Leftover] string status)
{ {
await _service.AddPlaying(t, status); await _service.AddPlaying(statusType, status);
await Response().Confirm(strs.ropl_added).SendAsync(); await Response().Confirm(strs.ropl_added).SendAsync();
} }

View File

@@ -58,7 +58,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
: rotatingStatuses[index++]; : rotatingStatuses[index++];
var statusText = await _repService.ReplaceAsync(playingStatus.Status, new (client: _client)); var statusText = await _repService.ReplaceAsync(playingStatus.Status, new (client: _client));
await _selfService.SetGameAsync(statusText, (ActivityType)playingStatus.Type); await _selfService.SetActivityAsync(statusText, (ActivityType)playingStatus.Type);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -459,7 +459,7 @@ public partial class Administration
await Response().Confirm(strs.bot_name(Format.Bold(newName))).SendAsync(); await Response().Confirm(strs.bot_name(Format.Bold(newName))).SendAsync();
} }
[Cmd] [Cmd]
[OwnerOnly] [OwnerOnly]
public async Task SetStatus([Leftover] SettableUserStatus status) public async Task SetStatus([Leftover] SettableUserStatus status)
@@ -491,14 +491,25 @@ public partial class Administration
[Cmd] [Cmd]
[OwnerOnly] [OwnerOnly]
public async Task SetGame(ActivityType type, [Leftover] string game = null) public async Task SetActivity(ActivityType? type, [Leftover] string game = null)
{ {
// var rep = new ReplacementBuilder().WithDefault(Context).Build(); // var rep = new ReplacementBuilder().WithDefault(Context).Build();
var repCtx = new ReplacementContext(ctx); var repCtx = new ReplacementContext(ctx);
await _service.SetGameAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type); await _service.SetActivityAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
await Response().Confirm(strs.set_game).SendAsync(); await Response().Confirm(strs.set_activity).SendAsync();
}
[Cmd]
[OwnerOnly]
public Task SetActivity([Leftover] string game = null)
=> SetActivity(null, game);
public class SetActivityOptions
{
public ActivityType? Type { get; set; }
public string Game { get; set; }
} }
[Cmd] [Cmd]
@@ -541,11 +552,11 @@ public partial class Administration
return; return;
} }
var repCtx = new ReplacementContext(ctx); var repCtx = new ReplacementContext(ctx);
text = await repSvc.ReplaceAsync(text, repCtx); text = await repSvc.ReplaceAsync(text, repCtx);
await Response().Channel(ch).Text(text).SendAsync(); await Response().Channel(ch).Text(text).SendAsync();
await ctx.OkAsync(); await ctx.OkAsync();
} }

View File

@@ -85,13 +85,14 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
autoCommands = uow.Set<AutoCommand>().AsNoTracking() autoCommands = uow.Set<AutoCommand>()
.Where(x => x.Interval >= 5) .AsNoTracking()
.AsEnumerable() .Where(x => x.Interval >= 5)
.GroupBy(x => x.GuildId) .AsEnumerable()
.ToDictionary(x => x.Key, .GroupBy(x => x.GuildId)
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent()) .ToDictionary(x => x.Key,
.ToConcurrent(); y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent();
var startupCommands = uow.Set<AutoCommand>().AsNoTracking().Where(x => x.Interval == 0); var startupCommands = uow.Set<AutoCommand>().AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands) foreach (var cmd in startupCommands)
@@ -170,18 +171,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
private async Task LoadOwnerChannels() private async Task LoadOwnerChannels()
{ {
var channels = await _creds.OwnerIds.Select(id => var channels = await _creds.OwnerIds.Select(id =>
{ {
var user = _client.GetUser(id); var user = _client.GetUser(id);
if (user is null) if (user is null)
return Task.FromResult<IDMChannel>(null); return Task.FromResult<IDMChannel>(null);
return user.CreateDMChannelAsync(); return user.CreateDMChannelAsync();
}) })
.WhenAll(); .WhenAll();
ownerChannels = channels.Where(x => x is not null) ownerChannels = channels.Where(x => x is not null)
.ToDictionary(x => x.Recipient.Id, x => x) .ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary(); .ToImmutableDictionary();
if (!ownerChannels.Any()) if (!ownerChannels.Any())
{ {
@@ -400,7 +401,10 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
{ {
try try
{ {
await _client.SetGameAsync(data.Name, data.Link, data.Type); if (data.Type is { } activityType)
await _client.SetGameAsync(data.Name, data.Link, activityType);
else
await _client.SetCustomStatusAsync(data.Name);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -408,7 +412,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
} }
}); });
public Task SetGameAsync(string game, ActivityType type) public Task SetActivityAsync(string game, ActivityType? type)
=> _pubSub.Pub(_activitySetKey, => _pubSub.Pub(_activitySetKey,
new() new()
{ {
@@ -430,10 +434,10 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
{ {
public string Name { get; init; } public string Name { get; init; }
public string Link { get; init; } public string Link { get; init; }
public ActivityType Type { get; init; } public ActivityType? Type { get; init; }
} }
/// <summary> /// <summary>
/// Adds the specified <paramref name="users"/> to the database. If a database user with placeholder name /// Adds the specified <paramref name="users"/> to the database. If a database user with placeholder name
/// and discriminator is present in <paramref name="users"/>, their name and discriminator get updated accordingly. /// and discriminator is present in <paramref name="users"/>, their name and discriminator get updated accordingly.
@@ -443,41 +447,46 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
/// <returns>A tuple with the amount of new users added and old users updated.</returns> /// <returns>A tuple with the amount of new users added and old users updated.</returns>
public async Task<(long UsersAdded, long UsersUpdated)> RefreshUsersAsync(List<IUser> users) public async Task<(long UsersAdded, long UsersUpdated)> RefreshUsersAsync(List<IUser> users)
{ {
await using var ctx = _db.GetDbContext(); await using var ctx = _db.GetDbContext();
var presentDbUsers = await ctx.GetTable<DiscordUser>() var presentDbUsers = await ctx.GetTable<DiscordUser>()
.Select(x => new { x.UserId, x.Username, x.Discriminator }) .Select(x => new
.Where(x => users.Select(y => y.Id).Contains(x.UserId)) {
.ToArrayAsyncEF(); x.UserId,
x.Username,
x.Discriminator
})
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
.ToArrayAsyncEF();
var usersToAdd = users var usersToAdd = users
.Where(x => !presentDbUsers.Select(x => x.UserId).Contains(x.Id)) .Where(x => !presentDbUsers.Select(x => x.UserId).Contains(x.Id))
.Select(x => new DiscordUser() .Select(x => new DiscordUser()
{ {
UserId = x.Id, UserId = x.Id,
AvatarId = x.AvatarId, AvatarId = x.AvatarId,
Username = x.Username, Username = x.Username,
Discriminator = x.Discriminator Discriminator = x.Discriminator
}); });
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied; var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
var toUpdateUserIds = presentDbUsers var toUpdateUserIds = presentDbUsers
.Where(x => x.Username == "Unknown" && x.Discriminator == "????") .Where(x => x.Username == "Unknown" && x.Discriminator == "????")
.Select(x => x.UserId) .Select(x => x.UserId)
.ToArray(); .ToArray();
foreach (var user in users.Where(x => toUpdateUserIds.Contains(x.Id))) foreach (var user in users.Where(x => toUpdateUserIds.Contains(x.Id)))
{ {
await ctx.GetTable<DiscordUser>() await ctx.GetTable<DiscordUser>()
.Where(x => x.UserId == user.Id) .Where(x => x.UserId == user.Id)
.UpdateAsync(x => new DiscordUser() .UpdateAsync(x => new DiscordUser()
{ {
Username = user.Username, Username = user.Username,
Discriminator = user.Discriminator, Discriminator = user.Discriminator,
// .award tends to set AvatarId and DateAdded to NULL, so account for that. // .award tends to set AvatarId and DateAdded to NULL, so account for that.
AvatarId = user.AvatarId, AvatarId = user.AvatarId,
DateAdded = x.DateAdded ?? DateTime.UtcNow DateAdded = x.DateAdded ?? DateTime.UtcNow
}); });
} }
return (added, toUpdateUserIds.Length); return (added, toUpdateUserIds.Length);

View File

@@ -243,43 +243,54 @@ public class UserPunishService : INService, IReadyExecutor
public async Task CheckAllWarnExpiresAsync() public async Task CheckAllWarnExpiresAsync()
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var cleared = await uow.Set<Warning>()
.Where(x => uow.Set<GuildConfig>() var toClear = await uow.GetTable<Warning>()
.Any(y => y.GuildId == x.GuildId .Where(x => uow.GetTable<GuildConfig>()
&& y.WarnExpireHours > 0 .Count(y => y.GuildId == x.GuildId
&& y.WarnExpireAction == WarnExpireAction.Clear) && y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Clear)
> 0
&& x.Forgiven == false && x.Forgiven == false
&& x.DateAdded && x.DateAdded
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>() < DateTime.UtcNow.AddHours(-uow.GetTable<GuildConfig>()
.Where(y => x.GuildId == y.GuildId) .Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours) .Select(y => y.WarnExpireHours)
.First())) .First()))
.UpdateAsync(_ => new() .Select(x => x.Id)
{ .ToListAsyncLinqToDB();
Forgiven = true,
ForgivenBy = "expiry"
});
var deleted = await uow.Set<Warning>() var cleared = await uow.GetTable<Warning>()
.Where(x => uow.Set<GuildConfig>() .Where(x => toClear.Contains(x.Id))
.Any(y => y.GuildId == x.GuildId .UpdateAsync(_ => new()
&& y.WarnExpireHours > 0 {
&& y.WarnExpireAction == WarnExpireAction.Delete) Forgiven = true,
&& x.DateAdded ForgivenBy = "expiry"
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>() });
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours) var toDelete = await uow.GetTable<Warning>()
.First())) .Where(x => uow.GetTable<GuildConfig>()
.DeleteAsync(); .Count(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Delete)
> 0
&& x.DateAdded
< DateTime.UtcNow.AddHours(-uow.GetTable<GuildConfig>()
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours)
.First()))
.Select(x => x.Id)
.ToListAsyncLinqToDB();
var deleted = await uow.GetTable<Warning>()
.Where(x => toDelete.Contains(x.Id))
.DeleteAsync();
if (cleared > 0 || deleted > 0) if (cleared > 0 || deleted > 0)
{ {
Log.Information("Cleared {ClearedWarnings} warnings and deleted {DeletedWarnings} warnings due to expiry", Log.Information("Cleared {ClearedWarnings} warnings and deleted {DeletedWarnings} warnings due to expiry",
cleared, cleared,
deleted); toDelete.Count);
} }
await uow.SaveChangesAsync();
} }
public async Task CheckWarnExpiresAsync(ulong guildId) public async Task CheckWarnExpiresAsync(ulong guildId)

View File

@@ -36,7 +36,7 @@ public static class NadekoExpressionExtensions
var repCtx = new ReplacementContext(client: client, var repCtx = new ReplacementContext(client: client,
guild: (ctx.Channel as ITextChannel)?.Guild as SocketGuild, guild: (ctx.Channel as ITextChannel)?.Guild as SocketGuild,
channel: ctx.Channel, channel: ctx.Channel,
users: ctx.Author user: ctx.Author
) )
.WithOverride("%target%", .WithOverride("%target%",
() => canMentionEveryone () => canMentionEveryone

View File

@@ -58,14 +58,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
[Cmd] [Cmd]
[UserPerm(GuildPerm.Administrator)] [UserPerm(GuildPerm.Administrator)]
public async Task ExprAddServer(string key, [Leftover] string message) public async Task ExprAddServer(string trigger, [Leftover] string response)
{ {
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key)) if (string.IsNullOrWhiteSpace(response) || string.IsNullOrWhiteSpace(trigger))
{ {
return; return;
} }
await ExprAddInternalAsync(key, message); await ExprAddInternalAsync(trigger, response);
} }
@@ -402,10 +402,9 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
} }
[Cmd] [Cmd]
[Ratelimit(300)]
public async Task ExprsImport([Leftover] string input = null) public async Task ExprsImport([Leftover] string input = null)
{ {
// todo cooldown on public bot for 1 day, limit 100
if (!AdminInGuildOrOwnerInDm()) if (!AdminInGuildOrOwnerInDm())
{ {
await Response().Error(strs.expr_insuff_perms).SendAsync(); await Response().Error(strs.expr_insuff_perms).SendAsync();

View File

@@ -27,17 +27,13 @@ public class GameStatusEvent : ICurrencyEvent
private readonly string _code; private readonly string _code;
private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
private readonly object _stopLock = new(); private readonly object _stopLock = new();
private readonly object _potLock = new(); private readonly object _potLock = new();
private readonly IMessageSenderService _sender; private readonly IMessageSenderService _sender;
private static readonly NadekoRandom _rng = new NadekoRandom();
public GameStatusEvent( public GameStatusEvent(
DiscordSocketClient client, DiscordSocketClient client,
ICurrencyService cs, ICurrencyService cs,
@@ -58,7 +54,8 @@ public class GameStatusEvent : ICurrencyEvent
_opts = opt; _opts = opt;
_sender = sender; _sender = sender;
// generate code // generate code
_code = new(_sneakyGameStatusChars.Shuffle().Take(5).ToArray());
_code = new kwum(_rng.Next(1_000_000, 10_000_000)).ToString();
_t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2)); _t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0) if (_opts.Hours > 0)
@@ -88,9 +85,9 @@ public class GameStatusEvent : ICurrencyEvent
if (_isPotLimited) if (_isPotLimited)
{ {
await msg.ModifyAsync(m => await msg.ModifyAsync(m =>
{ {
m.Embed = GetEmbed(PotSize).Build(); m.Embed = GetEmbed(PotSize).Build();
}); });
} }
Log.Information("Game status event awarded {Count} users {Amount} currency.{Remaining}", Log.Information("Game status event awarded {Count} users {Amount} currency.{Remaining}",

View File

@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Gambling.Common;
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; } = 8;
[Comment("""Currency settings""")] [Comment("""Currency settings""")]
public CurrencyConfig Currency { get; set; } public CurrencyConfig Currency { get; set; }
@@ -20,9 +20,9 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
public int MinBet { get; set; } = 0; public int MinBet { get; set; } = 0;
[Comment(""" [Comment("""
Maximum amount users can bet Maximum amount users can bet
Set 0 for unlimited 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""")]
@@ -35,14 +35,14 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
public GenerationConfig Generation { get; set; } public GenerationConfig Generation { get; set; }
[Comment(""" [Comment("""
Settings for timely command Settings for timely command
(letting people claim X amount of currency every Y hours) (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("""What is the bot's cut on some transactions""")] [Comment("""What is the bot's cut on some transactions""")]
public BotCutConfig BotCuts { get; set; } public BotCutConfig BotCuts { get; set; }
@@ -53,15 +53,15 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
public WaifuConfig Waifu { get; set; } public WaifuConfig Waifu { get; set; }
[Comment(""" [Comment("""
Amount of currency selfhosters will get PER pledged dollar CENT. Amount of currency selfhosters will get PER pledged dollar CENT.
1 = 100 currency per $. Used almost exclusively on public nadeko. 1 = 100 currency per $. Used almost exclusively on public nadeko.
""")] """)]
public decimal PatreonCurrencyPerCent { get; set; } = 1; public decimal PatreonCurrencyPerCent { get; set; } = 1;
[Comment(""" [Comment("""
Currency reward per vote. Currency reward per vote.
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting 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""")]
@@ -91,9 +91,9 @@ public class CurrencyConfig
public string Name { get; set; } = "Nadeko Flower"; public string Name { get; set; } = "Nadeko Flower";
[Comment(""" [Comment("""
For how long (in days) will the transactions be kept in the database (curtrs) For how long (in days) will the transactions be kept in the database (curtrs)
Set 0 to disable cleanup (keep transactions forever) Set 0 to disable cleanup (keep transactions forever)
""")] """)]
public int TransactionsLifetime { get; set; } = 0; public int TransactionsLifetime { get; set; } = 0;
} }
@@ -101,15 +101,15 @@ public class CurrencyConfig
public partial class TimelyConfig public partial class TimelyConfig
{ {
[Comment(""" [Comment("""
How much currency will the users get every time they run .timely command How much currency will the users get every time they run .timely command
setting to 0 or less will disable this feature setting to 0 or less will disable this feature
""")] """)]
public int Amount { get; set; } = 0; public int Amount { get; set; } = 0;
[Comment(""" [Comment("""
How often (in hours) can users claim currency with .timely command How often (in hours) can users claim currency with .timely command
setting to 0 or less will disable this feature setting to 0 or less will disable this feature
""")] """)]
public int Cooldown { get; set; } = 24; public int Cooldown { get; set; } = 24;
} }
@@ -124,10 +124,10 @@ public partial class BetFlipConfig
public partial class BetRollConfig public partial class BetRollConfig
{ {
[Comment(""" [Comment("""
When betroll is played, user will roll a number 0-100. 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. This setting will describe which multiplier is used for when the roll is higher than the given number.
Doesn't have to be ordered. 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()
@@ -155,17 +155,17 @@ public partial class BetRollConfig
public partial class GenerationConfig public partial class GenerationConfig
{ {
[Comment(""" [Comment("""
when currency is generated, should it also have a random password when currency is generated, should it also have a random password
associated with it which users have to type after the .pick command associated with it which users have to type after the .pick command
in order to get it in order to get it
""")] """)]
public bool HasPassword { get; set; } = true; public bool HasPassword { get; set; } = true;
[Comment(""" [Comment("""
Every message sent has a certain % chance to generate the currency Every message sent has a certain % chance to generate the currency
specify the percentage here (1 being 100%, 0 being 0% - for example specify the percentage here (1 being 100%, 0 being 0% - for example
default is 0.02, which is 2% 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""")]
@@ -175,9 +175,9 @@ public partial class GenerationConfig
public int MinAmount { get; set; } = 1; public int MinAmount { get; set; } = 1;
[Comment(""" [Comment("""
Maximum amount of currency that can spawn. Maximum amount of currency that can spawn.
Set to the same value as MinAmount to always spawn the same amount 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;
} }
@@ -185,9 +185,9 @@ public partial class GenerationConfig
public partial class DecayConfig public partial class DecayConfig
{ {
[Comment(""" [Comment("""
Percentage of user's current currency which will be deducted every 24h. Percentage of user's current currency which will be deducted every 24h.
0 - 1 (1 is 100%, 0.5 50%, 0 disabled) 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.""")]
@@ -219,15 +219,15 @@ public sealed partial class WaifuConfig
public MultipliersData Multipliers { get; set; } = new(); public MultipliersData Multipliers { get; set; } = new();
[Comment(""" [Comment("""
Settings for periodic waifu price decay. Settings for periodic waifu price decay.
Waifu price decays only if the waifu has no claimer. Waifu price decays only if the waifu has no claimer.
""")] """)]
public WaifuDecayConfig Decay { get; set; } = new(); public WaifuDecayConfig Decay { get; set; } = new();
[Comment(""" [Comment("""
List of items available for gifting. List of items available for gifting.
If negative is true, gift will instead reduce waifu value. If negative is true, gift will instead reduce waifu value.
""")] """)]
public List<WaifuItemModel> Items { get; set; } = []; public List<WaifuItemModel> Items { get; set; } = [];
public WaifuConfig() public WaifuConfig()
@@ -274,19 +274,26 @@ public sealed partial class WaifuConfig
public class WaifuDecayConfig public class WaifuDecayConfig
{ {
[Comment(""" [Comment("""
Percentage (0 - 100) of the waifu value to reduce. Unclaimed waifus will decay by this percentage (0 - 100).
Set 0 to disable Default is 0 (disabled)
For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$) 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 UnclaimedDecayPercent { get; set; } = 0;
[Comment("""
Claimed waifus will decay by this percentage (0 - 100).
Default is 0 (disabled)
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 ClaimedDecayPercent { 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(""" [Comment("""
Minimum waifu price required for the decay to be applied. 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. 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;
} }
} }
@@ -295,54 +302,54 @@ public sealed partial class WaifuConfig
public sealed partial class MultipliersData public sealed partial class MultipliersData
{ {
[Comment(""" [Comment("""
Multiplier for waifureset. Default 150. Multiplier for waifureset. Default 150.
Formula (at the time of writing this): Formula (at the time of writing this):
price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up 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(""" [Comment("""
The minimum amount of currency that you have to pay The minimum amount of currency that you have to pay
in order to buy a waifu who doesn't have a crush on you. in order to buy a waifu who doesn't have a crush on you.
Default is 1.1 Default is 1.1
Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her. Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
(100 * 1.1 = 110) (100 * 1.1 = 110)
""")] """)]
public decimal NormalClaim { get; set; } = 1.1m; public decimal NormalClaim { get; set; } = 1.1m;
[Comment(""" [Comment("""
The minimum amount of currency that you have to pay The minimum amount of currency that you have to pay
in order to buy a waifu that has a crush on you. in order to buy a waifu that has a crush on you.
Default is 0.88 Default is 0.88
Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her. Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her.
(100 * 0.88 = 88) (100 * 0.88 = 88)
""")] """)]
public decimal CrushClaim { get; set; } = 0.88M; public decimal CrushClaim { get; set; } = 0.88M;
[Comment(""" [Comment("""
When divorcing a waifu, her new value will be her current value multiplied by this number. 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) 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(""" [Comment("""
All gift prices will be multiplied by this number. All gift prices will be multiplied by this number.
Default 1 (meaning no effect) Default 1 (meaning no effect)
""")] """)]
public decimal AllGiftPrices { get; set; } = 1.0M; public decimal AllGiftPrices { get; set; } = 1.0M;
[Comment(""" [Comment("""
What percentage of the value of the gift will a waifu gain when she's gifted. What percentage of the value of the gift will a waifu gain when she's gifted.
Default 0.95 (meaning 95%) 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) 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(""" [Comment("""
What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'. 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%) 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) 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;
} }

View File

@@ -174,7 +174,7 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
c.Version = 5; c.Version = 5;
}); });
} }
if (data.Version < 6) if (data.Version < 6)
{ {
ModifyConfig(c => ModifyConfig(c =>
@@ -190,5 +190,14 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
c.Version = 7; c.Version = 7;
}); });
} }
if (data.Version < 8)
{
ModifyConfig(c =>
{
c.Version = 8;
c.Waifu.Decay.UnclaimedDecayPercent = 0;
});
}
} }
} }

View File

@@ -48,7 +48,7 @@ public class PlantPickService : INService, IExecNoCommand
_rng = new(); _rng = new();
_client = client; _client = client;
_gss = gss; _gss = gss;
using var uow = db.GetDbContext(); using var uow = db.GetDbContext();
var guildIds = client.Guilds.Select(x => x.Id).ToList(); var guildIds = client.Guilds.Select(x => x.Id).ToList();
var configs = uow.Set<GuildConfig>() var configs = uow.Set<GuildConfig>()
@@ -111,13 +111,17 @@ public class PlantPickService : INService, IExecNoCommand
{ {
var curImg = await _images.GetCurrencyImageAsync(); var curImg = await _images.GetCurrencyImageAsync();
if (curImg is null)
return (new MemoryStream(), null);
if (string.IsNullOrWhiteSpace(pass)) if (string.IsNullOrWhiteSpace(pass))
{ {
// determine the extension // determine the extension
using var load = _ = Image.Load(curImg, out var format); using var load = Image.Load(curImg);
var format = load.Metadata.DecodedImageFormat;
// return the image // return the image
return (curImg.ToStream(), format.FileExtensions.FirstOrDefault() ?? "png"); return (curImg.ToStream(), format?.FileExtensions.FirstOrDefault() ?? "png");
} }
// get the image stream and extension // get the image stream and extension
@@ -134,16 +138,17 @@ public class PlantPickService : INService, IExecNoCommand
{ {
// draw lower, it looks better // draw lower, it looks better
pass = pass.TrimTo(10, true).ToLowerInvariant(); pass = pass.TrimTo(10, true).ToLowerInvariant();
using var img = Image.Load<Rgba32>(curImg, out var format); using var img = Image.Load<Rgba32>(curImg);
// choose font size based on the image height, so that it's visible // choose font size based on the image height, so that it's visible
var font = _fonts.NotoSans.CreateFont(img.Height / 12.0f, FontStyle.Bold); var font = _fonts.NotoSans.CreateFont(img.Height / 12.0f, FontStyle.Bold);
img.Mutate(x => img.Mutate(x =>
{ {
// measure the size of the text to be drawing // measure the size of the text to be drawing
var size = TextMeasurer.Measure(pass, new TextOptions(font) var size = TextMeasurer.MeasureSize(pass,
{ new RichTextOptions(font)
Origin = new PointF(0, 0) {
}); Origin = new PointF(0, 0)
});
// fill the background with black, add 5 pixels on each side to make it look better // fill the background with black, add 5 pixels on each side to make it look better
x.FillPolygon(Color.ParseHex("00000080"), x.FillPolygon(Color.ParseHex("00000080"),
@@ -156,7 +161,8 @@ public class PlantPickService : INService, IExecNoCommand
x.DrawText(pass, font, Color.White, new(0, 0)); x.DrawText(pass, font, Color.White, new(0, 0));
}); });
// return image as a stream for easy sending // return image as a stream for easy sending
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png"); var format = img.Metadata.DecodedImageFormat;
return (img.ToStream(format), format?.FileExtensions.FirstOrDefault() ?? "png");
} }
private Task PotentialFlowerGeneration(IUserMessage imsg) private Task PotentialFlowerGeneration(IUserMessage imsg)
@@ -256,7 +262,8 @@ public class PlantPickService : INService, IExecNoCommand
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant(); pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
// gets all plants in this channel with the same password // gets all plants in this channel with the same password
var entries = uow.Set<PlantedCurrency>().AsQueryable() var entries = uow.Set<PlantedCurrency>()
.AsQueryable()
.Where(x => x.ChannelId == ch.Id && pass == x.Password) .Where(x => x.ChannelId == ch.Id && pass == x.Password)
.ToList(); .ToList();
// sum how much currency that is, and get all of the message ids (so that i can delete them) // sum how much currency that is, and get all of the message ids (so that i can delete them)
@@ -368,15 +375,16 @@ public class PlantPickService : INService, IExecNoCommand
string pass) string pass)
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
uow.Set<PlantedCurrency>().Add(new() uow.Set<PlantedCurrency>()
{ .Add(new()
Amount = amount, {
GuildId = gid, Amount = amount,
ChannelId = cid, GuildId = gid,
Password = pass, ChannelId = cid,
UserId = uid, Password = pass,
MessageId = mid UserId = uid,
}); MessageId = mid
});
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
} }
} }

View File

@@ -167,18 +167,19 @@ public partial class Gambling
long ownedAmount; long ownedAmount;
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
ownedAmount = uow.Set<DiscordUser>().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount ownedAmount = uow.Set<DiscordUser>()
.FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
?? 0; ?? 0;
} }
var slotBg = await _images.GetSlotBgAsync(); var slotBg = await _images.GetSlotBgAsync();
var bgImage = Image.Load<Rgba32>(slotBg, out _); var bgImage = Image.Load<Rgba32>(slotBg);
var numbers = new int[3]; var numbers = new int[3];
result.Rolls.CopyTo(numbers, 0); result.Rolls.CopyTo(numbers, 0);
Color fontColor = Config.Slots.CurrencyFontColor; Color fontColor = Config.Slots.CurrencyFontColor;
bgImage.Mutate(x => x.DrawText(new TextOptions(_fonts.DottyFont.CreateFont(65)) bgImage.Mutate<Rgba32>(x => x.DrawText(new RichTextOptions(_fonts.DottyFont.CreateFont(65))
{ {
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
@@ -190,7 +191,7 @@ public partial class Gambling
var bottomFont = _fonts.DottyFont.CreateFont(50); var bottomFont = _fonts.DottyFont.CreateFont(50);
bgImage.Mutate(x => x.DrawText(new TextOptions(bottomFont) bgImage.Mutate(x => x.DrawText(new RichTextOptions(bottomFont)
{ {
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,

View File

@@ -70,7 +70,11 @@ public partial class Gambling
return; return;
} }
var msg = GetText(strs.waifu_claimed(Format.Bold(target.ToString()), N(amount))); var msg = GetText(strs.waifu_claimed(
Format.Bold(ctx.User.ToString()),
Format.Bold(target.ToString()),
N(amount)));
if (w.Affinity?.UserId == ctx.User.Id) if (w.Affinity?.UserId == ctx.User.Id)
msg += "\n" + GetText(strs.waifu_fulfilled(target, N(w.Price))); msg += "\n" + GetText(strs.waifu_fulfilled(target, N(w.Price)));
else else
@@ -191,13 +195,21 @@ public partial class Gambling
} }
if (user is null) if (user is null)
{
await Response().Confirm(strs.waifu_affinity_reset).SendAsync(); await Response().Confirm(strs.waifu_affinity_reset).SendAsync();
}
else if (oldAff is null) else if (oldAff is null)
await Response().Confirm(strs.waifu_affinity_set(Format.Bold(user.ToString()))).SendAsync(); {
await Response()
.Confirm(strs.waifu_affinity_set(Format.Bold(ctx.User.ToString()), Format.Bold(user.ToString())))
.SendAsync();
}
else else
{ {
await Response() await Response()
.Confirm(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()), .Confirm(strs.waifu_affinity_changed(
Format.Bold(ctx.User.ToString()),
Format.Bold(oldAff.ToString()),
Format.Bold(user.ToString()))) Format.Bold(user.ToString())))
.SendAsync(); .SendAsync();
} }

View File

@@ -531,11 +531,18 @@ public class WaifuService : INService, IReadyExecutor
{ {
try try
{ {
var multi = _gss.Data.Waifu.Decay.Percent / 100f; var decay = _gss.Data.Waifu.Decay;
var minPrice = _gss.Data.Waifu.Decay.MinPrice;
var decayInterval = _gss.Data.Waifu.Decay.HourInterval;
if (multi is < 0f or > 1f || decayInterval < 0) var unclaimedMulti = 1 - (decay.UnclaimedDecayPercent / 100f);
var claimedMulti = 1 - (decay.ClaimedDecayPercent / 100f);
var minPrice = decay.MinPrice;
var decayInterval = decay.HourInterval;
if (decayInterval <= 0)
continue;
if ((unclaimedMulti < 0 || unclaimedMulti > 1) && (claimedMulti < 0 || claimedMulti > 1))
continue; continue;
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@@ -554,14 +561,28 @@ public class WaifuService : INService, IReadyExecutor
await _cache.AddAsync(_waifuDecayKey, nowB); await _cache.AddAsync(_waifuDecayKey, nowB);
await using var uow = _db.GetDbContext(); if (unclaimedMulti is > 0 and <= 1)
{
await using var uow = _db.GetDbContext();
await uow.GetTable<WaifuInfo>() await uow.GetTable<WaifuInfo>()
.Where(x => x.Price > minPrice && x.ClaimerId == null) .Where(x => x.Price > minPrice && x.ClaimerId == null)
.UpdateAsync(old => new() .UpdateAsync(old => new()
{ {
Price = (long)(old.Price * multi) Price = (long)(old.Price * unclaimedMulti)
}); });
}
if (claimedMulti is > 0 and <= 1)
{
await using var uow = _db.GetDbContext();
await uow.GetTable<WaifuInfo>()
.Where(x => x.Price > minPrice && x.ClaimerId != null)
.UpdateAsync(old => new()
{
Price = (long)(old.Price * claimedMulti)
});
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -63,6 +63,8 @@ public static class WaifuExtensions
public static async Task<WaifuInfoStats> GetWaifuInfoAsync(this DbContext ctx, ulong userId) public static async Task<WaifuInfoStats> GetWaifuInfoAsync(this DbContext ctx, ulong userId)
{ {
await ctx.EnsureUserCreatedAsync(userId);
await ctx.Set<WaifuInfo>() await ctx.Set<WaifuInfo>()
.ToLinqToDBTable() .ToLinqToDBTable()
.InsertOrUpdateAsync(() => new() .InsertOrUpdateAsync(() => new()
@@ -78,7 +80,8 @@ public static class WaifuExtensions
WaifuId = ctx.Set<DiscordUser>().Where(x => x.UserId == userId).Select(x => x.Id).First() WaifuId = ctx.Set<DiscordUser>().Where(x => x.UserId == userId).Select(x => x.Id).First()
}); });
var toReturn = ctx.Set<WaifuInfo>().AsQueryable() var toReturn = ctx.Set<WaifuInfo>()
.AsQueryable()
.Where(w => w.WaifuId .Where(w => w.WaifuId
== ctx.Set<DiscordUser>() == ctx.Set<DiscordUser>()
.AsQueryable() .AsQueryable()

View File

@@ -175,8 +175,6 @@ public sealed partial class Help : NadekoModule<HelpService>
return strs.module_description_gambling; return strs.module_description_gambling;
case "music": case "music":
return strs.module_description_music; return strs.module_description_music;
case "nsfw":
return strs.module_description_nsfw;
case "permissions": case "permissions":
return strs.module_description_permissions; return strs.module_description_permissions;
case "xp": case "xp":
@@ -211,8 +209,6 @@ public sealed partial class Help : NadekoModule<HelpService>
return "💰"; return "💰";
case "music": case "music":
return "🎶"; return "🎶";
case "nsfw":
return "😳";
case "permissions": case "permissions":
return "🚓"; return "🚓";
case "xp": case "xp":

View File

@@ -31,7 +31,7 @@ public class HelpService : IExecNoCommand, INService
return; return;
} }
var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, users: msg.Author) var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, user: msg.Author)
.WithOverride("%prefix%", () => _bss.Data.Prefix) .WithOverride("%prefix%", () => _bss.Data.Prefix)
.WithOverride("%bot.prefix%", () => _bss.Data.Prefix); .WithOverride("%bot.prefix%", () => _bss.Data.Prefix);

View File

@@ -136,7 +136,7 @@ public sealed partial class Music
Provider = s.Platform.ToString(), Provider = s.Platform.ToString(),
ProviderType = (MusicType)s.Platform, ProviderType = (MusicType)s.Platform,
Title = s.Title, Title = s.Title,
Query = s.Platform == MusicPlatform.Local ? s.GetStreamUrl().Result!.Trim('"') : s.Url Query = s.Url
}) })
.ToList(); .ToList();

View File

@@ -1,4 +1,5 @@
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
using NadekoBot.Modules.Music.Resolvers;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace NadekoBot.Modules.Music.Services; namespace NadekoBot.Modules.Music.Services;
@@ -8,7 +9,7 @@ public sealed class MusicService : IMusicService, IPlaceholderProvider
private readonly AyuVoiceStateService _voiceStateService; private readonly AyuVoiceStateService _voiceStateService;
private readonly ITrackResolveProvider _trackResolveProvider; private readonly ITrackResolveProvider _trackResolveProvider;
private readonly DbService _db; private readonly DbService _db;
private readonly IYoutubeResolver _ytResolver; private readonly IYoutubeResolverFactory _ytResolver;
private readonly ILocalTrackResolver _localResolver; private readonly ILocalTrackResolver _localResolver;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly IBotStrings _strings; private readonly IBotStrings _strings;
@@ -24,7 +25,7 @@ public sealed class MusicService : IMusicService, IPlaceholderProvider
AyuVoiceStateService voiceStateService, AyuVoiceStateService voiceStateService,
ITrackResolveProvider trackResolveProvider, ITrackResolveProvider trackResolveProvider,
DbService db, DbService db,
IYoutubeResolver ytResolver, IYoutubeResolverFactory ytResolver,
ILocalTrackResolver localResolver, ILocalTrackResolver localResolver,
DiscordSocketClient client, DiscordSocketClient client,
IBotStrings strings, IBotStrings strings,
@@ -93,7 +94,7 @@ public sealed class MusicService : IMusicService, IPlaceholderProvider
public async Task<int> EnqueueYoutubePlaylistAsync(IMusicPlayer mp, string query, string queuer) public async Task<int> EnqueueYoutubePlaylistAsync(IMusicPlayer mp, string query, string queuer)
{ {
var count = 0; var count = 0;
await foreach (var track in _ytResolver.ResolveTracksFromPlaylistAsync(query)) await foreach (var track in _ytResolver.GetYoutubeResolver().ResolveTracksFromPlaylistAsync(query))
{ {
if (mp.IsKilled) if (mp.IsKilled)
break; break;
@@ -139,6 +140,7 @@ public sealed class MusicService : IMusicService, IPlaceholderProvider
var mp = new MusicPlayer(queue, var mp = new MusicPlayer(queue,
resolver, resolver,
_ytResolver,
proxy, proxy,
_googleApiService, _googleApiService,
settings.QualityPreset, settings.QualityPreset,

View File

@@ -8,5 +8,4 @@ public interface ITrackInfo
public string Thumbnail { get; } public string Thumbnail { get; }
public TimeSpan Duration { get; } public TimeSpan Duration { get; }
public MusicPlatform Platform { get; } public MusicPlatform Platform { get; }
public ValueTask<string?> GetStreamUrl();
} }

View File

@@ -4,8 +4,8 @@ namespace NadekoBot.Modules.Music;
public interface IYoutubeResolver : IPlatformQueryResolver public interface IYoutubeResolver : IPlatformQueryResolver
{ {
public Regex YtVideoIdRegex { get; }
public Task<ITrackInfo?> ResolveByIdAsync(string id); public Task<ITrackInfo?> ResolveByIdAsync(string id);
IAsyncEnumerable<ITrackInfo> ResolveTracksFromPlaylistAsync(string query); IAsyncEnumerable<ITrackInfo> ResolveTracksFromPlaylistAsync(string query);
Task<ITrackInfo?> ResolveByQueryAsync(string query, bool tryExtractingId); Task<ITrackInfo?> ResolveByQueryAsync(string query, bool tryExtractingId);
Task<string?> GetStreamUrl(string query);
} }

View File

@@ -1,5 +1,6 @@
using NadekoBot.Voice; using NadekoBot.Voice;
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
using NadekoBot.Modules.Music.Resolvers;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -27,6 +28,7 @@ public sealed class MusicPlayer : IMusicPlayer
private readonly IMusicQueue _queue; private readonly IMusicQueue _queue;
private readonly ITrackResolveProvider _trackResolveProvider; private readonly ITrackResolveProvider _trackResolveProvider;
private readonly IYoutubeResolverFactory _ytResolverFactory;
private readonly IVoiceProxy _proxy; private readonly IVoiceProxy _proxy;
private readonly IGoogleApiService _googleApiService; private readonly IGoogleApiService _googleApiService;
private readonly ISongBuffer _songBuffer; private readonly ISongBuffer _songBuffer;
@@ -41,6 +43,7 @@ public sealed class MusicPlayer : IMusicPlayer
public MusicPlayer( public MusicPlayer(
IMusicQueue queue, IMusicQueue queue,
ITrackResolveProvider trackResolveProvider, ITrackResolveProvider trackResolveProvider,
IYoutubeResolverFactory ytResolverFactory,
IVoiceProxy proxy, IVoiceProxy proxy,
IGoogleApiService googleApiService, IGoogleApiService googleApiService,
QualityPreset qualityPreset, QualityPreset qualityPreset,
@@ -48,6 +51,7 @@ public sealed class MusicPlayer : IMusicPlayer
{ {
_queue = queue; _queue = queue;
_trackResolveProvider = trackResolveProvider; _trackResolveProvider = trackResolveProvider;
_ytResolverFactory = ytResolverFactory;
_proxy = proxy; _proxy = proxy;
_googleApiService = googleApiService; _googleApiService = googleApiService;
AutoPlay = autoPlay; AutoPlay = autoPlay;
@@ -118,7 +122,7 @@ public sealed class MusicPlayer : IMusicPlayer
// make sure song buffer is ready to be (re)used // make sure song buffer is ready to be (re)used
_songBuffer.Reset(); _songBuffer.Reset();
var streamUrl = await track.GetStreamUrl(); var streamUrl = await GetStreamUrl(track);
// start up the data source // start up the data source
using var source = FfmpegTrackDataSource.CreateAsync( using var source = FfmpegTrackDataSource.CreateAsync(
_vc.BitDepth, _vc.BitDepth,
@@ -256,6 +260,7 @@ public sealed class MusicPlayer : IMusicPlayer
IsStopped = true; IsStopped = true;
Log.Error("Please install ffmpeg and make sure it's added to your " Log.Error("Please install ffmpeg and make sure it's added to your "
+ "PATH environment variable before trying again"); + "PATH environment variable before trying again");
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -264,6 +269,7 @@ public sealed class MusicPlayer : IMusicPlayer
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "Unknown error in music loop: {ErrorMessage}", ex.Message); Log.Error(ex, "Unknown error in music loop: {ErrorMessage}", ex.Message);
await Task.Delay(3_000);
} }
finally finally
{ {
@@ -303,6 +309,14 @@ public sealed class MusicPlayer : IMusicPlayer
} }
} }
private async Task<string?> GetStreamUrl(IQueuedTrackInfo track)
{
if (track.TrackInfo is SimpleTrackInfo sti)
return sti.StreamUrl;
return await _ytResolverFactory.GetYoutubeResolver().GetStreamUrl(track.TrackInfo.Id);
}
private bool? CopyChunkToOutput(ISongBuffer sb, VoiceClient vc) private bool? CopyChunkToOutput(ISongBuffer sb, VoiceClient vc)
{ {
var data = sb.Read(vc.InputLength, out var length); var data = sb.Read(vc.InputLength, out var length);

View File

@@ -28,9 +28,6 @@ public sealed partial class MusicQueue
TrackInfo = trackInfo; TrackInfo = trackInfo;
Queuer = queuer; Queuer = queuer;
} }
public ValueTask<string?> GetStreamUrl()
=> TrackInfo.GetStreamUrl();
} }
} }

View File

@@ -6,11 +6,4 @@ public sealed record RemoteTrackInfo(
string Url, string Url,
string Thumbnail, string Thumbnail,
TimeSpan Duration, TimeSpan Duration,
MusicPlatform Platform, MusicPlatform Platform) : ITrackInfo;
Func<Task<string?>> _streamFactory) : ITrackInfo
{
private readonly Func<Task<string?>> _streamFactory = _streamFactory;
public async ValueTask<string?> GetStreamUrl()
=> await _streamFactory();
}

View File

@@ -24,7 +24,4 @@ public sealed class SimpleTrackInfo : ITrackInfo
Platform = platform; Platform = platform;
StreamUrl = streamUrl; StreamUrl = streamUrl;
} }
public ValueTask<string?> GetStreamUrl()
=> new(StreamUrl);
} }

View File

@@ -0,0 +1,12 @@
namespace NadekoBot.Modules.Music;
public sealed class InvTrackInfo : ITrackInfo
{
public required string Id { get; init; }
public required string Title { get; init; }
public required string Url { get; init; }
public required string Thumbnail { get; init; }
public required TimeSpan Duration { get; init; }
public required MusicPlatform Platform { get; init; }
public required string? StreamUrl { get; init; }
}

View File

@@ -0,0 +1,108 @@
using NadekoBot.Modules.Searches;
using System.Net.Http.Json;
namespace NadekoBot.Modules.Music;
public sealed class InvidiousYoutubeResolver : IYoutubeResolver
{
private readonly IHttpClientFactory _httpFactory;
private readonly SearchesConfigService _sc;
private readonly NadekoRandom _rng;
private string InvidiousApiUrl
=> _sc.Data.InvidiousInstances[_rng.Next(0, _sc.Data.InvidiousInstances.Count)];
public InvidiousYoutubeResolver(IHttpClientFactory httpFactory, SearchesConfigService sc)
{
_rng = new NadekoRandom();
_httpFactory = httpFactory;
_sc = sc;
}
public async Task<ITrackInfo?> ResolveByQueryAsync(string query)
{
using var http = _httpFactory.CreateClient();
var items = await http.GetFromJsonAsync<List<InvidiousSearchResponse>>(
$"{InvidiousApiUrl}/api/v1/search"
+ $"?q={query}"
+ $"&type=video");
if (items is null || items.Count == 0)
return null;
var res = items.First();
return new InvTrackInfo()
{
Id = res.VideoId,
Title = res.Title,
Url = $"https://youtube.com/watch?v={res.VideoId}",
Thumbnail = res.Thumbnails?.Select(x => x.Url).FirstOrDefault() ?? string.Empty,
Duration = TimeSpan.FromSeconds(res.LengthSeconds),
Platform = MusicPlatform.Youtube,
StreamUrl = null,
};
}
public async Task<ITrackInfo?> ResolveByIdAsync(string id)
=> await InternalResolveByIdAsync(id);
private async Task<InvTrackInfo?> InternalResolveByIdAsync(string id)
{
using var http = _httpFactory.CreateClient();
var res = await http.GetFromJsonAsync<InvidiousVideoResponse>(
$"{InvidiousApiUrl}/api/v1/videos/{id}");
if (res is null)
return null;
return new InvTrackInfo()
{
Id = res.VideoId,
Title = res.Title,
Url = $"https://youtube.com/watch?v={res.VideoId}",
Thumbnail = res.Thumbnails?.Select(x => x.Url).FirstOrDefault() ?? string.Empty,
Duration = TimeSpan.FromSeconds(res.LengthSeconds),
Platform = MusicPlatform.Youtube,
StreamUrl = res.AdaptiveFormats.FirstOrDefault(x => x.AudioQuality == "AUDIO_QUALITY_HIGH")?.Url
?? res.AdaptiveFormats.FirstOrDefault(x => x.AudioQuality == "AUDIO_QUALITY_MEDIUM")?.Url
?? res.AdaptiveFormats.FirstOrDefault(x => x.AudioQuality == "AUDIO_QUALITY_LOW")?.Url
};
}
public async IAsyncEnumerable<ITrackInfo> ResolveTracksFromPlaylistAsync(string query)
{
using var http = _httpFactory.CreateClient();
var res = await http.GetFromJsonAsync<InvidiousPlaylistResponse>(
$"{InvidiousApiUrl}/api/v1/search?type=video&q={query}");
if (res is null)
yield break;
foreach (var video in res.Videos)
{
yield return new InvTrackInfo()
{
Id = video.VideoId,
Title = video.Title,
Url = $"https://youtube.com/watch?v={video.VideoId}",
Thumbnail = video.Thumbnails?.Select(x => x.Url).FirstOrDefault() ?? string.Empty,
Duration = TimeSpan.FromSeconds(video.LengthSeconds),
Platform = MusicPlatform.Youtube,
StreamUrl = null
};
}
}
public Task<ITrackInfo?> ResolveByQueryAsync(string query, bool tryExtractingId)
=> ResolveByQueryAsync(query);
public async Task<string?> GetStreamUrl(string videoId)
{
var video = await InternalResolveByIdAsync(videoId);
return video?.StreamUrl;
}
}

View File

@@ -1,13 +1,15 @@
namespace NadekoBot.Modules.Music; using NadekoBot.Modules.Music.Resolvers;
namespace NadekoBot.Modules.Music;
public sealed class TrackResolveProvider : ITrackResolveProvider public sealed class TrackResolveProvider : ITrackResolveProvider
{ {
private readonly IYoutubeResolver _ytResolver; private readonly IYoutubeResolverFactory _ytResolver;
private readonly ILocalTrackResolver _localResolver; private readonly ILocalTrackResolver _localResolver;
private readonly IRadioResolver _radioResolver; private readonly IRadioResolver _radioResolver;
public TrackResolveProvider( public TrackResolveProvider(
IYoutubeResolver ytResolver, IYoutubeResolverFactory ytResolver,
ILocalTrackResolver localResolver, ILocalTrackResolver localResolver,
IRadioResolver radioResolver) IRadioResolver radioResolver)
{ {
@@ -23,19 +25,22 @@ public sealed class TrackResolveProvider : ITrackResolveProvider
case MusicPlatform.Radio: case MusicPlatform.Radio:
return _radioResolver.ResolveByQueryAsync(query); return _radioResolver.ResolveByQueryAsync(query);
case MusicPlatform.Youtube: case MusicPlatform.Youtube:
return _ytResolver.ResolveByQueryAsync(query); return _ytResolver.GetYoutubeResolver().ResolveByQueryAsync(query);
case MusicPlatform.Local: case MusicPlatform.Local:
return _localResolver.ResolveByQueryAsync(query); return _localResolver.ResolveByQueryAsync(query);
case null: case null:
var match = _ytResolver.YtVideoIdRegex.Match(query); var match = YoutubeHelpers.YtVideoIdRegex.Match(query);
if (match.Success) if (match.Success)
return _ytResolver.ResolveByIdAsync(match.Groups["id"].Value); return _ytResolver.GetYoutubeResolver().ResolveByIdAsync(match.Groups["id"].Value);
else if (Uri.TryCreate(query, UriKind.Absolute, out var uri) && uri.IsFile)
if (Uri.TryCreate(query, UriKind.Absolute, out var uri) && uri.IsFile)
return _localResolver.ResolveByQueryAsync(uri.AbsolutePath); return _localResolver.ResolveByQueryAsync(uri.AbsolutePath);
else if (IsRadioLink(query))
if (IsRadioLink(query))
return _radioResolver.ResolveByQueryAsync(query); return _radioResolver.ResolveByQueryAsync(query);
else
return _ytResolver.ResolveByQueryAsync(query, false); return _ytResolver.GetYoutubeResolver().ResolveByQueryAsync(query, false);
default: default:
Log.Error("Unsupported platform: {MusicPlatform}", forcePlatform); Log.Error("Unsupported platform: {MusicPlatform}", forcePlatform);
return Task.FromResult<ITrackInfo?>(null); return Task.FromResult<ITrackInfo?>(null);

Some files were not shown because too many files have changed in this diff Show More