diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md index 0b1e80ff..2bf7fe74 100644 --- a/ComprehensiveGuide.md +++ b/ComprehensiveGuide.md @@ -47,50 +47,4 @@ ________________________________________________________________________________ - On the left tab, access Credentials. There will be a line saying "If you wish to skip this step and create an API key, client ID or service account." Click on API Key, and then Server Key in the new window that appears. Enter in a name for the server key. A new window will appear with your Google API key. Copy the key. - Open up credentials.json. For "GoogleAPIKey", fill in with the new key. - Go to (https://soundcloud.com/you/apps/new). Enter a name for the app and create it. You will see a page with the title of your app, and a field labeled Client ID. Copy the ID. In credentials.json, fill in "SoundcloudClientID" with the copied ID. - -________________________________________________________________________________ - -#### Setting Up NadekoBot Permissions -###### NadekoBot's permissions can be set up to be very specific through commands in the Permissions module. -Each command or module can be turned on or off at: -- a user level (so specific users can or cannot use a command/module) -- a role level (so only certain roles have access to certain commands/module) -- a channel level (so certain commands can be limited to certain channels, which can prevent music / trivia / NSFW spam in serious channels) -- a server level. - -Use .modules to see a list of modules (sets of commands). -Use .commands [module_name] to see a list of commands in a certain module. - -Permissions use a semicolon as the prefix, so always start the command with a ;. - -Follow the semicolon with the letter of the level which you want to edit. -- "u" for Users. -- "r" for Roles. -- "c" for Channels. -- "s" for Servers. - -Follow the level with whether you want to edit the permissions of a command or a module. -- "c" for Command. -- "m" for Module. - -Follow with a space and then the command or module name (surround the command with quotation marks if there is a space within the command, for example "!m q" or "!m n"). - -Follow that with another space and, to enable it, type one of the following: [1, true, t, enable], or to disable it, one of the following: [0, false, f, disable]. - -Follow that with another space and the name of the user, role, channel. (depending on the first letter you picked) - -###### Examples -- **;rm NSFW 0 [Role_Name]** Disables the NSFW module for the role, . -- **;cc "!m n" 0 [Channel_Name]** Disables skipping to the next song in the channel, . -- **;uc "!m q" 1 [User_Name]** Enables queuing of songs for the user, . -- **;sm Gambling 0** Disables gambling in the server. - -Check permissions by using the letter of the level you want to check followed by a p, and then the name of the level in which you want to check. If there is no name, it will default to yourself for users, the @everyone role for roles, and the channel in which the command is sent for channels. - -###### Examples -- ;cp [Channel_Name] -- ;rp [Role_Name] - -Insert an **a** before the level to edit the permission for all commands / modules for all users / roles / channels / server. - -Reference the Help command (-h) for more Permissions related commands. +- Restart your computer. diff --git a/LinuxSetup.md b/LinuxSetup.md index 14152c90..2126fe44 100644 --- a/LinuxSetup.md +++ b/LinuxSetup.md @@ -121,12 +121,10 @@ Note if the command is not be initiated, hit **Enter** ######NOW WE NEED TO IMPORT SOME DISCORD CERTS **13)** -
mozroots --import --ask-remove --machine
-
+`certmgr -ssl https://discordapp.com` **14)** -
certmgr --ssl https://gateway.discord.gg
-
+`certmgr --ssl https://gateway.discord.gg` Type `yes` and hit Enter **(three times - as it will ask for three times)** diff --git a/NadekoBot.sln b/NadekoBot.sln index fd630f8d..096b6db8 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -41,32 +41,32 @@ Global {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/NadekoBot/Classes/DBHandler.cs b/NadekoBot/Classes/DBHandler.cs index afbfb765..9fb664f9 100644 --- a/NadekoBot/Classes/DBHandler.cs +++ b/NadekoBot/Classes/DBHandler.cs @@ -198,7 +198,7 @@ Limit 20 OFFSET ?", num * 20); { using (var conn = new SQLiteConnection(FilePath)) { - return conn.Table().Take(n).ToList().OrderBy(cs => -cs.Value); + return conn.Table().OrderByDescending(cs => cs.Value).Take(n).ToList(); } } } diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs index 93527acd..1b162209 100644 --- a/NadekoBot/Classes/Extensions.cs +++ b/NadekoBot/Classes/Extensions.cs @@ -215,7 +215,7 @@ namespace NadekoBot.Extensions sb.Append($"{ Format.Code(item.Key)}\n"); sb.AppendLine(" `└─`" + Format.Bold(item.Value)); } - + } else { @@ -224,7 +224,7 @@ namespace NadekoBot.Extensions sb.Append($"{ Format.Code(item.ToString())} \n"); } } - + return sb.ToString(); } /// @@ -352,5 +352,15 @@ namespace NadekoBot.Extensions await Task.Run(() => images.Merge(reverseScaleFactor)).ConfigureAwait(false); public static string Unmention(this string str) => str.Replace("@", "ම"); + + public static Stream ToStream(this string str) + { + var sw = new StreamWriter(new MemoryStream()); + sw.Write(str); + sw.Flush(); + sw.BaseStream.Position = 0; + return sw.BaseStream; + } + } } 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/SearchHelper.cs b/NadekoBot/Classes/SearchHelper.cs index 9525ec63..42f70398 100644 --- a/NadekoBot/Classes/SearchHelper.cs +++ b/NadekoBot/Classes/SearchHelper.cs @@ -377,5 +377,13 @@ namespace NadekoBot.Classes return url; } } + + public static string ShowInPrettyCode(IEnumerable items, Func howToPrint, int cols = 3) + { + var i = 0; + return "```xl\n" + string.Join("\n", items.GroupBy(item => (i++) / cols) + .Select(ig => string.Join("", ig.Select(el => howToPrint(el))))) + + $"\n```"; + } } } diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs index b1458f37..9c0086a0 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 { @@ -111,7 +150,19 @@ namespace NadekoBot.Classes } [JsonIgnore] - private ObservableCollection observingStreams; + private ObservableCollection generateCurrencyChannels; + public ObservableCollection GenerateCurrencyChannels { + get { return generateCurrencyChannels; } + set { + generateCurrencyChannels = value; + if (value != null) + generateCurrencyChannels.CollectionChanged += (s, e) => + { + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + }; + } + } [JsonIgnore] private bool autoDeleteMessagesOnCommand = false; @@ -137,6 +188,9 @@ namespace NadekoBot.Classes } } + + [JsonIgnore] + private ObservableCollection observingStreams; public ObservableCollection ObservingStreams { get { return observingStreams; } set { @@ -165,6 +219,8 @@ 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/lib/ScaredFingers.UnitsConversion.dll b/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll similarity index 100% rename from NadekoBot/lib/ScaredFingers.UnitsConversion.dll rename to NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll diff --git a/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb b/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.pdb similarity index 100% rename from NadekoBot/lib/ScaredFingers.UnitsConversion.pdb rename to NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.pdb diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs index 8689ea4a..36538813 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -6,8 +6,11 @@ using NadekoBot.DataModels; using NadekoBot.Extensions; using NadekoBot.Modules.Administration.Commands; using NadekoBot.Modules.Permissions.Classes; +using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading.Tasks; namespace NadekoBot.Modules.Administration @@ -76,7 +79,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "restart") - .Description("Restarts the bot. Might not work.") + .Description("Restarts the bot. Might not work. **Bot Owner Only**") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { @@ -902,6 +905,33 @@ namespace NadekoBot.Modules.Administration await e.Channel.SendMessage("`Done.`").ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "savechat") + .Description("Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150`") + .Parameter("cnt", ParameterType.Required) + .AddCheck(SimpleCheckers.OwnerOnly()) + .Do(async e => + { + var cntstr = e.GetArg("cnt")?.Trim(); + int cnt; + if (!int.TryParse(cntstr, out cnt)) + return; + ulong? lastmsgId = null; + var sb = new StringBuilder(); + var msgs = new List(cnt); + while (cnt > 0) + { + var dlcnt = cnt < 100 ? cnt : 100; + + var dledMsgs = await e.Channel.DownloadMessages(dlcnt, lastmsgId); + if (!dledMsgs.Any()) + break; + msgs.AddRange(dledMsgs); + lastmsgId = msgs[msgs.Count - 1].Id; + cnt -= 100; + } + await e.User.SendFile($"Chatlog-{e.Server.Name}/#{e.Channel.Name}-{DateTime.Now}.txt", JsonConvert.SerializeObject(new { Messages = msgs.Select(s => s.ToString()) }, Formatting.Indented).ToStream()); + }); + }); } } diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs index 3809df90..4aba4b10 100644 --- a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs @@ -2,6 +2,7 @@ using Discord.Commands; using NadekoBot.Classes; using NadekoBot.Modules.Permissions.Classes; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -45,14 +46,88 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "listcustreact") .Alias(Prefix + "lcr") - .Description($"Lists all current custom reactions (paginated with 5 commands per page).\n**Usage**:{Prefix}lcr 1") + .Description($"Lists all current custom reactions (paginated with 30 commands per page).\n**Usage**:{Prefix}lcr 1") .Parameter("num", ParameterType.Required) .Do(async e => { int num; - if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) return; - string result = GetCustomsOnPage(num - 1); //People prefer starting with 1 - await e.Channel.SendMessage(result).ConfigureAwait(false); + if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) num = 1; + var cmds = GetCustomsOnPage(num - 1); + if (!cmds.Any()) + { + await e.Channel.SendMessage(""); + } + else + { + string result = SearchHelper.ShowInPrettyCode(cmds, s => $"{s,-25}"); //People prefer starting with 1 + await e.Channel.SendMessage($"`Showing page {num}:`\n" + result).ConfigureAwait(false); + } + }); + + cgb.CreateCommand(Prefix + "showcustreact") + .Alias(Prefix + "scr") + .Description($"Shows all possible responses from a single custom reaction.\n**Usage**:{Prefix}scr %mention% bb") + .Parameter("name", ParameterType.Unparsed) + .Do(async e => + { + var name = e.GetArg("name")?.Trim(); + if (string.IsNullOrWhiteSpace(name)) + return; + if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) + { + await e.Channel.SendMessage("`Can't find that custom reaction.`").ConfigureAwait(false); + return; + } + var items = NadekoBot.Config.CustomReactions[name]; + var message = new StringBuilder($"Responses for {Format.Bold(name)}:\n"); + var last = items.Last(); + + int i = 1; + foreach (var reaction in items) + { + message.AppendLine($"[{i++}] " + Format.Code(reaction)); + } + await e.Channel.SendMessage(message.ToString()); + }); + + cgb.CreateCommand(Prefix + "editcustreact") + .Alias(Prefix + "ecr") + .Description("Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr \"%mention% disguise\" 2 Test 123`") + .Parameter("name", ParameterType.Required) + .Parameter("index", ParameterType.Required) + .Parameter("message", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) + .Do(async e => + { + var name = e.GetArg("name")?.Trim(); + if (string.IsNullOrWhiteSpace(name)) + return; + var indexstr = e.GetArg("index")?.Trim(); + if (string.IsNullOrWhiteSpace(indexstr)) + return; + var msg = e.GetArg("message")?.Trim(); + if (string.IsNullOrWhiteSpace(msg)) + return; + + + + if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) + { + await e.Channel.SendMessage("`Could not find given commandname`").ConfigureAwait(false); + return; + } + + int index; + if (!int.TryParse(indexstr, out index) || index < 1 || index > NadekoBot.Config.CustomReactions[name].Count) + { + await e.Channel.SendMessage("`Invalid index.`").ConfigureAwait(false); + return; + } + index = index - 1; + NadekoBot.Config.CustomReactions[name][index] = msg; + + await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); + await e.Channel.SendMessage($"Edited response #{index + 1} from `{name}`").ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "delcustreact") @@ -99,19 +174,21 @@ namespace NadekoBot.Modules.Administration.Commands }); } - private readonly int ItemsPerPage = 5; + private readonly int ItemsPerPage = 30; - private string GetCustomsOnPage(int page) + private IEnumerable GetCustomsOnPage(int page) { var items = NadekoBot.Config.CustomReactions.Skip(page * ItemsPerPage).Take(ItemsPerPage); if (!items.Any()) { - return $"No items on page {page + 1}."; + return Enumerable.Empty(); } + return items.Select(kvp => kvp.Key); + /* var message = new StringBuilder($"--- Custom reactions - page {page + 1} ---\n"); foreach (var cr in items) { - message.Append($"{ Format.Code(cr.Key)}\n"); + message.Append($"{Format.Code(cr.Key)}\n"); int i = 1; var last = cr.Value.Last(); foreach (var reaction in cr.Value) @@ -123,6 +200,7 @@ namespace NadekoBot.Modules.Administration.Commands } } return message.ToString() + "\n"; + */ } } } diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 7b7c1a48..8f51c9e5 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -4,19 +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 { - - private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); - private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); - private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); - private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; public LogCommand(DiscordModule module) : base(module) @@ -58,8 +51,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; if (e.Before.Name != e.After.Name) await ch.SendMessage($@"`{prettyCurrentTime}` **Channel Name Changed** `#{e.Before.Name}` (*{e.After.Id}*) @@ -76,8 +72,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"❗`{prettyCurrentTime}`❗`Channel Deleted:` #{e.Channel.Name} (*{e.Channel.Id}*)").ConfigureAwait(false); } @@ -88,8 +87,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`🆕`Channel Created:` #{e.Channel.Mention} (*{e.Channel.Id}*)").ConfigureAwait(false); } @@ -100,8 +102,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`♻`User was unbanned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -112,8 +117,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`✅`User joined:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -124,8 +132,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`❗`User left:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -136,35 +147,28 @@ namespace NadekoBot.Modules.Administration.Commands { try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"❗`{prettyCurrentTime}`❌`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } catch { } } - public Func DoFunc() => async e => - { - Channel ch; - if (!logs.TryRemove(e.Server, out ch)) - { - logs.TryAdd(e.Server, e.Channel); - await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); - 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; + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; if (!string.IsNullOrWhiteSpace(e.Message.Text)) { @@ -188,8 +192,11 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; if (!string.IsNullOrWhiteSpace(e.Message.Text)) { @@ -212,8 +219,11 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage( $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` @@ -225,19 +235,28 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` } private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) { + var config = SpecificConfigurations.Default.Of(e.Server.Id); try { - Channel ch; - if (loggingPresences.TryGetValue(e.Server, out ch)) - if (e.Before.Status != e.After.Status) + var chId = config.LogPresenceChannel; + if (chId != null) + { + Channel ch; + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) != null) { - await ch.SendMessage($"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false); + if (e.Before.Status != e.After.Status) + { + await ch.SendMessage($"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false); + } } + } } catch { } try { + ulong notifyChBeforeId; + ulong notifyChAfterId; Channel notifyChBefore = null; Channel notifyChAfter = null; var beforeVch = e.Before.VoiceChannel; @@ -246,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, out notifyChBefore)) + 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, out notifyChAfter)) + if (afterVch != null && config.VoiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) { notifyJoin = true; } @@ -272,8 +291,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` try { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; string str = $"🕔`{prettyCurrentTime}`"; if (e.Before.Name != e.After.Name) @@ -331,21 +353,36 @@ $@"🕔`{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; + + SpecificConfigurations.Default.Of (e.Server.Id).LogServerChannel = null; + 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 => { - Channel ch; - if (!loggingPresences.TryRemove(e.Server, out ch)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel; + if (chId == null) { - loggingPresences.TryAdd(e.Server, e.Channel); + SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel = e.Channel.Id; await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); return; } - + SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel = null; await e.Channel.SendMessage($"**User presence notifications disabled.**").ConfigureAwait(false); }); @@ -356,11 +393,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, e.Channel); + config.VoiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); } await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false); return; @@ -371,10 +409,10 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` await e.Channel.SendMessage("💢 You are not in a voice channel right now. If you are, please rejoin it.").ConfigureAwait(false); return; } - Channel throwaway; - if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out throwaway)) + ulong throwaway; + if (!config.VoiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) { - voiceChannelLog.TryAdd(e.User.VoiceChannel, e.Channel); + 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/Administration/Commands/PlayingRotate.cs b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs index e3589342..aa7570cb 100644 --- a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs +++ b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs @@ -51,11 +51,9 @@ namespace NadekoBot.Modules.Administration.Commands { if (PlayingPlaceholders.Count == 0 || NadekoBot.Config.RotatingStatuses.Count == 0 - || i >= PlayingPlaceholders.Count || i >= NadekoBot.Config.RotatingStatuses.Count) { - i = -1; - return; + i = 0; } status = NadekoBot.Config.RotatingStatuses[i]; status = PlayingPlaceholders.Aggregate(status, diff --git a/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs b/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs deleted file mode 100644 index 48ef01f4..00000000 --- a/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Discord.Commands; -using NadekoBot.Modules; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.Conversations.Commands -{ - internal class CopyCommand : DiscordCommand - { - private readonly HashSet CopiedUsers = new HashSet(); - - public CopyCommand(DiscordModule module) : base(module) - { - NadekoBot.Client.MessageReceived += Client_MessageReceived; - } - - private async void Client_MessageReceived(object sender, Discord.MessageEventArgs e) - { - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - try - { - if (string.IsNullOrWhiteSpace(e.Message.Text)) - return; - if (CopiedUsers.Contains(e.User.Id)) - { - await e.Channel.SendMessage(e.Message.Text).ConfigureAwait(false); - } - } - catch { } - } - - public Func DoFunc() => async e => - { - if (CopiedUsers.Contains(e.User.Id)) return; - - CopiedUsers.Add(e.User.Id); - await e.Channel.SendMessage(" I'll start copying you now.").ConfigureAwait(false); - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand("copyme") - .Alias("cm") - .Description("Nadeko starts copying everything you say. Disable with cs") - .Do(DoFunc()); - - cgb.CreateCommand("cs") - .Alias("copystop") - .Description("Nadeko stops copying you") - .Do(StopCopy()); - } - - private Func StopCopy() => async e => - { - if (!CopiedUsers.Contains(e.User.Id)) return; - - CopiedUsers.Remove(e.User.Id); - await e.Channel.SendMessage(" I wont copy anymore.").ConfigureAwait(false); - }; - } -} diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/NadekoBot/Modules/Conversations/Conversations.cs index bb928110..606084b1 100644 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ b/NadekoBot/Modules/Conversations/Conversations.cs @@ -1,7 +1,6 @@ using Discord; using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.Conversations.Commands; using NadekoBot.DataModels; using NadekoBot.Extensions; using NadekoBot.Modules.Conversations.Commands; @@ -19,7 +18,6 @@ namespace NadekoBot.Modules.Conversations private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥"; public Conversations() { - commands.Add(new CopyCommand(this)); commands.Add(new RipCommand(this)); } @@ -255,21 +253,6 @@ namespace NadekoBot.Modules.Conversations await e.Channel.SendMessage(construct).ConfigureAwait(false); }); - cgb.CreateCommand("av") - .Alias("avatar") - .Parameter("mention", ParameterType.Required) - .Description("Shows a mentioned person's avatar.\n**Usage**: ~av @X") - .Do(async e => - { - var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false); - }); - }); } diff --git a/NadekoBot/Modules/Gambling/DrawCommand.cs b/NadekoBot/Modules/Gambling/DrawCommand.cs index 2d3116ee..0f7d92e9 100644 --- a/NadekoBot/Modules/Gambling/DrawCommand.cs +++ b/NadekoBot/Modules/Gambling/DrawCommand.cs @@ -79,7 +79,7 @@ namespace NadekoBot.Modules.Gambling await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream()).ConfigureAwait(false); if (cardObjects.Count == 5) { - await e.Channel.SendMessage(Cards.GetHandValue(cardObjects)).ConfigureAwait(false); + await e.Channel.SendMessage($"{e.User.Mention} `{Cards.GetHandValue(cardObjects)}`").ConfigureAwait(false); } } catch (Exception ex) diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/NadekoBot/Modules/Games/Commands/PlantPick.cs index 86db0d1f..cbc4b01a 100644 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ b/NadekoBot/Modules/Games/Commands/PlantPick.cs @@ -1,10 +1,12 @@ using Discord; using Discord.Commands; using NadekoBot.Classes; +using NadekoBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; namespace NadekoBot.Modules.Games.Commands @@ -18,11 +20,31 @@ namespace NadekoBot.Modules.Games.Commands /// class PlantPick : DiscordCommand { + + private Random rng; public PlantPick(DiscordModule module) : base(module) { - + NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; + rng = new Random(); } + + private async void PotentialFlowerGeneration(object sender, Discord.MessageEventArgs e) + { + if (e.Server == null || e.Channel.IsPrivate) + return; + var config = Classes.SpecificConfigurations.Default.Of(e.Server.Id); + if (config.GenerateCurrencyChannels.Contains(e.Channel.Id)) + { + var rnd = Math.Abs(GetRandomNumber()); + if ((rnd % 50) == 0) + { + var msg = await e.Channel.SendFile(GetRandomCurrencyImagePath()); + await e.Channel.SendMessage($"❗ A random {NadekoBot.Config.CurrencyName} appeared! Pick it up by typing `>pick`"); + plantedFlowerChannels.AddOrUpdate(e.Channel.Id, msg, (u, m) => { m.Delete().GetAwaiter().GetResult(); return msg; }); + } + } + } //channelid/messageid pair ConcurrentDictionary plantedFlowerChannels = new ConcurrentDictionary(); @@ -65,8 +87,7 @@ namespace NadekoBot.Modules.Games.Commands return; } - var rng = new Random(); - var file = Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + var file = GetRandomCurrencyImagePath(); Message msg; //todo send message after, not in lock if (file == null) @@ -80,6 +101,38 @@ namespace NadekoBot.Modules.Games.Commands await Task.Delay(20000).ConfigureAwait(false); await msg2.Delete().ConfigureAwait(false); }); + + cgb.CreateCommand(Prefix + "gencurrency") + .Alias(Prefix + "gc") + .Description($"Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a {NadekoBot.Config.CurrencyName}. Requires Manage Messages permission. | `>gc`") + .AddCheck(SimpleCheckers.ManageMessages()) + .Do(async e => + { + var config = SpecificConfigurations.Default.Of(e.Server.Id); + if (config.GenerateCurrencyChannels.Remove(e.Channel.Id)) + { + await e.Channel.SendMessage("`Currency generation disabled on this channel.`"); + } + else + { + config.GenerateCurrencyChannels.Add(e.Channel.Id); + await e.Channel.SendMessage("`Currency generation enabled on this channel.`"); + } + }); + } + + private string GetRandomCurrencyImagePath() => + Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + + int GetRandomNumber() + { + using (RNGCryptoServiceProvider rg = new RNGCryptoServiceProvider()) + { + byte[] rno = new byte[4]; + rg.GetBytes(rno); + int randomvalue = BitConverter.ToInt32(rno, 0); + return randomvalue; + } } } } diff --git a/NadekoBot/Modules/Help/Commands/HelpCommand.cs b/NadekoBot/Modules/Help/Commands/HelpCommand.cs index 512799b6..c1157574 100644 --- a/NadekoBot/Modules/Help/Commands/HelpCommand.cs +++ b/NadekoBot/Modules/Help/Commands/HelpCommand.cs @@ -86,7 +86,7 @@ Version: `{NadekoStats.Instance.BotVersion}`"; .Description("Sends a readme and a guide links to the channel.") .Do(async e => await e.Channel.SendMessage( -@"**FULL README**: +@"**Wiki with all info**: **WINDOWS SETUP GUIDE**: diff --git a/NadekoBot/Modules/Help/HelpModule.cs b/NadekoBot/Modules/Help/HelpModule.cs index 1b8ad4ec..0b17e336 100644 --- a/NadekoBot/Modules/Help/HelpModule.cs +++ b/NadekoBot/Modules/Help/HelpModule.cs @@ -1,5 +1,6 @@ using Discord.Commands; using Discord.Modules; +using NadekoBot.Classes; using NadekoBot.Classes.Help.Commands; using NadekoBot.Extensions; using NadekoBot.Modules.Permissions.Classes; @@ -54,10 +55,8 @@ namespace NadekoBot.Modules.Help } var i = 0; if (module != "customreactions" && module != "conversations") - await e.Channel.SendMessage("`List Of Commands:`\n```xl\n" + - string.Join("\n", cmdsArray.GroupBy(item => (i++) / 3) - .Select(ig => string.Join("", ig.Select(el => $"{el.Text,-15}" + $"{"[" + el.Aliases.FirstOrDefault() + "]",-8}")))) - + $"\n```") + await e.Channel.SendMessage("`List Of Commands:`\n" + SearchHelper.ShowInPrettyCode(cmdsArray, + el => $"{el.Text,-15}{"[" + el.Aliases.FirstOrDefault() + "]",-8}")) .ConfigureAwait(false); else await e.Channel.SendMessage("`List Of Commands:`\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}"))); diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index 62eabc33..e1cd32bb 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -50,7 +50,7 @@ namespace NadekoBot.Modules.Music.Classes private bool Destroyed { get; set; } = false; public bool RepeatSong { get; private set; } = false; public bool RepeatPlaylist { get; private set; } = false; - public bool Autoplay { get; private set; } = false; + public bool Autoplay { get; set; } = false; public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) { @@ -174,6 +174,7 @@ namespace NadekoBot.Modules.Music.Classes throw new ArgumentNullException(nameof(s)); lock (playlistLock) { + s.MusicPlayer = this; playlist.Add(s); } } diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/NadekoBot/Modules/Music/Classes/Song.cs index 520ab3d8..f9c7f3ae 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/NadekoBot/Modules/Music/Classes/Song.cs @@ -54,7 +54,7 @@ namespace NadekoBot.Modules.Music.Classes } } - private Song(SongInfo songInfo) + public Song(SongInfo songInfo) { this.SongInfo = songInfo; } @@ -67,6 +67,12 @@ namespace NadekoBot.Modules.Music.Classes return s; } + public Song SetMusicPlayer(MusicPlayer mp) + { + this.MusicPlayer = mp; + return this; + } + private Task BufferSong(CancellationToken cancelToken) => Task.Factory.StartNew(async () => { diff --git a/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/NadekoBot/Modules/Music/Classes/SoundCloud.cs index 00216fbb..159d0d9e 100644 --- a/NadekoBot/Modules/Music/Classes/SoundCloud.cs +++ b/NadekoBot/Modules/Music/Classes/SoundCloud.cs @@ -1,4 +1,5 @@ using NadekoBot.Classes; +using Newtonsoft.Json; using System; using System.Threading.Tasks; @@ -34,18 +35,22 @@ namespace NadekoBot.Modules.Music.Classes public class SoundCloudVideo { - public string Kind = ""; - public long Id = 0; - public SoundCloudUser User = new SoundCloudUser(); - public string Title = ""; + public string Kind { get; set; } = ""; + public long Id { get; set; } = 0; + public SoundCloudUser User { get; set; } = new SoundCloudUser(); + public string Title { get; set; } = ""; + [JsonIgnore] public string FullName => User.Name + " - " + Title; - public bool Streamable = false; + public bool Streamable { get; set; } = false; + [JsonProperty("permalink_url")] + public string TrackLink { get; set; } = ""; + [JsonIgnore] public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Creds.SoundCloudClientID}"; } public class SoundCloudUser { [Newtonsoft.Json.JsonProperty("username")] - public string Name; + public string Name { get; set; } } /* {"kind":"track", diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index bf88a5a6..59c86031 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -6,6 +6,7 @@ using NadekoBot.DataModels; using NadekoBot.Extensions; using NadekoBot.Modules.Music.Classes; using NadekoBot.Modules.Permissions.Classes; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -52,30 +53,27 @@ namespace NadekoBot.Modules.Music cgb.CreateCommand("stop") .Alias("s") .Description("Stops the music and clears the playlist. Stays in the channel.\n**Usage**: `!m s`") - .Do(async e => + .Do(e => { - await Task.Run(() => + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) - musicPlayer.Stop(); - }).ConfigureAwait(false); + musicPlayer.Autoplay = false; + musicPlayer.Stop(); + } }); cgb.CreateCommand("destroy") .Alias("d") .Description("Completely stops the music and unbinds the bot from the channel. " + "(may cause weird behaviour)\n**Usage**: `!m d`") - .Do(async e => + .Do(e => { - await Task.Run(() => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) - musicPlayer.Destroy(); - }).ConfigureAwait(false); + MusicPlayer musicPlayer; + if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) + musicPlayer.Destroy(); }); cgb.CreateCommand("pause") @@ -110,6 +108,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`") @@ -188,7 +201,7 @@ namespace NadekoBot.Modules.Music cgb.CreateCommand("defvol") .Alias("dv") .Description("Sets the default music volume when music playback is started (0-100)." + - " Does not persist through restarts.\n**Usage**: `!m dv 80`") + " Persists through restarts.\n**Usage**: `!m dv 80`") .Parameter("val", ParameterType.Required) .Do(async e => { @@ -302,6 +315,37 @@ namespace NadekoBot.Modules.Music await msg.Edit("🎵 `Playlist queue complete.`").ConfigureAwait(false); }); + cgb.CreateCommand("soundcloudpl") + .Alias("scpl") + .Description("Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony`") + .Parameter("pl", ParameterType.Unparsed) + .Do(async e => + { + var pl = e.GetArg("pl")?.Trim(); + + if (string.IsNullOrWhiteSpace(pl)) + return; + + var scvids = JObject.Parse(await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Creds.SoundCloudClientID}"))["tracks"].ToObject(); + await QueueSong(e.Channel, e.User.VoiceChannel, scvids[0].TrackLink); + + MusicPlayer mp; + if (!MusicPlayers.TryGetValue(e.Server, out mp)) + return; + + foreach (var svideo in scvids.Skip(1)) + { + mp.AddSong(new Song(new Classes.SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + })); + } + }); + cgb.CreateCommand("localplaylst") .Alias("lopl") .Description("Queues all songs from a directory. **Bot Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") @@ -757,8 +801,6 @@ namespace NadekoBot.Modules.Music return mp; }); var resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); - resolvedSong.MusicPlayer = musicPlayer; - musicPlayer.AddSong(resolvedSong); if (!silent) { diff --git a/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs new file mode 100644 index 00000000..ebbdbf34 --- /dev/null +++ b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs @@ -0,0 +1,46 @@ +using Discord.Commands; +using NadekoBot.Classes; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace NadekoBot.Modules.Searches.Commands +{ + class MemegenCommands : DiscordCommand + { + public MemegenCommands(DiscordModule module) : base(module) + { + } + + internal override void Init(CommandGroupBuilder cgb) + { + cgb.CreateCommand(Prefix + "memelist") + .Description("Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/") + .Do(async e => + { + int i = 0; + await e.Channel.SendMessage("`List Of Commands:`\n```xl\n" + + string.Join("\n", JsonConvert.DeserializeObject>(await SearchHelper.GetResponseStringAsync("http://memegen.link/templates/")) + .Select(kvp => Path.GetFileName(kvp.Value)) + .GroupBy(item => (i++) / 4) + .Select(ig => string.Join("", ig.Select(el => $"{el,-17}")))) + + $"\n```").ConfigureAwait(false); + }); + + cgb.CreateCommand(Prefix + "memegen") + .Description("Generates a meme from memelist with top and bottom text. | `~memegen biw \"gets iced coffee\" \"in the winter\"`") + .Parameter("meme", ParameterType.Required) + .Parameter("toptext", ParameterType.Required) + .Parameter("bottext", ParameterType.Required) + .Do(async e => + { + var meme = e.GetArg("meme"); + var top = Uri.EscapeDataString(e.GetArg("toptext").Replace(' ', '-')); + var bot = Uri.EscapeDataString(e.GetArg("bottext").Replace(' ', '-')); + await e.Channel.SendMessage($"http://memegen.link/{meme}/{top}/{bot}.jpg"); + }); + } + } +} diff --git a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs index a9d92d9f..8af28d0f 100644 --- a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Searches.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "osu") - .Description("Shows osu stats for a player.\n**Usage**: `~osu Name` or `~osu Name `") + .Description("Shows osu stats for a player.\n**Usage**: `~osu Name` or `~osu Name taiko`") .Parameter("usr", ParameterType.Required) .Parameter("mode", ParameterType.Unparsed) .Do(async e => diff --git a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs index ffa38e5c..5895c932 100644 --- a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs +++ b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs @@ -73,7 +73,7 @@ namespace NadekoBot.Modules.Searches.Commands checkTimer.Start(); } - private async Task> GetStreamStatus(StreamNotificationConfig stream) + private async Task> GetStreamStatus(StreamNotificationConfig stream, bool checkCache = true) { bool isLive; string response; @@ -83,7 +83,7 @@ namespace NadekoBot.Modules.Searches.Commands { case StreamNotificationConfig.StreamType.Hitbox: var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}"; - if (cachedStatuses.TryGetValue(hitboxUrl, out result)) + if (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result)) return result; response = await SearchHelper.GetResponseStringAsync(hitboxUrl).ConfigureAwait(false); data = JObject.Parse(response); @@ -93,7 +93,7 @@ namespace NadekoBot.Modules.Searches.Commands return result; case StreamNotificationConfig.StreamType.Twitch: var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}"; - if (cachedStatuses.TryGetValue(twitchUrl, out result)) + if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result)) return result; response = await SearchHelper.GetResponseStringAsync(twitchUrl).ConfigureAwait(false); data = JObject.Parse(response); @@ -103,7 +103,7 @@ namespace NadekoBot.Modules.Searches.Commands return result; case StreamNotificationConfig.StreamType.Beam: var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}"; - if (cachedStatuses.TryGetValue(beamUrl, out result)) + if (checkCache && cachedStatuses.TryGetValue(beamUrl, out result)) return result; response = await SearchHelper.GetResponseStringAsync(beamUrl).ConfigureAwait(false); data = JObject.Parse(response); @@ -143,6 +143,93 @@ namespace NadekoBot.Modules.Searches.Commands .Parameter("username", ParameterType.Unparsed) .Do(TrackStream(StreamNotificationConfig.StreamType.Beam)); + cgb.CreateCommand(Module.Prefix + "checkhitbox") + .Alias(Module.Prefix + "chhb") + .Description("Checks if a certain user is streaming on the hitbox platform." + + "\n**Usage**: ~chhb SomeStreamer") + .Parameter("username", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.ManageServer()) + .Do(async e => + { + var stream = e.GetArg("username")?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new StreamNotificationConfig + { + Username = stream, + Type = StreamNotificationConfig.StreamType.Hitbox + })); + if (streamStatus.Item1) + { + await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`"); + } + } + catch + { + await e.Channel.SendMessage("No channel found."); + } + }); + + cgb.CreateCommand(Module.Prefix + "checktwitch") + .Alias(Module.Prefix + "chtw") + .Description("Checks if a certain user is streaming on the twitch platform." + + "\n**Usage**: ~chtw SomeStreamer") + .AddCheck(SimpleCheckers.ManageServer()) + .Parameter("username", ParameterType.Unparsed) + .Do(async e => + { + var stream = e.GetArg("username")?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new StreamNotificationConfig + { + Username = stream, + Type = StreamNotificationConfig.StreamType.Twitch + })); + if (streamStatus.Item1) + { + await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`"); + } + } + catch + { + await e.Channel.SendMessage("No channel found."); + } + }); + + cgb.CreateCommand(Module.Prefix + "checkbeam") + .Alias(Module.Prefix + "chbm") + .Description("Checks if a certain user is streaming on the beam platform." + + "\n**Usage**: ~chbm SomeStreamer") + .AddCheck(SimpleCheckers.ManageServer()) + .Parameter("username", ParameterType.Unparsed) + .Do(async e => + { + var stream = e.GetArg("username")?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new StreamNotificationConfig + { + Username = stream, + Type = StreamNotificationConfig.StreamType.Beam + })); + if (streamStatus.Item1) + { + await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`"); + } + } + catch + { + await e.Channel.SendMessage("No channel found."); + } + }); + cgb.CreateCommand(Module.Prefix + "removestream") .Alias(Module.Prefix + "rms") .Description("Removes notifications of a certain streamer on this channel." + diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index b65394c4..8b03984d 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -31,6 +31,7 @@ namespace NadekoBot.Modules.Searches commands.Add(new CalcCommand(this)); commands.Add(new OsuCommands(this)); commands.Add(new PokemonSearchCommands(this)); + commands.Add(new MemegenCommands(this)); rng = new Random(); } @@ -184,29 +185,30 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "ir") .Description("Pulls a random image using a search parameter.\n**Usage**: ~ir cute kitten") .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("query"))) - return; - try - { - var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=50&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; - var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); - var items = obj["items"] as JArray; - await e.Channel.SendMessage(items[rng.Next(0, items.Count)]["link"].ToString()).ConfigureAwait(false); - } - catch (HttpRequestException exception) - { - if (exception.Message.Contains("403 (Forbidden)")) - { - await e.Channel.SendMessage("Daily limit reached!"); - } - else - { - await e.Channel.SendMessage("Something went wrong."); - } - } - }); + .Do(async e => + { + if (string.IsNullOrWhiteSpace(e.GetArg("query"))) + return; + try + { + var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; + var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); + var items = obj["items"] as JArray; + await e.Channel.SendMessage(items[0]["link"].ToString()).ConfigureAwait(false); + } + catch (HttpRequestException exception) + { + if (exception.Message.Contains("403 (Forbidden)")) + { + await e.Channel.SendMessage("Daily limit reached!"); + } + else + { + await e.Channel.SendMessage("Something went wrong."); + } + } + }); + cgb.CreateCommand(Prefix + "lmgtfy") .Description("Google something for an idiot.") .Parameter("ffs", ParameterType.Unparsed) @@ -475,6 +477,21 @@ $@"🌍 **Weather for** 【{obj["target"]}】 } }); + cgb.CreateCommand(Prefix + "av") + .Alias(Prefix + "avatar") + .Parameter("mention", ParameterType.Required) + .Description("Shows a mentioned person's avatar.\n**Usage**: ~av @X") + .Do(async e => + { + var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); + if (usr == null) + { + await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); + return; + } + await e.Channel.SendMessage(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false); + }); + }); } } diff --git a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs index 34ea9112..1743af45 100644 --- a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs +++ b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs @@ -2,10 +2,12 @@ // License: Code Project Open License // http://www.codeproject.com/info/cpol10.aspx +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using System.Web; namespace NadekoBot.Modules.Translator.Helpers @@ -26,32 +28,6 @@ namespace NadekoBot.Modules.Translator.Helpers return GoogleTranslator._languageModeMap.Keys.OrderBy(p => p); } } - - /// - /// Gets the time taken to perform the translation. - /// - public TimeSpan TranslationTime { - get; - private set; - } - - /// - /// Gets the url used to speak the translation. - /// - /// The url used to speak the translation. - public string TranslationSpeechUrl { - get; - private set; - } - - /// - /// Gets the error. - /// - public Exception Error { - get; - private set; - } - #endregion #region Public methods @@ -63,92 +39,28 @@ namespace NadekoBot.Modules.Translator.Helpers /// The source language. /// The target language. /// The translation. - public string Translate + public async Task Translate (string sourceText, string sourceLanguage, string targetLanguage) { // Initialize - this.Error = null; - this.TranslationSpeechUrl = null; - this.TranslationTime = TimeSpan.Zero; DateTime tmStart = DateTime.Now; - string translation = string.Empty; string text = string.Empty; - try + + // Download translation + string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", + GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage), + GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), + HttpUtility.UrlEncode(sourceText)); + using (HttpClient http = new HttpClient()) { - // Download translation - string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", - GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage), - GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), - HttpUtility.UrlEncode(sourceText)); - using (WebClient wc = new WebClient()) - { - wc.Headers.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 = wc.DownloadString(url); - } - - // Get translated text - // Get phrase collection - // string text = File.ReadAllText(outputFile); - int index = text.IndexOf(string.Format(",,\"{0}\"", GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage))); - if (index == -1) - { - // Translation of single word - int startQuote = text.IndexOf('\"'); - if (startQuote != -1) - { - int endQuote = text.IndexOf('\"', startQuote + 1); - if (endQuote != -1) - { - translation = text.Substring(startQuote + 1, endQuote - startQuote - 1); - } - } - else - { - // Translation of phrase - text = text.Substring(0, index); - text = text.Replace("],[", ","); - text = text.Replace("]", string.Empty); - text = text.Replace("[", string.Empty); - text = text.Replace("\",\"", "\""); - - // Get translated phrases - string[] phrases = text.Split(new[] { '\"' }, StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; (i < phrases.Count()); i += 2) - { - string translatedPhrase = phrases[i]; - if (translatedPhrase.StartsWith(",,")) - { - i--; - continue; - } - translation += translatedPhrase + " "; - } - } - - // Fix up translation - translation = translation.Trim(); - translation = translation.Replace(" ?", "?"); - translation = translation.Replace(" !", "!"); - translation = translation.Replace(" ,", ","); - translation = translation.Replace(" .", "."); - translation = translation.Replace(" ;", ";"); - - // And translation speech URL - this.TranslationSpeechUrl = string.Format("https://translate.googleapis.com/translate_tts?ie=UTF-8&q={0}&tl={1}&total=1&idx=0&textlen={2}&client=gtx", - HttpUtility.UrlEncode(translation), GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), translation.Length); - } - } - catch (Exception ex) - { - this.Error = ex; + 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 result - this.TranslationTime = DateTime.Now - tmStart; - return translation; + return JArray.Parse(text)[0][0][0].ToString(); } #endregion diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs index 29ac4036..e4512980 100644 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ b/NadekoBot/Modules/Translator/TranslateCommand.cs @@ -27,13 +27,17 @@ namespace NadekoBot.Modules.Translator await e.Channel.SendIsTyping().ConfigureAwait(false); string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0]; string to = e.GetArg("langs").ToLowerInvariant().Split('>')[1]; + var text = e.GetArg("text")?.Trim(); + if (string.IsNullOrWhiteSpace(text)) + return; - string translation = t.Translate(e.GetArg("text"), from, to); + string translation = await t.Translate(text, from, to).ConfigureAwait(false); await e.Channel.SendMessage(translation).ConfigureAwait(false); } - catch + catch (Exception ex) { - await e.Channel.SendMessage("Bad input format, or sth went wrong...").ConfigureAwait(false); + Console.WriteLine(ex); + await e.Channel.SendMessage("Bad input format, or something went wrong...").ConfigureAwait(false); } }; diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index 3d57402e..84efa690 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -134,6 +134,7 @@ + @@ -149,6 +150,7 @@ + @@ -210,7 +212,6 @@ - @@ -496,7 +497,7 @@ - + diff --git a/README.md b/README.md index 36b09bd8..4992761a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) +[![Discord](https://discordapp.com/api/servers/117523346618318850/widget.png)](https://discord.gg/0ehQwTK2RBjAxzEY) # NadekoBot ## [Click here to invite nadeko to your discord server](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) diff --git a/commandlist.md b/commandlist.md index 3c581b68..a6d2e202 100644 --- a/commandlist.md +++ b/commandlist.md @@ -2,7 +2,7 @@ ######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` #NadekoBot List Of Commands -Version: `NadekoBot v0.9.6015.37609` +Version: `NadekoBot v0.9.6030.3793` ### Help Command and aliases | Description | Usage ----------------|--------------|------- @@ -44,24 +44,23 @@ Command and aliases | Description | Usage `.lsar` | Lists all self-assignable roles. `.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer `.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer -`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!` -`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** -`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server -`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel -`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser `.addcustreact`, `.acr` | Add a custom reaction. Guide here: **Bot Owner Only!** | .acr "hello" I love saying hello to %user% -`.listcustreact`, `.lcr` | Lists all current custom reactions (paginated with 5 commands per page). | .lcr 1 +`.listcustreact`, `.lcr` | Lists all current custom reactions (paginated with 30 commands per page). | .lcr 1 +`.showcustreact`, `.scr` | Shows all possible responses from a single custom reaction. | .scr %mention% bb +`.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123` `.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index) `.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. Type `.aar` to disable, `.aar Role Name` to enable `.leave` | Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331` -`.restart` | Restarts the bot. Might not work. +`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. +`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. +`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. +`.restart` | Restarts the bot. Might not work. **Bot Owner Only** `.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest `.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin `.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` `.removeallroles`, `.rar` | Removes all roles from a mentioned user. | .rar @User `.createrole`, `.cr` | Creates a role with a given name. | `.r Awesome Role` `.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55` -`.roles` | List all roles on this server or a single user if specified. `.ban`, `.b` | Bans a user by id or name with an optional message. | .b "@some Guy" Your behaviour is toxic. `.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | .sb "@some Guy" Your behaviour is toxic. `.kick`, `.k` | Kicks a mentioned user. @@ -75,30 +74,38 @@ Command and aliases | Description | Usage `.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. `.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic` `.setchanlname`, `.schn` | Changed the name of the current channel. -`.userid`, `.uid` | Shows user ID. -`.channelid`, `.cid` | Shows current channel ID. -`.serverid`, `.sid` | Shows current server ID. -`.stats` | Shows some basic stats for Nadeko. -`.dysyd` | Shows some basic stats for Nadeko. `.heap` | Shows allocated memory - **Bot Owner Only!** `.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!** `.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** -`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. **Bot Owner Only!** +`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg` `.setgame` | Sets the bots game. **Bot Owner Only!** -`.checkmyperms` | Checks your userspecific permissions on this channel. -`.commsuser` | Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Bot Owner Only!** -`.commsserver` | Sets a server for through-bot communication. **Bot Owner Only!** -`.commschannel` | Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Bot Owner Only!** -`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | .send Message text multi word! +`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!` `.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. -`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. `.unstuck` | Clears the message queue. **Bot Owner Only!** `.donators` | List of lovely people who donated to keep this project alive. `.donadd` | Add a donator to the database. `.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | .announce Useless spam -`.whoplays` | Shows a list of users who are playing the specified game. `.leave` | Leaves a server with a supplied ID. | `.leave 493243292839` +`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150` + +### Utility +Command and aliases | Description | Usage +----------------|--------------|------- +`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!` +`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** +`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server +`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel +`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser +`.whoplays` | Shows a list of users who are playing the specified game. +`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. +`.checkmyperms` | Checks your userspecific permissions on this channel. +`.stats` | Shows some basic stats for Nadeko. +`.dysyd` | Shows some basic stats for Nadeko. +`.userid`, `.uid` | Shows user ID. +`.channelid`, `.cid` | Shows current channel ID. +`.serverid`, `.sid` | Shows current server ID. +`.roles` | List all roles on this server or a single user if specified. ### Permissions Command and aliases | Description | Usage @@ -145,8 +152,6 @@ Command and aliases | Description | Usage `..` | Adds a new quote with the specified name (single word) and message (no limit). | .. abc My message `...` | Shows a random quote with a specified name. | .. abc `..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc` -`@BotName copyme`, `@BotName cm` | Nadeko starts copying everything you say. Disable with cs -`@BotName cs`, `@BotName copystop` | Nadeko stops copying you `@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000 `@BotName uptime` | Shows how long Nadeko has been running for. `@BotName die` | Works only for the owner. Shuts the bot down. @@ -156,7 +161,6 @@ Command and aliases | Description | Usage `@BotName slm` | Shows the message where you were last mentioned in this channel (checks last 10k messages) `@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.** `@BotName ab` | Try to get 'abalabahaha' -`@BotName av`, `@BotName avatar` | Shows a mentioned person's avatar. | ~av @X ### Gambling Command and aliases | Description | Usage @@ -186,6 +190,7 @@ Command and aliases | Description | Usage `>pollend` | Stops active poll on this server and prints the results in this channel. `>pick` | Picks a flower planted in this channel. `>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) +`>gencurrency`, `>gc` | Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a NadekoFlower. Requires Manage Messages permission. | `>gc` `>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | >leet 3 Hello `>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more `>8ball` | Ask the 8ball a yes/no question. @@ -200,15 +205,16 @@ Command and aliases | Description | Usage `!m destroy`, `!m d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d` `!m pause`, `!m p` | Pauses or Unpauses the song. | `!m p` `!m queue`, `!m q`, `!m yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!m q Dream Of Venice` -`!m listqueue`, `!m lq` | Lists up to 15 currently queued songs. | `!m lq` +`!m listqueue`, `!m lq` | Lists 15 currently queued songs per page. Default page is 1. | `!m lq` or `!m lq 2` `!m nowplaying`, `!m np` | Shows the song currently playing. | `!m np` `!m volume`, `!m vol` | Sets the music volume 0-100% | `!m vol 50` -`!m defvol`, `!m dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80` +`!m defvol`, `!m dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!m dv 80` `!m mute`, `!m min` | Sets the music volume to 0% | `!m min` `!m max` | Sets the music volume to 100% (real max is actually 150%). | `!m max` `!m half` | Sets the music volume to 50%. | `!m half` `!m shuffle`, `!m sh` | Shuffles the current playlist. | `!m sh` `!m playlist`, `!m pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name` +`!m soundcloudpl`, `!m scpl` | Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony` `!m localplaylst`, `!m lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m lopl C:/music/classical` `!m radio`, `!m ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf | `!m ra radio link here` `!m local`, `!m lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3` @@ -221,8 +227,10 @@ Command and aliases | Description | Usage `!m save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1` `!m load` | Loads a playlist under a certain name. | `!m load classical-1` `!m playlists`, `!m pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1` +`!m deleteplaylist`, `!m delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m delpls animu-5` `!m goto` | Goes to a specific time in seconds in a song. `!m getlink`, `!m gl` | Shows a link to the currently playing song. +`!m autoplay`, `!m ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) ### Searches Command and aliases | Description | Usage @@ -232,13 +240,22 @@ Command and aliases | Description | Usage `~hitbox`, `~hb` | Notifies this channel when a certain user starts streaming. | ~hitbox SomeStreamer `~twitch`, `~tw` | Notifies this channel when a certain user starts streaming. | ~twitch SomeStreamer `~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | ~beam SomeStreamer +`~checkhitbox`, `~chhb` | Checks if a certain user is streaming on the hitbox platform. | ~chhb SomeStreamer +`~checktwitch`, `~chtw` | Checks if a certain user is streaming on the twitch platform. | ~chtw SomeStreamer +`~checkbeam`, `~chbm` | Checks if a certain user is streaming on the beam platform. | ~chbm SomeStreamer `~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | ~rms SomeGuy `~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls `~convert` | Convert quantities from>to. Like `~convert m>km 1000` `~convertlist` | List of the convertable dimensions and currencies. `~wowjoke` | Get one of Kwoth's penultimate WoW jokes. `~calculate`, `~calc` | Evaluate a mathematical expression. | ~calc 1+1 -`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. +`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko` +`~osu b` | Shows information about an osu beatmap. | ~osu b https://osu.ppy.sh/s/127712 +`~osu top5` | Displays a user's top 5 plays. | ~osu top5 Name +`~pokemon`, `~poke` | Searches for a pokemon. +`~pokemonability`, `~pokab` | Searches for a pokemon ability. +`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ +`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` `~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | ~we Moscow RF `~yt` | Searches youtubes and shows the first result `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. @@ -248,10 +265,7 @@ Command and aliases | Description | Usage `~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | ~i cute kitten `~ir` | Pulls a random image using a search parameter. | ~ir cute kitten `~lmgtfy` | Google something for an idiot. -`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera -`~osu u` | Shows osu stats for a player. Optional mode. | ~osu u rrtyui std -`~osu b` | Shows osu stats for a beatmap. | ~osu b https://osu.ppy.sh/b/992685 -`~osu top5` | Shows an osu player's top 5 plays. Optional mode. | ~osu top5 Dusk ctb +`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera `~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple `~#` | Searches Tagdef.com for a hashtag. | ~# ff `~quote` | Shows a random quote. @@ -266,6 +280,7 @@ Command and aliases | Description | Usage `~wiki` | Gives you back a wikipedia link `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` `~videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. +`~av`, `~avatar` | Shows a mentioned person's avatar. | ~av @X ### NSFW Command and aliases | Description | Usage