mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-03 16:24:27 -05:00 
			
		
		
		
	Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3
This commit is contained in:
		@@ -21,7 +21,7 @@
 | 
				
			|||||||
#### Prerequisites
 | 
					#### Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Windows 8 or later (64-bit)
 | 
					- Windows 8 or later (64-bit)
 | 
				
			||||||
- [Create a Discord Bot application and invite the bot to your server](../../creds-guide.md)
 | 
					- [Create a Discord Bot application and invite the bot to your server](../creds-guide.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Optional**
 | 
					**Optional**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,8 @@ using LinqToDB;
 | 
				
			|||||||
using LinqToDB.EntityFrameworkCore;
 | 
					using LinqToDB.EntityFrameworkCore;
 | 
				
			||||||
using NadekoBot.Db;
 | 
					using NadekoBot.Db;
 | 
				
			||||||
using Serilog;
 | 
					using Serilog;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace NadekoBot.Modules.Administration.Services
 | 
					namespace NadekoBot.Modules.Administration.Services
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -21,6 +23,11 @@ namespace NadekoBot.Modules.Administration.Services
 | 
				
			|||||||
        private readonly DiscordSocketClient _client;
 | 
					        private readonly DiscordSocketClient _client;
 | 
				
			||||||
        private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
 | 
					        private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Contains the (Message ID, User ID) of reaction roles that are currently being processed.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        private readonly ConcurrentHashSet<(ulong, ulong)> _reacting = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public RoleCommandsService(DiscordSocketClient client, DbService db,
 | 
					        public RoleCommandsService(DiscordSocketClient client, DbService db,
 | 
				
			||||||
            Bot bot)
 | 
					            Bot bot)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -38,19 +45,13 @@ namespace NadekoBot.Modules.Administration.Services
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
 | 
					        private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var _ = Task.Run(async () =>
 | 
					            _ = Task.Run(async () =>
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                try
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!reaction.User.IsSpecified ||
 | 
					                if (!reaction.User.IsSpecified ||
 | 
				
			||||||
                    reaction.User.Value.IsBot ||
 | 
					                    reaction.User.Value.IsBot ||
 | 
				
			||||||
                        !(reaction.User.Value is SocketGuildUser gusr))
 | 
					                    reaction.User.Value is not SocketGuildUser gusr ||
 | 
				
			||||||
                        return;
 | 
					                    chan is not SocketGuildChannel gch ||
 | 
				
			||||||
 | 
					                    !_models.TryGetValue(gch.Guild.Id, out var confs))
 | 
				
			||||||
                    if (!(chan is SocketGuildChannel gch))
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (!_models.TryGetValue(gch.Guild.Id, out var confs))
 | 
					 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
 | 
					                var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
 | 
				
			||||||
@@ -60,39 +61,30 @@ namespace NadekoBot.Modules.Administration.Services
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // compare emote names for backwards compatibility :facepalm:
 | 
					                // compare emote names for backwards compatibility :facepalm:
 | 
				
			||||||
                var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
 | 
					                var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (reactionRole != null)
 | 
					                if (reactionRole != null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                        if (conf.Exclusive)
 | 
					                    if (!conf.Exclusive)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                            var roleIds = conf.ReactionRoles.Select(x => x.RoleId)
 | 
					                        await AddReactionRoleAsync(gusr, reactionRole);
 | 
				
			||||||
                                .Where(x => x != reactionRole.RoleId)
 | 
					                        return;
 | 
				
			||||||
                                .Select(x => gusr.Guild.GetRole(x))
 | 
					                    }
 | 
				
			||||||
                                .Where(x => x != null);
 | 
					
 | 
				
			||||||
 | 
					                    // If same (message, user) are being processed in an exclusive rero, quit
 | 
				
			||||||
 | 
					                    if (!_reacting.Add((msg.Id, reaction.UserId)))
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            var __ = Task.Run(async () =>
 | 
					 | 
				
			||||||
                            {
 | 
					 | 
				
			||||||
                    try
 | 
					                    try
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                                    //if the role is exclusive,
 | 
					                        var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None);
 | 
				
			||||||
                                    // remove all other reactions user added to the message
 | 
					                        var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
 | 
				
			||||||
                                    var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
 | 
					 | 
				
			||||||
                                    foreach (var r in dl.Reactions)
 | 
					 | 
				
			||||||
                                    {
 | 
					 | 
				
			||||||
                                        if (r.Key.Name == reaction.Emote.Name)
 | 
					 | 
				
			||||||
                                            continue;
 | 
					 | 
				
			||||||
                                        try { await dl.RemoveReactionAsync(r.Key, gusr).ConfigureAwait(false); } catch { }
 | 
					 | 
				
			||||||
                                        await Task.Delay(100).ConfigureAwait(false);
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                catch { }
 | 
					 | 
				
			||||||
                            });
 | 
					 | 
				
			||||||
                            await gusr.RemoveRolesAsync(roleIds).ConfigureAwait(false);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        var toAdd = gusr.Guild.GetRole(reactionRole.RoleId);
 | 
					                        await Task.WhenAll(removeExclusiveTask, addRoleTask).ConfigureAwait(false);
 | 
				
			||||||
                        if (toAdd != null && !gusr.Roles.Contains(toAdd))
 | 
					                    }
 | 
				
			||||||
 | 
					                    finally
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                            await gusr.AddRolesAsync(new[] { toAdd }).ConfigureAwait(false);
 | 
					                        // Free (message/user) for another exclusive rero
 | 
				
			||||||
 | 
					                        _reacting.TryRemove((msg.Id, reaction.UserId));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
@@ -105,8 +97,6 @@ namespace NadekoBot.Modules.Administration.Services
 | 
				
			|||||||
                        }).ConfigureAwait(false);
 | 
					                        }).ConfigureAwait(false);
 | 
				
			||||||
                    Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
 | 
					                    Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                catch { }
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Task.CompletedTask;
 | 
					            return Task.CompletedTask;
 | 
				
			||||||
@@ -114,16 +104,16 @@ namespace NadekoBot.Modules.Administration.Services
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
 | 
					        private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var _ = Task.Run(async () =>
 | 
					            _ = Task.Run(async () =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (!reaction.User.IsSpecified ||
 | 
					                    if (!reaction.User.IsSpecified ||
 | 
				
			||||||
                        reaction.User.Value.IsBot ||
 | 
					                        reaction.User.Value.IsBot ||
 | 
				
			||||||
                        !(reaction.User.Value is SocketGuildUser gusr))
 | 
					                        reaction.User.Value is not SocketGuildUser gusr)
 | 
				
			||||||
                        return;
 | 
					                        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (!(chan is SocketGuildChannel gch))
 | 
					                    if (chan is not SocketGuildChannel gch)
 | 
				
			||||||
                        return;
 | 
					                        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (!_models.TryGetValue(gch.Guild.Id, out var confs))
 | 
					                    if (!_models.TryGetValue(gch.Guild.Id, out var confs))
 | 
				
			||||||
@@ -193,5 +183,71 @@ namespace NadekoBot.Modules.Administration.Services
 | 
				
			|||||||
                uow.SaveChanges();
 | 
					                uow.SaveChanges();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Adds a reaction role to the specified user.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="user">A Discord guild user.</param>
 | 
				
			||||||
 | 
					        /// <param name="dbRero">The database settings of this reaction role.</param>
 | 
				
			||||||
 | 
					        private Task AddReactionRoleAsync(SocketGuildUser user, ReactionRole dbRero)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var toAdd = user.Guild.GetRole(dbRero.RoleId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return (toAdd != null && !user.Roles.Contains(toAdd))
 | 
				
			||||||
 | 
					                ? user.AddRoleAsync(toAdd)
 | 
				
			||||||
 | 
					                : Task.CompletedTask;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Removes the exclusive reaction roles and reactions from the specified user.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
 | 
				
			||||||
 | 
					        /// <param name="user">A Discord guild user.</param>
 | 
				
			||||||
 | 
					        /// <param name="reaction">The Discord reaction of the user.</param>
 | 
				
			||||||
 | 
					        /// <param name="dbReroMsg">The database entry of the reaction role message.</param>
 | 
				
			||||||
 | 
					        /// <param name="dbRero">The database settings of this reaction role.</param>
 | 
				
			||||||
 | 
					        /// <param name="cToken">A cancellation token to cancel the operation.</param>
 | 
				
			||||||
 | 
					        /// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
 | 
				
			||||||
 | 
					        /// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
 | 
				
			||||||
 | 
					        private Task RemoveExclusiveReactionRoleAsync(Cacheable<IUserMessage, ulong> reactionMessage, SocketGuildUser user, SocketReaction reaction, ReactionRoleMessage dbReroMsg, ReactionRole dbRero, CancellationToken cToken = default)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cToken.ThrowIfCancellationRequested();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var roleIds = dbReroMsg.ReactionRoles.Select(x => x.RoleId)
 | 
				
			||||||
 | 
					                .Where(x => x != dbRero.RoleId)
 | 
				
			||||||
 | 
					                .Select(x => user.Guild.GetRole(x))
 | 
				
			||||||
 | 
					                .Where(x => x != null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var removeRolesTask = user.RemoveRolesAsync(roleIds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Task.WhenAll(removeReactionsTask, removeRolesTask);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Removes old reactions from an exclusive reaction role.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
 | 
				
			||||||
 | 
					        /// <param name="user">A Discord guild user.</param>
 | 
				
			||||||
 | 
					        /// <param name="reaction">The Discord reaction of the user.</param>
 | 
				
			||||||
 | 
					        /// <param name="cToken">A cancellation token to cancel the operation.</param>
 | 
				
			||||||
 | 
					        /// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
 | 
				
			||||||
 | 
					        /// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
 | 
				
			||||||
 | 
					        private async Task RemoveOldReactionsAsync(Cacheable<IUserMessage, ulong> reactionMessage, SocketGuildUser user, SocketReaction reaction, CancellationToken cToken = default)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cToken.ThrowIfCancellationRequested();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //if the role is exclusive,
 | 
				
			||||||
 | 
					            // remove all other reactions user added to the message
 | 
				
			||||||
 | 
					            var dl = await reactionMessage.GetOrDownloadAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            foreach (var r in dl.Reactions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (r.Key.Name == reaction.Emote.Name)
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                try { await dl.RemoveReactionAsync(r.Key, user).ConfigureAwait(false); } catch { }
 | 
				
			||||||
 | 
					                await Task.Delay(100, cToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user