diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs index 0ca8d78a..b602c8b4 100644 --- a/src/NadekoBot/Modules/Administration/Administration.cs +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -52,7 +52,7 @@ namespace NadekoBot.Modules.Administration bool shouldDelete; using (var uow = DbHandler.UnitOfWork()) { - shouldDelete = uow.GuildConfigs.For(channel.Guild.Id).DeleteMessageOnCommand; + shouldDelete = uow.GuildConfigs.For(channel.Guild.Id, set => set).DeleteMessageOnCommand; } if (shouldDelete) @@ -128,15 +128,15 @@ namespace NadekoBot.Modules.Administration public async Task Delmsgoncmd(IUserMessage umsg) { var channel = (ITextChannel)umsg.Channel; - GuildConfig conf; + bool enabled; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(channel.Guild.Id); - conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand; - uow.GuildConfigs.Update(conf); + var conf = uow.GuildConfigs.For(channel.Guild.Id, set => set); + enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand; + await uow.CompleteAsync(); } - if (conf.DeleteMessageOnCommand) + if (enabled) await channel.SendMessageAsync("✅ **Now automatically deleting successful command invokations.**").ConfigureAwait(false); else await channel.SendMessageAsync("❗**Stopped automatic deletion of successful command invokations.**").ConfigureAwait(false); @@ -399,7 +399,7 @@ namespace NadekoBot.Modules.Administration using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); config.MuteRoleName = name; GuildMuteRoles.AddOrUpdate(channel.Guild.Id, name, (id, old) => name); await uow.CompleteAsync().ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs b/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs index 54f73ae5..8cf15cb7 100644 --- a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs @@ -30,7 +30,7 @@ namespace NadekoBot.Modules.Administration GuildConfig conf; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(user.Guild.Id); + conf = uow.GuildConfigs.For(user.Guild.Id, set => set); } if (conf.AutoAssignRoleId == 0) @@ -57,13 +57,12 @@ namespace NadekoBot.Modules.Administration GuildConfig conf; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(channel.Guild.Id); + conf = uow.GuildConfigs.For(channel.Guild.Id, set => set); if (role == null) conf.AutoAssignRoleId = 0; else conf.AutoAssignRoleId = role.Id; - uow.GuildConfigs.Update(conf); await uow.CompleteAsync().ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 42faf111..ed0aee0d 100644 --- a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -1,6 +1,7 @@ using Discord; using Discord.Commands; using Discord.WebSocket; +using Microsoft.EntityFrameworkCore; using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; @@ -698,7 +699,9 @@ namespace NadekoBot.Modules.Administration bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting; + var logSetting = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredVoicePresenceChannelIds)) + .LogSetting; GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); enabled = logSetting.LogVoicePresence = !logSetting.LogVoicePresence; if (enabled) diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration.cs b/src/NadekoBot/Modules/Administration/Commands/Migration.cs index 5ce821d9..7027488a 100644 --- a/src/NadekoBot/Modules/Administration/Commands/Migration.cs +++ b/src/NadekoBot/Modules/Administration/Commands/Migration.cs @@ -109,7 +109,7 @@ namespace NadekoBot.Modules.Administration var byeMsg = (string)reader["ByeText"]; var grdel = false; var byedel = grdel; - var gc = uow.GuildConfigs.For(gid); + var gc = uow.GuildConfigs.For(gid, set => set); if (greetDM) gc.SendDmGreetMessage = greet; diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs index 1a8bb600..2e8a0b87 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -27,7 +27,7 @@ namespace NadekoBot.Modules.Administration bool newval; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages; await uow.CompleteAsync().ConfigureAwait(false); } @@ -132,7 +132,7 @@ namespace NadekoBot.Modules.Administration bool areExclusive; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles; await uow.CompleteAsync(); @@ -153,7 +153,7 @@ namespace NadekoBot.Modules.Administration IEnumerable roles; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(channel.Guild.Id); + conf = uow.GuildConfigs.For(channel.Guild.Id, set => set); roles = uow.SelfAssignedRoles.GetFromGuild(channel.Guild.Id); } SelfAssignedRole roleModel; @@ -207,11 +207,11 @@ namespace NadekoBot.Modules.Administration var channel = (ITextChannel)umsg.Channel; var guildUser = (IGuildUser)umsg.Author; - GuildConfig conf; + bool autoDeleteSelfAssignedRoleMessages; IEnumerable roles; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(channel.Guild.Id); + autoDeleteSelfAssignedRoleMessages = uow.GuildConfigs.For(channel.Guild.Id, set => set).AutoDeleteSelfAssignedRoleMessages; roles = uow.SelfAssignedRoles.GetFromGuild(channel.Guild.Id); } SelfAssignedRole roleModel; @@ -236,7 +236,7 @@ namespace NadekoBot.Modules.Administration } var msg = await channel.SendMessageAsync($"🆗 You no longer have **{role.Name}** role.").ConfigureAwait(false); - if (conf.AutoDeleteSelfAssignedRoleMessages) + if (autoDeleteSelfAssignedRoleMessages) { var t = Task.Run(async () => { diff --git a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs index 3afcb46c..77960838 100644 --- a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs @@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Administration GuildConfig conf; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(user.Guild.Id); + conf = uow.GuildConfigs.For(user.Guild.Id, set => set); } if (!conf.SendChannelByeMessage) return; @@ -76,7 +76,7 @@ namespace NadekoBot.Modules.Administration GuildConfig conf; using (var uow = DbHandler.UnitOfWork()) { - conf = uow.GuildConfigs.For(user.Guild.Id); + conf = uow.GuildConfigs.For(user.Guild.Id, set => set); } if (conf.SendChannelGreetMessage) @@ -147,9 +147,9 @@ namespace NadekoBot.Modules.Administration using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(id); + var conf = uow.GuildConfigs.For(id, set => set); conf.AutoDeleteGreetMessagesTimer = timer; - uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); } } @@ -174,10 +174,10 @@ namespace NadekoBot.Modules.Administration bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(guildId); + var conf = uow.GuildConfigs.For(guildId, set => set); enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; conf.GreetMessageChannelId = channelId; - uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); } return enabled; @@ -192,12 +192,12 @@ namespace NadekoBot.Modules.Administration if (string.IsNullOrWhiteSpace(text)) { - GuildConfig config; + string channelGreetMessageText; using (var uow = DbHandler.UnitOfWork()) { - config = uow.GuildConfigs.For(channel.Guild.Id); + channelGreetMessageText = uow.GuildConfigs.For(channel.Guild.Id, set => set).ChannelGreetMessageText; } - await channel.SendMessageAsync("ℹ️ Current **greet** message: `" + config.ChannelGreetMessageText?.SanitizeMentions() + "`"); + await channel.SendMessageAsync("ℹ️ Current **greet** message: `" + channelGreetMessageText?.SanitizeMentions() + "`"); return; } @@ -218,11 +218,10 @@ namespace NadekoBot.Modules.Administration bool greetMsgEnabled; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(guildId); + var conf = uow.GuildConfigs.For(guildId, set => set); conf.ChannelGreetMessageText = message; greetMsgEnabled = conf.SendChannelGreetMessage; - uow.GuildConfigs.Update(conf); uow.Complete(); } return greetMsgEnabled; @@ -248,9 +247,9 @@ namespace NadekoBot.Modules.Administration bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(guildId); + var conf = uow.GuildConfigs.For(guildId, set => set); enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; - uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); } return enabled; @@ -295,7 +294,6 @@ namespace NadekoBot.Modules.Administration conf.DmGreetMessageText = message; greetMsgEnabled = conf.SendDmGreetMessage; - uow.GuildConfigs.Update(conf); uow.Complete(); } return greetMsgEnabled; @@ -321,10 +319,10 @@ namespace NadekoBot.Modules.Administration bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(guildId); + var conf = uow.GuildConfigs.For(guildId, set => set); enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage; conf.ByeMessageChannelId = channelId; - uow.GuildConfigs.Update(conf); + await uow.CompleteAsync(); } return enabled; @@ -339,12 +337,12 @@ namespace NadekoBot.Modules.Administration if (string.IsNullOrWhiteSpace(text)) { - GuildConfig config; + string byeMessageText; using (var uow = DbHandler.UnitOfWork()) { - config = uow.GuildConfigs.For(channel.Guild.Id); + byeMessageText = uow.GuildConfigs.For(channel.Guild.Id, set => set).ChannelByeMessageText; } - await channel.SendMessageAsync("ℹ️ Current **bye** message: `" + config.ChannelByeMessageText?.SanitizeMentions() + "`"); + await channel.SendMessageAsync("ℹ️ Current **bye** message: `" + byeMessageText?.SanitizeMentions() + "`"); return; } @@ -365,11 +363,10 @@ namespace NadekoBot.Modules.Administration bool byeMsgEnabled; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(guildId); + var conf = uow.GuildConfigs.For(guildId, set => set); conf.ChannelByeMessageText = message; byeMsgEnabled = conf.SendChannelByeMessage; - uow.GuildConfigs.Update(conf); uow.Complete(); } return byeMsgEnabled; @@ -397,9 +394,9 @@ namespace NadekoBot.Modules.Administration using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(id); + var conf = uow.GuildConfigs.For(id, set => set); conf.AutoDeleteByeMessagesTimer = timer; - uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs index 96906532..8c7c4e00 100644 --- a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs @@ -58,7 +58,7 @@ namespace NadekoBot.Modules.Administration catch { } using (var uow = DbHandler.UnitOfWork()) { - uow.GuildConfigs.For(guild.Id).VoicePlusTextEnabled = false; + uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false; voicePlusTextCache.TryRemove(guild.Id); await uow.CompleteAsync().ConfigureAwait(false); } @@ -134,7 +134,7 @@ namespace NadekoBot.Modules.Administration bool isEnabled; using (var uow = DbHandler.UnitOfWork()) { - var conf = uow.GuildConfigs.For(guild.Id); + var conf = uow.GuildConfigs.For(guild.Id, set => set); isEnabled = conf.VoicePlusTextEnabled = !conf.VoicePlusTextEnabled; await uow.CompleteAsync().ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index 893cc1a0..0ac7e6e1 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -1,6 +1,7 @@ using Discord; using Discord.Commands; using Discord.WebSocket; +using Microsoft.EntityFrameworkCore; using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; @@ -166,7 +167,7 @@ namespace NadekoBot.Modules.Games bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var guildConfig = uow.GuildConfigs.For(channel.Id); + var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); var toAdd = new GCChannelId() { ChannelId = channel.Id }; if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index de2baa84..c0586225 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -220,7 +220,7 @@ namespace NadekoBot.Modules.Music } using (var uow = DbHandler.UnitOfWork()) { - uow.GuildConfigs.For(channel.Guild.Id).DefaultMusicVolume = val / 100.0f; + uow.GuildConfigs.For(channel.Guild.Id, set => set).DefaultMusicVolume = val / 100.0f; uow.Complete(); } await channel.SendMessageAsync($"🎵 `Default volume set to {val}%`").ConfigureAwait(false); @@ -747,7 +747,7 @@ namespace NadekoBot.Modules.Music float vol = 1;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume; using (var uow = DbHandler.UnitOfWork()) { - vol = uow.GuildConfigs.For(textCh.Guild.Id).DefaultMusicVolume; + vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume; } var mp = new MusicPlayer(voiceCh, vol); diff --git a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs index 28e3ec40..ac459dd0 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs +++ b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs @@ -1,5 +1,6 @@ using Discord; using Discord.Commands; +using Microsoft.EntityFrameworkCore; using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; @@ -46,7 +47,7 @@ namespace NadekoBot.Modules.Permissions using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns)); var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); config.CommandCooldowns.RemoveWhere(cc => cc.CommandName == command.Text.ToLowerInvariant()); diff --git a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs index c1e35d2d..a581e007 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs +++ b/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs @@ -1,5 +1,6 @@ using Discord; using Discord.Commands; +using Microsoft.EntityFrameworkCore; using NadekoBot.Attributes; using NadekoBot.Services; using System.Collections.Concurrent; @@ -68,7 +69,7 @@ namespace NadekoBot.Modules.Permissions bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); enabled = config.FilterInvites = !config.FilterInvites; await uow.CompleteAsync().ConfigureAwait(false); } @@ -94,7 +95,7 @@ namespace NadekoBot.Modules.Permissions int removed; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FilterInvitesChannelIds)); removed = config.FilterInvitesChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id); if (removed == 0) { @@ -127,7 +128,7 @@ namespace NadekoBot.Modules.Permissions bool enabled; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); enabled = config.FilterWords = !config.FilterWords; await uow.CompleteAsync().ConfigureAwait(false); } @@ -153,7 +154,7 @@ namespace NadekoBot.Modules.Permissions int removed; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FilterWordsChannelIds)); removed = config.FilterWordsChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id); if (removed == 0) { @@ -191,7 +192,7 @@ namespace NadekoBot.Modules.Permissions int removed; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FilteredWords)); removed = config.FilteredWords.RemoveWhere(fw => fw.Word == word); diff --git a/src/NadekoBot/Modules/Permissions/Permissions.cs b/src/NadekoBot/Modules/Permissions/Permissions.cs index 6b624257..8d6c32ea 100644 --- a/src/NadekoBot/Modules/Permissions/Permissions.cs +++ b/src/NadekoBot/Modules/Permissions/Permissions.cs @@ -51,7 +51,7 @@ namespace NadekoBot.Modules.Permissions using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); config.VerbosePermissions = action.Value; Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() { @@ -72,7 +72,7 @@ namespace NadekoBot.Modules.Permissions var channel = (ITextChannel)msg.Channel; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set); if (role == null) { await channel.SendMessageAsync($"ℹ️ Current permission role is **{config.PermissionRole}**.").ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs b/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs index 8a768b20..3251288a 100644 --- a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs @@ -12,6 +12,11 @@ using NadekoBot.Services.Database.Models; using System.Net.Http; using Discord.WebSocket; using NadekoBot.Attributes; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using NLog; +using NadekoBot.Services.Database; +using NadekoBot.Extensions; namespace NadekoBot.Modules.Searches { @@ -19,87 +24,112 @@ namespace NadekoBot.Modules.Searches { public class StreamStatus { - public StreamStatus(string link, bool isLive, string views) - { - Link = link; - IsLive = isLive; - Views = views; - } - public bool IsLive { get; set; } - public string Link { 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 { private Timer checkTimer { get; } private ConcurrentDictionary oldCachedStatuses = new ConcurrentDictionary(); private ConcurrentDictionary cachedStatuses = new ConcurrentDictionary(); + private Logger _log { get; } + private bool FirstPass { get; set; } = true; public StreamNotificationCommands() { + + _log = NLog.LogManager.GetCurrentClassLogger(); checkTimer = new Timer(async (state) => { oldCachedStatuses = new ConcurrentDictionary(cachedStatuses); - cachedStatuses = new ConcurrentDictionary(); - try + cachedStatuses.Clear(); + IEnumerable streams; + using (var uow = DbHandler.UnitOfWork()) { - IEnumerable streams; - using (var uow = DbHandler.UnitOfWork()) - { - streams = uow.GuildConfigs.GetAllFollowedStreams(); - } - foreach (var stream in streams) - { - StreamStatus data; - try - { - data = await GetStreamStatus(stream).ConfigureAwait(false); - if (data == null) - return; - } - catch - { - continue; - } - - StreamStatus oldData; - oldCachedStatuses.TryGetValue(data.Link, out oldData); - - if (oldData == null || data.IsLive != oldData.IsLive) - { - if (FirstPass) - continue; - var server = NadekoBot.Client.GetGuild(stream.GuildId); - var channel = server?.GetTextChannel(stream.ChannelId); - if (channel == null) - continue; - var msg = $"`{stream.Username}`'s stream is now " + - $"**{(data.IsLive ? "ONLINE" : "OFFLINE")}** with " + - $"**{data.Views}** viewers."; - if (data.IsLive) - if (stream.Type == FollowedStream.FollowedStreamType.Hitbox) - msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】"; - else if (stream.Type == FollowedStream.FollowedStreamType.Twitch) - msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】"; - else if (stream.Type == FollowedStream.FollowedStreamType.Beam) - msg += $"\n`Here is the Link:`【 http://www.beam.pro/{stream.Username}/ 】"; - try { await channel.SendMessageAsync(msg).ConfigureAwait(false); } catch { } - } - } - FirstPass = false; + streams = uow.GuildConfigs.GetAllFollowedStreams(); } - catch { } + + 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 msg = $"`{fs.Username}`'s stream is now " + + $"**{(newStatus.IsLive ? "ONLINE" : "OFFLINE")}** with " + + $"**{newStatus.Views}** viewers."; + + var server = NadekoBot.Client.GetGuild(fs.GuildId); + var channel = server?.GetTextChannel(fs.ChannelId); + if (channel == null) + return; + if (newStatus.IsLive) + msg += "\n" + fs.GetLink(); + try { await channel.SendMessageAsync(msg).ConfigureAwait(false); } catch { } + } + } + catch (Exception ex) + { + + } + })); + + FirstPass = false; }, null, TimeSpan.Zero, TimeSpan.FromSeconds(60)); } private async Task GetStreamStatus(FollowedStream stream, bool checkCache = true) { - bool isLive; string response; - JObject data; StreamStatus result; switch (stream.Type) { @@ -111,10 +141,16 @@ namespace NadekoBot.Modules.Searches { response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false); } - data = JObject.Parse(response); - isLive = data["media_is_live"].ToString() == "1"; - result = new StreamStatus(hitboxUrl, isLive, data["media_views"].ToString()); - cachedStatuses.TryAdd(hitboxUrl, result); + var hbData = JsonConvert.DeserializeObject(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)}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6"; @@ -124,10 +160,18 @@ namespace NadekoBot.Modules.Searches { response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false); } - data = JObject.Parse(response); - isLive = !string.IsNullOrWhiteSpace(data["stream"].ToString()); - result = new StreamStatus(twitchUrl, isLive, isLive ? data["stream"]["viewers"].ToString() : "0"); - cachedStatuses.TryAdd(twitchUrl, result); + var twData = JsonConvert.DeserializeObject(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}"; @@ -137,10 +181,17 @@ namespace NadekoBot.Modules.Searches { response = await http.GetStringAsync(beamUrl).ConfigureAwait(false); } - data = JObject.Parse(response); - isLive = data["online"].ToObject() == true; - result = new StreamStatus(beamUrl, isLive, data["viewersCurrent"].ToString()); - cachedStatuses.TryAdd(beamUrl, result); + + var bmData = JsonConvert.DeserializeObject(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; @@ -178,7 +229,10 @@ namespace NadekoBot.Modules.Searches IEnumerable streams; using (var uow = DbHandler.UnitOfWork()) { - streams = uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams; + streams = uow.GuildConfigs + .For(channel.Guild.Id, + set => set.Include(gc => gc.FollowedStreams)) + .FollowedStreams; } if (!streams.Any()) @@ -198,30 +252,33 @@ namespace NadekoBot.Modules.Searches [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequirePermission(GuildPermission.ManageMessages)] - public async Task RemoveStream(IUserMessage msg, [Remainder] string username) + public async Task RemoveStream(IUserMessage msg, FollowedStream.FollowedStreamType type, [Remainder] string username) { var channel = (ITextChannel)msg.Channel; username = username.ToLowerInvariant().Trim(); - FollowedStream toRemove; + var fs = new FollowedStream() + { + ChannelId = channel.Id, + Username = username, + Type = type + }; + + bool removed; using (var uow = DbHandler.UnitOfWork()) { - var config = uow.GuildConfigs.For(channel.Guild.Id); - var streams = config.FollowedStreams; - toRemove = streams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToLowerInvariant() == username).FirstOrDefault(); - if (toRemove != null) - { - config.FollowedStreams = new HashSet(streams.Except(new[] { toRemove })); - await uow.CompleteAsync(); - } + var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams)); + removed = config.FollowedStreams.Remove(fs); + if (removed) + await uow.CompleteAsync().ConfigureAwait(false); } - if (toRemove == null) + if (!removed) { await channel.SendMessageAsync(":anger: No such stream.").ConfigureAwait(false); return; } - await channel.SendMessageAsync($":ok: Removed `{toRemove.Username}`'s stream ({toRemove.Type}) from notifications.").ConfigureAwait(false); + await channel.SendMessageAsync($":ok: Removed `{username}`'s stream ({type}) from notifications.").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -238,7 +295,7 @@ namespace NadekoBot.Modules.Searches var streamStatus = (await GetStreamStatus(new FollowedStream { Username = stream, - Type = platform + Type = platform, })); if (streamStatus.IsLive) { @@ -265,16 +322,7 @@ namespace NadekoBot.Modules.Searches Username = username, Type = type, }; - bool exists; - using (var uow = DbHandler.UnitOfWork()) - { - exists = uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToLowerInvariant().Trim() == username).Any(); - } - if (exists) - { - await channel.SendMessageAsync($":anger: I am already following `{username}` ({type}) stream on this channel.").ConfigureAwait(false); - return; - } + StreamStatus data; try { @@ -285,22 +333,35 @@ namespace NadekoBot.Modules.Searches await channel.SendMessageAsync(":anger: Stream probably doesn't exist.").ConfigureAwait(false); return; } - var msg = $"Stream is currently **{(data.IsLive ? "ONLINE" : "OFFLINE")}** with **{data.Views}** viewers"; - if (data.IsLive) - if (type == FollowedStream.FollowedStreamType.Hitbox) - msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】"; - else if (type == FollowedStream.FollowedStreamType.Twitch) - msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】"; - else if (type == FollowedStream.FollowedStreamType.Beam) - msg += $"\n`Here is the Link:`【 https://beam.pro/{stream.Username}/ 】"; + using (var uow = DbHandler.UnitOfWork()) { - uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Add(stream); - await uow.CompleteAsync(); + uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams)) + .FollowedStreams + .Add(stream); + await uow.CompleteAsync().ConfigureAwait(false); } + var msg = $"Stream is currently **{(data.IsLive ? "ONLINE" : "OFFLINE")}** with **{data.Views}** viewers"; + if (data.IsLive) + msg += stream.GetLink(); msg = $":ok: I will notify this channel when status changes.\n{msg}"; await channel.SendMessageAsync(msg).ConfigureAwait(false); } } } + + public static class FollowedStreamExtensions + { + public static string GetLink(this FollowedStream fs) + { + //todo C#7 + if (fs.Type == FollowedStream.FollowedStreamType.Hitbox) + return $"\n`Here is the Link:`【 http://www.hitbox.tv/{fs.Username}/ 】"; + else if (fs.Type == FollowedStream.FollowedStreamType.Twitch) + return $"\n`Here is the Link:`【 http://www.twitch.tv/{fs.Username}/ 】"; + else if (fs.Type == FollowedStream.FollowedStreamType.Beam) + return $"\n`Here is the Link:`【 https://beam.pro/{fs.Username}/ 】"; + return "???"; + } + } } \ No newline at end of file diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index d64bce83..d6568b6e 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -5280,7 +5280,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Removes notifications of a certain streamer on this channel.. + /// Looks up a localized string similar to Removes notifications of a certain streamer from a certain platform on this channel.. /// public static string removestream_desc { get { @@ -5289,7 +5289,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to `{0}rms SomeGuy`. + /// Looks up a localized string similar to `{0}rms Twitch SomeGuy` or `{0}rms Beam SomeOtherGuy`. /// public static string removestream_usage { get { diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index e2df60fe..dc97bd25 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -1759,10 +1759,10 @@ removestream rms - Removes notifications of a certain streamer on this channel. + Removes notifications of a certain streamer from a certain platform on this channel. - `{0}rms SomeGuy` + `{0}rms Twitch SomeGuy` or `{0}rms Beam SomeOtherGuy` liststreams ls diff --git a/src/NadekoBot/Services/Database/Models/FollowedStream.cs b/src/NadekoBot/Services/Database/Models/FollowedStream.cs index 817f003f..e0784a2b 100644 --- a/src/NadekoBot/Services/Database/Models/FollowedStream.cs +++ b/src/NadekoBot/Services/Database/Models/FollowedStream.cs @@ -11,5 +11,17 @@ { Twitch, Hitbox, Beam } + + public override int GetHashCode() => + ChannelId.GetHashCode() ^ Username.GetHashCode(); + + public override bool Equals(object obj) + { + var fs = obj as FollowedStream; + if (fs == null) + return false; + + return fs.ChannelId == ChannelId && fs.Username.ToLowerInvariant().Trim() == Username.ToLowerInvariant().Trim(); + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs index ca3b651a..4e9b0f09 100644 --- a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs @@ -1,11 +1,14 @@ -using NadekoBot.Services.Database.Models; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; +using System; using System.Collections.Generic; +using System.Linq; namespace NadekoBot.Services.Database.Repositories { public interface IGuildConfigRepository : IRepository { - GuildConfig For(ulong guildId); + GuildConfig For(ulong guildId, Func, IQueryable> includes = null); GuildConfig PermissionsFor(ulong guildId); IEnumerable PermissionsForAll(); GuildConfig SetNewRootPermission(ulong guildId, Permission p); diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs index d90662ba..96f0cc22 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using NadekoBot.Modules.Permissions; +using System; namespace NadekoBot.Services.Database.Repositories.Impl { @@ -33,20 +34,30 @@ namespace NadekoBot.Services.Database.Repositories.Impl /// /// /// - public GuildConfig For(ulong guildId) + public GuildConfig For(ulong guildId, Func, IQueryable> includes = null) { - var config = _set - .Include(gc => gc.FollowedStreams) - .Include(gc => gc.LogSetting) - .ThenInclude(ls => ls.IgnoredChannels) - .Include(gc => gc.LogSetting) - .ThenInclude(ls => ls.IgnoredVoicePresenceChannelIds) - .Include(gc => gc.FilterInvitesChannelIds) - .Include(gc => gc.FilterWordsChannelIds) - .Include(gc => gc.FilteredWords) - .Include(gc => gc.GenerateCurrencyChannelIds) - .Include(gc => gc.CommandCooldowns) - .FirstOrDefault(c => c.GuildId == guildId); + GuildConfig config; + + if (includes == null) + { + config = _set + .Include(gc => gc.FollowedStreams) + .Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredChannels) + .Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredVoicePresenceChannelIds) + .Include(gc => gc.FilterInvitesChannelIds) + .Include(gc => gc.FilterWordsChannelIds) + .Include(gc => gc.FilteredWords) + .Include(gc => gc.GenerateCurrencyChannelIds) + .Include(gc => gc.CommandCooldowns) + .FirstOrDefault(c => c.GuildId == guildId); + } + else + { + var set = includes(_set); + config = set.FirstOrDefault(c => c.GuildId == guildId); + } if (config == null) {