diff --git a/NadekoBot/Classes/ObservableConcurrentDictionary.cs b/NadekoBot/Classes/ObservableConcurrentDictionary.cs new file mode 100644 index 00000000..cd364328 --- /dev/null +++ b/NadekoBot/Classes/ObservableConcurrentDictionary.cs @@ -0,0 +1,204 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: ObservableConcurrentDictionary.cs +// +//-------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +namespace System.Collections.Concurrent +{ + /// + /// Provides a thread-safe dictionary for use with data binding. + /// + /// Specifies the type of the keys in this collection. + /// Specifies the type of the values in this collection. + [DebuggerDisplay("Count={Count}")] + public class ObservableConcurrentDictionary : + ICollection>, IDictionary, + INotifyCollectionChanged, INotifyPropertyChanged + { + private readonly SynchronizationContext _context; + private readonly ConcurrentDictionary _dictionary; + + /// + /// Initializes an instance of the ObservableConcurrentDictionary class. + /// + public ObservableConcurrentDictionary() + { + _context = AsyncOperationManager.SynchronizationContext; + _dictionary = new ConcurrentDictionary(); + } + + /// Event raised when the collection changes. + public event NotifyCollectionChangedEventHandler CollectionChanged; + /// Event raised when a property on the collection changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary. + /// + private void NotifyObserversOfChange() + { + var collectionHandler = CollectionChanged; + var propertyHandler = PropertyChanged; + if (collectionHandler != null || propertyHandler != null) + { + _context.Post(s => + { + if (collectionHandler != null) + { + collectionHandler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + if (propertyHandler != null) + { + propertyHandler(this, new PropertyChangedEventArgs("Count")); + propertyHandler(this, new PropertyChangedEventArgs("Keys")); + propertyHandler(this, new PropertyChangedEventArgs("Values")); + } + }, null); + } + } + + /// Attempts to add an item to the dictionary, notifying observers of any changes. + /// The item to be added. + /// Whether the add was successful. + private bool TryAddWithNotification(KeyValuePair item) + { + return TryAddWithNotification(item.Key, item.Value); + } + + /// Attempts to add an item to the dictionary, notifying observers of any changes. + /// The key of the item to be added. + /// The value of the item to be added. + /// Whether the add was successful. + private bool TryAddWithNotification(TKey key, TValue value) + { + bool result = _dictionary.TryAdd(key, value); + if (result) NotifyObserversOfChange(); + return result; + } + + /// Attempts to remove an item from the dictionary, notifying observers of any changes. + /// The key of the item to be removed. + /// The value of the item removed. + /// Whether the removal was successful. + private bool TryRemoveWithNotification(TKey key, out TValue value) + { + bool result = _dictionary.TryRemove(key, out value); + if (result) NotifyObserversOfChange(); + return result; + } + + /// Attempts to add or update an item in the dictionary, notifying observers of any changes. + /// The key of the item to be updated. + /// The new value to set for the item. + /// Whether the update was successful. + private void UpdateWithNotification(TKey key, TValue value) + { + _dictionary[key] = value; + NotifyObserversOfChange(); + } + + #region ICollection> Members + void ICollection>.Add(KeyValuePair item) + { + TryAddWithNotification(item); + } + + void ICollection>.Clear() + { + ((ICollection>)_dictionary).Clear(); + NotifyObserversOfChange(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return ((ICollection>)_dictionary).Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_dictionary).CopyTo(array, arrayIndex); + } + + int ICollection>.Count { + get { return ((ICollection>)_dictionary).Count; } + } + + bool ICollection>.IsReadOnly { + get { return ((ICollection>)_dictionary).IsReadOnly; } + } + + bool ICollection>.Remove(KeyValuePair item) + { + TValue temp; + return TryRemoveWithNotification(item.Key, out temp); + } + #endregion + + #region IEnumerable> Members + IEnumerator> IEnumerable>.GetEnumerator() + { + return ((ICollection>)_dictionary).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((ICollection>)_dictionary).GetEnumerator(); + } + #endregion + + #region IDictionary Members + public void Add(TKey key, TValue value) + { + TryAddWithNotification(key, value); + } + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public ICollection Keys { + get { return _dictionary.Keys; } + } + + public bool Remove(TKey key) + { + TValue temp; + return TryRemoveWithNotification(key, out temp); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return _dictionary.TryGetValue(key, out value); + } + + public bool TryAdd(TKey key, TValue value) + { + return TryAddWithNotification(key, value); + } + + public ICollection Values { + get { return _dictionary.Values; } + } + + public TValue this[TKey key] { + get { return _dictionary[key]; } + set { UpdateWithNotification(key, value); } + } + + public bool TryRemove(TKey key, out TValue value) + { + return TryRemoveWithNotification(key, out value); + } + #endregion + } +} \ No newline at end of file diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs index b8c2cc9d..bbc4eadd 100644 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ b/NadekoBot/Classes/ServerSpecificConfig.cs @@ -82,6 +82,45 @@ namespace NadekoBot.Classes } } + [JsonProperty("LogChannel")] + private ulong? logServerChannel = null; + [JsonIgnore] + public ulong? LogServerChannel { + get { return logServerChannel; } + set { + logServerChannel = value; + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + } + } + + [JsonProperty("LogPresenceChannel")] + private ulong? logPresenceChannel = null; + [JsonIgnore] + public ulong? LogPresenceChannel { + get { return logPresenceChannel; } + set { + logPresenceChannel = value; + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + } + } + + [JsonIgnore] + private ObservableConcurrentDictionary voiceChannelLog; + public ObservableConcurrentDictionary VoiceChannelLog { + get { return voiceChannelLog; } + set { + voiceChannelLog = value; + if (value != null) + voiceChannelLog.CollectionChanged += (s, e) => + { + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + }; + } + } + [JsonIgnore] private ObservableCollection listOfSelfAssignableRoles; public ObservableCollection ListOfSelfAssignableRoles { @@ -110,7 +149,6 @@ namespace NadekoBot.Classes [JsonIgnore] private ObservableCollection generateCurrencyChannels; - public ObservableCollection GenerateCurrencyChannels { get { return generateCurrencyChannels; } set { @@ -124,9 +162,6 @@ namespace NadekoBot.Classes } } - [JsonIgnore] - private ObservableCollection observingStreams; - [JsonIgnore] private bool autoDeleteMessagesOnCommand = false; public bool AutoDeleteMessagesOnCommand { @@ -138,6 +173,9 @@ namespace NadekoBot.Classes } } + + [JsonIgnore] + private ObservableCollection observingStreams; public ObservableCollection ObservingStreams { get { return observingStreams; } set { @@ -167,6 +205,7 @@ namespace NadekoBot.Classes ListOfSelfAssignableRoles = new ObservableCollection(); ObservingStreams = new ObservableCollection(); GenerateCurrencyChannels = new ObservableCollection(); + VoiceChannelLog = new ObservableConcurrentDictionary(); } public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 41bbdcd5..3cd92485 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -4,22 +4,12 @@ using NadekoBot.Classes; using NadekoBot.Extensions; using NadekoBot.Modules.Permissions.Classes; using System; -using System.Collections.Concurrent; using System.Linq; -using System.Threading.Tasks; namespace NadekoBot.Modules.Administration.Commands { internal class LogCommand : DiscordCommand { - - //server-channel - private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); - //server-channel - private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); - //channel-channel - private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); - private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; public LogCommand(DiscordModule module) : base(module) @@ -61,8 +51,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -82,8 +72,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -97,8 +87,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -112,8 +102,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -127,8 +117,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -142,8 +132,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -157,8 +147,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -168,29 +158,14 @@ namespace NadekoBot.Modules.Administration.Commands catch { } } - public Func DoFunc() => async e => - { - ulong chId; - if (!logs.TryRemove(e.Server.Id, out chId)) - { - logs.TryAdd(e.Server.Id, e.Channel.Id); - await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); - return; - } - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); - }; - private async void MsgRecivd(object sender, MessageEventArgs e) { try { if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -217,8 +192,8 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -244,8 +219,8 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -260,10 +235,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` } private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) { + var config = SpecificConfigurations.Default.Of(e.Server.Id); try { - ulong chId; - if (!loggingPresences.TryGetValue(e.Server.Id, out chId)) + var chId = config.LogServerChannel; + if (chId != null) { Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) != null) @@ -289,11 +265,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` var notifyJoin = false; if ((beforeVch != null || afterVch != null) && (beforeVch != afterVch)) // this means we need to notify for sure. { - if (beforeVch != null && voiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null) + if (beforeVch != null && config.VoiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null) { notifyLeave = true; } - if (afterVch != null && voiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) + if (afterVch != null && config.VoiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) { notifyJoin = true; } @@ -315,8 +291,8 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -377,17 +353,30 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` .Description("Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!**") .AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.ManageServer()) - .Do(DoFunc()); + .Do(async e => + { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + { + SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id; + await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); + return; + } + Channel ch; + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) + return; + await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); + }); cgb.CreateCommand(Module.Prefix + "userpresence") .Description("Starts logging to this channel when someone from the server goes online/offline/idle.") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { - ulong chId; - if (!loggingPresences.TryRemove(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) { - loggingPresences.TryAdd(e.Server.Id, e.Channel.Id); + SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id; await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); return; } @@ -402,11 +391,12 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` .Do(async e => { + var config = SpecificConfigurations.Default.Of(e.Server.Id); if (e.GetArg("all")?.ToLower() == "all") { foreach (var voiceChannel in e.Server.VoiceChannels) { - voiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); + config.VoiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); } await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false); return; @@ -418,9 +408,9 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` return; } ulong throwaway; - if (!voiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) + if (!config.VoiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) { - voiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id); + config.VoiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id); await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); } else diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index 2d0dbd86..f2e2071a 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -105,6 +105,21 @@ namespace NadekoBot.Modules.Music } }); + //cgb.CreateCommand("soundcloudqueue") + // .Alias("sq") + // .Description("Queue a soundcloud song using keywords. Bot will join your voice channel." + + // "**You must be in a voice channel**.\n**Usage**: `!m sq Dream Of Venice`") + // .Parameter("query", ParameterType.Unparsed) + // .Do(async e => + // { + // await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("query")).ConfigureAwait(false); + // if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) + // { + // await Task.Delay(10000).ConfigureAwait(false); + // await e.Message.Delete().ConfigureAwait(false); + // } + // }); + cgb.CreateCommand("listqueue") .Alias("lq") .Description("Lists 15 currently queued songs per page. Default page is 1.\n**Usage**: `!m lq` or `!m lq 2`") diff --git a/NadekoBot/lib/ScaredFingers.UnitsConversion.dll b/NadekoBot/lib/ScaredFingers.UnitsConversion.dll deleted file mode 100644 index 8b121811..00000000 Binary files a/NadekoBot/lib/ScaredFingers.UnitsConversion.dll and /dev/null differ diff --git a/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb b/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb deleted file mode 100644 index f63024ad..00000000 Binary files a/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb and /dev/null differ