Games done, admin half done
This commit is contained in:
		@@ -7,82 +7,54 @@ using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using static NadekoBot.Modules.Permissions.Permissions;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using NLog;
 | 
			
		||||
using NadekoBot.Modules.Permissions;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Administration
 | 
			
		||||
{
 | 
			
		||||
    [NadekoModule("Administration", ".")]
 | 
			
		||||
    public partial class Administration : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly ConcurrentHashSet<ulong> deleteMessagesOnCommand;
 | 
			
		||||
        private IGuild _nadekoSupportServer;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
        private readonly AdministrationService _admin;
 | 
			
		||||
 | 
			
		||||
        private new static readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        static Administration()
 | 
			
		||||
        public Administration(DbHandler db, AdministrationService admin)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler;
 | 
			
		||||
 | 
			
		||||
            deleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
 | 
			
		||||
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _admin = admin;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var channel = msg.Channel as SocketTextChannel;
 | 
			
		||||
                    if (channel == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    if (deleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
 | 
			
		||||
                        await msg.DeleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn(ex, "Delmsgoncmd errored...");
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
        ////todo permissions
 | 
			
		||||
        //[NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        //[RequireContext(ContextType.Guild)]
 | 
			
		||||
        //[RequireUserPermission(GuildPermission.Administrator)]
 | 
			
		||||
        //public async Task ResetPermissions()
 | 
			
		||||
        //{
 | 
			
		||||
        //    using (var uow = _db.UnitOfWork)
 | 
			
		||||
        //    {
 | 
			
		||||
        //        var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id);
 | 
			
		||||
        //        config.Permissions = Permissionv2.GetDefaultPermlist;
 | 
			
		||||
        //        await uow.CompleteAsync();
 | 
			
		||||
        //        UpdateCache(config);
 | 
			
		||||
        //    }
 | 
			
		||||
        //    await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
 | 
			
		||||
        //}
 | 
			
		||||
        //[NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        //[OwnerOnly]
 | 
			
		||||
        //public async Task ResetGlobalPermissions()
 | 
			
		||||
        //{
 | 
			
		||||
        //    using (var uow = _db.UnitOfWork)
 | 
			
		||||
        //    {
 | 
			
		||||
        //        var gc = uow.BotConfig.GetOrCreate();
 | 
			
		||||
        //        gc.BlockedCommands.Clear();
 | 
			
		||||
        //        gc.BlockedModules.Clear();
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [RequireUserPermission(GuildPermission.Administrator)]
 | 
			
		||||
        public async Task ResetPermissions()
 | 
			
		||||
        {
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            {
 | 
			
		||||
                var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id);
 | 
			
		||||
                config.Permissions = Permissionv2.GetDefaultPermlist;
 | 
			
		||||
                await uow.CompleteAsync();
 | 
			
		||||
                UpdateCache(config);
 | 
			
		||||
            }
 | 
			
		||||
            await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async Task ResetGlobalPermissions()
 | 
			
		||||
        {
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            {
 | 
			
		||||
                var gc = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                gc.BlockedCommands.Clear();
 | 
			
		||||
                gc.BlockedModules.Clear();
 | 
			
		||||
 | 
			
		||||
                GlobalPermissionCommands.BlockedCommands.Clear();
 | 
			
		||||
                GlobalPermissionCommands.BlockedModules.Clear();
 | 
			
		||||
                await uow.CompleteAsync();
 | 
			
		||||
            }
 | 
			
		||||
            await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
        //        GlobalPermissionCommands.BlockedCommands.Clear();
 | 
			
		||||
        //        GlobalPermissionCommands.BlockedModules.Clear();
 | 
			
		||||
        //        await uow.CompleteAsync();
 | 
			
		||||
        //    }
 | 
			
		||||
        //    await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false);
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
@@ -91,7 +63,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        public async Task Delmsgoncmd()
 | 
			
		||||
        {
 | 
			
		||||
            bool enabled;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
 | 
			
		||||
                enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
 | 
			
		||||
@@ -100,12 +72,12 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            }
 | 
			
		||||
            if (enabled)
 | 
			
		||||
            {
 | 
			
		||||
                deleteMessagesOnCommand.Add(Context.Guild.Id);
 | 
			
		||||
                _admin.DeleteMessagesOnCommand.Add(Context.Guild.Id);
 | 
			
		||||
                await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                deleteMessagesOnCommand.TryRemove(Context.Guild.Id);
 | 
			
		||||
                _admin.DeleteMessagesOnCommand.TryRemove(Context.Guild.Id);
 | 
			
		||||
                await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -454,19 +426,18 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            await Context.Channel.SendMessageAsync(send).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IGuild _nadekoSupportServer;
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Donators()
 | 
			
		||||
        {
 | 
			
		||||
            IEnumerable<Donator> donatorsOrdered;
 | 
			
		||||
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                donatorsOrdered = uow.Donators.GetDonatorsOrdered();
 | 
			
		||||
            }
 | 
			
		||||
            await Context.Channel.SendConfirmAsync(GetText("donators"), string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            _nadekoSupportServer = _nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850);
 | 
			
		||||
            _nadekoSupportServer = _nadekoSupportServer ?? (await Context.Client.GetGuildAsync(117523346618318850));
 | 
			
		||||
 | 
			
		||||
            var patreonRole = _nadekoSupportServer?.GetRole(236667642088259585);
 | 
			
		||||
            if (patreonRole == null)
 | 
			
		||||
@@ -482,7 +453,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        public async Task Donadd(IUser donator, int amount)
 | 
			
		||||
        {
 | 
			
		||||
            Donator don;
 | 
			
		||||
            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount);
 | 
			
		||||
                await uow.CompleteAsync();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ using Discord.WebSocket;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
@@ -19,232 +20,13 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class MuteCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
 | 
			
		||||
            private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
 | 
			
		||||
            private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> UnmuteTimers { get; }
 | 
			
		||||
                = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
 | 
			
		||||
            private readonly MuteService _service;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            public static event Action<IGuildUser, MuteType> UserMuted = delegate { };
 | 
			
		||||
            public static event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            public enum MuteType {
 | 
			
		||||
                Voice,
 | 
			
		||||
                Chat,
 | 
			
		||||
                All
 | 
			
		||||
            }
 | 
			
		||||
            private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
 | 
			
		||||
 | 
			
		||||
            private static readonly new Logger _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            static MuteCommands()
 | 
			
		||||
            public MuteCommands(MuteService service, DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                var configs = NadekoBot.AllGuildConfigs;
 | 
			
		||||
                GuildMuteRoles = new ConcurrentDictionary<ulong, string>(configs
 | 
			
		||||
                        .Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
 | 
			
		||||
                        .ToDictionary(c => c.GuildId, c => c.MuteRoleName));
 | 
			
		||||
 | 
			
		||||
                MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs.ToDictionary(
 | 
			
		||||
                    k => k.GuildId,
 | 
			
		||||
                    v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
 | 
			
		||||
                ));
 | 
			
		||||
 | 
			
		||||
                foreach (var conf in configs)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var x in conf.UnmuteTimers)
 | 
			
		||||
                    {
 | 
			
		||||
                        TimeSpan after;
 | 
			
		||||
                        if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
 | 
			
		||||
                        {
 | 
			
		||||
                            after = TimeSpan.FromMinutes(2);
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            after = x.UnmuteAt - DateTime.UtcNow;
 | 
			
		||||
                        }
 | 
			
		||||
                        StartUnmuteTimer(conf.GuildId, x.UserId, after);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                NadekoBot.Client.UserJoined += Client_UserJoined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static async Task Client_UserJoined(IGuildUser usr)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
 | 
			
		||||
 | 
			
		||||
                    if (muted == null || !muted.Contains(usr.Id))
 | 
			
		||||
                        return;
 | 
			
		||||
                    await MuteUser(usr).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    LogManager.GetCurrentClassLogger().Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
                    
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task MuteUser(IGuildUser usr)
 | 
			
		||||
            {
 | 
			
		||||
                await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
 | 
			
		||||
                var muteRole = await GetMuteRole(usr.Guild);
 | 
			
		||||
                if (!usr.RoleIds.Contains(muteRole.Id))
 | 
			
		||||
                    await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
 | 
			
		||||
                StopUnmuteTimer(usr.GuildId, usr.Id);
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(usr.Guild.Id,
 | 
			
		||||
                        set => set.Include(gc => gc.MutedUsers)
 | 
			
		||||
                            .Include(gc => gc.UnmuteTimers));
 | 
			
		||||
                    config.MutedUsers.Add(new MutedUserId()
 | 
			
		||||
                    {
 | 
			
		||||
                        UserId = usr.Id
 | 
			
		||||
                    });
 | 
			
		||||
                    if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
 | 
			
		||||
                        muted.Add(usr.Id);
 | 
			
		||||
 | 
			
		||||
                    config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
 | 
			
		||||
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                UserMuted(usr, MuteType.All);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task UnmuteUser(IGuildUser usr)
 | 
			
		||||
            {
 | 
			
		||||
                StopUnmuteTimer(usr.GuildId, usr.Id);
 | 
			
		||||
                try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
 | 
			
		||||
                try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); } catch { /*ignore*/ }
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)
 | 
			
		||||
                        .Include(gc => gc.UnmuteTimers));
 | 
			
		||||
                    config.MutedUsers.Remove(new MutedUserId()
 | 
			
		||||
                    {
 | 
			
		||||
                        UserId = usr.Id
 | 
			
		||||
                    });
 | 
			
		||||
                    if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
 | 
			
		||||
                        muted.TryRemove(usr.Id);
 | 
			
		||||
 | 
			
		||||
                    config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
 | 
			
		||||
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                UserUnmuted(usr, MuteType.All);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task<IRole>GetMuteRole(IGuild guild)
 | 
			
		||||
            {
 | 
			
		||||
                const string defaultMuteRoleName = "nadeko-mute";
 | 
			
		||||
 | 
			
		||||
                var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
 | 
			
		||||
 | 
			
		||||
                var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
 | 
			
		||||
                if (muteRole == null)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    //if it doesn't exist, create it 
 | 
			
		||||
                    try { muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None).ConfigureAwait(false); }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        //if creations fails,  maybe the name is not correct, find default one, if doesn't work, create default one
 | 
			
		||||
                        muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
 | 
			
		||||
                            await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
 | 
			
		||||
                        {
 | 
			
		||||
                            await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
 | 
			
		||||
                                    .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            await Task.Delay(200).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        // ignored
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return muteRole;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task TimedMute(IGuildUser user, TimeSpan after)
 | 
			
		||||
            {
 | 
			
		||||
                await MuteUser(user).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(user.GuildId, set => set.Include(x => x.UnmuteTimers));
 | 
			
		||||
                    config.UnmuteTimers.Add(new UnmuteTimer()
 | 
			
		||||
                    {
 | 
			
		||||
                        UserId = user.Id,
 | 
			
		||||
                        UnmuteAt = DateTime.UtcNow + after,
 | 
			
		||||
                    }); // add teh unmute timer to the database
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
                }
 | 
			
		||||
                StartUnmuteTimer(user.GuildId, user.Id, after); // start the timer
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void StartUnmuteTimer(ulong guildId, ulong userId, TimeSpan after)
 | 
			
		||||
            {
 | 
			
		||||
                //load the unmute timers for this guild
 | 
			
		||||
                var userUnmuteTimers = UnmuteTimers.GetOrAdd(guildId, new ConcurrentDictionary<ulong, Timer>());
 | 
			
		||||
 | 
			
		||||
                //unmute timer to be added
 | 
			
		||||
                var toAdd = new Timer(async _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var guild = NadekoBot.Client.GetGuild(guildId); // load the guild
 | 
			
		||||
                        if (guild == null)
 | 
			
		||||
                        {
 | 
			
		||||
                            RemoveUnmuteTimerFromDb(guildId, userId);
 | 
			
		||||
                            return; // if guild can't be found, just remove the timer from db
 | 
			
		||||
                        }
 | 
			
		||||
                        // unmute the user, this will also remove the timer from the db
 | 
			
		||||
                        await UnmuteUser(guild.GetUser(userId)).ConfigureAwait(false); 
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        RemoveUnmuteTimerFromDb(guildId, userId); // if unmute errored, just remove unmute from db
 | 
			
		||||
                        Administration._log.Warn("Couldn't unmute user {0} in guild {1}", userId, guildId);
 | 
			
		||||
                        Administration._log.Warn(ex);
 | 
			
		||||
                    }
 | 
			
		||||
                }, null, after, Timeout.InfiniteTimeSpan);
 | 
			
		||||
 | 
			
		||||
                //add it, or stop the old one and add this one
 | 
			
		||||
                userUnmuteTimers.AddOrUpdate(userId, (key) => toAdd, (key, old) =>
 | 
			
		||||
                {
 | 
			
		||||
                    old.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                    return toAdd;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static void StopUnmuteTimer(ulong guildId, ulong userId)
 | 
			
		||||
            {
 | 
			
		||||
                if (!UnmuteTimers.TryGetValue(guildId, out ConcurrentDictionary<ulong, Timer> userUnmuteTimers)) return;
 | 
			
		||||
 | 
			
		||||
                if (userUnmuteTimers.TryRemove(userId, out Timer removed))
 | 
			
		||||
                {
 | 
			
		||||
                    removed.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static void RemoveUnmuteTimerFromDb(ulong guildId, ulong userId)
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(guildId, set => set.Include(x => x.UnmuteTimers));
 | 
			
		||||
                    config.UnmuteTimers.RemoveWhere(x => x.UserId == userId);
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
                }
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -257,11 +39,11 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
 | 
			
		||||
                    config.MuteRoleName = name;
 | 
			
		||||
                    GuildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name);
 | 
			
		||||
                    _service.GuildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name);
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                await ReplyConfirmLocalized("mute_role_set").ConfigureAwait(false);
 | 
			
		||||
@@ -283,7 +65,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await MuteUser(user).ConfigureAwait(false);
 | 
			
		||||
                    await _service.MuteUser(user).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_muted", Format.Bold(user.ToString())).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -303,7 +85,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    return;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await TimedMute(user, TimeSpan.FromMinutes(minutes)).ConfigureAwait(false);
 | 
			
		||||
                    await _service.TimedMute(user, TimeSpan.FromMinutes(minutes)).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_muted_time", Format.Bold(user.ToString()), minutes).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
@@ -321,7 +103,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await UnmuteUser(user).ConfigureAwait(false);
 | 
			
		||||
                    await _service.UnmuteUser(user).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -337,8 +119,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await user.AddRoleAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
 | 
			
		||||
                    UserMuted(user, MuteType.Chat);
 | 
			
		||||
                    await _service.MuteUser(user, MuteType.Chat).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -354,8 +135,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await user.RemoveRoleAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
 | 
			
		||||
                    UserUnmuted(user, MuteType.Chat);
 | 
			
		||||
                    await _service.UnmuteUser(user, MuteType.Chat).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -367,12 +147,11 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.MuteMembers)]
 | 
			
		||||
            public async Task VoiceMute(IGuildUser user)
 | 
			
		||||
            public async Task VoiceMute([Remainder] IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false);
 | 
			
		||||
                    UserMuted(user, MuteType.Voice);
 | 
			
		||||
                    await _service.MuteUser(user, MuteType.Voice).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -384,12 +163,11 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.MuteMembers)]
 | 
			
		||||
            public async Task VoiceUnmute(IGuildUser user)
 | 
			
		||||
            public async Task VoiceUnmute([Remainder] IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false);
 | 
			
		||||
                    UserUnmuted(user, MuteType.Voice);
 | 
			
		||||
                    await _service.UnmuteUser(user, MuteType.Voice).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NLog;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Administration
 | 
			
		||||
{
 | 
			
		||||
@@ -23,34 +24,20 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class SelfCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static volatile bool _forwardDMs;
 | 
			
		||||
            private static volatile bool _forwardDMsToAllOwners;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            private static readonly object _locker = new object();
 | 
			
		||||
            private readonly SelfService _service;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
            private readonly IImagesService _images;
 | 
			
		||||
 | 
			
		||||
            private new static readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
            static SelfCommands()
 | 
			
		||||
            public SelfCommands(DbHandler db, SelfService service, DiscordShardedClient client,
 | 
			
		||||
                IImagesService images)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                    _forwardDMs = config.ForwardMessages;
 | 
			
		||||
                    _forwardDMsToAllOwners = config.ForwardToAllOwners;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    while (!NadekoBot.Ready)
 | 
			
		||||
                        await Task.Delay(1000);
 | 
			
		||||
 | 
			
		||||
                    foreach (var cmd in NadekoBot.BotConfig.StartupCommands)
 | 
			
		||||
                    {
 | 
			
		||||
                        await NadekoBot.CommandHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText);
 | 
			
		||||
                        await Task.Delay(400).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                _db = db;
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _client = client;
 | 
			
		||||
                _images = images;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -69,7 +56,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    VoiceChannelId = guser.VoiceChannel?.Id,
 | 
			
		||||
                    VoiceChannelName = guser.VoiceChannel?.Name,
 | 
			
		||||
                };
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.BotConfig
 | 
			
		||||
                       .GetOrCreate(set => set.Include(x => x.StartupCommands))
 | 
			
		||||
@@ -96,7 +83,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    return;
 | 
			
		||||
                page -= 1;
 | 
			
		||||
                IEnumerable<StartupCommand> scmds;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    scmds = uow.BotConfig
 | 
			
		||||
                       .GetOrCreate(set => set.Include(x => x.StartupCommands))
 | 
			
		||||
@@ -148,7 +135,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            public async Task StartupCommandRemove([Remainder] string cmdText)
 | 
			
		||||
            {
 | 
			
		||||
                StartupCommand cmd;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var cmds = uow.BotConfig
 | 
			
		||||
                       .GetOrCreate(set => set.Include(x => x.StartupCommands))
 | 
			
		||||
@@ -174,7 +161,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task StartupCommandsClear()
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.BotConfig
 | 
			
		||||
                       .GetOrCreate(set => set.Include(x => x.StartupCommands))
 | 
			
		||||
@@ -190,14 +177,13 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task ForwardMessages()
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                    lock (_locker)
 | 
			
		||||
                        _forwardDMs = config.ForwardMessages = !config.ForwardMessages;
 | 
			
		||||
                    _service.ForwardDMs = config.ForwardMessages = !config.ForwardMessages;
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
                }
 | 
			
		||||
                if (_forwardDMs)
 | 
			
		||||
                if (_service.ForwardDMs)
 | 
			
		||||
                    await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false);
 | 
			
		||||
                else
 | 
			
		||||
                    await ReplyConfirmLocalized("fwdm_stop").ConfigureAwait(false);
 | 
			
		||||
@@ -207,83 +193,83 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task ForwardToAll()
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                    lock (_locker)
 | 
			
		||||
                        _forwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
 | 
			
		||||
                        _service.ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
                }
 | 
			
		||||
                if (_forwardDMsToAllOwners)
 | 
			
		||||
                if (_service.ForwardDMsToAllOwners)
 | 
			
		||||
                    await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false);
 | 
			
		||||
                else
 | 
			
		||||
                    await ReplyConfirmLocalized("fwall_stop").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task HandleDmForwarding(IUserMessage msg, ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels)
 | 
			
		||||
            {
 | 
			
		||||
                if (_forwardDMs && ownerChannels.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    var title = GetTextStatic("dm_from",
 | 
			
		||||
                                    NadekoBot.Localization.DefaultCultureInfo,
 | 
			
		||||
                                    typeof(Administration).Name.ToLowerInvariant()) +
 | 
			
		||||
                                $" [{msg.Author}]({msg.Author.Id})";
 | 
			
		||||
            //todo dm forwarding
 | 
			
		||||
            //public async Task HandleDmForwarding(IUserMessage msg, ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels)
 | 
			
		||||
            //{
 | 
			
		||||
            //    if (_service.ForwardDMs && ownerChannels.Length > 0)
 | 
			
		||||
            //    {
 | 
			
		||||
            //        var title = _strings.GetText("dm_from",
 | 
			
		||||
            //                        NadekoBot.Localization.DefaultCultureInfo,
 | 
			
		||||
            //                        typeof(Administration).Name.ToLowerInvariant()) +
 | 
			
		||||
            //                    $" [{msg.Author}]({msg.Author.Id})";
 | 
			
		||||
 | 
			
		||||
                    var attachamentsTxt = GetTextStatic("attachments",
 | 
			
		||||
                        NadekoBot.Localization.DefaultCultureInfo,
 | 
			
		||||
                        typeof(Administration).Name.ToLowerInvariant());
 | 
			
		||||
            //        var attachamentsTxt = GetTextStatic("attachments",
 | 
			
		||||
            //            NadekoBot.Localization.DefaultCultureInfo,
 | 
			
		||||
            //            typeof(Administration).Name.ToLowerInvariant());
 | 
			
		||||
 | 
			
		||||
                    var toSend = msg.Content;
 | 
			
		||||
            //        var toSend = msg.Content;
 | 
			
		||||
 | 
			
		||||
                    if (msg.Attachments.Count > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
 | 
			
		||||
                                  string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (_forwardDMsToAllOwners)
 | 
			
		||||
                    {
 | 
			
		||||
                        var allOwnerChannels = await Task.WhenAll(ownerChannels
 | 
			
		||||
                            .Select(x => x.Value))
 | 
			
		||||
                            .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
 | 
			
		||||
                        {
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                await ownerCh.SendConfirmAsync(title, toSend).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                            catch
 | 
			
		||||
                            {
 | 
			
		||||
                                _log.Warn("Can't contact owner with id {0}", ownerCh.Recipient.Id);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                            var firstOwnerChannel = await ownerChannels[0];
 | 
			
		||||
                            if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
 | 
			
		||||
                            {
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                                catch
 | 
			
		||||
                                {
 | 
			
		||||
                                    // ignored
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            //        if (msg.Attachments.Count > 0)
 | 
			
		||||
            //        {
 | 
			
		||||
            //            toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
 | 
			
		||||
            //                      string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
 | 
			
		||||
            //        }
 | 
			
		||||
 | 
			
		||||
            //        if (_service.ForwardDMsToAllOwners)
 | 
			
		||||
            //        {
 | 
			
		||||
            //            var allOwnerChannels = await Task.WhenAll(ownerChannels
 | 
			
		||||
            //                .Select(x => x.Value))
 | 
			
		||||
            //                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            //            foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
 | 
			
		||||
            //            {
 | 
			
		||||
            //                try
 | 
			
		||||
            //                {
 | 
			
		||||
            //                    await ownerCh.SendConfirmAsync(title, toSend).ConfigureAwait(false);
 | 
			
		||||
            //                }
 | 
			
		||||
            //                catch
 | 
			
		||||
            //                {
 | 
			
		||||
            //                    _log.Warn("Can't contact owner with id {0}", ownerCh.Recipient.Id);
 | 
			
		||||
            //                }
 | 
			
		||||
            //            }
 | 
			
		||||
            //        }
 | 
			
		||||
            //        else
 | 
			
		||||
            //        {
 | 
			
		||||
            //                var firstOwnerChannel = await ownerChannels[0];
 | 
			
		||||
            //                if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
 | 
			
		||||
            //                {
 | 
			
		||||
            //                    try
 | 
			
		||||
            //                    {
 | 
			
		||||
            //                        await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
 | 
			
		||||
            //                    }
 | 
			
		||||
            //                    catch
 | 
			
		||||
            //                    {
 | 
			
		||||
            //                        // ignored
 | 
			
		||||
            //                    }
 | 
			
		||||
            //                }
 | 
			
		||||
            //            }
 | 
			
		||||
            //        }
 | 
			
		||||
            //    }
 | 
			
		||||
            
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task ConnectShard(int shardid)
 | 
			
		||||
            {
 | 
			
		||||
                var shard = NadekoBot.Client.GetShard(shardid);
 | 
			
		||||
                var shard = _client.GetShard(shardid);
 | 
			
		||||
 | 
			
		||||
                if (shard == null)
 | 
			
		||||
                {
 | 
			
		||||
@@ -307,15 +293,15 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            public async Task Leave([Remainder] string guildStr)
 | 
			
		||||
            {
 | 
			
		||||
                guildStr = guildStr.Trim().ToUpperInvariant();
 | 
			
		||||
                var server = NadekoBot.Client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
 | 
			
		||||
                    NadekoBot.Client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
 | 
			
		||||
                var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
 | 
			
		||||
                    _client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
 | 
			
		||||
 | 
			
		||||
                if (server == null)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("no_server").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (server.OwnerId != NadekoBot.Client.CurrentUser.Id)
 | 
			
		||||
                if (server.OwnerId != _client.CurrentUser.Id)
 | 
			
		||||
                {
 | 
			
		||||
                    await server.LeaveAsync().ConfigureAwait(false);
 | 
			
		||||
                    await ReplyConfirmLocalized("left_server", Format.Bold(server.Name)).ConfigureAwait(false);
 | 
			
		||||
@@ -351,7 +337,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(newName))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
 | 
			
		||||
                await _client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("bot_name", Format.Bold(newName)).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -360,7 +346,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task SetStatus([Remainder] SettableUserStatus status)
 | 
			
		||||
            {
 | 
			
		||||
                await NadekoBot.Client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
 | 
			
		||||
                await _client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("bot_status", Format.Bold(status.ToString())).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -380,7 +366,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                        await sr.CopyToAsync(imgStream);
 | 
			
		||||
                        imgStream.Position = 0;
 | 
			
		||||
 | 
			
		||||
                        await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
 | 
			
		||||
                        await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -391,7 +377,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task SetGame([Remainder] string game = null)
 | 
			
		||||
            {
 | 
			
		||||
                await NadekoBot.Client.SetGameAsync(game).ConfigureAwait(false);
 | 
			
		||||
                await _client.SetGameAsync(game).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("set_game").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -402,7 +388,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            {
 | 
			
		||||
                name = name ?? "";
 | 
			
		||||
 | 
			
		||||
                await NadekoBot.Client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false);
 | 
			
		||||
                await _client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("set_stream").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -418,7 +404,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (ids.Length != 2)
 | 
			
		||||
                    return;
 | 
			
		||||
                var sid = ulong.Parse(ids[0]);
 | 
			
		||||
                var server = NadekoBot.Client.Guilds.FirstOrDefault(s => s.Id == sid);
 | 
			
		||||
                var server = _client.Guilds.FirstOrDefault(s => s.Id == sid);
 | 
			
		||||
 | 
			
		||||
                if (server == null)
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -455,7 +441,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task Announce([Remainder] string message)
 | 
			
		||||
            {
 | 
			
		||||
                var channels = NadekoBot.Client.Guilds.Select(g => g.DefaultChannel).ToArray();
 | 
			
		||||
                var channels = _client.Guilds.Select(g => g.DefaultChannel).ToArray();
 | 
			
		||||
                if (channels == null)
 | 
			
		||||
                    return;
 | 
			
		||||
                await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync(GetText("message_from_bo", Context.User.ToString()), message)))
 | 
			
		||||
@@ -468,7 +454,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [OwnerOnly]
 | 
			
		||||
            public async Task ReloadImages()
 | 
			
		||||
            {
 | 
			
		||||
                var time = await NadekoBot.Images.Reload().ConfigureAwait(false);
 | 
			
		||||
                var time = _images.Reload();
 | 
			
		||||
                await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,9 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Administration
 | 
			
		||||
@@ -18,158 +13,13 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class ServerGreetCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            //make this to a field in the guildconfig table
 | 
			
		||||
            private readonly GreetSettingsService _greetService;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            private new static Logger _log { get; }
 | 
			
		||||
            private static readonly GreetSettingsService greetService;
 | 
			
		||||
 | 
			
		||||
            static ServerGreetCommands()
 | 
			
		||||
            public ServerGreetCommands(GreetSettingsService greetService, DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.UserJoined += UserJoined;
 | 
			
		||||
                NadekoBot.Client.UserLeft += UserLeft;
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
                //todo di
 | 
			
		||||
                greetService = NadekoBot.GreetSettingsService;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static Task UserLeft(IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var conf = greetService.GetOrAddSettingsForGuild(user.GuildId);
 | 
			
		||||
 | 
			
		||||
                        if (!conf.SendChannelByeMessage) return;
 | 
			
		||||
                        var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId);
 | 
			
		||||
 | 
			
		||||
                        if (channel == null) //maybe warn the server owner that the channel is missing
 | 
			
		||||
                            return;
 | 
			
		||||
                        CREmbed embedData;
 | 
			
		||||
                        if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData))
 | 
			
		||||
                        {
 | 
			
		||||
                            embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                            embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                            embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                if (conf.AutoDeleteByeMessagesTimer > 0)
 | 
			
		||||
                                {
 | 
			
		||||
                                    toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                            if (string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                return;
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
                                if (conf.AutoDeleteByeMessagesTimer > 0)
 | 
			
		||||
                                {
 | 
			
		||||
                                    toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        // ignored
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static Task UserJoined(IGuildUser user)
 | 
			
		||||
            {
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var conf = greetService.GetOrAddSettingsForGuild(user.GuildId);
 | 
			
		||||
 | 
			
		||||
                        if (conf.SendChannelGreetMessage)
 | 
			
		||||
                        {
 | 
			
		||||
                            var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
 | 
			
		||||
                            if (channel != null) //maybe warn the server owner that the channel is missing
 | 
			
		||||
                            {
 | 
			
		||||
 | 
			
		||||
                                CREmbed embedData;
 | 
			
		||||
                                if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
 | 
			
		||||
                                {
 | 
			
		||||
                                    embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    try
 | 
			
		||||
                                    {
 | 
			
		||||
                                        var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                        if (conf.AutoDeleteGreetMessagesTimer > 0)
 | 
			
		||||
                                        {
 | 
			
		||||
                                            toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    if (!string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                    {
 | 
			
		||||
                                        try
 | 
			
		||||
                                        {
 | 
			
		||||
                                            var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
                                            if (conf.AutoDeleteGreetMessagesTimer > 0)
 | 
			
		||||
                                            {
 | 
			
		||||
                                                toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (conf.SendDmGreetMessage)
 | 
			
		||||
                        {
 | 
			
		||||
                            var channel = await user.CreateDMChannelAsync();
 | 
			
		||||
 | 
			
		||||
                            if (channel != null)
 | 
			
		||||
                            {
 | 
			
		||||
                                CREmbed embedData;
 | 
			
		||||
                                if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
 | 
			
		||||
                                {
 | 
			
		||||
                                    embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    try
 | 
			
		||||
                                    {
 | 
			
		||||
                                        await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                    if (!string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                    {
 | 
			
		||||
                                        await channel.SendConfirmAsync(msg).ConfigureAwait(false);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        // ignored
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
                _greetService = greetService;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -180,7 +30,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (timer < 0 || timer > 600)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await ServerGreetCommands.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
 | 
			
		||||
                await _greetService.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (timer > 0)
 | 
			
		||||
                    await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false);
 | 
			
		||||
@@ -188,29 +38,12 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    await ReplyConfirmLocalized("greetdel_off").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static async Task SetGreetDel(ulong id, int timer)
 | 
			
		||||
            {
 | 
			
		||||
                if (timer < 0 || timer > 600)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                {
 | 
			
		||||
                    var conf = uow.GuildConfigs.For(id, set => set);
 | 
			
		||||
                    conf.AutoDeleteGreetMessagesTimer = timer;
 | 
			
		||||
 | 
			
		||||
                    var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
                    greetService.GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageGuild)]
 | 
			
		||||
            public async Task Greet()
 | 
			
		||||
            {
 | 
			
		||||
                var enabled = await greetService.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
 | 
			
		||||
                var enabled = await _greetService.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (enabled)
 | 
			
		||||
                    await ReplyConfirmLocalized("greet_on").ConfigureAwait(false);
 | 
			
		||||
@@ -226,7 +59,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                {
 | 
			
		||||
                    string channelGreetMessageText;
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -234,7 +67,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var sendGreetEnabled = greetService.SetGreetMessage(Context.Guild.Id, ref text);
 | 
			
		||||
                var sendGreetEnabled = _greetService.SetGreetMessage(Context.Guild.Id, ref text);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("greetmsg_new").ConfigureAwait(false);
 | 
			
		||||
                if (!sendGreetEnabled)
 | 
			
		||||
@@ -246,7 +79,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageGuild)]
 | 
			
		||||
            public async Task GreetDm()
 | 
			
		||||
            {
 | 
			
		||||
                var enabled = await greetService.SetGreetDm(Context.Guild.Id).ConfigureAwait(false);
 | 
			
		||||
                var enabled = await _greetService.SetGreetDm(Context.Guild.Id).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (enabled)
 | 
			
		||||
                    await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false);
 | 
			
		||||
@@ -262,7 +95,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                {
 | 
			
		||||
                    GuildConfig config;
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        config = uow.GuildConfigs.For(Context.Guild.Id);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -270,7 +103,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var sendGreetEnabled = greetService.SetGreetDmMessage(Context.Guild.Id, ref text);
 | 
			
		||||
                var sendGreetEnabled = _greetService.SetGreetDmMessage(Context.Guild.Id, ref text);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("greetdmmsg_new").ConfigureAwait(false);
 | 
			
		||||
                if (!sendGreetEnabled)
 | 
			
		||||
@@ -282,7 +115,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageGuild)]
 | 
			
		||||
            public async Task Bye()
 | 
			
		||||
            {
 | 
			
		||||
                var enabled = await greetService.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
 | 
			
		||||
                var enabled = await _greetService.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (enabled)
 | 
			
		||||
                    await ReplyConfirmLocalized("bye_on").ConfigureAwait(false);
 | 
			
		||||
@@ -298,7 +131,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                {
 | 
			
		||||
                    string byeMessageText;
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -306,7 +139,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var sendByeEnabled = greetService.SetByeMessage(Context.Guild.Id, ref text);
 | 
			
		||||
                var sendByeEnabled = _greetService.SetByeMessage(Context.Guild.Id, ref text);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("byemsg_new").ConfigureAwait(false);
 | 
			
		||||
                if (!sendByeEnabled)
 | 
			
		||||
@@ -318,7 +151,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageGuild)]
 | 
			
		||||
            public async Task ByeDel(int timer = 30)
 | 
			
		||||
            {
 | 
			
		||||
                await greetService.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false);
 | 
			
		||||
                await _greetService.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                if (timer > 0)
 | 
			
		||||
                    await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
@@ -18,6 +19,16 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class UserPunishCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
            private readonly MuteService _muteService;
 | 
			
		||||
 | 
			
		||||
            public UserPunishCommands(DbHandler db, MuteService muteService)
 | 
			
		||||
            {
 | 
			
		||||
                _db = db;
 | 
			
		||||
 | 
			
		||||
                _muteService = muteService;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private async Task<PunishmentAction?> InternalWarn(IGuild guild, ulong userId, string modName, string reason)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(reason))
 | 
			
		||||
@@ -36,7 +47,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
 | 
			
		||||
                int warnings = 1;
 | 
			
		||||
                List<WarningPunishment> ps;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments))
 | 
			
		||||
                        .WarnPunishments;
 | 
			
		||||
@@ -62,9 +73,9 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    {
 | 
			
		||||
                        case PunishmentAction.Mute:
 | 
			
		||||
                            if (p.Time == 0)
 | 
			
		||||
                                await MuteCommands.MuteUser(user).ConfigureAwait(false);
 | 
			
		||||
                                await _muteService.MuteUser(user).ConfigureAwait(false);
 | 
			
		||||
                            else
 | 
			
		||||
                                await MuteCommands.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false);
 | 
			
		||||
                                await _muteService.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case PunishmentAction.Kick:
 | 
			
		||||
                            await user.KickAsync().ConfigureAwait(false);
 | 
			
		||||
@@ -147,7 +158,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (page < 0)
 | 
			
		||||
                    return;
 | 
			
		||||
                Warning[] warnings;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    warnings = uow.Warnings.For(Context.Guild.Id, userId);
 | 
			
		||||
                }
 | 
			
		||||
@@ -192,7 +203,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            [RequireUserPermission(GuildPermission.BanMembers)]
 | 
			
		||||
            public async Task Warnclear(ulong userId)
 | 
			
		||||
            {
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    await uow.Warnings.ForgiveAll(Context.Guild.Id, userId, Context.User.ToString()).ConfigureAwait(false);
 | 
			
		||||
                    uow.Complete();
 | 
			
		||||
@@ -212,7 +223,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (number <= 0)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
 | 
			
		||||
                    ps.RemoveAll(x => x.Count == number);
 | 
			
		||||
@@ -239,7 +250,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                if (number <= 0)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
 | 
			
		||||
                    var p = ps.FirstOrDefault(x => x.Count == number);
 | 
			
		||||
@@ -260,7 +271,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            public async Task WarnPunishList()
 | 
			
		||||
            {
 | 
			
		||||
                WarningPunishment[] ps;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    ps = uow.GuildConfigs.For(Context.Guild.Id, gc => gc.Include(x => x.WarnPunishments))
 | 
			
		||||
                        .WarnPunishments
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Administration
 | 
			
		||||
{
 | 
			
		||||
@@ -18,86 +19,13 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class VcRoleCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
 | 
			
		||||
            private readonly VcRoleService _service;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            static VcRoleCommands()
 | 
			
		||||
            public VcRoleCommands(VcRoleService service, DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
 | 
			
		||||
                VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
 | 
			
		||||
                foreach (var gconf in NadekoBot.AllGuildConfigs)
 | 
			
		||||
                {
 | 
			
		||||
                    var g = NadekoBot.Client.GetGuild(gconf.GuildId);
 | 
			
		||||
                    if (g == null)
 | 
			
		||||
                        continue; //todo delete everything from db if guild doesn't exist?
 | 
			
		||||
 | 
			
		||||
                    var infos = new ConcurrentDictionary<ulong, IRole>();
 | 
			
		||||
                    VcRoles.TryAdd(gconf.GuildId, infos);
 | 
			
		||||
                    foreach (var ri in gconf.VcRoleInfos)
 | 
			
		||||
                    {
 | 
			
		||||
                        var role = g.GetRole(ri.RoleId);
 | 
			
		||||
                        if (role == null)
 | 
			
		||||
                            continue; //todo remove this entry from db
 | 
			
		||||
 | 
			
		||||
                        infos.TryAdd(ri.VoiceChannelId, role);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
 | 
			
		||||
                SocketVoiceState newState)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                var gusr = usr as SocketGuildUser;
 | 
			
		||||
                if (gusr == null)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var oldVc = oldState.VoiceChannel;
 | 
			
		||||
                var newVc = newState.VoiceChannel;
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        if (oldVc != newVc)
 | 
			
		||||
                        {
 | 
			
		||||
                            ulong guildId;
 | 
			
		||||
                            guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
 | 
			
		||||
 | 
			
		||||
                            if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
 | 
			
		||||
                            {
 | 
			
		||||
                                //remove old
 | 
			
		||||
                                if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (gusr.Roles.Contains(role))
 | 
			
		||||
                                    {
 | 
			
		||||
                                        try
 | 
			
		||||
                                        {
 | 
			
		||||
                                            await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
 | 
			
		||||
                                            await Task.Delay(500).ConfigureAwait(false);
 | 
			
		||||
                                        }
 | 
			
		||||
                                        catch
 | 
			
		||||
                                        {
 | 
			
		||||
                                            await Task.Delay(200).ConfigureAwait(false);
 | 
			
		||||
                                            await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
 | 
			
		||||
                                            await Task.Delay(500).ConfigureAwait(false);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                //add new
 | 
			
		||||
                                if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (!gusr.Roles.Contains(role))
 | 
			
		||||
                                        await gusr.AddRoleAsync(role).ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        Administration._log.Warn(ex);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -118,14 +46,14 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var guildVcRoles = VcRoles.GetOrAdd(user.GuildId, new ConcurrentDictionary<ulong, IRole>());
 | 
			
		||||
                var guildVcRoles = _service.VcRoles.GetOrAdd(user.GuildId, new ConcurrentDictionary<ulong, IRole>());
 | 
			
		||||
 | 
			
		||||
                if (role == null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (guildVcRoles.TryRemove(vc.Id, out role))
 | 
			
		||||
                    {
 | 
			
		||||
                        await ReplyConfirmLocalized("vcrole_removed", Format.Bold(vc.Name)).ConfigureAwait(false);
 | 
			
		||||
                        using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                        using (var uow = _db.UnitOfWork)
 | 
			
		||||
                        {
 | 
			
		||||
                            var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
                            conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id);
 | 
			
		||||
@@ -136,7 +64,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    guildVcRoles.AddOrUpdate(vc.Id, role, (key, old) => role);
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos));
 | 
			
		||||
                        conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id); // remove old one
 | 
			
		||||
@@ -157,7 +85,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
            {
 | 
			
		||||
                var guild = (SocketGuild) Context.Guild;
 | 
			
		||||
                string text;
 | 
			
		||||
                if (VcRoles.TryGetValue(Context.Guild.Id, out ConcurrentDictionary<ulong, IRole> roles))
 | 
			
		||||
                if (_service.VcRoles.TryGetValue(Context.Guild.Id, out ConcurrentDictionary<ulong, IRole> roles))
 | 
			
		||||
                {
 | 
			
		||||
                    if (!roles.Any())
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
@@ -21,140 +22,15 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class VoicePlusTextCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private new static readonly Logger _log;
 | 
			
		||||
            private readonly VplusTService _service;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            private static readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
            private static readonly ConcurrentHashSet<ulong> _voicePlusTextCache;
 | 
			
		||||
            private static readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
 | 
			
		||||
            static VoicePlusTextCommands()
 | 
			
		||||
            public VoicePlusTextCommands(VplusTService service, DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                var sw = Stopwatch.StartNew();
 | 
			
		||||
 | 
			
		||||
                _voicePlusTextCache = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
 | 
			
		||||
                NadekoBot.Client.UserVoiceStateUpdated += UserUpdatedEventHandler;
 | 
			
		||||
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
                _service = service;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
 | 
			
		||||
            {
 | 
			
		||||
                var user = (iuser as SocketGuildUser);
 | 
			
		||||
                var guild = user?.Guild;
 | 
			
		||||
 | 
			
		||||
                if (guild == null)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var botUserPerms = guild.CurrentUser.GuildPermissions;
 | 
			
		||||
 | 
			
		||||
                if (before.VoiceChannel == after.VoiceChannel)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                if (!_voicePlusTextCache.Contains(guild.Id))
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
 | 
			
		||||
                        if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
 | 
			
		||||
                        {
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                await guild.Owner.SendErrorAsync(
 | 
			
		||||
                                    GetTextStatic("vt_exit",
 | 
			
		||||
                                        NadekoBot.Localization.GetCultureInfo(guild),
 | 
			
		||||
                                        typeof(Administration).Name.ToLowerInvariant(),
 | 
			
		||||
                                        Format.Bold(guild.Name))).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                            catch
 | 
			
		||||
                            {
 | 
			
		||||
                                // ignored
 | 
			
		||||
                            }
 | 
			
		||||
                            using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                            {
 | 
			
		||||
                                uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
 | 
			
		||||
                                _voicePlusTextCache.TryRemove(guild.Id);
 | 
			
		||||
                                await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
 | 
			
		||||
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            await semaphore.WaitAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            var beforeVch = before.VoiceChannel;
 | 
			
		||||
                            if (beforeVch != null)
 | 
			
		||||
                            {
 | 
			
		||||
                                var beforeRoleName = GetRoleName(beforeVch);
 | 
			
		||||
                                var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
 | 
			
		||||
                                if (beforeRole != null)
 | 
			
		||||
                                    try
 | 
			
		||||
                                    {
 | 
			
		||||
                                        _log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
 | 
			
		||||
                                        await user.RemoveRoleAsync(beforeRole).ConfigureAwait(false);
 | 
			
		||||
                                        await Task.Delay(200).ConfigureAwait(false);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    catch (Exception ex)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        _log.Warn(ex);
 | 
			
		||||
                                    }
 | 
			
		||||
                            }
 | 
			
		||||
                            var afterVch = after.VoiceChannel;
 | 
			
		||||
                            if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
 | 
			
		||||
                            {
 | 
			
		||||
                                var roleName = GetRoleName(afterVch);
 | 
			
		||||
                                var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
 | 
			
		||||
                                                  (IRole) await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                                ITextChannel textChannel = guild.TextChannels
 | 
			
		||||
                                                            .FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
 | 
			
		||||
                                if (textChannel == null)
 | 
			
		||||
                                {
 | 
			
		||||
                                    var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
                                    try { await guild.CurrentUser.AddRoleAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
 | 
			
		||||
                                    await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
                                    await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
 | 
			
		||||
                                        readMessages: PermValue.Allow,
 | 
			
		||||
                                        sendMessages: PermValue.Allow))
 | 
			
		||||
                                            .ConfigureAwait(false);
 | 
			
		||||
                                    await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
                                    await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
 | 
			
		||||
                                        readMessages: PermValue.Deny,
 | 
			
		||||
                                        sendMessages: PermValue.Deny))
 | 
			
		||||
                                            .ConfigureAwait(false);
 | 
			
		||||
                                    await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                                _log.Warn("Adding role " + roleToAdd.Name + " to user " + user.Username);
 | 
			
		||||
                                await user.AddRoleAsync(roleToAdd).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        finally
 | 
			
		||||
                        {
 | 
			
		||||
                            semaphore.Release();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Warn(ex);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static string GetChannelName(string voiceName) =>
 | 
			
		||||
                _channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
 | 
			
		||||
 | 
			
		||||
            private static string GetRoleName(IVoiceChannel ch) =>
 | 
			
		||||
                "nvoice-" + ch.Id;
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageRoles)]
 | 
			
		||||
@@ -184,7 +60,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    bool isEnabled;
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        var conf = uow.GuildConfigs.For(guild.Id, set => set);
 | 
			
		||||
                        isEnabled = conf.VoicePlusTextEnabled = !conf.VoicePlusTextEnabled;
 | 
			
		||||
@@ -192,7 +68,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!isEnabled)
 | 
			
		||||
                    {
 | 
			
		||||
                        _voicePlusTextCache.TryRemove(guild.Id);
 | 
			
		||||
                        _service.VoicePlusTextCache.TryRemove(guild.Id);
 | 
			
		||||
                        foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice")))
 | 
			
		||||
                        {
 | 
			
		||||
                            try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { }
 | 
			
		||||
@@ -207,7 +83,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                        await ReplyConfirmLocalized("vt_disabled").ConfigureAwait(false);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    _voicePlusTextCache.Add(guild.Id);
 | 
			
		||||
                    _service.VoicePlusTextCache.Add(guild.Id);
 | 
			
		||||
                    await ReplyConfirmLocalized("vt_enabled").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
@@ -236,7 +112,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                var voiceChannels = await guild.GetVoiceChannelsAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var boundTextChannels = textChannels.Where(c => c.Name.EndsWith("-voice"));
 | 
			
		||||
                var validTxtChannelNames = new HashSet<string>(voiceChannels.Select(c => GetChannelName(c.Name).ToLowerInvariant()));
 | 
			
		||||
                var validTxtChannelNames = new HashSet<string>(voiceChannels.Select(c => _service.GetChannelName(c.Name).ToLowerInvariant()));
 | 
			
		||||
                var invalidTxtChannels = boundTextChannels.Where(c => !validTxtChannelNames.Contains(c.Name));
 | 
			
		||||
 | 
			
		||||
                foreach (var c in invalidTxtChannels)
 | 
			
		||||
@@ -246,7 +122,7 @@ namespace NadekoBot.Modules.Administration
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                var boundRoles = guild.Roles.Where(r => r.Name.StartsWith("nvoice-"));
 | 
			
		||||
                var validRoleNames = new HashSet<string>(voiceChannels.Select(c => GetRoleName(c).ToLowerInvariant()));
 | 
			
		||||
                var validRoleNames = new HashSet<string>(voiceChannels.Select(c => _service.GetRoleName(c).ToLowerInvariant()));
 | 
			
		||||
                var invalidRoles = boundRoles.Where(r => !validRoleNames.Contains(r.Name));
 | 
			
		||||
 | 
			
		||||
                foreach (var r in invalidRoles)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,16 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class Acropobia : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            //channelId, game
 | 
			
		||||
            public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
 | 
			
		||||
 | 
			
		||||
            public Acropobia(DiscordShardedClient client)
 | 
			
		||||
            {
 | 
			
		||||
                _client = client;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            public async Task Acro(int time = 60)
 | 
			
		||||
@@ -32,7 +39,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                    return;
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
 | 
			
		||||
                var game = new AcrophobiaGame(channel, time);
 | 
			
		||||
                var game = new AcrophobiaGame(_client, _strings, channel, time);
 | 
			
		||||
                if (AcrophobiaGames.TryAdd(channel.Id, game))
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
@@ -59,6 +66,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            Voting
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //todo Isolate, this shouldn't print or anything like that.
 | 
			
		||||
        public class AcrophobiaGame
 | 
			
		||||
        {
 | 
			
		||||
            private readonly ITextChannel _channel;
 | 
			
		||||
@@ -79,10 +87,14 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            //text, votes
 | 
			
		||||
            private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>();
 | 
			
		||||
            private readonly Logger _log;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
            private readonly NadekoStrings _strings;
 | 
			
		||||
 | 
			
		||||
            public AcrophobiaGame(ITextChannel channel, int time)
 | 
			
		||||
            public AcrophobiaGame(DiscordShardedClient client, NadekoStrings strings, ITextChannel channel, int time)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                _client = client;
 | 
			
		||||
                _strings = strings;
 | 
			
		||||
 | 
			
		||||
                _channel = channel;
 | 
			
		||||
                _time = time;
 | 
			
		||||
@@ -123,7 +135,7 @@ $@"--
 | 
			
		||||
 | 
			
		||||
            public async Task Run()
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived += PotentialAcro;
 | 
			
		||||
                _client.MessageReceived += PotentialAcro;
 | 
			
		||||
                var embed = GetEmbed();
 | 
			
		||||
 | 
			
		||||
                //SUBMISSIONS PHASE
 | 
			
		||||
@@ -292,14 +304,14 @@ $@"--
 | 
			
		||||
 | 
			
		||||
            public void EnsureStopped()
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived -= PotentialAcro;
 | 
			
		||||
                _client.MessageReceived -= PotentialAcro;
 | 
			
		||||
                if (!_source.IsCancellationRequested)
 | 
			
		||||
                    _source.Cancel();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private string GetText(string key, params object[] replacements)
 | 
			
		||||
                => GetTextStatic(key,
 | 
			
		||||
                    NadekoBot.Localization.GetCultureInfo(_channel.Guild),
 | 
			
		||||
                => _strings.GetText(key,
 | 
			
		||||
                    _channel.Guild.Id,
 | 
			
		||||
                    typeof(Games).Name.ToLowerInvariant(),
 | 
			
		||||
                    replacements);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,10 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Games
 | 
			
		||||
{
 | 
			
		||||
@@ -19,72 +13,13 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class CleverBotCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private new static Logger _log { get; }
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
            private readonly GamesService _games;
 | 
			
		||||
 | 
			
		||||
            public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; }
 | 
			
		||||
 | 
			
		||||
            static CleverBotCommands()
 | 
			
		||||
            public CleverBotCommands(DbHandler db, GamesService games)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                var sw = Stopwatch.StartNew();
 | 
			
		||||
                
 | 
			
		||||
                CleverbotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
 | 
			
		||||
                    NadekoBot.AllGuildConfigs
 | 
			
		||||
                        .Where(gc => gc.CleverbotEnabled)
 | 
			
		||||
                        .ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => new ChatterBotSession(gc.GuildId), true)));
 | 
			
		||||
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static string PrepareMessage(IUserMessage msg, out ChatterBotSession cleverbot)
 | 
			
		||||
            {
 | 
			
		||||
                var channel = msg.Channel as ITextChannel;
 | 
			
		||||
                cleverbot = null;
 | 
			
		||||
 | 
			
		||||
                if (channel == null)
 | 
			
		||||
                    return null;
 | 
			
		||||
 | 
			
		||||
                Lazy<ChatterBotSession> lazyCleverbot;
 | 
			
		||||
                if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out lazyCleverbot))
 | 
			
		||||
                    return null;
 | 
			
		||||
 | 
			
		||||
                cleverbot = lazyCleverbot.Value;
 | 
			
		||||
 | 
			
		||||
                var nadekoId = NadekoBot.Client.CurrentUser.Id;
 | 
			
		||||
                var normalMention = $"<@{nadekoId}> ";
 | 
			
		||||
                var nickMention = $"<@!{nadekoId}> ";
 | 
			
		||||
                string message;
 | 
			
		||||
                if (msg.Content.StartsWith(normalMention))
 | 
			
		||||
                {
 | 
			
		||||
                    message = msg.Content.Substring(normalMention.Length).Trim();
 | 
			
		||||
                }
 | 
			
		||||
                else if (msg.Content.StartsWith(nickMention))
 | 
			
		||||
                {
 | 
			
		||||
                    message = msg.Content.Substring(nickMention.Length).Trim();
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return message;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static async Task<bool> TryAsk(ChatterBotSession cleverbot, ITextChannel channel, string message)
 | 
			
		||||
            {
 | 
			
		||||
                await channel.TriggerTypingAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var response = await cleverbot.Think(message).ConfigureAwait(false);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false); // try twice :\
 | 
			
		||||
                }
 | 
			
		||||
                return true;
 | 
			
		||||
                _db = db;
 | 
			
		||||
                _games = games;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -94,10 +29,9 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            {
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
 | 
			
		||||
                Lazy<ChatterBotSession> throwaway;
 | 
			
		||||
                if (CleverbotGuilds.TryRemove(channel.Guild.Id, out throwaway))
 | 
			
		||||
                if (_games.CleverbotGuilds.TryRemove(channel.Guild.Id, out Lazy<ChatterBotSession> throwaway))
 | 
			
		||||
                {
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    using (var uow = _db.UnitOfWork)
 | 
			
		||||
                    {
 | 
			
		||||
                        uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, false);
 | 
			
		||||
                        await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
@@ -106,9 +40,9 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                CleverbotGuilds.TryAdd(channel.Guild.Id, new Lazy<ChatterBotSession>(() => new ChatterBotSession(Context.Guild.Id), true));
 | 
			
		||||
                _games.CleverbotGuilds.TryAdd(channel.Guild.Id, new Lazy<ChatterBotSession>(() => new ChatterBotSession(Context.Guild.Id), true));
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, true);
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
@@ -118,41 +52,6 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class ChatterBotSession
 | 
			
		||||
        {
 | 
			
		||||
            private static NadekoRandom rng { get; } = new NadekoRandom();
 | 
			
		||||
            public string ChatterbotId { get; }
 | 
			
		||||
            public string ChannelId { get; }
 | 
			
		||||
            private int _botId = 6;
 | 
			
		||||
 | 
			
		||||
            public ChatterBotSession(ulong channelId)
 | 
			
		||||
            {
 | 
			
		||||
                ChannelId = channelId.ToString().ToBase64();
 | 
			
		||||
                ChatterbotId = rng.Next(0, 1000000).ToString().ToBase64();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private string apiEndpoint => "http://api.program-o.com/v2/chatbot/" +
 | 
			
		||||
                                          $"?bot_id={_botId}&" +
 | 
			
		||||
                                          "say={0}&" +
 | 
			
		||||
                                          $"convo_id=nadekobot_{ChatterbotId}_{ChannelId}&" +
 | 
			
		||||
                                          "format=json";
 | 
			
		||||
 | 
			
		||||
            public async Task<string> Think(string message)
 | 
			
		||||
            {
 | 
			
		||||
                using (var http = new HttpClient())
 | 
			
		||||
                {
 | 
			
		||||
                    var res = await http.GetStringAsync(string.Format(apiEndpoint, message)).ConfigureAwait(false);
 | 
			
		||||
                    var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
 | 
			
		||||
                    //Console.WriteLine(cbr.Convo_id);
 | 
			
		||||
                    return cbr.BotSay.Replace("<br/>", "\n");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class ChatterBotResponse
 | 
			
		||||
        {
 | 
			
		||||
            public string Convo_id { get; set; }
 | 
			
		||||
            public string BotSay { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -56,6 +56,7 @@ namespace NadekoBot.Modules.Games.Hangman
 | 
			
		||||
    public class HangmanGame: IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
        public IMessageChannel GameChannel { get; }
 | 
			
		||||
        public HashSet<char> Guesses { get; } = new HashSet<char>();
 | 
			
		||||
@@ -81,9 +82,11 @@ namespace NadekoBot.Modules.Games.Hangman
 | 
			
		||||
 | 
			
		||||
        public event Action<HangmanGame> OnEnded;
 | 
			
		||||
 | 
			
		||||
        public HangmanGame(IMessageChannel channel, string type)
 | 
			
		||||
        public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _client = client;
 | 
			
		||||
 | 
			
		||||
            this.GameChannel = channel;
 | 
			
		||||
            this.TermType = type.ToTitleCase();
 | 
			
		||||
        }
 | 
			
		||||
@@ -95,12 +98,12 @@ namespace NadekoBot.Modules.Games.Hangman
 | 
			
		||||
            if (this.Term == null)
 | 
			
		||||
                throw new KeyNotFoundException("Can't find a term with that type. Use hangmanlist command.");
 | 
			
		||||
            // start listening for answers when game starts
 | 
			
		||||
            NadekoBot.Client.MessageReceived += PotentialGuess;
 | 
			
		||||
            _client.MessageReceived += PotentialGuess;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task End()
 | 
			
		||||
        {
 | 
			
		||||
            NadekoBot.Client.MessageReceived -= PotentialGuess;
 | 
			
		||||
            _client.MessageReceived -= PotentialGuess;
 | 
			
		||||
            OnEnded(this);
 | 
			
		||||
            var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman();
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle("Hangman Game")
 | 
			
		||||
@@ -199,7 +202,7 @@ namespace NadekoBot.Modules.Games.Hangman
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            NadekoBot.Client.MessageReceived -= PotentialGuess;
 | 
			
		||||
            _client.MessageReceived -= PotentialGuess;
 | 
			
		||||
            OnEnded = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ using System.Collections.Concurrent;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Modules.Games.Hangman;
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Games
 | 
			
		||||
{
 | 
			
		||||
@@ -14,6 +15,13 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class HangmanCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            public HangmanCommands(DiscordShardedClient client)
 | 
			
		||||
            {
 | 
			
		||||
                _client = client;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //channelId, game
 | 
			
		||||
            public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -25,7 +33,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Hangman([Remainder]string type = "All")
 | 
			
		||||
            {
 | 
			
		||||
                var hm = new HangmanGame(Context.Channel, type);
 | 
			
		||||
                var hm = new HangmanGame(_client, Context.Channel, type);
 | 
			
		||||
 | 
			
		||||
                if (!HangmanGames.TryAdd(Context.Channel.Id, hm))
 | 
			
		||||
                {
 | 
			
		||||
@@ -35,8 +43,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
 | 
			
		||||
                hm.OnEnded += g =>
 | 
			
		||||
                {
 | 
			
		||||
                    HangmanGame throwaway;
 | 
			
		||||
                    HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
 | 
			
		||||
                    HangmanGames.TryRemove(g.GameChannel.Id, out HangmanGame throwaway);
 | 
			
		||||
                };
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
@@ -45,8 +52,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    try { await Context.Channel.SendErrorAsync(GetText("hangman_start_errored") + " " + ex.Message).ConfigureAwait(false); } catch { }
 | 
			
		||||
                    HangmanGame throwaway;
 | 
			
		||||
                    HangmanGames.TryRemove(Context.Channel.Id, out throwaway);
 | 
			
		||||
                    HangmanGames.TryRemove(Context.Channel.Id, out HangmanGame throwaway);
 | 
			
		||||
                    throwaway.Dispose();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Games.Models
 | 
			
		||||
{
 | 
			
		||||
    public class TypingGame
 | 
			
		||||
    {
 | 
			
		||||
        public const float WORD_VALUE = 4.5f;
 | 
			
		||||
        public ITextChannel Channel { get; }
 | 
			
		||||
        public string CurrentSentence { get; private set; }
 | 
			
		||||
        public bool IsActive { get; private set; }
 | 
			
		||||
        private readonly Stopwatch sw;
 | 
			
		||||
        private readonly List<ulong> finishedUserIds;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly GamesService _games;
 | 
			
		||||
 | 
			
		||||
        private Logger _log { get; }
 | 
			
		||||
 | 
			
		||||
        public TypingGame(GamesService games, DiscordShardedClient client, ITextChannel channel)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _games = games;
 | 
			
		||||
            _client = client;
 | 
			
		||||
 | 
			
		||||
            this.Channel = channel;
 | 
			
		||||
            IsActive = false;
 | 
			
		||||
            sw = new Stopwatch();
 | 
			
		||||
            finishedUserIds = new List<ulong>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> Stop()
 | 
			
		||||
        {
 | 
			
		||||
            if (!IsActive) return false;
 | 
			
		||||
            _client.MessageReceived -= AnswerReceived;
 | 
			
		||||
            finishedUserIds.Clear();
 | 
			
		||||
            IsActive = false;
 | 
			
		||||
            sw.Stop();
 | 
			
		||||
            sw.Reset();
 | 
			
		||||
            try { await Channel.SendConfirmAsync("Typing contest stopped.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Start()
 | 
			
		||||
        {
 | 
			
		||||
            if (IsActive) return; // can't start running game
 | 
			
		||||
            IsActive = true;
 | 
			
		||||
            CurrentSentence = GetRandomSentence();
 | 
			
		||||
            var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await Channel.SendConfirmAsync($@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var msg = await Channel.SendMessageAsync("Starting new typing contest in **3**...").ConfigureAwait(false);
 | 
			
		||||
                await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **2**...").ConfigureAwait(false);
 | 
			
		||||
                    await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                    await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **1**...").ConfigureAwait(false);
 | 
			
		||||
                    await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
 | 
			
		||||
                await msg.ModifyAsync(m => m.Content = Format.Bold(Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions())).ConfigureAwait(false);
 | 
			
		||||
                sw.Start();
 | 
			
		||||
                HandleAnswers();
 | 
			
		||||
 | 
			
		||||
                while (i > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                    i--;
 | 
			
		||||
                    if (!IsActive)
 | 
			
		||||
                        return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            catch { }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                await Stop().ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetRandomSentence()
 | 
			
		||||
        {
 | 
			
		||||
            if (_games.TypingArticles.Any())
 | 
			
		||||
                return _games.TypingArticles[new NadekoRandom().Next(0, _games.TypingArticles.Count)].Text;
 | 
			
		||||
            else
 | 
			
		||||
                return $"No typing articles found. Use {NadekoBot.Prefix}typeadd command to add a new article for typing.";
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void HandleAnswers()
 | 
			
		||||
        {
 | 
			
		||||
            _client.MessageReceived += AnswerReceived;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task AnswerReceived(SocketMessage imsg)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (imsg.Author.IsBot)
 | 
			
		||||
                    return;
 | 
			
		||||
                var msg = imsg as SocketUserMessage;
 | 
			
		||||
                if (msg == null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (this.Channel == null || this.Channel.Id != msg.Channel.Id) return;
 | 
			
		||||
 | 
			
		||||
                var guess = msg.Content;
 | 
			
		||||
 | 
			
		||||
                var distance = CurrentSentence.LevenshteinDistance(guess);
 | 
			
		||||
                var decision = Judge(distance, guess.Length);
 | 
			
		||||
                if (decision && !finishedUserIds.Contains(msg.Author.Id))
 | 
			
		||||
                {
 | 
			
		||||
                    var elapsed = sw.Elapsed;
 | 
			
		||||
                    var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60;
 | 
			
		||||
                    finishedUserIds.Add(msg.Author.Id);
 | 
			
		||||
                    await this.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
 | 
			
		||||
                        .WithTitle($"{msg.Author} finished the race!")
 | 
			
		||||
                        .AddField(efb => efb.WithName("Place").WithValue($"#{finishedUserIds.Count}").WithIsInline(true))
 | 
			
		||||
                        .AddField(efb => efb.WithName("WPM").WithValue($"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*").WithIsInline(true))
 | 
			
		||||
                        .AddField(efb => efb.WithName("Errors").WithValue(distance.ToString()).WithIsInline(true)))
 | 
			
		||||
                            .ConfigureAwait(false);
 | 
			
		||||
                    if (finishedUserIds.Count % 4 == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        await this.Channel.SendConfirmAsync($":exclamation: A lot of people finished, here is the text for those still typing:\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions()}**").ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool Judge(int errors, int textLength) => errors <= textLength / 25;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
@@ -28,89 +29,20 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class PlantPickCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static ConcurrentHashSet<ulong> generationChannels { get; }
 | 
			
		||||
            //channelid/message
 | 
			
		||||
            private static ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>();
 | 
			
		||||
            //channelId/last generation
 | 
			
		||||
            private static ConcurrentDictionary<ulong, DateTime> lastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
 | 
			
		||||
            private readonly CurrencyHandler _ch;
 | 
			
		||||
            private readonly BotConfig _bc;
 | 
			
		||||
            private readonly GamesService _games;
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
            static PlantPickCommands()
 | 
			
		||||
            public PlantPickCommands(BotConfig bc, CurrencyHandler ch, GamesService games,
 | 
			
		||||
                DbHandler db)
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived += PotentialFlowerGeneration;
 | 
			
		||||
                generationChannels = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs
 | 
			
		||||
                    .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
 | 
			
		||||
                _bc = bc;
 | 
			
		||||
                _ch = ch;
 | 
			
		||||
                _games = games;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static Task PotentialFlowerGeneration(SocketMessage imsg)
 | 
			
		||||
            {
 | 
			
		||||
                var msg = imsg as SocketUserMessage;
 | 
			
		||||
                if (msg == null || msg.IsAuthor() || msg.Author.IsBot)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var channel = imsg.Channel as ITextChannel;
 | 
			
		||||
                if (channel == null)
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                if (!generationChannels.Contains(channel.Id))
 | 
			
		||||
                    return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                var _ = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var lastGeneration = lastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
 | 
			
		||||
                        var rng = new NadekoRandom();
 | 
			
		||||
 | 
			
		||||
                        //todo i'm stupid :rofl: wtg kwoth. real async programming :100: :ok_hand: :100: :100: :thumbsup:
 | 
			
		||||
                        if (DateTime.Now - TimeSpan.FromSeconds(NadekoBot.BotConfig.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again
 | 
			
		||||
                            return;
 | 
			
		||||
 | 
			
		||||
                        var num = rng.Next(1, 101) + NadekoBot.BotConfig.CurrencyGenerationChance * 100;
 | 
			
		||||
 | 
			
		||||
                        if (num > 100)
 | 
			
		||||
                        {
 | 
			
		||||
                            lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
 | 
			
		||||
 | 
			
		||||
                            var dropAmount = NadekoBot.BotConfig.CurrencyDropAmount;
 | 
			
		||||
 | 
			
		||||
                            if (dropAmount > 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                var msgs = new IUserMessage[dropAmount];
 | 
			
		||||
                                var prefix = NadekoBot.ModulePrefixes[typeof(Games).Name];
 | 
			
		||||
                                var toSend = dropAmount == 1 
 | 
			
		||||
                                    ? GetLocalText(channel, "curgen_sn", NadekoBot.BotConfig.CurrencySign) 
 | 
			
		||||
                                        + " " + GetLocalText(channel, "pick_sn", prefix)
 | 
			
		||||
                                    : GetLocalText(channel, "curgen_pl", dropAmount, NadekoBot.BotConfig.CurrencySign)
 | 
			
		||||
                                        + " " + GetLocalText(channel, "pick_pl", prefix);
 | 
			
		||||
                                var file = GetRandomCurrencyImage();
 | 
			
		||||
                                using (var fileStream = file.Value.ToStream())
 | 
			
		||||
                                {
 | 
			
		||||
                                    var sent = await channel.SendFileAsync(
 | 
			
		||||
                                        fileStream,
 | 
			
		||||
                                        file.Key,
 | 
			
		||||
                                        toSend).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                                    msgs[0] = sent;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                plantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; });
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        LogManager.GetCurrentClassLogger().Warn(ex);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public static string GetLocalText(ITextChannel channel, string key, params object[] replacements) =>
 | 
			
		||||
                NadekoTopLevelModule.GetTextStatic(key,
 | 
			
		||||
                    NadekoBot.Localization.GetCultureInfo(channel.GuildId),
 | 
			
		||||
                    typeof(Games).Name.ToLowerInvariant(),
 | 
			
		||||
                    replacements);
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            public async Task Pick()
 | 
			
		||||
@@ -120,16 +52,15 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                List<IUserMessage> msgs;
 | 
			
		||||
 | 
			
		||||
                try { await Context.Message.DeleteAsync().ConfigureAwait(false); } catch { }
 | 
			
		||||
                if (!plantedFlowers.TryRemove(channel.Id, out msgs))
 | 
			
		||||
                if (!_games.PlantedFlowers.TryRemove(channel.Id, out List<IUserMessage> msgs))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                await CurrencyHandler.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {NadekoBot.BotConfig.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
 | 
			
		||||
                var msg = await ReplyConfirmLocalized("picked", msgs.Count + NadekoBot.BotConfig.CurrencySign)
 | 
			
		||||
                await _ch.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {_bc.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
 | 
			
		||||
                var msg = await ReplyConfirmLocalized("picked", msgs.Count + _bc.CurrencySign)
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
                msg.DeleteAfter(10);
 | 
			
		||||
            }
 | 
			
		||||
@@ -141,21 +72,21 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                if (amount < 1)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false);
 | 
			
		||||
                var removed = await _ch.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {_bc.CurrencyName}", amount, false).ConfigureAwait(false);
 | 
			
		||||
                if (!removed)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("not_enough", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
 | 
			
		||||
                    await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var imgData = GetRandomCurrencyImage();
 | 
			
		||||
                var imgData = _games.GetRandomCurrencyImage();
 | 
			
		||||
 | 
			
		||||
                //todo upload all currency images to transfer.sh and use that one as cdn
 | 
			
		||||
                //and then 
 | 
			
		||||
 | 
			
		||||
                var msgToSend = GetText("planted",
 | 
			
		||||
                    Format.Bold(Context.User.ToString()),
 | 
			
		||||
                    amount + NadekoBot.BotConfig.CurrencySign,
 | 
			
		||||
                    amount + _bc.CurrencySign,
 | 
			
		||||
                    Prefix);
 | 
			
		||||
 | 
			
		||||
                if (amount > 1)
 | 
			
		||||
@@ -172,7 +103,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                var msgs = new IUserMessage[amount];
 | 
			
		||||
                msgs[0] = msg;
 | 
			
		||||
 | 
			
		||||
                plantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) =>
 | 
			
		||||
                _games.PlantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) =>
 | 
			
		||||
                {
 | 
			
		||||
                    old.AddRange(msgs);
 | 
			
		||||
                    return old;
 | 
			
		||||
@@ -190,7 +121,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
 | 
			
		||||
                bool enabled;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
 | 
			
		||||
 | 
			
		||||
@@ -198,13 +129,13 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                    if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
 | 
			
		||||
                    {
 | 
			
		||||
                        guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
 | 
			
		||||
                        generationChannels.Add(channel.Id);
 | 
			
		||||
                        _games.GenerationChannels.Add(channel.Id);
 | 
			
		||||
                        enabled = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        guildConfig.GenerateCurrencyChannelIds.Remove(toAdd);
 | 
			
		||||
                        generationChannels.TryRemove(channel.Id);
 | 
			
		||||
                        _games.GenerationChannels.TryRemove(channel.Id);
 | 
			
		||||
                        enabled = false;
 | 
			
		||||
                    }
 | 
			
		||||
                    await uow.CompleteAsync();
 | 
			
		||||
@@ -218,14 +149,6 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                    await ReplyConfirmLocalized("curgen_disabled").ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
 | 
			
		||||
            {
 | 
			
		||||
                var rng = new NadekoRandom();
 | 
			
		||||
                var images = NadekoBot.Images.Currency;
 | 
			
		||||
 | 
			
		||||
                return images[rng.Next(0, images.Length)];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using ImageSharp.Processing;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Games
 | 
			
		||||
{
 | 
			
		||||
@@ -19,6 +20,12 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
        public class PollCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            public PollCommands(DiscordShardedClient client)
 | 
			
		||||
            {
 | 
			
		||||
                _client = client;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireUserPermission(GuildPermission.ManageMessages)]
 | 
			
		||||
@@ -54,7 +61,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                if (data.Length < 3)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var poll = new Poll(Context.Message, data[0], data.Skip(1), isPublic: isPublic);
 | 
			
		||||
                var poll = new Poll(_client, _strings, Context.Message, data[0], data.Skip(1), isPublic: isPublic);
 | 
			
		||||
                if (ActivePolls.TryAdd(channel.Guild.Id, poll))
 | 
			
		||||
                {
 | 
			
		||||
                    await poll.StartPoll().ConfigureAwait(false);
 | 
			
		||||
@@ -83,10 +90,16 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            private string[] answers { get; }
 | 
			
		||||
            private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
 | 
			
		||||
            private readonly string _question;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
            private readonly NadekoStrings _strings;
 | 
			
		||||
 | 
			
		||||
            public bool IsPublic { get; }
 | 
			
		||||
 | 
			
		||||
            public Poll(IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
 | 
			
		||||
            public Poll(DiscordShardedClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
 | 
			
		||||
            {
 | 
			
		||||
                _client = client;
 | 
			
		||||
                _strings = strings;
 | 
			
		||||
 | 
			
		||||
                _originalMessage = umsg;
 | 
			
		||||
                _guild = ((ITextChannel)umsg.Channel).Guild;
 | 
			
		||||
                _question = question;
 | 
			
		||||
@@ -134,7 +147,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
 | 
			
		||||
            public async Task StartPoll()
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived += Vote;
 | 
			
		||||
                _client.MessageReceived += Vote;
 | 
			
		||||
                var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n";
 | 
			
		||||
                var num = 1;
 | 
			
		||||
                msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
 | 
			
		||||
@@ -147,7 +160,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
 | 
			
		||||
            public async Task StopPoll()
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived -= Vote;
 | 
			
		||||
                _client.MessageReceived -= Vote;
 | 
			
		||||
                await _originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -205,8 +218,8 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private string GetText(string key, params object[] replacements)
 | 
			
		||||
                => NadekoTopLevelModule.GetTextStatic(key,
 | 
			
		||||
                    NadekoBot.Localization.GetCultureInfo(_guild.Id),
 | 
			
		||||
                => _strings.GetText(key,
 | 
			
		||||
                    _guild.Id,
 | 
			
		||||
                    typeof(Games).Name.ToLowerInvariant(),
 | 
			
		||||
                    replacements);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,11 @@ using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Games.Commands.Models;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Modules.Games.Models;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
@@ -19,145 +16,18 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
{
 | 
			
		||||
    public partial class Games
 | 
			
		||||
    {
 | 
			
		||||
        public class TypingGame
 | 
			
		||||
        {
 | 
			
		||||
            public const float WORD_VALUE = 4.5f;
 | 
			
		||||
            public ITextChannel Channel { get; }
 | 
			
		||||
            public string CurrentSentence { get; private set; }
 | 
			
		||||
            public bool IsActive { get; private set; }
 | 
			
		||||
            private readonly Stopwatch sw;
 | 
			
		||||
            private readonly List<ulong> finishedUserIds;
 | 
			
		||||
            private Logger _log { get; }
 | 
			
		||||
 | 
			
		||||
            public TypingGame(ITextChannel channel)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
                this.Channel = channel;
 | 
			
		||||
                IsActive = false;
 | 
			
		||||
                sw = new Stopwatch();
 | 
			
		||||
                finishedUserIds = new List<ulong>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public async Task<bool> Stop()
 | 
			
		||||
            {
 | 
			
		||||
                if (!IsActive) return false;
 | 
			
		||||
                NadekoBot.Client.MessageReceived -= AnswerReceived;
 | 
			
		||||
                finishedUserIds.Clear();
 | 
			
		||||
                IsActive = false;
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                sw.Reset();
 | 
			
		||||
                try { await Channel.SendConfirmAsync("Typing contest stopped.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public async Task Start()
 | 
			
		||||
            {
 | 
			
		||||
                if (IsActive) return; // can't start running game
 | 
			
		||||
                IsActive = true;
 | 
			
		||||
                CurrentSentence = GetRandomSentence();
 | 
			
		||||
                var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await Channel.SendConfirmAsync($@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    var msg = await Channel.SendMessageAsync("Starting new typing contest in **3**...").ConfigureAwait(false);
 | 
			
		||||
                    await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **2**...").ConfigureAwait(false);
 | 
			
		||||
                        await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                        await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **1**...").ConfigureAwait(false);
 | 
			
		||||
                        await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
 | 
			
		||||
                    await msg.ModifyAsync(m => m.Content = Format.Bold(Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions())).ConfigureAwait(false);
 | 
			
		||||
                    sw.Start();
 | 
			
		||||
                    HandleAnswers();
 | 
			
		||||
 | 
			
		||||
                    while (i > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                        i--;
 | 
			
		||||
                        if (!IsActive)
 | 
			
		||||
                            return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                catch { }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    await Stop().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public string GetRandomSentence()
 | 
			
		||||
            {
 | 
			
		||||
                if (SpeedTypingCommands.TypingArticles.Any())
 | 
			
		||||
                    return SpeedTypingCommands.TypingArticles[new NadekoRandom().Next(0, SpeedTypingCommands.TypingArticles.Count)].Text;
 | 
			
		||||
                else
 | 
			
		||||
                    return $"No typing articles found. Use {NadekoBot.ModulePrefixes[typeof(Games).Name]}typeadd command to add a new article for typing.";
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private void HandleAnswers()
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived += AnswerReceived;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private async Task AnswerReceived(SocketMessage imsg)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (imsg.Author.IsBot)
 | 
			
		||||
                        return;
 | 
			
		||||
                    var msg = imsg as SocketUserMessage;
 | 
			
		||||
                    if (msg == null)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    if (this.Channel == null || this.Channel.Id != msg.Channel.Id) return;
 | 
			
		||||
 | 
			
		||||
                    var guess = msg.Content;
 | 
			
		||||
 | 
			
		||||
                    var distance = CurrentSentence.LevenshteinDistance(guess);
 | 
			
		||||
                    var decision = Judge(distance, guess.Length);
 | 
			
		||||
                    if (decision && !finishedUserIds.Contains(msg.Author.Id))
 | 
			
		||||
                    {
 | 
			
		||||
                        var elapsed = sw.Elapsed;
 | 
			
		||||
                        var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60;
 | 
			
		||||
                        finishedUserIds.Add(msg.Author.Id);
 | 
			
		||||
                        await this.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
 | 
			
		||||
                            .WithTitle($"{msg.Author} finished the race!")
 | 
			
		||||
                            .AddField(efb => efb.WithName("Place").WithValue($"#{finishedUserIds.Count}").WithIsInline(true))
 | 
			
		||||
                            .AddField(efb => efb.WithName("WPM").WithValue($"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*").WithIsInline(true))
 | 
			
		||||
                            .AddField(efb => efb.WithName("Errors").WithValue(distance.ToString()).WithIsInline(true)))
 | 
			
		||||
                                .ConfigureAwait(false);
 | 
			
		||||
                        if (finishedUserIds.Count % 4 == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            await this.Channel.SendConfirmAsync($":exclamation: A lot of people finished, here is the text for those still typing:\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions()}**").ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private bool Judge(int errors, int textLength) => errors <= textLength / 25;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class SpeedTypingCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            public static List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
 | 
			
		||||
 | 
			
		||||
            private const string _typingArticlesPath = "data/typing_articles2.json";
 | 
			
		||||
 | 
			
		||||
            static SpeedTypingCommands()
 | 
			
		||||
            {
 | 
			
		||||
                try { TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(_typingArticlesPath)); } catch { }
 | 
			
		||||
            }
 | 
			
		||||
            public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
 | 
			
		||||
            private readonly GamesService _games;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            public SpeedTypingCommands(DiscordShardedClient client, GamesService games)
 | 
			
		||||
            {
 | 
			
		||||
                _games = games;
 | 
			
		||||
                _client = client;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
@@ -165,7 +35,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            {
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
 | 
			
		||||
                var game = RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(channel));
 | 
			
		||||
                var game = RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(_games, _client, channel));
 | 
			
		||||
 | 
			
		||||
                if (game.IsActive)
 | 
			
		||||
                {
 | 
			
		||||
@@ -202,13 +72,14 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            {
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
 | 
			
		||||
                TypingArticles.Add(new TypingArticle
 | 
			
		||||
                _games.TypingArticles.Add(new TypingArticle
 | 
			
		||||
                {
 | 
			
		||||
                    Title = $"Text added on {DateTime.UtcNow} by {Context.User}",
 | 
			
		||||
                    Text = text.SanitizeMentions(),
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                File.WriteAllText(_typingArticlesPath, JsonConvert.SerializeObject(TypingArticles));
 | 
			
		||||
                //todo move this to service
 | 
			
		||||
                File.WriteAllText(_games.TypingArticlesPath, JsonConvert.SerializeObject(_games.TypingArticles));
 | 
			
		||||
 | 
			
		||||
                await channel.SendConfirmAsync("Added new article for typing game.").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -222,7 +93,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                if (page < 1)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var articles = TypingArticles.Skip((page - 1) * 15).Take(15).ToArray();
 | 
			
		||||
                var articles = _games.TypingArticles.Skip((page - 1) * 15).Take(15).ToArray();
 | 
			
		||||
 | 
			
		||||
                if (!articles.Any())
 | 
			
		||||
                {
 | 
			
		||||
@@ -242,13 +113,13 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
 | 
			
		||||
                index -= 1;
 | 
			
		||||
                if (index < 0 || index >= TypingArticles.Count)
 | 
			
		||||
                if (index < 0 || index >= _games.TypingArticles.Count)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var removed = TypingArticles[index];
 | 
			
		||||
                TypingArticles.RemoveAt(index);
 | 
			
		||||
                var removed = _games.TypingArticles[index];
 | 
			
		||||
                _games.TypingArticles.RemoveAt(index);
 | 
			
		||||
 | 
			
		||||
                File.WriteAllText(_typingArticlesPath, JsonConvert.SerializeObject(TypingArticles));
 | 
			
		||||
                File.WriteAllText(_games.TypingArticlesPath, JsonConvert.SerializeObject(_games.TypingArticles));
 | 
			
		||||
 | 
			
		||||
                await channel.SendConfirmAsync($"`Removed typing article:` #{index + 1} - {removed.Text.TrimTo(50)}")
 | 
			
		||||
                             .ConfigureAwait(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NLog;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text;
 | 
			
		||||
@@ -20,6 +21,12 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
 | 
			
		||||
 | 
			
		||||
            private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            public TicTacToeCommands(DiscordShardedClient client)
 | 
			
		||||
            {
 | 
			
		||||
                _client = client;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
@@ -39,7 +46,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                        });
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    game = new TicTacToe(channel, (IGuildUser)Context.User);
 | 
			
		||||
                    game = new TicTacToe(_strings, _client, channel, (IGuildUser)Context.User);
 | 
			
		||||
                    _games.Add(channel.Id, game);
 | 
			
		||||
                    await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
@@ -79,10 +86,15 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
 | 
			
		||||
            private IUserMessage _previousMessage;
 | 
			
		||||
            private Timer _timeoutTimer;
 | 
			
		||||
            private readonly NadekoStrings _strings;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
 | 
			
		||||
            public TicTacToe(ITextChannel channel, IGuildUser firstUser)
 | 
			
		||||
            public TicTacToe(NadekoStrings strings, DiscordShardedClient client, ITextChannel channel, IGuildUser firstUser)
 | 
			
		||||
            {
 | 
			
		||||
                _channel = channel;
 | 
			
		||||
                _strings = strings;
 | 
			
		||||
                _client = client;
 | 
			
		||||
 | 
			
		||||
                _users = new[] { firstUser, null };
 | 
			
		||||
                _state = new int?[,] {
 | 
			
		||||
                    { null, null, null },
 | 
			
		||||
@@ -95,8 +107,8 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private string GetText(string key, params object[] replacements) =>
 | 
			
		||||
                NadekoTopLevelModule.GetTextStatic(key,
 | 
			
		||||
                    NadekoBot.Localization.GetCultureInfo(_channel.GuildId),
 | 
			
		||||
                _strings.GetText(key,
 | 
			
		||||
                    _channel.GuildId,
 | 
			
		||||
                    typeof(Games).Name.ToLowerInvariant(),
 | 
			
		||||
                    replacements);
 | 
			
		||||
 | 
			
		||||
@@ -206,7 +218,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                    }
 | 
			
		||||
                }, null, 15000, Timeout.Infinite);
 | 
			
		||||
 | 
			
		||||
                NadekoBot.Client.MessageReceived += Client_MessageReceived;
 | 
			
		||||
                _client.MessageReceived += Client_MessageReceived;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                _previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
 | 
			
		||||
@@ -283,14 +295,14 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                            {
 | 
			
		||||
                                reason = GetText("ttt_matched_three");
 | 
			
		||||
                                _winner = _users[_curUserIndex];
 | 
			
		||||
                                NadekoBot.Client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
                                _client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
                                OnEnded?.Invoke(this);
 | 
			
		||||
                            }
 | 
			
		||||
                            else if (IsDraw())
 | 
			
		||||
                            {
 | 
			
		||||
                                reason = GetText("ttt_a_draw");
 | 
			
		||||
                                _phase = Phase.Ended;
 | 
			
		||||
                                NadekoBot.Client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
                                _client.MessageReceived -= Client_MessageReceived;
 | 
			
		||||
                                OnEnded?.Invoke(this);
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ using Discord.Net;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
@@ -18,6 +19,10 @@ namespace NadekoBot.Modules.Games.Trivia
 | 
			
		||||
    {
 | 
			
		||||
        private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly BotConfig _bc;
 | 
			
		||||
        private readonly CurrencyHandler _ch;
 | 
			
		||||
 | 
			
		||||
        public IGuild Guild { get; }
 | 
			
		||||
        public ITextChannel Channel { get; }
 | 
			
		||||
@@ -38,9 +43,15 @@ namespace NadekoBot.Modules.Games.Trivia
 | 
			
		||||
 | 
			
		||||
        public int WinRequirement { get; }
 | 
			
		||||
 | 
			
		||||
        public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq, bool isPokemon)
 | 
			
		||||
        public TriviaGame(NadekoStrings strings, DiscordShardedClient client, BotConfig bc,
 | 
			
		||||
            CurrencyHandler ch, IGuild guild, ITextChannel channel,
 | 
			
		||||
            bool showHints, int winReq, bool isPokemon)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _bc = bc;
 | 
			
		||||
            _ch = ch;
 | 
			
		||||
 | 
			
		||||
            ShowHints = showHints;
 | 
			
		||||
            Guild = guild;
 | 
			
		||||
@@ -50,8 +61,8 @@ namespace NadekoBot.Modules.Games.Trivia
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetText(string key, params object[] replacements) =>
 | 
			
		||||
            NadekoTopLevelModule.GetTextStatic(key,
 | 
			
		||||
                NadekoBot.Localization.GetCultureInfo(Channel.GuildId),
 | 
			
		||||
            _strings.GetText(key,
 | 
			
		||||
                Channel.GuildId,
 | 
			
		||||
                typeof(Games).Name.ToLowerInvariant(),
 | 
			
		||||
                replacements);
 | 
			
		||||
 | 
			
		||||
@@ -99,7 +110,7 @@ namespace NadekoBot.Modules.Games.Trivia
 | 
			
		||||
                //receive messages
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    NadekoBot.Client.MessageReceived += PotentialGuess;
 | 
			
		||||
                    _client.MessageReceived += PotentialGuess;
 | 
			
		||||
 | 
			
		||||
                    //allow people to guess
 | 
			
		||||
                    GameActive = true;
 | 
			
		||||
@@ -128,7 +139,7 @@ namespace NadekoBot.Modules.Games.Trivia
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    GameActive = false;
 | 
			
		||||
                    NadekoBot.Client.MessageReceived -= PotentialGuess;
 | 
			
		||||
                    _client.MessageReceived -= PotentialGuess;
 | 
			
		||||
                }
 | 
			
		||||
                if (!triviaCancelSource.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
@@ -214,9 +225,9 @@ namespace NadekoBot.Modules.Games.Trivia
 | 
			
		||||
                    {
 | 
			
		||||
                        // ignored
 | 
			
		||||
                    }
 | 
			
		||||
                    var reward = NadekoBot.BotConfig.TriviaCurrencyReward;
 | 
			
		||||
                    var reward = _bc.TriviaCurrencyReward;
 | 
			
		||||
                    if (reward > 0)
 | 
			
		||||
                        await CurrencyHandler.AddCurrencyAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false);
 | 
			
		||||
                        await _ch.AddCurrencyAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Games.Trivia;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
@@ -14,8 +17,19 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class TriviaCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly CurrencyHandler _ch;
 | 
			
		||||
            private readonly DiscordShardedClient _client;
 | 
			
		||||
            private readonly BotConfig _bc;
 | 
			
		||||
 | 
			
		||||
            public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
 | 
			
		||||
 | 
			
		||||
            public TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyHandler ch)
 | 
			
		||||
            {
 | 
			
		||||
                _ch = ch;
 | 
			
		||||
                _client = client;
 | 
			
		||||
                _bc = bc;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            public Task Trivia([Remainder] string additionalArgs = "")
 | 
			
		||||
@@ -35,7 +49,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                var showHints = !additionalArgs.Contains("nohint");
 | 
			
		||||
                var isPokemon = additionalArgs.Contains("pokemon");
 | 
			
		||||
 | 
			
		||||
                var trivia = new TriviaGame(channel.Guild, channel, showHints, winReq, isPokemon);
 | 
			
		||||
                var trivia = new TriviaGame(_strings, _client, _bc, _ch, channel.Guild, channel, showHints, winReq, isPokemon);
 | 
			
		||||
                if (RunningTrivias.TryAdd(channel.Guild.Id, trivia))
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
 
 | 
			
		||||
@@ -14,19 +14,20 @@ using System.Net.Http;
 | 
			
		||||
using ImageSharp;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NLog;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Games
 | 
			
		||||
{
 | 
			
		||||
    [NadekoModule("Games", ">")]
 | 
			
		||||
    public partial class Games : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly ImmutableArray<string> _8BallResponses = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray();
 | 
			
		||||
        private readonly GamesService _games;
 | 
			
		||||
        private readonly IImagesService _images;
 | 
			
		||||
 | 
			
		||||
        private static readonly Timer _t = new Timer((_) =>
 | 
			
		||||
        public Games(GamesService games, IImagesService images)
 | 
			
		||||
        {
 | 
			
		||||
            _girlRatings.Clear();
 | 
			
		||||
 | 
			
		||||
        }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
 | 
			
		||||
            _games = games;
 | 
			
		||||
            _images = images;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Choose([Remainder] string list = null)
 | 
			
		||||
@@ -48,7 +49,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
 | 
			
		||||
            await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
 | 
			
		||||
                               .AddField(efb => efb.WithName("❓ " + GetText("question") ).WithValue(question).WithIsInline(false))
 | 
			
		||||
                               .AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_8BallResponses[new NadekoRandom().Next(0, _8BallResponses.Length)]).WithIsInline(false)));
 | 
			
		||||
                               .AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_games.EightBallResponses[new NadekoRandom().Next(0, _games.EightBallResponses.Length)]).WithIsInline(false)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -94,7 +95,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            else if ((pick == 0 && nadekoPick == 1) ||
 | 
			
		||||
                     (pick == 1 && nadekoPick == 2) ||
 | 
			
		||||
                     (pick == 2 && nadekoPick == 0))
 | 
			
		||||
                msg = GetText("rps_win", NadekoBot.Client.CurrentUser.Mention,
 | 
			
		||||
                msg = GetText("rps_win", Context.Client.CurrentUser.Mention,
 | 
			
		||||
                    getRpsPick(nadekoPick), getRpsPick(pick));
 | 
			
		||||
            else
 | 
			
		||||
                msg = GetText("rps_win", Context.User.Mention, getRpsPick(pick),
 | 
			
		||||
@@ -103,73 +104,12 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
            await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static readonly ConcurrentDictionary<ulong, GirlRating> _girlRatings = new ConcurrentDictionary<ulong, GirlRating>();
 | 
			
		||||
 | 
			
		||||
        public class GirlRating
 | 
			
		||||
        {
 | 
			
		||||
            private static readonly Logger _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            public double Crazy { get; }
 | 
			
		||||
            public double Hot { get; }
 | 
			
		||||
            public int Roll { get; }
 | 
			
		||||
            public string Advice { get; }
 | 
			
		||||
            public AsyncLazy<string> Url { get; }
 | 
			
		||||
 | 
			
		||||
            public GirlRating(double crazy, double hot, int roll, string advice)
 | 
			
		||||
            {
 | 
			
		||||
                Crazy = crazy;
 | 
			
		||||
                Hot = hot;
 | 
			
		||||
                Roll = roll;
 | 
			
		||||
                Advice = advice; // convenient to have it here, even though atm there are only few different ones.
 | 
			
		||||
 | 
			
		||||
                Url = new AsyncLazy<string>(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        using (var ms = new MemoryStream(NadekoBot.Images.WifeMatrix.ToArray(), false))
 | 
			
		||||
                        using (var img = new ImageSharp.Image(ms))
 | 
			
		||||
                        {
 | 
			
		||||
                            const int minx = 35;
 | 
			
		||||
                            const int miny = 385;
 | 
			
		||||
                            const int length = 345;
 | 
			
		||||
 | 
			
		||||
                            var pointx = (int)(minx + length * (Hot / 10));
 | 
			
		||||
                            var pointy = (int)(miny - length * ((Crazy - 4) / 6));
 | 
			
		||||
                            
 | 
			
		||||
                            using (var pointMs = new MemoryStream(NadekoBot.Images.RategirlDot.ToArray(), false))
 | 
			
		||||
                            using (var pointImg = new ImageSharp.Image(pointMs))
 | 
			
		||||
                            {
 | 
			
		||||
                                img.DrawImage(pointImg, 100, default(Size), new Point(pointx - 10, pointy - 10));
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            string url;
 | 
			
		||||
                            using (var http = new HttpClient())
 | 
			
		||||
                            using (var imgStream = new MemoryStream())
 | 
			
		||||
                            {
 | 
			
		||||
                                img.Save(imgStream);
 | 
			
		||||
                                var byteContent = new ByteArrayContent(imgStream.ToArray());
 | 
			
		||||
                                http.AddFakeHeaders();
 | 
			
		||||
 | 
			
		||||
                                var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent);
 | 
			
		||||
                                url = await reponse.Content.ReadAsStringAsync();
 | 
			
		||||
                            }
 | 
			
		||||
                            return url;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        _log.Warn(ex);
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task RateGirl(IGuildUser usr)
 | 
			
		||||
        {
 | 
			
		||||
            var gr = _girlRatings.GetOrAdd(usr.Id, GetGirl);
 | 
			
		||||
            var gr = _games.GirlRatings.GetOrAdd(usr.Id, GetGirl);
 | 
			
		||||
            var img = await gr.Url;
 | 
			
		||||
            await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
 | 
			
		||||
                .WithTitle("Girl Rating For " + usr)
 | 
			
		||||
@@ -255,7 +195,7 @@ namespace NadekoBot.Modules.Games
 | 
			
		||||
                         "and maybe look at how to replicate that.";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new GirlRating(crazy, hot, roll, advice);
 | 
			
		||||
            return new GirlRating(_images, crazy, hot, roll, advice);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,184 +0,0 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class GoogleTranslator
 | 
			
		||||
    {
 | 
			
		||||
        private static GoogleTranslator _instance;
 | 
			
		||||
        public static GoogleTranslator Instance = _instance ?? (_instance = new GoogleTranslator());
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
 | 
			
		||||
        private readonly Dictionary<string, string> _languageDictionary;
 | 
			
		||||
 | 
			
		||||
        static GoogleTranslator() { }
 | 
			
		||||
        private GoogleTranslator() {
 | 
			
		||||
            _languageDictionary = new Dictionary<string, string>() {
 | 
			
		||||
                    { "afrikaans", "af"},
 | 
			
		||||
                    { "albanian", "sq"},
 | 
			
		||||
                    { "arabic", "ar"},
 | 
			
		||||
                    { "armenian", "hy"},
 | 
			
		||||
                    { "azerbaijani", "az"},
 | 
			
		||||
                    { "basque", "eu"},
 | 
			
		||||
                    { "belarusian", "be"},
 | 
			
		||||
                    { "bengali", "bn"},
 | 
			
		||||
                    { "bulgarian", "bg"},
 | 
			
		||||
                    { "catalan", "ca"},
 | 
			
		||||
                    { "chinese-traditional", "zh-TW"},
 | 
			
		||||
                    { "chinese-simplified", "zh-CN"},
 | 
			
		||||
                    { "chinese", "zh-CN"},
 | 
			
		||||
                    { "croatian", "hr"},
 | 
			
		||||
                    { "czech", "cs"},
 | 
			
		||||
                    { "danish", "da"},
 | 
			
		||||
                    { "dutch", "nl"},
 | 
			
		||||
                    { "english", "en"},
 | 
			
		||||
                    { "esperanto", "eo"},
 | 
			
		||||
                    { "estonian", "et"},
 | 
			
		||||
                    { "filipino", "tl"},
 | 
			
		||||
                    { "finnish", "fi"},
 | 
			
		||||
                    { "french", "fr"},
 | 
			
		||||
                    { "galician", "gl"},
 | 
			
		||||
                    { "german", "de"},
 | 
			
		||||
                    { "georgian", "ka"},
 | 
			
		||||
                    { "greek", "el"},
 | 
			
		||||
                    { "haitian Creole", "ht"},
 | 
			
		||||
                    { "hebrew", "iw"},
 | 
			
		||||
                    { "hindi", "hi"},
 | 
			
		||||
                    { "hungarian", "hu"},
 | 
			
		||||
                    { "icelandic", "is"},
 | 
			
		||||
                    { "indonesian", "id"},
 | 
			
		||||
                    { "irish", "ga"},
 | 
			
		||||
                    { "italian", "it"},
 | 
			
		||||
                    { "japanese", "ja"},
 | 
			
		||||
                    { "korean", "ko"},
 | 
			
		||||
                    { "lao", "lo"},
 | 
			
		||||
                    { "latin", "la"},
 | 
			
		||||
                    { "latvian", "lv"},
 | 
			
		||||
                    { "lithuanian", "lt"},
 | 
			
		||||
                    { "macedonian", "mk"},
 | 
			
		||||
                    { "malay", "ms"},
 | 
			
		||||
                    { "maltese", "mt"},
 | 
			
		||||
                    { "norwegian", "no"},
 | 
			
		||||
                    { "persian", "fa"},
 | 
			
		||||
                    { "polish", "pl"},
 | 
			
		||||
                    { "portuguese", "pt"},
 | 
			
		||||
                    { "romanian", "ro"},
 | 
			
		||||
                    { "russian", "ru"},
 | 
			
		||||
                    { "serbian", "sr"},
 | 
			
		||||
                    { "slovak", "sk"},
 | 
			
		||||
                    { "slovenian", "sl"},
 | 
			
		||||
                    { "spanish", "es"},
 | 
			
		||||
                    { "swahili", "sw"},
 | 
			
		||||
                    { "swedish", "sv"},
 | 
			
		||||
                    { "tamil", "ta"},
 | 
			
		||||
                    { "telugu", "te"},
 | 
			
		||||
                    { "thai", "th"},
 | 
			
		||||
                    { "turkish", "tr"},
 | 
			
		||||
                    { "ukrainian", "uk"},
 | 
			
		||||
                    { "urdu", "ur"},
 | 
			
		||||
                    { "vietnamese", "vi"},
 | 
			
		||||
                    { "welsh", "cy"},
 | 
			
		||||
                    { "yiddish", "yi"},
 | 
			
		||||
 | 
			
		||||
                    { "af", "af"},
 | 
			
		||||
                    { "sq", "sq"},
 | 
			
		||||
                    { "ar", "ar"},
 | 
			
		||||
                    { "hy", "hy"},
 | 
			
		||||
                    { "az", "az"},
 | 
			
		||||
                    { "eu", "eu"},
 | 
			
		||||
                    { "be", "be"},
 | 
			
		||||
                    { "bn", "bn"},
 | 
			
		||||
                    { "bg", "bg"},
 | 
			
		||||
                    { "ca", "ca"},
 | 
			
		||||
                    { "zh-tw", "zh-TW"},
 | 
			
		||||
                    { "zh-cn", "zh-CN"},
 | 
			
		||||
                    { "hr", "hr"},
 | 
			
		||||
                    { "cs", "cs"},
 | 
			
		||||
                    { "da", "da"},
 | 
			
		||||
                    { "nl", "nl"},
 | 
			
		||||
                    { "en", "en"},
 | 
			
		||||
                    { "eo", "eo"},
 | 
			
		||||
                    { "et", "et"},
 | 
			
		||||
                    { "tl", "tl"},
 | 
			
		||||
                    { "fi", "fi"},
 | 
			
		||||
                    { "fr", "fr"},
 | 
			
		||||
                    { "gl", "gl"},
 | 
			
		||||
                    { "de", "de"},
 | 
			
		||||
                    { "ka", "ka"},
 | 
			
		||||
                    { "el", "el"},
 | 
			
		||||
                    { "ht", "ht"},
 | 
			
		||||
                    { "iw", "iw"},
 | 
			
		||||
                    { "hi", "hi"},
 | 
			
		||||
                    { "hu", "hu"},
 | 
			
		||||
                    { "is", "is"},
 | 
			
		||||
                    { "id", "id"},
 | 
			
		||||
                    { "ga", "ga"},
 | 
			
		||||
                    { "it", "it"},
 | 
			
		||||
                    { "ja", "ja"},
 | 
			
		||||
                    { "ko", "ko"},
 | 
			
		||||
                    { "lo", "lo"},
 | 
			
		||||
                    { "la", "la"},
 | 
			
		||||
                    { "lv", "lv"},
 | 
			
		||||
                    { "lt", "lt"},
 | 
			
		||||
                    { "mk", "mk"},
 | 
			
		||||
                    { "ms", "ms"},
 | 
			
		||||
                    { "mt", "mt"},
 | 
			
		||||
                    { "no", "no"},
 | 
			
		||||
                    { "fa", "fa"},
 | 
			
		||||
                    { "pl", "pl"},
 | 
			
		||||
                    { "pt", "pt"},
 | 
			
		||||
                    { "ro", "ro"},
 | 
			
		||||
                    { "ru", "ru"},
 | 
			
		||||
                    { "sr", "sr"},
 | 
			
		||||
                    { "sk", "sk"},
 | 
			
		||||
                    { "sl", "sl"},
 | 
			
		||||
                    { "es", "es"},
 | 
			
		||||
                    { "sw", "sw"},
 | 
			
		||||
                    { "sv", "sv"},
 | 
			
		||||
                    { "ta", "ta"},
 | 
			
		||||
                    { "te", "te"},
 | 
			
		||||
                    { "th", "th"},
 | 
			
		||||
                    { "tr", "tr"},
 | 
			
		||||
                    { "uk", "uk"},
 | 
			
		||||
                    { "ur", "ur"},
 | 
			
		||||
                    { "vi", "vi"},
 | 
			
		||||
                    { "cy", "cy"},
 | 
			
		||||
                    { "yi", "yi"},
 | 
			
		||||
                };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
 | 
			
		||||
        {
 | 
			
		||||
            string text;
 | 
			
		||||
 | 
			
		||||
            if(!_languageDictionary.ContainsKey(sourceLanguage) || 
 | 
			
		||||
               !_languageDictionary.ContainsKey(targetLanguage))
 | 
			
		||||
                throw new ArgumentException();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            var url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
 | 
			
		||||
                                        ConvertToLanguageCode(sourceLanguage),
 | 
			
		||||
                                        ConvertToLanguageCode(targetLanguage),
 | 
			
		||||
                                       WebUtility.UrlEncode(sourceText));
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
 | 
			
		||||
                text = await http.GetStringAsync(url).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string ConvertToLanguageCode(string language)
 | 
			
		||||
        {
 | 
			
		||||
            string mode;
 | 
			
		||||
            _languageDictionary.TryGetValue(language, out mode);
 | 
			
		||||
            return mode;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Searches.Models;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using NLog;
 | 
			
		||||
@@ -19,27 +20,11 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class JokeCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static List<WoWJoke> wowJokes { get; } = new List<WoWJoke>();
 | 
			
		||||
            private static List<MagicItem> magicItems { get; } = new List<MagicItem>();
 | 
			
		||||
            private new static readonly Logger _log;
 | 
			
		||||
            private readonly SearchesService _searches;
 | 
			
		||||
 | 
			
		||||
            static JokeCommands()
 | 
			
		||||
            public JokeCommands(SearchesService searches)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
                if (File.Exists("data/wowjokes.json"))
 | 
			
		||||
                {
 | 
			
		||||
                    wowJokes = JsonConvert.DeserializeObject<List<WoWJoke>>(File.ReadAllText("data/wowjokes.json"));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    _log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded.");
 | 
			
		||||
 | 
			
		||||
                if (File.Exists("data/magicitems.json"))
 | 
			
		||||
                {
 | 
			
		||||
                    magicItems = JsonConvert.DeserializeObject<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    _log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
 | 
			
		||||
                _searches = searches;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -75,24 +60,24 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task WowJoke()
 | 
			
		||||
            {
 | 
			
		||||
                if (!wowJokes.Any())
 | 
			
		||||
                if (!_searches.WowJokes.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var joke = wowJokes[new NadekoRandom().Next(0, wowJokes.Count)];
 | 
			
		||||
                var joke = _searches.WowJokes[new NadekoRandom().Next(0, _searches.WowJokes.Count)];
 | 
			
		||||
                await Context.Channel.SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task MagicItem()
 | 
			
		||||
            {
 | 
			
		||||
                if (!wowJokes.Any())
 | 
			
		||||
                if (!_searches.WowJokes.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var item = magicItems[new NadekoRandom().Next(0, magicItems.Count)];
 | 
			
		||||
                var item = _searches.MagicItems[new NadekoRandom().Next(0, _searches.MagicItems.Count)];
 | 
			
		||||
 | 
			
		||||
                await Context.Channel.SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                obj["name"].GetHashCode();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string[] trashTalk { get; } = { "Better ban your counters. You are going to carry the game anyway.",
 | 
			
		||||
        private static readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.",
 | 
			
		||||
                                                "Go with the flow. Don't think. Just ban one of these.",
 | 
			
		||||
                                                "DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.",
 | 
			
		||||
                                                "Ask your teammates what would they like to play, and ban that.",
 | 
			
		||||
@@ -40,7 +40,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                using (var http = new HttpClient())
 | 
			
		||||
                {
 | 
			
		||||
                    var data = JObject.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" +
 | 
			
		||||
                                                    $"api_key={NadekoBot.Credentials.LoLApiKey}&page=1&" +
 | 
			
		||||
                                                    $"api_key={_creds.LoLApiKey}&page=1&" +
 | 
			
		||||
                                                    $"limit={showCount}")
 | 
			
		||||
                                                    .ConfigureAwait(false))["data"] as JArray;
 | 
			
		||||
                    var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList();
 | 
			
		||||
@@ -127,7 +127,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
//                                  await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
 | 
			
		||||
//                                  return;
 | 
			
		||||
//                              }
 | 
			
		||||
//                          var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Credentials.LOLAPIKey}").ConfigureAwait(false));
 | 
			
		||||
//                          var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={_creds.LOLAPIKey}").ConfigureAwait(false));
 | 
			
		||||
//                          JToken data = null;
 | 
			
		||||
//                          if (role != null)
 | 
			
		||||
//                          {
 | 
			
		||||
@@ -168,7 +168,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
//                                  roles[i] = ">" + roles[i] + "<";
 | 
			
		||||
//                          }
 | 
			
		||||
//                          var general = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/" +
 | 
			
		||||
//                                                                                               $"champs/{name}?api_key={NadekoBot.Credentials.LOLAPIKey}")
 | 
			
		||||
//                                                                                               $"champs/{name}?api_key={_creds.LOLAPIKey}")
 | 
			
		||||
//                                                                                                .ConfigureAwait(false))
 | 
			
		||||
//                                              .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"];
 | 
			
		||||
//                          if (general == null)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Net;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches.Models
 | 
			
		||||
{
 | 
			
		||||
    public class ImdbMovie
 | 
			
		||||
    {
 | 
			
		||||
        public bool Status { get; set; }
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
        public string Title { get; set; }
 | 
			
		||||
        public string OriginalTitle { get; set; }
 | 
			
		||||
        public string Year { get; set; }
 | 
			
		||||
        public string Rating { get; set; }
 | 
			
		||||
        public string Plot { get; set; }
 | 
			
		||||
        public string Poster { get; set; }
 | 
			
		||||
        public List<string> Genres { get; set; }
 | 
			
		||||
        public string ImdbURL { get; set; }
 | 
			
		||||
 | 
			
		||||
        public Dictionary<string, string> Aka { get; set; }
 | 
			
		||||
 | 
			
		||||
        public override string ToString() =>
 | 
			
		||||
$@"`Title:` {WebUtility.HtmlDecode(Title)} {(string.IsNullOrEmpty(OriginalTitle) ? "" : $"({OriginalTitle})")}
 | 
			
		||||
`Year:` {Year}
 | 
			
		||||
`Rating:` {Rating}
 | 
			
		||||
`Genre:` {GenresAsString}
 | 
			
		||||
`Link:` <{ImdbURL}>
 | 
			
		||||
`Plot:` {System.Net.WebUtility.HtmlDecode(Plot.TrimTo(500))}
 | 
			
		||||
`Poster:` " + NadekoBot.Google.ShortenUrl(Poster).GetAwaiter().GetResult();
 | 
			
		||||
        public string GenresAsString =>
 | 
			
		||||
                string.Join(", ", Genres);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.API;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
@@ -12,7 +13,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
 | 
			
		||||
    {
 | 
			
		||||
        private const string queryUrl =  "http://www.omdbapi.com/?t={0}&y=&plot=full&r=json";
 | 
			
		||||
 | 
			
		||||
        public static async Task<OmdbMovie> FindMovie(string name)
 | 
			
		||||
        public static async Task<OmdbMovie> FindMovie(string name, IGoogleApiService google)
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
@@ -20,7 +21,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
 | 
			
		||||
                var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
 | 
			
		||||
                if (movie?.Title == null)
 | 
			
		||||
                    return null;
 | 
			
		||||
                movie.Poster = await NadekoBot.Google.ShortenUrl(movie.Poster);
 | 
			
		||||
                movie.Poster = await google.ShortenUrl(movie.Poster);
 | 
			
		||||
                return movie;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
@@ -17,6 +18,15 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class OsuCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private readonly IGoogleApiService _google;
 | 
			
		||||
            private readonly IBotCredentials _creds;
 | 
			
		||||
 | 
			
		||||
            public OsuCommands(IGoogleApiService google, IBotCredentials creds)
 | 
			
		||||
            {
 | 
			
		||||
                _google = google;
 | 
			
		||||
                _creds = creds;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Osu(string usr, [Remainder] string mode = null)
 | 
			
		||||
            {
 | 
			
		||||
@@ -51,7 +61,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Osub([Remainder] string map)
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey))
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("osu_api_key").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -65,7 +75,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        var mapId = ResolveMap(map);
 | 
			
		||||
                        var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&{mapId}";
 | 
			
		||||
                        var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={_creds.OsuApiKey}&{mapId}";
 | 
			
		||||
                        var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false))[0];
 | 
			
		||||
                        var sb = new System.Text.StringBuilder();
 | 
			
		||||
                        var starRating = Math.Round(double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2);
 | 
			
		||||
@@ -86,7 +96,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            public async Task Osu5(string user, [Remainder] string mode = null)
 | 
			
		||||
            {
 | 
			
		||||
                var channel = (ITextChannel)Context.Channel;
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey))
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
 | 
			
		||||
                {
 | 
			
		||||
                    await channel.SendErrorAsync("An osu! API key is required.").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -107,12 +117,12 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                            m = ResolveGameMode(mode);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var reqString = $"https://osu.ppy.sh/api/get_user_best?k={NadekoBot.Credentials.OsuApiKey}&u={Uri.EscapeDataString(user)}&type=string&limit=5&m={m}";
 | 
			
		||||
                        var reqString = $"https://osu.ppy.sh/api/get_user_best?k={_creds.OsuApiKey}&u={Uri.EscapeDataString(user)}&type=string&limit=5&m={m}";
 | 
			
		||||
                        var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false));
 | 
			
		||||
                        var sb = new System.Text.StringBuilder($"`Top 5 plays for {user}:`\n```xl" + Environment.NewLine);
 | 
			
		||||
                        foreach (var item in obj)
 | 
			
		||||
                        {
 | 
			
		||||
                            var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&b={item["beatmap_id"]}";
 | 
			
		||||
                            var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={_creds.OsuApiKey}&b={item["beatmap_id"]}";
 | 
			
		||||
                            var map = JArray.Parse(await http.GetStringAsync(mapReqString).ConfigureAwait(false))[0];
 | 
			
		||||
                            var pp = Math.Round(double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2);
 | 
			
		||||
                            var acc = CalculateAcc(item, m);
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
            public async Task Placelist()
 | 
			
		||||
            {
 | 
			
		||||
                await Context.Channel.SendConfirmAsync(GetText("list_of_place_tags", NadekoBot.ModulePrefixes[typeof(Searches).Name]), 
 | 
			
		||||
                await Context.Channel.SendConfirmAsync(GetText("list_of_place_tags", NadekoBot.Prefix), 
 | 
			
		||||
                    typesStr)
 | 
			
		||||
                             .ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ using Discord.Commands;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Modules.Searches.Models;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
@@ -17,28 +18,14 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class PokemonSearchCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static Dictionary<string, SearchPokemon> pokemons { get; } = new Dictionary<string, SearchPokemon>();
 | 
			
		||||
            private static Dictionary<string, SearchPokemonAbility> pokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
 | 
			
		||||
            private readonly SearchesService _searches;
 | 
			
		||||
 | 
			
		||||
            public const string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
 | 
			
		||||
            public Dictionary<string, SearchPokemon> Pokemons => _searches.Pokemons;
 | 
			
		||||
            public Dictionary<string, SearchPokemonAbility> PokemonAbilities => _searches.PokemonAbilities;
 | 
			
		||||
 | 
			
		||||
            public const string PokemonListFile = "data/pokemon/pokemon_list7.json";
 | 
			
		||||
            private new static readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
            static PokemonSearchCommands()
 | 
			
		||||
            public PokemonSearchCommands(SearchesService searches)
 | 
			
		||||
            {
 | 
			
		||||
                _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
                if (File.Exists(PokemonListFile))
 | 
			
		||||
                {
 | 
			
		||||
                    pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(PokemonListFile));
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    _log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded.");
 | 
			
		||||
                if (File.Exists(PokemonAbilitiesFile))
 | 
			
		||||
                    pokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(PokemonAbilitiesFile));
 | 
			
		||||
                else
 | 
			
		||||
                    _log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
 | 
			
		||||
                _searches = searches;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -48,7 +35,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(pokemon))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                foreach (var kvp in pokemons)
 | 
			
		||||
                foreach (var kvp in Pokemons)
 | 
			
		||||
                {
 | 
			
		||||
                    if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
 | 
			
		||||
                    {
 | 
			
		||||
@@ -71,7 +58,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                ability = ability?.Trim().ToUpperInvariant().Replace(" ", "");
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(ability))
 | 
			
		||||
                    return;
 | 
			
		||||
                foreach (var kvp in pokemonAbilities)
 | 
			
		||||
                foreach (var kvp in PokemonAbilities)
 | 
			
		||||
                {
 | 
			
		||||
                    if (kvp.Key.ToUpperInvariant() == ability)
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,195 +1,30 @@
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches
 | 
			
		||||
{
 | 
			
		||||
    public partial class Searches
 | 
			
		||||
    {
 | 
			
		||||
        public class StreamStatus
 | 
			
		||||
        {
 | 
			
		||||
            public bool IsLive { get; set; }
 | 
			
		||||
            public string ApiLink { get; set; }
 | 
			
		||||
            public string Views { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class HitboxResponse {
 | 
			
		||||
            public bool Success { get; set; } = true;
 | 
			
		||||
            [JsonProperty("media_is_live")]
 | 
			
		||||
            public string MediaIsLive { get; set; }
 | 
			
		||||
            public bool IsLive  => MediaIsLive == "1";
 | 
			
		||||
            [JsonProperty("media_views")]
 | 
			
		||||
            public string Views { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class TwitchResponse
 | 
			
		||||
        {
 | 
			
		||||
            public string Error { get; set; } = null;
 | 
			
		||||
            public bool IsLive => Stream != null;
 | 
			
		||||
            public StreamInfo Stream { get; set; }
 | 
			
		||||
 | 
			
		||||
            public class StreamInfo
 | 
			
		||||
            {
 | 
			
		||||
                public int Viewers { get; set; }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class BeamResponse
 | 
			
		||||
        {
 | 
			
		||||
            public string Error { get; set; } = null;
 | 
			
		||||
 | 
			
		||||
            [JsonProperty("online")]
 | 
			
		||||
            public bool IsLive { get; set; }
 | 
			
		||||
            public int ViewersCurrent { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class StreamNotFoundException : Exception
 | 
			
		||||
        {
 | 
			
		||||
            public StreamNotFoundException(string message) : base("Stream '" + message + "' not found.")
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class StreamNotificationCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static readonly Timer _checkTimer;
 | 
			
		||||
            private static readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
 | 
			
		||||
            private readonly DbHandler _db;
 | 
			
		||||
            private readonly StreamNotificationService _service;
 | 
			
		||||
 | 
			
		||||
            private static bool firstPass { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
            static StreamNotificationCommands()
 | 
			
		||||
            public StreamNotificationCommands(DbHandler db, StreamNotificationService service)
 | 
			
		||||
            {
 | 
			
		||||
                _checkTimer = new Timer(async (state) =>
 | 
			
		||||
                {
 | 
			
		||||
                    var oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(_cachedStatuses);
 | 
			
		||||
                    _cachedStatuses.Clear();
 | 
			
		||||
                    IEnumerable<FollowedStream> streams;
 | 
			
		||||
                    using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                    {
 | 
			
		||||
                        streams = uow.GuildConfigs.GetAllFollowedStreams();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    await Task.WhenAll(streams.Select(async fs =>
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
 | 
			
		||||
                            if (firstPass)
 | 
			
		||||
                            {
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            StreamStatus oldStatus;
 | 
			
		||||
                            if (oldCachedStatuses.TryGetValue(newStatus.ApiLink, out oldStatus) &&
 | 
			
		||||
                                oldStatus.IsLive != newStatus.IsLive)
 | 
			
		||||
                            {
 | 
			
		||||
                                var server = NadekoBot.Client.GetGuild(fs.GuildId);
 | 
			
		||||
                                var channel = server?.GetTextChannel(fs.ChannelId);
 | 
			
		||||
                                if (channel == null)
 | 
			
		||||
                                    return;
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    await channel.EmbedAsync(fs.GetEmbed(newStatus, channel.Guild.Id)).ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                                catch
 | 
			
		||||
                                {
 | 
			
		||||
                                    // ignored
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        catch
 | 
			
		||||
                        {
 | 
			
		||||
                            // ignored
 | 
			
		||||
                        }
 | 
			
		||||
                    }));
 | 
			
		||||
 | 
			
		||||
                    firstPass = false;
 | 
			
		||||
                }, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
 | 
			
		||||
            {
 | 
			
		||||
                string response;
 | 
			
		||||
                StreamStatus result;
 | 
			
		||||
                switch (stream.Type)
 | 
			
		||||
                {
 | 
			
		||||
                    case FollowedStream.FollowedStreamType.Hitbox:
 | 
			
		||||
                        var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}";
 | 
			
		||||
                        if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result))
 | 
			
		||||
                            return result;
 | 
			
		||||
                        using (var http = new HttpClient())
 | 
			
		||||
                        {
 | 
			
		||||
                            response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        var hbData = JsonConvert.DeserializeObject<HitboxResponse>(response);
 | 
			
		||||
                        if (!hbData.Success)
 | 
			
		||||
                            throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
 | 
			
		||||
                        result = new StreamStatus()
 | 
			
		||||
                        {
 | 
			
		||||
                            IsLive = hbData.IsLive,
 | 
			
		||||
                            ApiLink = hitboxUrl,
 | 
			
		||||
                            Views = hbData.Views
 | 
			
		||||
                        };
 | 
			
		||||
                        _cachedStatuses.AddOrUpdate(hitboxUrl, result, (key, old) => result);
 | 
			
		||||
                        return result;
 | 
			
		||||
                    case FollowedStream.FollowedStreamType.Twitch:
 | 
			
		||||
                        var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username.ToLowerInvariant())}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6";
 | 
			
		||||
                        if (checkCache && _cachedStatuses.TryGetValue(twitchUrl, out result))
 | 
			
		||||
                            return result;
 | 
			
		||||
                        using (var http = new HttpClient())
 | 
			
		||||
                        {
 | 
			
		||||
                            response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        var twData = JsonConvert.DeserializeObject<TwitchResponse>(response);
 | 
			
		||||
                        if (twData.Error != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
 | 
			
		||||
                        }
 | 
			
		||||
                        result = new StreamStatus()
 | 
			
		||||
                        {
 | 
			
		||||
                            IsLive = twData.IsLive,
 | 
			
		||||
                            ApiLink = twitchUrl,
 | 
			
		||||
                            Views = twData.Stream?.Viewers.ToString() ?? "0"
 | 
			
		||||
                        };
 | 
			
		||||
                        _cachedStatuses.AddOrUpdate(twitchUrl, result, (key, old) => result);
 | 
			
		||||
                        return result;
 | 
			
		||||
                    case FollowedStream.FollowedStreamType.Beam:
 | 
			
		||||
                        var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username.ToLowerInvariant()}";
 | 
			
		||||
                        if (checkCache && _cachedStatuses.TryGetValue(beamUrl, out result))
 | 
			
		||||
                            return result;
 | 
			
		||||
                        using (var http = new HttpClient())
 | 
			
		||||
                        {
 | 
			
		||||
                            response = await http.GetStringAsync(beamUrl).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var bmData = JsonConvert.DeserializeObject<BeamResponse>(response);
 | 
			
		||||
                        if (bmData.Error != null)
 | 
			
		||||
                            throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
 | 
			
		||||
                        result = new StreamStatus()
 | 
			
		||||
                        {
 | 
			
		||||
                            IsLive = bmData.IsLive,
 | 
			
		||||
                            ApiLink = beamUrl,
 | 
			
		||||
                            Views = bmData.ViewersCurrent.ToString()
 | 
			
		||||
                        };
 | 
			
		||||
                        _cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
 | 
			
		||||
                        return result;
 | 
			
		||||
                    default:
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
                _db = db;
 | 
			
		||||
                _service = service;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -218,7 +53,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            public async Task ListStreams()
 | 
			
		||||
            {
 | 
			
		||||
                IEnumerable<FollowedStream> streams;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    streams = uow.GuildConfigs
 | 
			
		||||
                                 .For(Context.Guild.Id, 
 | 
			
		||||
@@ -260,7 +95,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                bool removed;
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.FollowedStreams));
 | 
			
		||||
                    removed = config.FollowedStreams.Remove(fs);
 | 
			
		||||
@@ -287,7 +122,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                    return;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var streamStatus = (await GetStreamStatus(new FollowedStream
 | 
			
		||||
                    var streamStatus = (await _service.GetStreamStatus(new FollowedStream
 | 
			
		||||
                    {
 | 
			
		||||
                        Username = stream,
 | 
			
		||||
                        Type = platform,
 | 
			
		||||
@@ -325,7 +160,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                StreamStatus status;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    status = await GetStreamStatus(fs).ConfigureAwait(false);
 | 
			
		||||
                    status = await _service.GetStreamStatus(fs).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
@@ -333,53 +168,15 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                using (var uow = DbHandler.UnitOfWork())
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
 | 
			
		||||
                                    .FollowedStreams
 | 
			
		||||
                                    .Add(fs);
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                await channel.EmbedAsync(fs.GetEmbed(status, Context.Guild.Id), GetText("stream_tracked")).ConfigureAwait(false);
 | 
			
		||||
                await channel.EmbedAsync(_service.GetEmbed(fs, status, Context.Guild.Id), GetText("stream_tracked")).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class FollowedStreamExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static EmbedBuilder GetEmbed(this FollowedStream fs, Searches.StreamStatus status, ulong guildId)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle(fs.Username)
 | 
			
		||||
                                          .WithUrl(fs.GetLink())
 | 
			
		||||
                                          .AddField(efb => efb.WithName(fs.GetText("status"))
 | 
			
		||||
                                                            .WithValue(status.IsLive ? "Online" : "Offline")
 | 
			
		||||
                                                            .WithIsInline(true))
 | 
			
		||||
                                          .AddField(efb => efb.WithName(fs.GetText("viewers"))
 | 
			
		||||
                                                            .WithValue(status.IsLive ? status.Views : "-")
 | 
			
		||||
                                                            .WithIsInline(true))
 | 
			
		||||
                                          .AddField(efb => efb.WithName(fs.GetText("platform"))
 | 
			
		||||
                                                            .WithValue(fs.Type.ToString())
 | 
			
		||||
                                                            .WithIsInline(true))
 | 
			
		||||
                                          .WithColor(status.IsLive ? NadekoBot.OkColor : NadekoBot.ErrorColor);
 | 
			
		||||
 | 
			
		||||
            return embed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetText(this FollowedStream fs, string key, params object[] replacements) =>
 | 
			
		||||
            NadekoTopLevelModule.GetTextStatic(key,
 | 
			
		||||
                NadekoBot.Localization.GetCultureInfo(fs.GuildId),
 | 
			
		||||
                typeof(Searches).Name.ToLowerInvariant(),
 | 
			
		||||
                replacements);
 | 
			
		||||
 | 
			
		||||
        public static string GetLink(this FollowedStream fs)
 | 
			
		||||
        {
 | 
			
		||||
            if (fs.Type == FollowedStream.FollowedStreamType.Hitbox)
 | 
			
		||||
                return $"http://www.hitbox.tv/{fs.Username}/";
 | 
			
		||||
            if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
 | 
			
		||||
                return $"http://www.twitch.tv/{fs.Username}/";
 | 
			
		||||
            if (fs.Type == FollowedStream.FollowedStreamType.Beam)
 | 
			
		||||
                return $"https://beam.pro/{fs.Username}/";
 | 
			
		||||
            return "??";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,54 +7,23 @@ using System.Threading.Tasks;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches
 | 
			
		||||
{
 | 
			
		||||
    public partial class Searches
 | 
			
		||||
    {
 | 
			
		||||
        public struct UserChannelPair
 | 
			
		||||
        {
 | 
			
		||||
            public ulong UserId { get; set; }
 | 
			
		||||
            public ulong ChannelId { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Group]
 | 
			
		||||
        public class TranslateCommands : NadekoSubmodule
 | 
			
		||||
        {
 | 
			
		||||
            private static ConcurrentDictionary<ulong, bool> translatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
 | 
			
		||||
            private static ConcurrentDictionary<UserChannelPair, string> userLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
 | 
			
		||||
            private readonly SearchesService _searches;
 | 
			
		||||
            private readonly IGoogleApiService _google;
 | 
			
		||||
 | 
			
		||||
            static TranslateCommands()
 | 
			
		||||
            public TranslateCommands(SearchesService searches, IGoogleApiService google)
 | 
			
		||||
            {
 | 
			
		||||
                NadekoBot.Client.MessageReceived += async (msg) =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var umsg = msg as SocketUserMessage;
 | 
			
		||||
                        if (umsg == null)
 | 
			
		||||
                            return;
 | 
			
		||||
 | 
			
		||||
                        bool autoDelete;
 | 
			
		||||
                        if (!translatedChannels.TryGetValue(umsg.Channel.Id, out autoDelete))
 | 
			
		||||
                            return;
 | 
			
		||||
                        var key = new UserChannelPair()
 | 
			
		||||
                        {
 | 
			
		||||
                            UserId = umsg.Author.Id,
 | 
			
		||||
                            ChannelId = umsg.Channel.Id,
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        string langs;
 | 
			
		||||
                        if (!userLanguages.TryGetValue(key, out langs))
 | 
			
		||||
                            return;
 | 
			
		||||
 | 
			
		||||
                        var text = await TranslateInternal(langs, umsg.Resolve(TagHandling.Ignore))
 | 
			
		||||
                                            .ConfigureAwait(false);
 | 
			
		||||
                        if (autoDelete)
 | 
			
		||||
                            try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
 | 
			
		||||
                        await umsg.Channel.SendConfirmAsync($"{umsg.Author.Mention} `:` " + text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch { }
 | 
			
		||||
                };
 | 
			
		||||
                _searches = searches;
 | 
			
		||||
                _google = google;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
@@ -63,7 +32,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
 | 
			
		||||
                    var translation = await TranslateInternal(langs, text);
 | 
			
		||||
                    var translation = await _searches.Translate(langs, text);
 | 
			
		||||
                    await Context.Channel.SendConfirmAsync(GetText("translation") + " " + langs, translation).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -72,19 +41,6 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static async Task<string> TranslateInternal(string langs, [Remainder] string text = null)
 | 
			
		||||
            {
 | 
			
		||||
                var langarr = langs.ToLowerInvariant().Split('>');
 | 
			
		||||
                if (langarr.Length != 2)
 | 
			
		||||
                    throw new ArgumentException();
 | 
			
		||||
                var from = langarr[0];
 | 
			
		||||
                var to = langarr[1];
 | 
			
		||||
                text = text?.Trim();
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                    throw new ArgumentException();
 | 
			
		||||
                return (await GoogleTranslator.Instance.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public enum AutoDeleteAutoTranslate
 | 
			
		||||
            {
 | 
			
		||||
                Del,
 | 
			
		||||
@@ -101,18 +57,17 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
 | 
			
		||||
                if (autoDelete == AutoDeleteAutoTranslate.Del)
 | 
			
		||||
                {
 | 
			
		||||
                    translatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
 | 
			
		||||
                    _searches.TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
 | 
			
		||||
                    await ReplyConfirmLocalized("atl_ad_started").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                bool throwaway;
 | 
			
		||||
                if (translatedChannels.TryRemove(channel.Id, out throwaway))
 | 
			
		||||
                
 | 
			
		||||
                if (_searches.TranslatedChannels.TryRemove(channel.Id, out var throwaway))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyConfirmLocalized("atl_stopped").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (translatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
 | 
			
		||||
                if (_searches.TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyConfirmLocalized("atl_started").ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
@@ -130,7 +85,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(langs))
 | 
			
		||||
                {
 | 
			
		||||
                    if (userLanguages.TryRemove(ucp, out langs))
 | 
			
		||||
                    if (_searches.UserLanguages.TryRemove(ucp, out langs))
 | 
			
		||||
                        await ReplyConfirmLocalized("atl_removed").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
@@ -141,13 +96,13 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                var from = langarr[0];
 | 
			
		||||
                var to = langarr[1];
 | 
			
		||||
 | 
			
		||||
                if (!GoogleTranslator.Instance.Languages.Contains(from) || !GoogleTranslator.Instance.Languages.Contains(to))
 | 
			
		||||
                if (!_google.Languages.Contains(from) || !_google.Languages.Contains(to))
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalized("invalid_lang").ConfigureAwait(false);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                userLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
 | 
			
		||||
                _searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
 | 
			
		||||
 | 
			
		||||
                await ReplyConfirmLocalized("atl_set", from, to).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
@@ -156,7 +111,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            [RequireContext(ContextType.Guild)]
 | 
			
		||||
            public async Task Translangs()
 | 
			
		||||
            {
 | 
			
		||||
                await Context.Channel.SendTableAsync(GoogleTranslator.Instance.Languages, str => $"{str,-15}", 3);
 | 
			
		||||
                await Context.Channel.SendTableAsync(_google.Languages, str => $"{str,-15}", 3);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ using Newtonsoft.Json;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
@@ -17,18 +16,27 @@ using NadekoBot.Modules.Searches.Commands.Models;
 | 
			
		||||
using AngleSharp;
 | 
			
		||||
using AngleSharp.Dom.Html;
 | 
			
		||||
using AngleSharp.Dom;
 | 
			
		||||
using System.Xml;
 | 
			
		||||
using Configuration = AngleSharp.Configuration;
 | 
			
		||||
using NadekoBot.Attributes;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using ImageSharp.Processing.Processors;
 | 
			
		||||
using ImageSharp;
 | 
			
		||||
using NadekoBot.Services.Searches;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches
 | 
			
		||||
{
 | 
			
		||||
    [NadekoModule("Searches", "~")]
 | 
			
		||||
    public partial class Searches : NadekoTopLevelModule
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly IGoogleApiService _google;
 | 
			
		||||
        private readonly SearchesService _searches;
 | 
			
		||||
 | 
			
		||||
        public Searches(IBotCredentials creds, IGoogleApiService google, SearchesService searches)
 | 
			
		||||
        {
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
            _google = google;
 | 
			
		||||
            _searches = searches;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Weather([Remainder] string query)
 | 
			
		||||
        {
 | 
			
		||||
@@ -59,16 +67,16 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Time([Remainder] string arg)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(arg) || string.IsNullOrWhiteSpace(NadekoBot.Credentials.GoogleApiKey))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(arg) || string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={NadekoBot.Credentials.GoogleApiKey}").ConfigureAwait(false);
 | 
			
		||||
                var res = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={_creds.GoogleApiKey}").ConfigureAwait(false);
 | 
			
		||||
                var obj = JsonConvert.DeserializeObject<GeolocationResult>(res);
 | 
			
		||||
 | 
			
		||||
                var currentSeconds = DateTime.UtcNow.UnixTimestamp();
 | 
			
		||||
                var timeRes = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}×tamp={currentSeconds}&key={NadekoBot.Credentials.GoogleApiKey}").ConfigureAwait(false);
 | 
			
		||||
                var timeRes = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}×tamp={currentSeconds}&key={_creds.GoogleApiKey}").ConfigureAwait(false);
 | 
			
		||||
                var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(timeRes);
 | 
			
		||||
 | 
			
		||||
                var time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset);
 | 
			
		||||
@@ -81,7 +89,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        public async Task Youtube([Remainder] string query = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (!await ValidateQuery(Context.Channel, query).ConfigureAwait(false)) return;
 | 
			
		||||
            var result = (await NadekoBot.Google.GetVideosByKeywordsAsync(query, 1)).FirstOrDefault();
 | 
			
		||||
            var result = (await _google.GetVideosByKeywordsAsync(query, 1)).FirstOrDefault();
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(result))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("no_results").ConfigureAwait(false);
 | 
			
		||||
@@ -97,7 +105,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            if (!(await ValidateQuery(Context.Channel, query).ConfigureAwait(false))) return;
 | 
			
		||||
            await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var movie = await OmdbProvider.FindMovie(query);
 | 
			
		||||
            var movie = await OmdbProvider.FindMovie(query, _google);
 | 
			
		||||
            if (movie == null)
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("imdb_fail").ConfigureAwait(false);
 | 
			
		||||
@@ -137,7 +145,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false);
 | 
			
		||||
                var res = await _google.GetImageAsync(terms).ConfigureAwait(false);
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
 | 
			
		||||
@@ -189,7 +197,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var res = await NadekoBot.Google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false);
 | 
			
		||||
                var res = await _google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false);
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
 | 
			
		||||
@@ -239,7 +247,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(ffs))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await Context.Channel.SendConfirmAsync("<" + await NadekoBot.Google.ShortenUrl($"http://lmgtfy.com/?q={ Uri.EscapeUriString(ffs) }") + ">")
 | 
			
		||||
            await Context.Channel.SendConfirmAsync("<" + await _google.ShortenUrl($"http://lmgtfy.com/?q={ Uri.EscapeUriString(ffs) }") + ">")
 | 
			
		||||
                           .ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -249,7 +257,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(arg))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var shortened = await NadekoBot.Google.ShortenUrl(arg).ConfigureAwait(false);
 | 
			
		||||
            var shortened = await _google.ShortenUrl(arg).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            if (shortened == arg)
 | 
			
		||||
            {
 | 
			
		||||
@@ -315,7 +323,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                .WithFooter(efb => efb.WithText(totalResults));
 | 
			
		||||
 | 
			
		||||
            var desc = await Task.WhenAll(results.Select(async res =>
 | 
			
		||||
                    $"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
 | 
			
		||||
                    $"[{Format.Bold(res?.Title)}]({(await _google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
 | 
			
		||||
                .ConfigureAwait(false);
 | 
			
		||||
            await Context.Channel.EmbedAsync(embed.WithDescription(string.Concat(desc))).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
@@ -339,7 +347,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                    if (items == null || items.Length == 0)
 | 
			
		||||
                        throw new KeyNotFoundException("Cannot find a card by that name");
 | 
			
		||||
                    var item = items[new NadekoRandom().Next(0, items.Length)];
 | 
			
		||||
                    var storeUrl = await NadekoBot.Google.ShortenUrl(item["store_url"].ToString());
 | 
			
		||||
                    var storeUrl = await _google.ShortenUrl(item["store_url"].ToString());
 | 
			
		||||
                    var cost = item["cost"].ToString();
 | 
			
		||||
                    var desc = item["text"].ToString();
 | 
			
		||||
                    var types = string.Join(",\n", item["types"].ToObject<string[]>());
 | 
			
		||||
@@ -351,7 +359,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                                    .AddField(efb => efb.WithName(GetText("store_url")).WithValue(storeUrl).WithIsInline(true))
 | 
			
		||||
                                    .AddField(efb => efb.WithName(GetText("cost")).WithValue(cost).WithIsInline(true))
 | 
			
		||||
                                    .AddField(efb => efb.WithName(GetText("types")).WithValue(types).WithIsInline(true));
 | 
			
		||||
                    //.AddField(efb => efb.WithName("Store Url").WithValue(await NadekoBot.Google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
 | 
			
		||||
                    //.AddField(efb => efb.WithName("Store Url").WithValue(await _google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
 | 
			
		||||
 | 
			
		||||
                    await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
@@ -369,7 +377,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(arg))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
@@ -379,7 +387,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                http.DefaultRequestHeaders.Clear();
 | 
			
		||||
                http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
 | 
			
		||||
                http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
 | 
			
		||||
                var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}")
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
                try
 | 
			
		||||
@@ -422,7 +430,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task Yodify([Remainder] string query = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
@@ -435,7 +443,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                http.DefaultRequestHeaders.Clear();
 | 
			
		||||
                http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
 | 
			
		||||
                http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
 | 
			
		||||
                http.DefaultRequestHeaders.Add("Accept", "text/plain");
 | 
			
		||||
                var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(query)}").ConfigureAwait(false);
 | 
			
		||||
                try
 | 
			
		||||
@@ -457,7 +465,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
        [NadekoCommand, Usage, Description, Aliases]
 | 
			
		||||
        public async Task UrbanDict([Remainder] string query = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
@@ -531,7 +539,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(query))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
 | 
			
		||||
                return;
 | 
			
		||||
@@ -542,7 +550,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                http.DefaultRequestHeaders.Clear();
 | 
			
		||||
                http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
 | 
			
		||||
                http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
 | 
			
		||||
                res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(query)}.json").ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -655,7 +663,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                usr = (IGuildUser)Context.User;
 | 
			
		||||
 | 
			
		||||
            var avatarUrl = usr.RealAvatarUrl();
 | 
			
		||||
            var shortenedAvatarUrl = await NadekoBot.Google.ShortenUrl(avatarUrl).ConfigureAwait(false);
 | 
			
		||||
            var shortenedAvatarUrl = await _google.ShortenUrl(avatarUrl).ConfigureAwait(false);
 | 
			
		||||
            await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
 | 
			
		||||
                .AddField(efb => efb.WithName("Username").WithValue(usr.ToString()).WithIsInline(false))
 | 
			
		||||
                .AddField(efb => efb.WithName("Avatar Url").WithValue(shortenedAvatarUrl).WithIsInline(false))
 | 
			
		||||
@@ -682,7 +690,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
                    var found = items["items"][0];
 | 
			
		||||
                    var response = $@"`{GetText("title")}` {found["title"]}
 | 
			
		||||
`{GetText("quality")}` {found["quality"]}
 | 
			
		||||
`{GetText("url")}:` {await NadekoBot.Google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}";
 | 
			
		||||
`{GetText("url")}:` {await _google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}";
 | 
			
		||||
                    await Context.Channel.SendMessageAsync(response).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
@@ -769,7 +777,7 @@ namespace NadekoBot.Modules.Searches
 | 
			
		||||
 | 
			
		||||
            tag = tag?.Trim() ?? "";
 | 
			
		||||
 | 
			
		||||
            var url = await InternalDapiSearch(tag, type).ConfigureAwait(false);
 | 
			
		||||
            var url = await _searches.DapiSearch(tag, type).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            if (url == null)
 | 
			
		||||
                await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results"));
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,8 @@ using NadekoBot.Services.Searches;
 | 
			
		||||
using NadekoBot.Services.ClashOfClans;
 | 
			
		||||
using NadekoBot.Services.Music;
 | 
			
		||||
using NadekoBot.Services.CustomReactions;
 | 
			
		||||
using NadekoBot.Services.Games;
 | 
			
		||||
using NadekoBot.Services.Administration;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot
 | 
			
		||||
{
 | 
			
		||||
@@ -73,11 +75,10 @@ namespace NadekoBot
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var google = new GoogleApiService(credentials);
 | 
			
		||||
            var strings = new NadekoStrings();
 | 
			
		||||
 | 
			
		||||
            var greetSettingsService = new GreetSettingsService(AllGuildConfigs, db);
 | 
			
		||||
 | 
			
		||||
            var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), db);
 | 
			
		||||
            var strings = new NadekoStrings(localization);
 | 
			
		||||
 | 
			
		||||
            var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, db);
 | 
			
		||||
 | 
			
		||||
            var commandService = new CommandService(new CommandServiceConfig()
 | 
			
		||||
            {
 | 
			
		||||
@@ -97,10 +98,18 @@ namespace NadekoBot
 | 
			
		||||
 | 
			
		||||
            //module services
 | 
			
		||||
            var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db);
 | 
			
		||||
            var searchesService = new SearchesService();
 | 
			
		||||
            var searchesService = new SearchesService(Client, google, db);
 | 
			
		||||
            var clashService = new ClashOfClansService(Client, db, localization, strings);
 | 
			
		||||
            var musicService = new MusicService(google, strings, localization, db, soundcloud, credentials);
 | 
			
		||||
            var crService = new CustomReactionsService(db, Client);
 | 
			
		||||
            var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, strings, images);
 | 
			
		||||
#region administration
 | 
			
		||||
            var administrationService = new AdministrationService(AllGuildConfigs, commandHandler);
 | 
			
		||||
            var selfService = new SelfService(this, commandHandler, db, BotConfig);
 | 
			
		||||
            var vcRoleService = new VcRoleService(Client, AllGuildConfigs);
 | 
			
		||||
            var vPlusTService = new VplusTService(Client, AllGuildConfigs, strings, db);
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //initialize Services
 | 
			
		||||
            Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces
 | 
			
		||||
@@ -124,6 +133,10 @@ namespace NadekoBot
 | 
			
		||||
                .Add<MusicService>(musicService)
 | 
			
		||||
                .Add<GreetSettingsService>(greetSettingsService)
 | 
			
		||||
                .Add<CustomReactionsService>(crService)
 | 
			
		||||
                .Add<GamesService>(gamesService)
 | 
			
		||||
                .Add(selfService)
 | 
			
		||||
                .Add(vcRoleService)
 | 
			
		||||
                .Add(vPlusTService)
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            commandHandler.AddServices(Services);
 | 
			
		||||
 
 | 
			
		||||
@@ -29,21 +29,22 @@
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Compile Remove="data\**\*;credentials.json;credentials_example.json" />
 | 
			
		||||
    <Compile Remove="Modules\Administration\**" />
 | 
			
		||||
    <Compile Remove="Modules\Games\**" />
 | 
			
		||||
    <Compile Remove="Modules\NSFW\**" />
 | 
			
		||||
    <Compile Remove="Modules\Permissions\**" />
 | 
			
		||||
    <Compile Remove="Modules\Searches\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Administration\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Games\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\NSFW\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Permissions\**" />
 | 
			
		||||
    <EmbeddedResource Remove="Modules\Searches\**" />
 | 
			
		||||
    <None Remove="Modules\Administration\**" />
 | 
			
		||||
    <None Remove="Modules\Games\**" />
 | 
			
		||||
    <None Remove="Modules\NSFW\**" />
 | 
			
		||||
    <None Remove="Modules\Permissions\**" />
 | 
			
		||||
    <None Remove="Modules\Searches\**" />
 | 
			
		||||
    <Compile Remove="Modules\Gambling\Commands\Lucky7Commands.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Administration.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Commands\MuteCommands.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Commands\SelfCommands.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Commands\ServerGreetCommands.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Commands\UserPunishCommands.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Commands\VcRoleCommands.cs" />
 | 
			
		||||
    <Compile Include="Modules\Administration\Commands\VoicePlusTextCommands.cs" />
 | 
			
		||||
    <None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </None>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.Commands;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class AdministrationService
 | 
			
		||||
    {
 | 
			
		||||
        public readonly ConcurrentHashSet<ulong> DeleteMessagesOnCommand;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        public AdministrationService(IEnumerable<GuildConfig> gcs, CommandHandler cmdHandler)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(gcs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
 | 
			
		||||
            cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var channel = msg.Channel as SocketTextChannel;
 | 
			
		||||
                    if (channel == null)
 | 
			
		||||
                        return;
 | 
			
		||||
                    if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
 | 
			
		||||
                        await msg.DeleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn("Delmsgoncmd errored...");
 | 
			
		||||
                    _log.Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										275
									
								
								src/NadekoBot/Services/Administration/MuteService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/NadekoBot/Services/Administration/MuteService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,275 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class MuteService
 | 
			
		||||
    {
 | 
			
		||||
        public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> UnmuteTimers { get; }
 | 
			
		||||
            = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
 | 
			
		||||
 | 
			
		||||
        public event Action<IGuildUser, MuteType> UserMuted = delegate { };
 | 
			
		||||
        public event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
 | 
			
		||||
 | 
			
		||||
        private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
 | 
			
		||||
 | 
			
		||||
        private readonly Logger _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public MuteService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _db = db;
 | 
			
		||||
 | 
			
		||||
            GuildMuteRoles = new ConcurrentDictionary<ulong, string>(gcs
 | 
			
		||||
                    .Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
 | 
			
		||||
                    .ToDictionary(c => c.GuildId, c => c.MuteRoleName));
 | 
			
		||||
 | 
			
		||||
            MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(gcs.ToDictionary(
 | 
			
		||||
                k => k.GuildId,
 | 
			
		||||
                v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
 | 
			
		||||
            ));
 | 
			
		||||
 | 
			
		||||
            foreach (var conf in gcs)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var x in conf.UnmuteTimers)
 | 
			
		||||
                {
 | 
			
		||||
                    TimeSpan after;
 | 
			
		||||
                    if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
 | 
			
		||||
                    {
 | 
			
		||||
                        after = TimeSpan.FromMinutes(2);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        after = x.UnmuteAt - DateTime.UtcNow;
 | 
			
		||||
                    }
 | 
			
		||||
                    StartUnmuteTimer(conf.GuildId, x.UserId, after);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _client.UserJoined += Client_UserJoined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Client_UserJoined(IGuildUser usr)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
 | 
			
		||||
 | 
			
		||||
                if (muted == null || !muted.Contains(usr.Id))
 | 
			
		||||
                    return;
 | 
			
		||||
                await MuteUser(usr).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                LogManager.GetCurrentClassLogger().Warn(ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task MuteUser(IGuildUser usr, MuteType type = MuteType.All)
 | 
			
		||||
        {
 | 
			
		||||
            if (type == MuteType.All)
 | 
			
		||||
            {
 | 
			
		||||
                await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
 | 
			
		||||
                var muteRole = await GetMuteRole(usr.Guild);
 | 
			
		||||
                if (!usr.RoleIds.Contains(muteRole.Id))
 | 
			
		||||
                    await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
 | 
			
		||||
                StopUnmuteTimer(usr.GuildId, usr.Id);
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(usr.Guild.Id,
 | 
			
		||||
                        set => set.Include(gc => gc.MutedUsers)
 | 
			
		||||
                            .Include(gc => gc.UnmuteTimers));
 | 
			
		||||
                    config.MutedUsers.Add(new MutedUserId()
 | 
			
		||||
                    {
 | 
			
		||||
                        UserId = usr.Id
 | 
			
		||||
                    });
 | 
			
		||||
                    if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
 | 
			
		||||
                        muted.Add(usr.Id);
 | 
			
		||||
 | 
			
		||||
                    config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
 | 
			
		||||
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                UserMuted(usr, MuteType.All);
 | 
			
		||||
            }
 | 
			
		||||
            else if (type == MuteType.Voice)
 | 
			
		||||
            {
 | 
			
		||||
                await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
 | 
			
		||||
                UserMuted(usr, MuteType.Voice);
 | 
			
		||||
            }
 | 
			
		||||
            else if (type == MuteType.Chat)
 | 
			
		||||
            {
 | 
			
		||||
                await usr.AddRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
 | 
			
		||||
                UserMuted(usr, MuteType.Chat);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task UnmuteUser(IGuildUser usr, MuteType type = MuteType.All)
 | 
			
		||||
        {
 | 
			
		||||
            if (type == MuteType.All)
 | 
			
		||||
            {
 | 
			
		||||
                StopUnmuteTimer(usr.GuildId, usr.Id);
 | 
			
		||||
                try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
 | 
			
		||||
                try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); } catch { /*ignore*/ }
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)
 | 
			
		||||
                        .Include(gc => gc.UnmuteTimers));
 | 
			
		||||
                    config.MutedUsers.Remove(new MutedUserId()
 | 
			
		||||
                    {
 | 
			
		||||
                        UserId = usr.Id
 | 
			
		||||
                    });
 | 
			
		||||
                    if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
 | 
			
		||||
                        muted.TryRemove(usr.Id);
 | 
			
		||||
 | 
			
		||||
                    config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
 | 
			
		||||
 | 
			
		||||
                    await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                UserUnmuted(usr, MuteType.All);
 | 
			
		||||
            }
 | 
			
		||||
            else if (type == MuteType.Voice)
 | 
			
		||||
            {
 | 
			
		||||
                await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
 | 
			
		||||
                UserUnmuted(usr, MuteType.Voice);
 | 
			
		||||
            }
 | 
			
		||||
            else if (type == MuteType.Chat)
 | 
			
		||||
            {
 | 
			
		||||
                await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
 | 
			
		||||
                UserUnmuted(usr, MuteType.Chat);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IRole> GetMuteRole(IGuild guild)
 | 
			
		||||
        {
 | 
			
		||||
            const string defaultMuteRoleName = "nadeko-mute";
 | 
			
		||||
 | 
			
		||||
            var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
 | 
			
		||||
 | 
			
		||||
            var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
 | 
			
		||||
            if (muteRole == null)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                //if it doesn't exist, create it 
 | 
			
		||||
                try { muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None).ConfigureAwait(false); }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    //if creations fails,  maybe the name is not correct, find default one, if doesn't work, create default one
 | 
			
		||||
                    muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
 | 
			
		||||
                        await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
 | 
			
		||||
                    {
 | 
			
		||||
                        await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
 | 
			
		||||
                                .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        await Task.Delay(200).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return muteRole;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task TimedMute(IGuildUser user, TimeSpan after)
 | 
			
		||||
        {
 | 
			
		||||
            await MuteUser(user).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var config = uow.GuildConfigs.For(user.GuildId, set => set.Include(x => x.UnmuteTimers));
 | 
			
		||||
                config.UnmuteTimers.Add(new UnmuteTimer()
 | 
			
		||||
                {
 | 
			
		||||
                    UserId = user.Id,
 | 
			
		||||
                    UnmuteAt = DateTime.UtcNow + after,
 | 
			
		||||
                }); // add teh unmute timer to the database
 | 
			
		||||
                uow.Complete();
 | 
			
		||||
            }
 | 
			
		||||
            StartUnmuteTimer(user.GuildId, user.Id, after); // start the timer
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void StartUnmuteTimer(ulong guildId, ulong userId, TimeSpan after)
 | 
			
		||||
        {
 | 
			
		||||
            //load the unmute timers for this guild
 | 
			
		||||
            var userUnmuteTimers = UnmuteTimers.GetOrAdd(guildId, new ConcurrentDictionary<ulong, Timer>());
 | 
			
		||||
 | 
			
		||||
            //unmute timer to be added
 | 
			
		||||
            var toAdd = new Timer(async _ =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var guild = _client.GetGuild(guildId); // load the guild
 | 
			
		||||
                    if (guild == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        RemoveUnmuteTimerFromDb(guildId, userId);
 | 
			
		||||
                        return; // if guild can't be found, just remove the timer from db
 | 
			
		||||
                    }
 | 
			
		||||
                    // unmute the user, this will also remove the timer from the db
 | 
			
		||||
                    await UnmuteUser(guild.GetUser(userId)).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    RemoveUnmuteTimerFromDb(guildId, userId); // if unmute errored, just remove unmute from db
 | 
			
		||||
                    _log.Warn("Couldn't unmute user {0} in guild {1}", userId, guildId);
 | 
			
		||||
                    _log.Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
            }, null, after, Timeout.InfiniteTimeSpan);
 | 
			
		||||
 | 
			
		||||
            //add it, or stop the old one and add this one
 | 
			
		||||
            userUnmuteTimers.AddOrUpdate(userId, (key) => toAdd, (key, old) =>
 | 
			
		||||
            {
 | 
			
		||||
                old.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
                return toAdd;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void StopUnmuteTimer(ulong guildId, ulong userId)
 | 
			
		||||
        {
 | 
			
		||||
            if (!UnmuteTimers.TryGetValue(guildId, out ConcurrentDictionary<ulong, Timer> userUnmuteTimers)) return;
 | 
			
		||||
 | 
			
		||||
            if (userUnmuteTimers.TryRemove(userId, out Timer removed))
 | 
			
		||||
            {
 | 
			
		||||
                removed.Change(Timeout.Infinite, Timeout.Infinite);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void RemoveUnmuteTimerFromDb(ulong guildId, ulong userId)
 | 
			
		||||
        {
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var config = uow.GuildConfigs.For(guildId, set => set.Include(x => x.UnmuteTimers));
 | 
			
		||||
                config.UnmuteTimers.RemoveWhere(x => x.UserId == userId);
 | 
			
		||||
                uow.Complete();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum MuteType
 | 
			
		||||
    {
 | 
			
		||||
        Voice,
 | 
			
		||||
        Chat,
 | 
			
		||||
        All
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								src/NadekoBot/Services/Administration/SelfService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/NadekoBot/Services/Administration/SelfService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class SelfService
 | 
			
		||||
    {
 | 
			
		||||
        public volatile bool ForwardDMs;
 | 
			
		||||
        public volatile bool ForwardDMsToAllOwners;
 | 
			
		||||
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
        private readonly NadekoBot _bot;
 | 
			
		||||
        private readonly CommandHandler _cmdHandler;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public SelfService(NadekoBot bot, CommandHandler cmdHandler, DbHandler db,
 | 
			
		||||
            BotConfig bc)
 | 
			
		||||
        {
 | 
			
		||||
            _bot = bot;
 | 
			
		||||
            _cmdHandler = cmdHandler;
 | 
			
		||||
            _db = db;
 | 
			
		||||
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var config = uow.BotConfig.GetOrCreate();
 | 
			
		||||
                ForwardDMs = config.ForwardMessages;
 | 
			
		||||
                ForwardDMsToAllOwners = config.ForwardToAllOwners;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                while (!bot.Ready)
 | 
			
		||||
                    await Task.Delay(1000);
 | 
			
		||||
 | 
			
		||||
                foreach (var cmd in bc.StartupCommands)
 | 
			
		||||
                {
 | 
			
		||||
                    await cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText);
 | 
			
		||||
                    await Task.Delay(400).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								src/NadekoBot/Services/Administration/VcRoleService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/NadekoBot/Services/Administration/VcRoleService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class VcRoleService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
 | 
			
		||||
 | 
			
		||||
        public VcRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
 | 
			
		||||
            VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
 | 
			
		||||
            foreach (var gconf in gcs)
 | 
			
		||||
            {
 | 
			
		||||
                var g = client.GetGuild(gconf.GuildId);
 | 
			
		||||
                if (g == null)
 | 
			
		||||
                    continue; //todo delete everything from db if guild doesn't exist?
 | 
			
		||||
 | 
			
		||||
                var infos = new ConcurrentDictionary<ulong, IRole>();
 | 
			
		||||
                VcRoles.TryAdd(gconf.GuildId, infos);
 | 
			
		||||
                foreach (var ri in gconf.VcRoleInfos)
 | 
			
		||||
                {
 | 
			
		||||
                    var role = g.GetRole(ri.RoleId);
 | 
			
		||||
                    if (role == null)
 | 
			
		||||
                        continue; //todo remove this entry from db
 | 
			
		||||
 | 
			
		||||
                    infos.TryAdd(ri.VoiceChannelId, role);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
 | 
			
		||||
            SocketVoiceState newState)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            var gusr = usr as SocketGuildUser;
 | 
			
		||||
            if (gusr == null)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            var oldVc = oldState.VoiceChannel;
 | 
			
		||||
            var newVc = newState.VoiceChannel;
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (oldVc != newVc)
 | 
			
		||||
                    {
 | 
			
		||||
                        ulong guildId;
 | 
			
		||||
                        guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
 | 
			
		||||
 | 
			
		||||
                        if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
 | 
			
		||||
                        {
 | 
			
		||||
                            //remove old
 | 
			
		||||
                            if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
 | 
			
		||||
                            {
 | 
			
		||||
                                if (gusr.Roles.Contains(role))
 | 
			
		||||
                                {
 | 
			
		||||
                                    try
 | 
			
		||||
                                    {
 | 
			
		||||
                                        await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
 | 
			
		||||
                                        await Task.Delay(500).ConfigureAwait(false);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    catch
 | 
			
		||||
                                    {
 | 
			
		||||
                                        await Task.Delay(200).ConfigureAwait(false);
 | 
			
		||||
                                        await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
 | 
			
		||||
                                        await Task.Delay(500).ConfigureAwait(false);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            //add new
 | 
			
		||||
                            if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
 | 
			
		||||
                            {
 | 
			
		||||
                                if (!gusr.Roles.Contains(role))
 | 
			
		||||
                                    await gusr.AddRoleAsync(role).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								src/NadekoBot/Services/Administration/VplusTService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/NadekoBot/Services/Administration/VplusTService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Administration
 | 
			
		||||
{
 | 
			
		||||
    public class VplusTService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
        public readonly ConcurrentHashSet<ulong> VoicePlusTextCache;
 | 
			
		||||
 | 
			
		||||
        private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        public VplusTService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings,
 | 
			
		||||
            DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            VoicePlusTextCache = new ConcurrentHashSet<ulong>(gcs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
 | 
			
		||||
            _client.UserVoiceStateUpdated += UserUpdatedEventHandler;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
 | 
			
		||||
        {
 | 
			
		||||
            var user = (iuser as SocketGuildUser);
 | 
			
		||||
            var guild = user?.Guild;
 | 
			
		||||
 | 
			
		||||
            if (guild == null)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            var botUserPerms = guild.CurrentUser.GuildPermissions;
 | 
			
		||||
 | 
			
		||||
            if (before.VoiceChannel == after.VoiceChannel)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            if (!VoicePlusTextCache.Contains(guild.Id))
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            await guild.Owner.SendErrorAsync(
 | 
			
		||||
                                _strings.GetText("vt_exit",
 | 
			
		||||
                                    guild.Id,
 | 
			
		||||
                                    "Administration".ToLowerInvariant(),
 | 
			
		||||
                                    Format.Bold(guild.Name))).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        catch
 | 
			
		||||
                        {
 | 
			
		||||
                            // ignored
 | 
			
		||||
                        }
 | 
			
		||||
                        using (var uow = _db.UnitOfWork)
 | 
			
		||||
                        {
 | 
			
		||||
                            uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
 | 
			
		||||
                            VoicePlusTextCache.TryRemove(guild.Id);
 | 
			
		||||
                            await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        await semaphore.WaitAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        var beforeVch = before.VoiceChannel;
 | 
			
		||||
                        if (beforeVch != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            var beforeRoleName = GetRoleName(beforeVch);
 | 
			
		||||
                            var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
 | 
			
		||||
                            if (beforeRole != null)
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    _log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
 | 
			
		||||
                                    await user.RemoveRoleAsync(beforeRole).ConfigureAwait(false);
 | 
			
		||||
                                    await Task.Delay(200).ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                                catch (Exception ex)
 | 
			
		||||
                                {
 | 
			
		||||
                                    _log.Warn(ex);
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                        var afterVch = after.VoiceChannel;
 | 
			
		||||
                        if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
 | 
			
		||||
                        {
 | 
			
		||||
                            var roleName = GetRoleName(afterVch);
 | 
			
		||||
                            var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
 | 
			
		||||
                                              (IRole)await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                            ITextChannel textChannel = guild.TextChannels
 | 
			
		||||
                                                        .FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
 | 
			
		||||
                            if (textChannel == null)
 | 
			
		||||
                            {
 | 
			
		||||
                                var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
                                try { await guild.CurrentUser.AddRoleAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
 | 
			
		||||
                                await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
                                await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
 | 
			
		||||
                                    readMessages: PermValue.Allow,
 | 
			
		||||
                                    sendMessages: PermValue.Allow))
 | 
			
		||||
                                        .ConfigureAwait(false);
 | 
			
		||||
                                await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
                                await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
 | 
			
		||||
                                    readMessages: PermValue.Deny,
 | 
			
		||||
                                    sendMessages: PermValue.Deny))
 | 
			
		||||
                                        .ConfigureAwait(false);
 | 
			
		||||
                                await Task.Delay(50).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                            _log.Warn("Adding role " + roleToAdd.Name + " to user " + user.Username);
 | 
			
		||||
                            await user.AddRoleAsync(roleToAdd).ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally
 | 
			
		||||
                    {
 | 
			
		||||
                        semaphore.Release();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetChannelName(string voiceName) =>
 | 
			
		||||
            _channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
 | 
			
		||||
 | 
			
		||||
        public string GetRoleName(IVoiceChannel ch) =>
 | 
			
		||||
            "nvoice-" + ch.Id;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -544,7 +544,7 @@ namespace NadekoBot.Services
 | 
			
		||||
                    //////int price;
 | 
			
		||||
                    //////if (Permissions.CommandCostCommands.CommandCosts.TryGetValue(cmd.Aliases.First().Trim().ToLowerInvariant(), out price) && price > 0)
 | 
			
		||||
                    //////{
 | 
			
		||||
                    //////    var success = await CurrencyHandler.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false);
 | 
			
		||||
                    //////    var success = await _ch.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false);
 | 
			
		||||
                    //////    if (!success)
 | 
			
		||||
                    //////    {
 | 
			
		||||
                    //////        return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command."));
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ namespace NadekoBot.Services
 | 
			
		||||
            var optionsBuilder = new DbContextOptionsBuilder();
 | 
			
		||||
            optionsBuilder.UseSqlite(creds.Db.ConnectionString);
 | 
			
		||||
            options = optionsBuilder.Options;
 | 
			
		||||
            //switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant())
 | 
			
		||||
            //switch (_creds.Db.Type.ToUpperInvariant())
 | 
			
		||||
            //{
 | 
			
		||||
            //    case "SQLITE":
 | 
			
		||||
            //        dbType = typeof(NadekoSqliteContext);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/NadekoBot/Services/Games/ChatterBotResponse.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/NadekoBot/Services/Games/ChatterBotResponse.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class ChatterBotResponse
 | 
			
		||||
    {
 | 
			
		||||
        public string Convo_id { get; set; }
 | 
			
		||||
        public string BotSay { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/NadekoBot/Services/Games/ChatterBotSession.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/NadekoBot/Services/Games/ChatterBotSession.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class ChatterBotSession
 | 
			
		||||
    {
 | 
			
		||||
        private static NadekoRandom rng { get; } = new NadekoRandom();
 | 
			
		||||
        public string ChatterbotId { get; }
 | 
			
		||||
        public string ChannelId { get; }
 | 
			
		||||
        private int _botId = 6;
 | 
			
		||||
 | 
			
		||||
        public ChatterBotSession(ulong channelId)
 | 
			
		||||
        {
 | 
			
		||||
            ChannelId = channelId.ToString().ToBase64();
 | 
			
		||||
            ChatterbotId = rng.Next(0, 1000000).ToString().ToBase64();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string apiEndpoint => "http://api.program-o.com/v2/chatbot/" +
 | 
			
		||||
                                      $"?bot_id={_botId}&" +
 | 
			
		||||
                                      "say={0}&" +
 | 
			
		||||
                                      $"convo_id=nadekobot_{ChatterbotId}_{ChannelId}&" +
 | 
			
		||||
                                      "format=json";
 | 
			
		||||
 | 
			
		||||
        public async Task<string> Think(string message)
 | 
			
		||||
        {
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                var res = await http.GetStringAsync(string.Format(apiEndpoint, message)).ConfigureAwait(false);
 | 
			
		||||
                var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
 | 
			
		||||
                //Console.WriteLine(cbr.Convo_id);
 | 
			
		||||
                return cbr.BotSay.Replace("<br/>", "\n");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										206
									
								
								src/NadekoBot/Services/Games/GamesService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								src/NadekoBot/Services/Games/GamesService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class GamesService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly BotConfig _bc;
 | 
			
		||||
 | 
			
		||||
        public readonly ConcurrentDictionary<ulong, GirlRating> GirlRatings = new ConcurrentDictionary<ulong, GirlRating>();
 | 
			
		||||
        public readonly ImmutableArray<string> EightBallResponses;
 | 
			
		||||
 | 
			
		||||
        private readonly Timer _t;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
        private readonly IImagesService _images;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        public ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; }
 | 
			
		||||
 | 
			
		||||
        public readonly string TypingArticlesPath = "data/typing_articles2.json";
 | 
			
		||||
        public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
 | 
			
		||||
 | 
			
		||||
        public GamesService(DiscordShardedClient client, BotConfig bc, IEnumerable<GuildConfig> gcs, 
 | 
			
		||||
            NadekoStrings strings, IImagesService images)
 | 
			
		||||
        {
 | 
			
		||||
            _bc = bc;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
            _images = images;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            //8ball
 | 
			
		||||
            EightBallResponses = _bc.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray();
 | 
			
		||||
 | 
			
		||||
            //girl ratings
 | 
			
		||||
            _t = new Timer((_) =>
 | 
			
		||||
            {
 | 
			
		||||
                GirlRatings.Clear();
 | 
			
		||||
 | 
			
		||||
            }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
 | 
			
		||||
 | 
			
		||||
            //cleverbot
 | 
			
		||||
            CleverbotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
 | 
			
		||||
                    gcs.Where(gc => gc.CleverbotEnabled)
 | 
			
		||||
                        .ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => new ChatterBotSession(gc.GuildId), true)));
 | 
			
		||||
 | 
			
		||||
            //plantpick
 | 
			
		||||
            client.MessageReceived += PotentialFlowerGeneration;
 | 
			
		||||
            GenerationChannels = new ConcurrentHashSet<ulong>(gcs
 | 
			
		||||
                .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(TypingArticlesPath));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                _log.Warn("Error while loading typing articles {0}", ex.ToString());
 | 
			
		||||
                TypingArticles = new List<TypingArticle>();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public string PrepareMessage(IUserMessage msg, out ChatterBotSession cleverbot)
 | 
			
		||||
        {
 | 
			
		||||
            var channel = msg.Channel as ITextChannel;
 | 
			
		||||
            cleverbot = null;
 | 
			
		||||
 | 
			
		||||
            if (channel == null)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            Lazy<ChatterBotSession> lazyCleverbot;
 | 
			
		||||
            if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out lazyCleverbot))
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            cleverbot = lazyCleverbot.Value;
 | 
			
		||||
 | 
			
		||||
            var nadekoId = _client.CurrentUser.Id;
 | 
			
		||||
            var normalMention = $"<@{nadekoId}> ";
 | 
			
		||||
            var nickMention = $"<@!{nadekoId}> ";
 | 
			
		||||
            string message;
 | 
			
		||||
            if (msg.Content.StartsWith(normalMention))
 | 
			
		||||
            {
 | 
			
		||||
                message = msg.Content.Substring(normalMention.Length).Trim();
 | 
			
		||||
            }
 | 
			
		||||
            else if (msg.Content.StartsWith(nickMention))
 | 
			
		||||
            {
 | 
			
		||||
                message = msg.Content.Substring(nickMention.Length).Trim();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return message;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> TryAsk(ChatterBotSession cleverbot, ITextChannel channel, string message)
 | 
			
		||||
        {
 | 
			
		||||
            await channel.TriggerTypingAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            var response = await cleverbot.Think(message).ConfigureAwait(false);
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false); // try twice :\
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ConcurrentHashSet<ulong> GenerationChannels { get; }
 | 
			
		||||
        //channelid/message
 | 
			
		||||
        public ConcurrentDictionary<ulong, List<IUserMessage>> PlantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>();
 | 
			
		||||
        //channelId/last generation
 | 
			
		||||
        public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
 | 
			
		||||
        
 | 
			
		||||
        public KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
 | 
			
		||||
        {
 | 
			
		||||
            var rng = new NadekoRandom();
 | 
			
		||||
            return _images.Currency[rng.Next(0, _images.Currency.Length)];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetText(ITextChannel ch, string key, params object[] rep)
 | 
			
		||||
            => _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep);
 | 
			
		||||
 | 
			
		||||
        private Task PotentialFlowerGeneration(SocketMessage imsg)
 | 
			
		||||
        {
 | 
			
		||||
            var msg = imsg as SocketUserMessage;
 | 
			
		||||
            if (msg == null || msg.Author.IsBot)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            var channel = imsg.Channel as ITextChannel;
 | 
			
		||||
            if (channel == null)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            if (!GenerationChannels.Contains(channel.Id))
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
 | 
			
		||||
                    var rng = new NadekoRandom();
 | 
			
		||||
 | 
			
		||||
                    //todo i'm stupid :rofl: wtg kwoth. real async programming :100: :ok_hand: :100: :100: :thumbsup:
 | 
			
		||||
                    if (DateTime.Now - TimeSpan.FromSeconds(_bc.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    var num = rng.Next(1, 101) + _bc.CurrencyGenerationChance * 100;
 | 
			
		||||
 | 
			
		||||
                    if (num > 100)
 | 
			
		||||
                    {
 | 
			
		||||
                        LastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
 | 
			
		||||
 | 
			
		||||
                        var dropAmount = _bc.CurrencyDropAmount;
 | 
			
		||||
 | 
			
		||||
                        if (dropAmount > 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            var msgs = new IUserMessage[dropAmount];
 | 
			
		||||
                            var prefix = NadekoBot.Prefix;
 | 
			
		||||
                            var toSend = dropAmount == 1
 | 
			
		||||
                                ? GetText(channel, "curgen_sn", _bc.CurrencySign)
 | 
			
		||||
                                    + " " + GetText(channel, "pick_sn", prefix)
 | 
			
		||||
                                : GetText(channel, "curgen_pl", dropAmount, _bc.CurrencySign)
 | 
			
		||||
                                    + " " + GetText(channel, "pick_pl", prefix);
 | 
			
		||||
                            var file = GetRandomCurrencyImage();
 | 
			
		||||
                            using (var fileStream = file.Value.ToStream())
 | 
			
		||||
                            {
 | 
			
		||||
                                var sent = await channel.SendFileAsync(
 | 
			
		||||
                                    fileStream,
 | 
			
		||||
                                    file.Key,
 | 
			
		||||
                                    toSend).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                                msgs[0] = sent;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            PlantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    LogManager.GetCurrentClassLogger().Warn(ex);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								src/NadekoBot/Services/Games/GirlRating.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/NadekoBot/Services/Games/GirlRating.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
using ImageSharp;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class GirlRating
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly Logger _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
        public double Crazy { get; }
 | 
			
		||||
        public double Hot { get; }
 | 
			
		||||
        public int Roll { get; }
 | 
			
		||||
        public string Advice { get; }
 | 
			
		||||
        public AsyncLazy<string> Url { get; }
 | 
			
		||||
 | 
			
		||||
        public GirlRating(IImagesService _images, double crazy, double hot, int roll, string advice)
 | 
			
		||||
        {
 | 
			
		||||
            Crazy = crazy;
 | 
			
		||||
            Hot = hot;
 | 
			
		||||
            Roll = roll;
 | 
			
		||||
            Advice = advice; // convenient to have it here, even though atm there are only few different ones.
 | 
			
		||||
 | 
			
		||||
            Url = new AsyncLazy<string>(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    using (var ms = new MemoryStream(_images.WifeMatrix.ToArray(), false))
 | 
			
		||||
                    using (var img = new ImageSharp.Image(ms))
 | 
			
		||||
                    {
 | 
			
		||||
                        const int minx = 35;
 | 
			
		||||
                        const int miny = 385;
 | 
			
		||||
                        const int length = 345;
 | 
			
		||||
 | 
			
		||||
                        var pointx = (int)(minx + length * (Hot / 10));
 | 
			
		||||
                        var pointy = (int)(miny - length * ((Crazy - 4) / 6));
 | 
			
		||||
 | 
			
		||||
                        using (var pointMs = new MemoryStream(_images.RategirlDot.ToArray(), false))
 | 
			
		||||
                        using (var pointImg = new ImageSharp.Image(pointMs))
 | 
			
		||||
                        {
 | 
			
		||||
                            img.DrawImage(pointImg, 100, default(Size), new Point(pointx - 10, pointy - 10));
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        string url;
 | 
			
		||||
                        using (var http = new HttpClient())
 | 
			
		||||
                        using (var imgStream = new MemoryStream())
 | 
			
		||||
                        {
 | 
			
		||||
                            img.Save(imgStream);
 | 
			
		||||
                            var byteContent = new ByteArrayContent(imgStream.ToArray());
 | 
			
		||||
                            http.AddFakeHeaders();
 | 
			
		||||
 | 
			
		||||
                            var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent);
 | 
			
		||||
                            url = await reponse.Content.ReadAsStringAsync();
 | 
			
		||||
                        }
 | 
			
		||||
                        return url;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _log.Warn(ex);
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace NadekoBot.Modules.Games.Commands.Models
 | 
			
		||||
namespace NadekoBot.Services.Games
 | 
			
		||||
{
 | 
			
		||||
    public class TypingArticle
 | 
			
		||||
    {
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.DataStructures;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
@@ -15,11 +19,158 @@ namespace NadekoBot.Services
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
 | 
			
		||||
        public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        public GreetSettingsService(IEnumerable<GuildConfig> guildConfigs, DbHandler db)
 | 
			
		||||
        public GreetSettingsService(DiscordShardedClient client, IEnumerable<GuildConfig> guildConfigs, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(guildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
 | 
			
		||||
 | 
			
		||||
            _client.UserJoined += UserJoined;
 | 
			
		||||
            _client.UserLeft += UserLeft;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task UserLeft(IGuildUser user)
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var conf = GetOrAddSettingsForGuild(user.GuildId);
 | 
			
		||||
 | 
			
		||||
                    if (!conf.SendChannelByeMessage) return;
 | 
			
		||||
                    var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId);
 | 
			
		||||
 | 
			
		||||
                    if (channel == null) //maybe warn the server owner that the channel is missing
 | 
			
		||||
                        return;
 | 
			
		||||
                    CREmbed embedData;
 | 
			
		||||
                    if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData))
 | 
			
		||||
                    {
 | 
			
		||||
                        embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                            if (conf.AutoDeleteByeMessagesTimer > 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                        if (string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                            return;
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
                            if (conf.AutoDeleteByeMessagesTimer > 0)
 | 
			
		||||
                            {
 | 
			
		||||
                                toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task UserJoined(IGuildUser user)
 | 
			
		||||
        {
 | 
			
		||||
            var _ = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var conf = GetOrAddSettingsForGuild(user.GuildId);
 | 
			
		||||
 | 
			
		||||
                    if (conf.SendChannelGreetMessage)
 | 
			
		||||
                    {
 | 
			
		||||
                        var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
 | 
			
		||||
                        if (channel != null) //maybe warn the server owner that the channel is missing
 | 
			
		||||
                        {
 | 
			
		||||
 | 
			
		||||
                            CREmbed embedData;
 | 
			
		||||
                            if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
 | 
			
		||||
                            {
 | 
			
		||||
                                embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                    if (conf.AutoDeleteGreetMessagesTimer > 0)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                if (!string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                {
 | 
			
		||||
                                    try
 | 
			
		||||
                                    {
 | 
			
		||||
                                        var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
 | 
			
		||||
                                        if (conf.AutoDeleteGreetMessagesTimer > 0)
 | 
			
		||||
                                        {
 | 
			
		||||
                                            toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (conf.SendDmGreetMessage)
 | 
			
		||||
                    {
 | 
			
		||||
                        var channel = await user.CreateDMChannelAsync();
 | 
			
		||||
 | 
			
		||||
                        if (channel != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            CREmbed embedData;
 | 
			
		||||
                            if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
 | 
			
		||||
                            {
 | 
			
		||||
                                embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                                catch (Exception ex) { _log.Warn(ex); }
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
 | 
			
		||||
                                if (!string.IsNullOrWhiteSpace(msg))
 | 
			
		||||
                                {
 | 
			
		||||
                                    await channel.SendConfirmAsync(msg).ConfigureAwait(false);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    // ignored
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
 | 
			
		||||
@@ -210,6 +361,23 @@ namespace NadekoBot.Services
 | 
			
		||||
                await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task SetGreetDel(ulong id, int timer)
 | 
			
		||||
        {
 | 
			
		||||
            if (timer < 0 || timer > 600)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using (var uow = _db.UnitOfWork)
 | 
			
		||||
            {
 | 
			
		||||
                var conf = uow.GuildConfigs.For(id, set => set);
 | 
			
		||||
                conf.AutoDeleteGreetMessagesTimer = timer;
 | 
			
		||||
 | 
			
		||||
                var toAdd = GreetSettings.Create(conf);
 | 
			
		||||
                GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
 | 
			
		||||
 | 
			
		||||
                await uow.CompleteAsync().ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class GreetSettings
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,8 @@ namespace NadekoBot.Services
 | 
			
		||||
        string CarbonKey { get; }
 | 
			
		||||
 | 
			
		||||
        DBConfig Db { get; }
 | 
			
		||||
        string SoundCloudClientId { get; set; }
 | 
			
		||||
        string SoundCloudClientId { get; }
 | 
			
		||||
        string OsuApiKey { get; }
 | 
			
		||||
 | 
			
		||||
        bool IsOwner(IUser u);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Google.Apis.Customsearch.v1.Data;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
@@ -6,12 +7,28 @@ namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public interface IGoogleApiService
 | 
			
		||||
    {
 | 
			
		||||
        IEnumerable<string> Languages { get; }
 | 
			
		||||
 | 
			
		||||
        Task<IEnumerable<string>> GetVideosByKeywordsAsync(string keywords, int count = 1);
 | 
			
		||||
        Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1);
 | 
			
		||||
        Task<IEnumerable<string>> GetRelatedVideosAsync(string url, int count = 1);
 | 
			
		||||
        Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50);
 | 
			
		||||
        Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds);
 | 
			
		||||
        Task<ImageResult> GetImageAsync(string query, int start = 1);
 | 
			
		||||
        Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage);
 | 
			
		||||
 | 
			
		||||
        Task<string> ShortenUrl(string url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public struct ImageResult
 | 
			
		||||
    {
 | 
			
		||||
        public Result.ImageData Image { get; }
 | 
			
		||||
        public string Link { get; }
 | 
			
		||||
 | 
			
		||||
        public ImageResult(Result.ImageData image, string link)
 | 
			
		||||
        {
 | 
			
		||||
            this.Image = image;
 | 
			
		||||
            this.Link = link;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
                    ? "d0bd7768e3a1a2d15430f0dccb871117"
 | 
			
		||||
                    : _soundcloudClientId;
 | 
			
		||||
            }
 | 
			
		||||
            set {
 | 
			
		||||
            private set {
 | 
			
		||||
                _soundcloudClientId = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,9 @@ using Google.Apis.Urlshortener.v1;
 | 
			
		||||
using Google.Apis.Urlshortener.v1.Data;
 | 
			
		||||
using NLog;
 | 
			
		||||
using Google.Apis.Customsearch.v1;
 | 
			
		||||
using Google.Apis.Customsearch.v1.Data;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Impl
 | 
			
		||||
{
 | 
			
		||||
@@ -20,7 +22,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
        private YouTubeService yt;
 | 
			
		||||
        private UrlshortenerService sh;
 | 
			
		||||
        private CustomsearchService cs;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        private Logger _log { get; }
 | 
			
		||||
 | 
			
		||||
        public GoogleApiService(IBotCredentials creds)
 | 
			
		||||
@@ -39,6 +41,7 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
            sh = new UrlshortenerService(bcs);
 | 
			
		||||
            cs = new CustomsearchService(bcs);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(keywords))
 | 
			
		||||
@@ -190,18 +193,6 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public struct ImageResult
 | 
			
		||||
        {
 | 
			
		||||
            public Result.ImageData Image { get; }
 | 
			
		||||
            public string Link { get; }
 | 
			
		||||
 | 
			
		||||
            public ImageResult(Result.ImageData image, string link)
 | 
			
		||||
            {
 | 
			
		||||
                this.Image = image;
 | 
			
		||||
                this.Link = link;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<ImageResult> GetImageAsync(string query, int start = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(query))
 | 
			
		||||
@@ -218,5 +209,168 @@ namespace NadekoBot.Services.Impl
 | 
			
		||||
 | 
			
		||||
            return new ImageResult(search.Items[0].Image, search.Items[0].Link);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
 | 
			
		||||
        private readonly Dictionary<string, string> _languageDictionary = new Dictionary<string, string>() {
 | 
			
		||||
                    { "afrikaans", "af"},
 | 
			
		||||
                    { "albanian", "sq"},
 | 
			
		||||
                    { "arabic", "ar"},
 | 
			
		||||
                    { "armenian", "hy"},
 | 
			
		||||
                    { "azerbaijani", "az"},
 | 
			
		||||
                    { "basque", "eu"},
 | 
			
		||||
                    { "belarusian", "be"},
 | 
			
		||||
                    { "bengali", "bn"},
 | 
			
		||||
                    { "bulgarian", "bg"},
 | 
			
		||||
                    { "catalan", "ca"},
 | 
			
		||||
                    { "chinese-traditional", "zh-TW"},
 | 
			
		||||
                    { "chinese-simplified", "zh-CN"},
 | 
			
		||||
                    { "chinese", "zh-CN"},
 | 
			
		||||
                    { "croatian", "hr"},
 | 
			
		||||
                    { "czech", "cs"},
 | 
			
		||||
                    { "danish", "da"},
 | 
			
		||||
                    { "dutch", "nl"},
 | 
			
		||||
                    { "english", "en"},
 | 
			
		||||
                    { "esperanto", "eo"},
 | 
			
		||||
                    { "estonian", "et"},
 | 
			
		||||
                    { "filipino", "tl"},
 | 
			
		||||
                    { "finnish", "fi"},
 | 
			
		||||
                    { "french", "fr"},
 | 
			
		||||
                    { "galician", "gl"},
 | 
			
		||||
                    { "german", "de"},
 | 
			
		||||
                    { "georgian", "ka"},
 | 
			
		||||
                    { "greek", "el"},
 | 
			
		||||
                    { "haitian Creole", "ht"},
 | 
			
		||||
                    { "hebrew", "iw"},
 | 
			
		||||
                    { "hindi", "hi"},
 | 
			
		||||
                    { "hungarian", "hu"},
 | 
			
		||||
                    { "icelandic", "is"},
 | 
			
		||||
                    { "indonesian", "id"},
 | 
			
		||||
                    { "irish", "ga"},
 | 
			
		||||
                    { "italian", "it"},
 | 
			
		||||
                    { "japanese", "ja"},
 | 
			
		||||
                    { "korean", "ko"},
 | 
			
		||||
                    { "lao", "lo"},
 | 
			
		||||
                    { "latin", "la"},
 | 
			
		||||
                    { "latvian", "lv"},
 | 
			
		||||
                    { "lithuanian", "lt"},
 | 
			
		||||
                    { "macedonian", "mk"},
 | 
			
		||||
                    { "malay", "ms"},
 | 
			
		||||
                    { "maltese", "mt"},
 | 
			
		||||
                    { "norwegian", "no"},
 | 
			
		||||
                    { "persian", "fa"},
 | 
			
		||||
                    { "polish", "pl"},
 | 
			
		||||
                    { "portuguese", "pt"},
 | 
			
		||||
                    { "romanian", "ro"},
 | 
			
		||||
                    { "russian", "ru"},
 | 
			
		||||
                    { "serbian", "sr"},
 | 
			
		||||
                    { "slovak", "sk"},
 | 
			
		||||
                    { "slovenian", "sl"},
 | 
			
		||||
                    { "spanish", "es"},
 | 
			
		||||
                    { "swahili", "sw"},
 | 
			
		||||
                    { "swedish", "sv"},
 | 
			
		||||
                    { "tamil", "ta"},
 | 
			
		||||
                    { "telugu", "te"},
 | 
			
		||||
                    { "thai", "th"},
 | 
			
		||||
                    { "turkish", "tr"},
 | 
			
		||||
                    { "ukrainian", "uk"},
 | 
			
		||||
                    { "urdu", "ur"},
 | 
			
		||||
                    { "vietnamese", "vi"},
 | 
			
		||||
                    { "welsh", "cy"},
 | 
			
		||||
                    { "yiddish", "yi"},
 | 
			
		||||
 | 
			
		||||
                    { "af", "af"},
 | 
			
		||||
                    { "sq", "sq"},
 | 
			
		||||
                    { "ar", "ar"},
 | 
			
		||||
                    { "hy", "hy"},
 | 
			
		||||
                    { "az", "az"},
 | 
			
		||||
                    { "eu", "eu"},
 | 
			
		||||
                    { "be", "be"},
 | 
			
		||||
                    { "bn", "bn"},
 | 
			
		||||
                    { "bg", "bg"},
 | 
			
		||||
                    { "ca", "ca"},
 | 
			
		||||
                    { "zh-tw", "zh-TW"},
 | 
			
		||||
                    { "zh-cn", "zh-CN"},
 | 
			
		||||
                    { "hr", "hr"},
 | 
			
		||||
                    { "cs", "cs"},
 | 
			
		||||
                    { "da", "da"},
 | 
			
		||||
                    { "nl", "nl"},
 | 
			
		||||
                    { "en", "en"},
 | 
			
		||||
                    { "eo", "eo"},
 | 
			
		||||
                    { "et", "et"},
 | 
			
		||||
                    { "tl", "tl"},
 | 
			
		||||
                    { "fi", "fi"},
 | 
			
		||||
                    { "fr", "fr"},
 | 
			
		||||
                    { "gl", "gl"},
 | 
			
		||||
                    { "de", "de"},
 | 
			
		||||
                    { "ka", "ka"},
 | 
			
		||||
                    { "el", "el"},
 | 
			
		||||
                    { "ht", "ht"},
 | 
			
		||||
                    { "iw", "iw"},
 | 
			
		||||
                    { "hi", "hi"},
 | 
			
		||||
                    { "hu", "hu"},
 | 
			
		||||
                    { "is", "is"},
 | 
			
		||||
                    { "id", "id"},
 | 
			
		||||
                    { "ga", "ga"},
 | 
			
		||||
                    { "it", "it"},
 | 
			
		||||
                    { "ja", "ja"},
 | 
			
		||||
                    { "ko", "ko"},
 | 
			
		||||
                    { "lo", "lo"},
 | 
			
		||||
                    { "la", "la"},
 | 
			
		||||
                    { "lv", "lv"},
 | 
			
		||||
                    { "lt", "lt"},
 | 
			
		||||
                    { "mk", "mk"},
 | 
			
		||||
                    { "ms", "ms"},
 | 
			
		||||
                    { "mt", "mt"},
 | 
			
		||||
                    { "no", "no"},
 | 
			
		||||
                    { "fa", "fa"},
 | 
			
		||||
                    { "pl", "pl"},
 | 
			
		||||
                    { "pt", "pt"},
 | 
			
		||||
                    { "ro", "ro"},
 | 
			
		||||
                    { "ru", "ru"},
 | 
			
		||||
                    { "sr", "sr"},
 | 
			
		||||
                    { "sk", "sk"},
 | 
			
		||||
                    { "sl", "sl"},
 | 
			
		||||
                    { "es", "es"},
 | 
			
		||||
                    { "sw", "sw"},
 | 
			
		||||
                    { "sv", "sv"},
 | 
			
		||||
                    { "ta", "ta"},
 | 
			
		||||
                    { "te", "te"},
 | 
			
		||||
                    { "th", "th"},
 | 
			
		||||
                    { "tr", "tr"},
 | 
			
		||||
                    { "uk", "uk"},
 | 
			
		||||
                    { "ur", "ur"},
 | 
			
		||||
                    { "vi", "vi"},
 | 
			
		||||
                    { "cy", "cy"},
 | 
			
		||||
                    { "yi", "yi"},
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
        public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
 | 
			
		||||
        {
 | 
			
		||||
            string text;
 | 
			
		||||
 | 
			
		||||
            if (!_languageDictionary.ContainsKey(sourceLanguage) ||
 | 
			
		||||
               !_languageDictionary.ContainsKey(targetLanguage))
 | 
			
		||||
                throw new ArgumentException();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
 | 
			
		||||
                                        ConvertToLanguageCode(sourceLanguage),
 | 
			
		||||
                                        ConvertToLanguageCode(targetLanguage),
 | 
			
		||||
                                       WebUtility.UrlEncode(sourceText));
 | 
			
		||||
            using (var http = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
 | 
			
		||||
                text = await http.GetStringAsync(url).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string ConvertToLanguageCode(string language)
 | 
			
		||||
        {
 | 
			
		||||
            string mode;
 | 
			
		||||
            _languageDictionary.TryGetValue(language, out mode);
 | 
			
		||||
            return mode;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,7 @@ using NLog;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using Discord;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
@@ -20,10 +21,13 @@ namespace NadekoBot.Services
 | 
			
		||||
        /// Used as failsafe in case response key doesn't exist in the selected or default language.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
 | 
			
		||||
        private readonly ILocalization _localization;
 | 
			
		||||
 | 
			
		||||
        public NadekoStrings()
 | 
			
		||||
        public NadekoStrings(ILocalization loc)
 | 
			
		||||
        {
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
            _localization = loc;
 | 
			
		||||
 | 
			
		||||
            var sw = Stopwatch.StartNew();
 | 
			
		||||
            var allLangsDict = new Dictionary<string, ImmutableDictionary<string, string>>(); // lang:(name:value)
 | 
			
		||||
            foreach (var file in Directory.GetFiles(stringsPath))
 | 
			
		||||
@@ -58,6 +62,9 @@ namespace NadekoBot.Services
 | 
			
		||||
            return val;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetText(string key, ulong guildId, string lowerModuleTypeName, params object[] replacements) =>
 | 
			
		||||
            GetText(key, _localization.GetCultureInfo(guildId), lowerModuleTypeName);
 | 
			
		||||
 | 
			
		||||
        public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
 | 
			
		||||
        {
 | 
			
		||||
            var text = GetString(lowerModuleTypeName + "_" + key, cultureInfo);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
namespace NadekoBot.Modules.Searches.Models
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    class MagicItem
 | 
			
		||||
    public class MagicItem
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public string Description { get; set; }
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches.Models
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class SearchPokemon
 | 
			
		||||
    {
 | 
			
		||||
@@ -1,4 +1,12 @@
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NLog;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Xml;
 | 
			
		||||
@@ -7,6 +15,101 @@ namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class SearchesService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly IGoogleApiService _google;
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
        private readonly Logger _log;
 | 
			
		||||
 | 
			
		||||
        public ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
 | 
			
		||||
        public ConcurrentDictionary<UserChannelPair, string> UserLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
 | 
			
		||||
        
 | 
			
		||||
        public readonly string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
 | 
			
		||||
        public readonly string PokemonListFile = "data/pokemon/pokemon_list7.json";
 | 
			
		||||
        public Dictionary<string, SearchPokemon> Pokemons { get; } = new Dictionary<string, SearchPokemon>();
 | 
			
		||||
        public Dictionary<string, SearchPokemonAbility> PokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
 | 
			
		||||
 | 
			
		||||
        public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
 | 
			
		||||
        public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
 | 
			
		||||
 | 
			
		||||
        public SearchesService(DiscordShardedClient client, IGoogleApiService google, DbHandler db)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _google = google;
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _log = LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
            //translate commands
 | 
			
		||||
            _client.MessageReceived += async (msg) =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var umsg = msg as SocketUserMessage;
 | 
			
		||||
                    if (umsg == null)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out var autoDelete))
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    var key = new UserChannelPair()
 | 
			
		||||
                    {
 | 
			
		||||
                        UserId = umsg.Author.Id,
 | 
			
		||||
                        ChannelId = umsg.Channel.Id,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    string langs;
 | 
			
		||||
                    if (!UserLanguages.TryGetValue(key, out langs))
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    var text = await Translate(langs, umsg.Resolve(TagHandling.Ignore))
 | 
			
		||||
                                        .ConfigureAwait(false);
 | 
			
		||||
                    if (autoDelete)
 | 
			
		||||
                        try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
 | 
			
		||||
                    await umsg.Channel.SendConfirmAsync($"{umsg.Author.Mention} `:` " + text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch { }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            //pokemon commands
 | 
			
		||||
            if (File.Exists(PokemonListFile))
 | 
			
		||||
            {
 | 
			
		||||
                Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(PokemonListFile));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                _log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded.");
 | 
			
		||||
            if (File.Exists(PokemonAbilitiesFile))
 | 
			
		||||
                PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(PokemonAbilitiesFile));
 | 
			
		||||
            else
 | 
			
		||||
                _log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
 | 
			
		||||
 | 
			
		||||
            //joke commands
 | 
			
		||||
            if (File.Exists("data/wowjokes.json"))
 | 
			
		||||
            {
 | 
			
		||||
                WowJokes = JsonConvert.DeserializeObject<List<WoWJoke>>(File.ReadAllText("data/wowjokes.json"));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                _log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded.");
 | 
			
		||||
 | 
			
		||||
            if (File.Exists("data/magicitems.json"))
 | 
			
		||||
            {
 | 
			
		||||
                MagicItems = JsonConvert.DeserializeObject<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                _log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<string> Translate(string langs, string text = null)
 | 
			
		||||
        {
 | 
			
		||||
            var langarr = langs.ToLowerInvariant().Split('>');
 | 
			
		||||
            if (langarr.Length != 2)
 | 
			
		||||
                throw new ArgumentException();
 | 
			
		||||
            var from = langarr[0];
 | 
			
		||||
            var to = langarr[1];
 | 
			
		||||
            text = text?.Trim();
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(text))
 | 
			
		||||
                throw new ArgumentException();
 | 
			
		||||
            return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<string> DapiSearch(string tag, DapiSearchType type)
 | 
			
		||||
        {
 | 
			
		||||
            tag = tag?.Replace(" ", "_");
 | 
			
		||||
@@ -65,4 +168,18 @@ namespace NadekoBot.Services.Searches
 | 
			
		||||
        Rule34,
 | 
			
		||||
        Yandere
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public struct UserChannelPair
 | 
			
		||||
    {
 | 
			
		||||
        public ulong UserId { get; set; }
 | 
			
		||||
        public ulong ChannelId { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public class StreamStatus
 | 
			
		||||
    {
 | 
			
		||||
        public bool IsLive { get; set; }
 | 
			
		||||
        public string ApiLink { get; set; }
 | 
			
		||||
        public string Views { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								src/NadekoBot/Services/Searches/StreamNotFoundException.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/NadekoBot/Services/Searches/StreamNotFoundException.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class StreamNotFoundException : Exception
 | 
			
		||||
    {
 | 
			
		||||
        public StreamNotFoundException(string message) : base($"Stream '{message}' not found.")
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										190
									
								
								src/NadekoBot/Services/Searches/StreamNotificationService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/NadekoBot/Services/Searches/StreamNotificationService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
using Discord;
 | 
			
		||||
using Discord.WebSocket;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class StreamNotificationService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Timer _streamCheckTimer;
 | 
			
		||||
        private bool firstStreamNotifPass { get; set; } = true;
 | 
			
		||||
        private readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
 | 
			
		||||
 | 
			
		||||
        private readonly DbHandler _db;
 | 
			
		||||
        private readonly DiscordShardedClient _client;
 | 
			
		||||
        private readonly NadekoStrings _strings;
 | 
			
		||||
 | 
			
		||||
        public StreamNotificationService(DbHandler db, DiscordShardedClient client, NadekoStrings strings)
 | 
			
		||||
        {
 | 
			
		||||
            _db = db;
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _strings = strings;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public StreamNotificationService()
 | 
			
		||||
        {
 | 
			
		||||
            _streamCheckTimer = new Timer(async (state) =>
 | 
			
		||||
            {
 | 
			
		||||
                var oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(_cachedStatuses);
 | 
			
		||||
                _cachedStatuses.Clear();
 | 
			
		||||
                IEnumerable<FollowedStream> streams;
 | 
			
		||||
                using (var uow = _db.UnitOfWork)
 | 
			
		||||
                {
 | 
			
		||||
                    streams = uow.GuildConfigs.GetAllFollowedStreams();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await Task.WhenAll(streams.Select(async fs =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
 | 
			
		||||
                        if (firstStreamNotifPass)
 | 
			
		||||
                        {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        StreamStatus oldStatus;
 | 
			
		||||
                        if (oldCachedStatuses.TryGetValue(newStatus.ApiLink, out oldStatus) &&
 | 
			
		||||
                            oldStatus.IsLive != newStatus.IsLive)
 | 
			
		||||
                        {
 | 
			
		||||
                            var server = _client.GetGuild(fs.GuildId);
 | 
			
		||||
                            var channel = server?.GetTextChannel(fs.ChannelId);
 | 
			
		||||
                            if (channel == null)
 | 
			
		||||
                                return;
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                await channel.EmbedAsync(GetEmbed(fs, newStatus, channel.Guild.Id)).ConfigureAwait(false);
 | 
			
		||||
                            }
 | 
			
		||||
                            catch
 | 
			
		||||
                            {
 | 
			
		||||
                                // ignored
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        // ignored
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
                firstStreamNotifPass = false;
 | 
			
		||||
            }, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
 | 
			
		||||
        {
 | 
			
		||||
            string response;
 | 
			
		||||
            StreamStatus result;
 | 
			
		||||
            switch (stream.Type)
 | 
			
		||||
            {
 | 
			
		||||
                case FollowedStream.FollowedStreamType.Hitbox:
 | 
			
		||||
                    var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}";
 | 
			
		||||
                    if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result))
 | 
			
		||||
                        return result;
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    var hbData = JsonConvert.DeserializeObject<HitboxResponse>(response);
 | 
			
		||||
                    if (!hbData.Success)
 | 
			
		||||
                        throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
 | 
			
		||||
                    result = new StreamStatus()
 | 
			
		||||
                    {
 | 
			
		||||
                        IsLive = hbData.IsLive,
 | 
			
		||||
                        ApiLink = hitboxUrl,
 | 
			
		||||
                        Views = hbData.Views
 | 
			
		||||
                    };
 | 
			
		||||
                    _cachedStatuses.AddOrUpdate(hitboxUrl, result, (key, old) => result);
 | 
			
		||||
                    return result;
 | 
			
		||||
                case FollowedStream.FollowedStreamType.Twitch:
 | 
			
		||||
                    var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username.ToLowerInvariant())}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6";
 | 
			
		||||
                    if (checkCache && _cachedStatuses.TryGetValue(twitchUrl, out result))
 | 
			
		||||
                        return result;
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    var twData = JsonConvert.DeserializeObject<TwitchResponse>(response);
 | 
			
		||||
                    if (twData.Error != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
 | 
			
		||||
                    }
 | 
			
		||||
                    result = new StreamStatus()
 | 
			
		||||
                    {
 | 
			
		||||
                        IsLive = twData.IsLive,
 | 
			
		||||
                        ApiLink = twitchUrl,
 | 
			
		||||
                        Views = twData.Stream?.Viewers.ToString() ?? "0"
 | 
			
		||||
                    };
 | 
			
		||||
                    _cachedStatuses.AddOrUpdate(twitchUrl, result, (key, old) => result);
 | 
			
		||||
                    return result;
 | 
			
		||||
                case FollowedStream.FollowedStreamType.Beam:
 | 
			
		||||
                    var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username.ToLowerInvariant()}";
 | 
			
		||||
                    if (checkCache && _cachedStatuses.TryGetValue(beamUrl, out result))
 | 
			
		||||
                        return result;
 | 
			
		||||
                    using (var http = new HttpClient())
 | 
			
		||||
                    {
 | 
			
		||||
                        response = await http.GetStringAsync(beamUrl).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var bmData = JsonConvert.DeserializeObject<BeamResponse>(response);
 | 
			
		||||
                    if (bmData.Error != null)
 | 
			
		||||
                        throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
 | 
			
		||||
                    result = new StreamStatus()
 | 
			
		||||
                    {
 | 
			
		||||
                        IsLive = bmData.IsLive,
 | 
			
		||||
                        ApiLink = beamUrl,
 | 
			
		||||
                        Views = bmData.ViewersCurrent.ToString()
 | 
			
		||||
                    };
 | 
			
		||||
                    _cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
 | 
			
		||||
                    return result;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public EmbedBuilder GetEmbed(FollowedStream fs, StreamStatus status, ulong guildId)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle(fs.Username)
 | 
			
		||||
                                          .WithUrl(GetLink(fs))
 | 
			
		||||
                                          .AddField(efb => efb.WithName(GetText(fs, "status"))
 | 
			
		||||
                                                            .WithValue(status.IsLive ? "Online" : "Offline")
 | 
			
		||||
                                                            .WithIsInline(true))
 | 
			
		||||
                                          .AddField(efb => efb.WithName(GetText(fs, "viewers"))
 | 
			
		||||
                                                            .WithValue(status.IsLive ? status.Views : "-")
 | 
			
		||||
                                                            .WithIsInline(true))
 | 
			
		||||
                                          .AddField(efb => efb.WithName(GetText(fs, "platform"))
 | 
			
		||||
                                                            .WithValue(fs.Type.ToString())
 | 
			
		||||
                                                            .WithIsInline(true))
 | 
			
		||||
                                          .WithColor(status.IsLive ? NadekoBot.OkColor : NadekoBot.ErrorColor);
 | 
			
		||||
 | 
			
		||||
            return embed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetText(FollowedStream fs, string key, params object[] replacements) =>
 | 
			
		||||
            _strings.GetText(key,
 | 
			
		||||
                fs.GuildId,
 | 
			
		||||
                "Searches".ToLowerInvariant(),
 | 
			
		||||
                replacements);
 | 
			
		||||
 | 
			
		||||
        public string GetLink(FollowedStream fs)
 | 
			
		||||
        {
 | 
			
		||||
            if (fs.Type == FollowedStream.FollowedStreamType.Hitbox)
 | 
			
		||||
                return $"http://www.hitbox.tv/{fs.Username}/";
 | 
			
		||||
            if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
 | 
			
		||||
                return $"http://www.twitch.tv/{fs.Username}/";
 | 
			
		||||
            if (fs.Type == FollowedStream.FollowedStreamType.Beam)
 | 
			
		||||
                return $"https://beam.pro/{fs.Username}/";
 | 
			
		||||
            return "??";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/NadekoBot/Services/Searches/StreamResponses.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/NadekoBot/Services/Searches/StreamResponses.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class HitboxResponse
 | 
			
		||||
    {
 | 
			
		||||
        public bool Success { get; set; } = true;
 | 
			
		||||
        [JsonProperty("media_is_live")]
 | 
			
		||||
        public string MediaIsLive { get; set; }
 | 
			
		||||
        public bool IsLive => MediaIsLive == "1";
 | 
			
		||||
        [JsonProperty("media_views")]
 | 
			
		||||
        public string Views { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class TwitchResponse
 | 
			
		||||
    {
 | 
			
		||||
        public string Error { get; set; } = null;
 | 
			
		||||
        public bool IsLive => Stream != null;
 | 
			
		||||
        public StreamInfo Stream { get; set; }
 | 
			
		||||
 | 
			
		||||
        public class StreamInfo
 | 
			
		||||
        {
 | 
			
		||||
            public int Viewers { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class BeamResponse
 | 
			
		||||
    {
 | 
			
		||||
        public string Error { get; set; } = null;
 | 
			
		||||
 | 
			
		||||
        [JsonProperty("online")]
 | 
			
		||||
        public bool IsLive { get; set; }
 | 
			
		||||
        public int ViewersCurrent { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace NadekoBot.Modules.Searches.Models
 | 
			
		||||
namespace NadekoBot.Services.Searches
 | 
			
		||||
{
 | 
			
		||||
    public class WoWJoke
 | 
			
		||||
    {
 | 
			
		||||
		Reference in New Issue
	
	Block a user