diff --git a/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs b/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs
index a2eaaa179..0b6dc0e85 100644
--- a/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs
+++ b/src/NadekoBot/Db/Extensions/DiscordUserExtensions.cs
@@ -4,11 +4,60 @@ using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
using NadekoBot.Services.Database;
+using System.Collections.Immutable;
namespace NadekoBot.Db;
public static class DiscordUserExtensions
{
+ ///
+ /// Adds the specified to the database. If a database user with placeholder name
+ /// and discriminator is present in , their name and discriminator get updated accordingly.
+ ///
+ /// This database context.
+ /// The users to add or update in the database.
+ /// A tuple with the amount of new users added and old users updated.
+ public static async Task<(long UsersAdded, long UsersUpdated)> RefreshUsersAsync(this NadekoContext ctx, List users)
+ {
+ var presentDbUsers = await ctx.DiscordUser
+ .Select(x => new { x.UserId, x.Username, x.Discriminator })
+ .Where(x => users.Select(y => y.Id).Contains(x.UserId))
+ .ToArrayAsyncEF();
+
+ var usersToAdd = users
+ .Where(x => !presentDbUsers.Select(x => x.UserId).Contains(x.Id))
+ .Select(x => new DiscordUser()
+ {
+ UserId = x.Id,
+ AvatarId = x.AvatarId,
+ Username = x.Username,
+ Discriminator = x.Discriminator
+ });
+
+ var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
+ var toUpdateUserIds = presentDbUsers
+ .Where(x => x.Username == "Unknown" && x.Discriminator == "????")
+ .Select(x => x.UserId)
+ .ToArray();
+
+ foreach (var user in users.Where(x => toUpdateUserIds.Contains(x.Id)))
+ {
+ await ctx.DiscordUser
+ .Where(x => x.UserId == user.Id)
+ .UpdateAsync(x => new DiscordUser()
+ {
+ Username = user.Username,
+ Discriminator = user.Discriminator,
+
+ // .award tends to set AvatarId and DateAdded to NULL, so account for that.
+ AvatarId = user.AvatarId,
+ DateAdded = x.DateAdded ?? DateTime.UtcNow
+ });
+ }
+
+ return (added, toUpdateUserIds.Length);
+ }
+
public static Task GetByUserIdAsync(
this IQueryable set,
ulong userId)
diff --git a/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs
index fb81c86bd..e76c49ba9 100644
--- a/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs
+++ b/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs
@@ -1,5 +1,6 @@
#nullable disable
using Nadeko.Medusa;
+using NadekoBot.Db;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Services.Database.Models;
@@ -22,19 +23,53 @@ public partial class Administration
private readonly IBotStrings _strings;
private readonly IMedusaLoaderService _medusaLoader;
private readonly ICoordinator _coord;
+ private readonly DbService _db;
public SelfCommands(
DiscordSocketClient client,
+ DbService db,
IBotStrings strings,
ICoordinator coord,
IMedusaLoaderService medusaLoader)
{
_client = client;
+ _db = db;
_strings = strings;
_coord = coord;
_medusaLoader = medusaLoader;
}
+
+ [Cmd]
+ [RequireContext(ContextType.Guild)]
+ [OwnerOnly]
+ public Task CacheUsers()
+ => CacheUsers(ctx.Guild);
+
+ [Cmd]
+ [OwnerOnly]
+ public async Task CacheUsers(IGuild guild)
+ {
+ var downloadUsersTask = guild.DownloadUsersAsync();
+ var message = await ReplyPendingLocalizedAsync(strs.cache_users_pending);
+ using var dbContext = _db.GetDbContext();
+
+ await downloadUsersTask;
+
+ var users = (await guild.GetUsersAsync(CacheMode.CacheOnly))
+ .Cast()
+ .ToList();
+
+ var (added, updated) = await dbContext.RefreshUsersAsync(users);
+
+ await message.ModifyAsync(x =>
+ x.Embed = _eb.Create()
+ .WithDescription(GetText(strs.cache_users_done(added, updated)))
+ .WithOkColor()
+ .Build()
+ );
+ }
+
[Cmd]
[OwnerOnly]
public async Task DoAs(IUser user, [Leftover] string message)
diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml
index 1efc29abe..5b7957e4a 100644
--- a/src/NadekoBot/data/aliases.yml
+++ b/src/NadekoBot/data/aliases.yml
@@ -1390,4 +1390,6 @@ autopublish:
- autopublish
doas:
- doas
- - execas
\ No newline at end of file
+ - execas
+cacheusers:
+ - cacheusers
\ No newline at end of file
diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml
index 2caec755d..8023f3180 100644
--- a/src/NadekoBot/data/strings/commands/commands.en-US.yml
+++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml
@@ -2363,3 +2363,8 @@ doas:
desc: "Execute the command as if you were the target user. Requires bot ownership and server administrator permission."
args:
- "@Thief .give all @Admin"
+cacheusers:
+ desc: Caches users of a Discord server and saves them to the database.
+ args:
+ - ""
+ - "serverId"
\ No newline at end of file
diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json
index 77d3ea9b0..50a16a795 100644
--- a/src/NadekoBot/data/strings/responses/responses.en-US.json
+++ b/src/NadekoBot/data/strings/responses/responses.en-US.json
@@ -1058,5 +1058,7 @@
"sticker_missing_name": "Please specify a name for the sticker.",
"thread_deleted": "Thread Deleted",
"thread_created": "Thread Created",
- "supported_languages": "Supported Languages"
+ "supported_languages": "Supported Languages",
+ "cache_users_pending": "Updating users, please wait...",
+ "cache_users_done": "{0} users were added and {1} users were updated."
}