Merge pull request #375 from Kwoth/dev

0.99.8
This commit is contained in:
Master Kwoth 2016-07-04 09:31:08 +02:00 committed by GitHub
commit 9e05e69cfb
46 changed files with 1756 additions and 616 deletions

4
.gitignore vendored
View File

@ -37,3 +37,7 @@ Tests/bin
NadekoBot/bin/Debug/data/nadekobot.sqlite NadekoBot/bin/Debug/data/nadekobot.sqlite
NadekoBot/bin/Debug/data/config.json NadekoBot/bin/Debug/data/config.json
NadekoBot/bin/Debug/data/ServerSpecificConfigs.json NadekoBot/bin/Debug/data/ServerSpecificConfigs.json
NadekoBot.sln.iml
.idea/workspace.xml
.idea/vcs.xml
.idea/modules.xml

View File

@ -41,32 +41,32 @@ Global
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|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.ActiveCfg = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|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.ActiveCfg = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|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.ActiveCfg = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|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.ActiveCfg = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = 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 {45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

View File

@ -32,7 +32,16 @@ namespace NadekoBot.Classes
conn.CreateTable<SongInfo>(); conn.CreateTable<SongInfo>();
conn.CreateTable<PlaylistSongInfo>(); conn.CreateTable<PlaylistSongInfo>();
conn.CreateTable<MusicPlaylist>(); conn.CreateTable<MusicPlaylist>();
conn.CreateTable<Incident>();
conn.Execute(Queries.TransactionTriggerQuery); conn.Execute(Queries.TransactionTriggerQuery);
try
{
conn.Execute(Queries.DeletePlaylistTriggerQuery);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
} }
} }
@ -94,6 +103,14 @@ namespace NadekoBot.Classes
} }
} }
internal void UpdateAll<T>(IEnumerable<T> objs) where T : IDataModel
{
using (var conn = new SQLiteConnection(FilePath))
{
conn.UpdateAll(objs);
}
}
internal HashSet<T> GetAllRows<T>() where T : IDataModel, new() internal HashSet<T> GetAllRows<T>() where T : IDataModel, new()
{ {
using (var conn = new SQLiteConnection(FilePath)) using (var conn = new SQLiteConnection(FilePath))
@ -181,7 +198,7 @@ Limit 20 OFFSET ?", num * 20);
{ {
using (var conn = new SQLiteConnection(FilePath)) using (var conn = new SQLiteConnection(FilePath))
{ {
return conn.Table<CurrencyState>().Take(n).ToList().OrderBy(cs => -cs.Value); return conn.Table<CurrencyState>().OrderBy(cs => -cs.Value).Take(n).ToList();
} }
} }
} }
@ -197,7 +214,7 @@ public class PlaylistData
public static class Queries public static class Queries
{ {
public static string TransactionTriggerQuery = @" public const string TransactionTriggerQuery = @"
CREATE TRIGGER IF NOT EXISTS OnTransactionAdded CREATE TRIGGER IF NOT EXISTS OnTransactionAdded
AFTER INSERT ON CurrencyTransaction AFTER INSERT ON CurrencyTransaction
BEGIN BEGIN
@ -208,4 +225,11 @@ INSERT OR REPLACE INTO CurrencyState (Id, UserId, Value, DateAdded)
NEW.DateAdded); NEW.DateAdded);
END END
"; ";
public const string DeletePlaylistTriggerQuery = @"
CREATE TRIGGER IF NOT EXISTS music_playlist
AFTER DELETE ON MusicPlaylist
FOR EACH ROW
BEGIN
DELETE FROM PlaylistSongInfo WHERE PlaylistId = OLD.Id;
END";
} }

View File

@ -352,5 +352,15 @@ namespace NadekoBot.Extensions
await Task.Run(() => images.Merge(reverseScaleFactor)).ConfigureAwait(false); await Task.Run(() => images.Merge(reverseScaleFactor)).ConfigureAwait(false);
public static string Unmention(this string str) => str.Replace("@", "ම"); 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;
}
} }
} }

View File

@ -1,15 +1,25 @@
using System; using NadekoBot.DataModels;
using System.IO; using System;
namespace NadekoBot.Classes { namespace NadekoBot.Classes
internal static class IncidentsHandler { {
public static void Add(ulong serverId, string text) { internal static class IncidentsHandler
Directory.CreateDirectory("data/incidents"); {
File.AppendAllText($"data/incidents/{serverId}.txt", text + "\n--------------------------\n"); public static void Add(ulong serverId, ulong channelId, string text)
{
var def = Console.ForegroundColor; var def = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"INCIDENT: {text}"); Console.WriteLine($"INCIDENT: {text}");
Console.ForegroundColor = def; Console.ForegroundColor = def;
var incident = new Incident
{
ChannelId = (long)channelId,
ServerId = (long)serverId,
Text = text,
Read = false
};
DbHandler.Instance.InsertData<Incident>(incident);
} }
} }
} }

View File

@ -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
{
/// <summary>
/// Provides a thread-safe dictionary for use with data binding.
/// </summary>
/// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
/// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
[DebuggerDisplay("Count={Count}")]
public class ObservableConcurrentDictionary<TKey, TValue> :
ICollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>,
INotifyCollectionChanged, INotifyPropertyChanged
{
private readonly SynchronizationContext _context;
private readonly ConcurrentDictionary<TKey, TValue> _dictionary;
/// <summary>
/// Initializes an instance of the ObservableConcurrentDictionary class.
/// </summary>
public ObservableConcurrentDictionary()
{
_context = AsyncOperationManager.SynchronizationContext;
_dictionary = new ConcurrentDictionary<TKey, TValue>();
}
/// <summary>Event raised when the collection changes.</summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>Event raised when a property on the collection changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
/// </summary>
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);
}
}
/// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
/// <param name="item">The item to be added.</param>
/// <returns>Whether the add was successful.</returns>
private bool TryAddWithNotification(KeyValuePair<TKey, TValue> item)
{
return TryAddWithNotification(item.Key, item.Value);
}
/// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
/// <param name="key">The key of the item to be added.</param>
/// <param name="value">The value of the item to be added.</param>
/// <returns>Whether the add was successful.</returns>
private bool TryAddWithNotification(TKey key, TValue value)
{
bool result = _dictionary.TryAdd(key, value);
if (result) NotifyObserversOfChange();
return result;
}
/// <summary>Attempts to remove an item from the dictionary, notifying observers of any changes.</summary>
/// <param name="key">The key of the item to be removed.</param>
/// <param name="value">The value of the item removed.</param>
/// <returns>Whether the removal was successful.</returns>
private bool TryRemoveWithNotification(TKey key, out TValue value)
{
bool result = _dictionary.TryRemove(key, out value);
if (result) NotifyObserversOfChange();
return result;
}
/// <summary>Attempts to add or update an item in the dictionary, notifying observers of any changes.</summary>
/// <param name="key">The key of the item to be updated.</param>
/// <param name="value">The new value to set for the item.</param>
/// <returns>Whether the update was successful.</returns>
private void UpdateWithNotification(TKey key, TValue value)
{
_dictionary[key] = value;
NotifyObserversOfChange();
}
#region ICollection<KeyValuePair<TKey,TValue>> Members
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
TryAddWithNotification(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
{
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Clear();
NotifyObserversOfChange();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
}
int ICollection<KeyValuePair<TKey, TValue>>.Count {
get { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).IsReadOnly; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
TValue temp;
return TryRemoveWithNotification(item.Key, out temp);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
TryAddWithNotification(key, value);
}
public bool ContainsKey(TKey key)
{
return _dictionary.ContainsKey(key);
}
public ICollection<TKey> 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<TValue> 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
}
}

View File

@ -176,6 +176,33 @@ namespace NadekoBot.Classes
return null; return null;
} }
public static async Task<string> GetRelatedVideoId(string id)
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id));
var match = new Regex("(?:youtu\\.be\\/|v=)(?<id>[\\da-zA-Z\\-_]*)").Match(id);
if (match.Length > 1)
{
id = match.Groups["id"].Value;
}
var response = await GetResponseStringAsync(
$"https://www.googleapis.com/youtube/v3/search?" +
$"part=snippet&maxResults=1&type=video" +
$"&relatedToVideoId={id}" +
$"&key={NadekoBot.Creds.GoogleAPIKey}").ConfigureAwait(false);
JObject obj = JObject.Parse(response);
var data = JsonConvert.DeserializeObject<YoutubeVideoSearch>(response);
if (data.items.Length > 0)
{
var toReturn = "http://www.youtube.com/watch?v=" + data.items[0].id.videoId.ToString();
return toReturn;
}
else
return null;
}
public static async Task<string> GetPlaylistIdByKeyword(string query) public static async Task<string> GetPlaylistIdByKeyword(string query)
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey))
@ -350,5 +377,13 @@ namespace NadekoBot.Classes
return url; return url;
} }
} }
public static string ShowInPrettyCode<T>(IEnumerable<T> items, Func<T, string> 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```";
}
} }
} }

View File

@ -66,6 +66,7 @@ namespace NadekoBot.Classes
get { return voicePlusTextEnabled; } get { return voicePlusTextEnabled; }
set { set {
voicePlusTextEnabled = value; voicePlusTextEnabled = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -76,10 +77,50 @@ namespace NadekoBot.Classes
get { return sendPrivateMessageOnMention; } get { return sendPrivateMessageOnMention; }
set { set {
sendPrivateMessageOnMention = value; sendPrivateMessageOnMention = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
[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<ulong, ulong> voiceChannelLog;
public ObservableConcurrentDictionary<ulong, ulong> VoiceChannelLog {
get { return voiceChannelLog; }
set {
voiceChannelLog = value;
if (value != null)
voiceChannelLog.CollectionChanged += (s, e) =>
{
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
};
}
}
[JsonIgnore] [JsonIgnore]
private ObservableCollection<ulong> listOfSelfAssignableRoles; private ObservableCollection<ulong> listOfSelfAssignableRoles;
public ObservableCollection<ulong> ListOfSelfAssignableRoles { public ObservableCollection<ulong> ListOfSelfAssignableRoles {
@ -106,6 +147,33 @@ namespace NadekoBot.Classes
} }
} }
[JsonIgnore]
private ObservableCollection<ulong> generateCurrencyChannels;
public ObservableCollection<ulong> GenerateCurrencyChannels {
get { return generateCurrencyChannels; }
set {
generateCurrencyChannels = value;
if (value != null)
generateCurrencyChannels.CollectionChanged += (s, e) =>
{
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
};
}
}
[JsonIgnore]
private bool autoDeleteMessagesOnCommand = false;
public bool AutoDeleteMessagesOnCommand {
get { return autoDeleteMessagesOnCommand; }
set {
autoDeleteMessagesOnCommand = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
}
}
[JsonIgnore] [JsonIgnore]
private ObservableCollection<StreamNotificationConfig> observingStreams; private ObservableCollection<StreamNotificationConfig> observingStreams;
public ObservableCollection<StreamNotificationConfig> ObservingStreams { public ObservableCollection<StreamNotificationConfig> ObservingStreams {
@ -121,10 +189,23 @@ namespace NadekoBot.Classes
} }
} }
[JsonIgnore]
private float defaultMusicVolume = 1f;
public float DefaultMusicVolume {
get { return defaultMusicVolume; }
set {
defaultMusicVolume = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
}
}
public ServerSpecificConfig() public ServerSpecificConfig()
{ {
ListOfSelfAssignableRoles = new ObservableCollection<ulong>(); ListOfSelfAssignableRoles = new ObservableCollection<ulong>();
ObservingStreams = new ObservableCollection<StreamNotificationConfig>(); ObservingStreams = new ObservableCollection<StreamNotificationConfig>();
GenerateCurrencyChannels = new ObservableCollection<ulong>();
VoiceChannelLog = new ObservableConcurrentDictionary<ulong, ulong>();
} }
public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); };

View File

@ -6,8 +6,11 @@ using NadekoBot.DataModels;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Commands; using NadekoBot.Modules.Administration.Commands;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
@ -24,11 +27,26 @@ namespace NadekoBot.Modules.Administration
commands.Add(new VoicePlusTextCommand(this)); commands.Add(new VoicePlusTextCommand(this));
commands.Add(new CrossServerTextChannel(this)); commands.Add(new CrossServerTextChannel(this));
commands.Add(new SelfAssignedRolesCommand(this)); commands.Add(new SelfAssignedRolesCommand(this));
commands.Add(new Remind(this));
commands.Add(new InfoCommands(this));
commands.Add(new CustomReactionsCommands(this)); commands.Add(new CustomReactionsCommands(this));
commands.Add(new AutoAssignRole(this)); commands.Add(new AutoAssignRole(this));
commands.Add(new SelfCommands(this)); commands.Add(new SelfCommands(this));
commands.Add(new IncidentsCommands(this));
NadekoBot.Client.GetService<CommandService>().CommandExecuted += DeleteCommandMessage;
}
private void DeleteCommandMessage(object sender, CommandEventArgs e)
{
if (e.Server == null || e.Channel.IsPrivate)
return;
var conf = SpecificConfigurations.Default.Of(e.Server.Id);
if (!conf.AutoDeleteMessagesOnCommand)
return;
try
{
e.Message.Delete();
}
catch { }
} }
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Administration; public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Administration;
@ -45,6 +63,21 @@ namespace NadekoBot.Modules.Administration
commands.ForEach(cmd => cmd.Init(cgb)); commands.ForEach(cmd => cmd.Init(cgb));
cgb.CreateCommand(Prefix + "delmsgoncmd")
.Description("Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only.")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e =>
{
var conf = SpecificConfigurations.Default.Of(e.Server.Id);
conf.AutoDeleteMessagesOnCommand = !conf.AutoDeleteMessagesOnCommand;
Classes.JSONModels.ConfigHandler.SaveConfig();
if (conf.AutoDeleteMessagesOnCommand)
await e.Channel.SendMessage("❗`Now automatically deleting successfull command invokations.`");
else
await e.Channel.SendMessage("❗`Stopped automatic deletion of successfull command invokations.`");
});
cgb.CreateCommand(Prefix + "restart") cgb.CreateCommand(Prefix + "restart")
.Description("Restarts the bot. Might not work.") .Description("Restarts the bot. Might not work.")
.AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.OwnerOnly())
@ -262,25 +295,9 @@ namespace NadekoBot.Modules.Administration
} }
}); });
cgb.CreateCommand(Prefix + "roles")
.Description("List all roles on this server or a single user if specified.")
.Parameter("user", ParameterType.Unparsed)
.Do(async e =>
{
if (!string.IsNullOrWhiteSpace(e.GetArg("user")))
{
var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr == null) return;
await e.Channel.SendMessage($"`List of roles for **{usr.Name}**:` \n• " + string.Join("\n• ", usr.Roles)).ConfigureAwait(false);
return;
}
await e.Channel.SendMessage("`List of roles:` \n• " + string.Join("\n• ", e.Server.Roles)).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "ban").Alias(Prefix + "b") cgb.CreateCommand(Prefix + "ban").Alias(Prefix + "b")
.Parameter("user", ParameterType.Required) .Parameter("user", ParameterType.Required)
.Parameter("msg", ParameterType.Optional) .Parameter("msg", ParameterType.Unparsed)
.Description("Bans a user by id or name with an optional message.\n**Usage**: .b \"@some Guy\" Your behaviour is toxic.") .Description("Bans a user by id or name with an optional message.\n**Usage**: .b \"@some Guy\" Your behaviour is toxic.")
.Do(async e => .Do(async e =>
{ {
@ -315,7 +332,7 @@ namespace NadekoBot.Modules.Administration
cgb.CreateCommand(Prefix + "softban").Alias(Prefix + "sb") cgb.CreateCommand(Prefix + "softban").Alias(Prefix + "sb")
.Parameter("user", ParameterType.Required) .Parameter("user", ParameterType.Required)
.Parameter("msg", ParameterType.Optional) .Parameter("msg", ParameterType.Unparsed)
.Description("Bans and then unbans a user by id or name with an optional message.\n**Usage**: .sb \"@some Guy\" Your behaviour is toxic.") .Description("Bans and then unbans a user by id or name with an optional message.\n**Usage**: .sb \"@some Guy\" Your behaviour is toxic.")
.Do(async e => .Do(async e =>
{ {
@ -599,40 +616,6 @@ namespace NadekoBot.Modules.Administration
await e.Channel.SendMessage(":ok: **New channel name set.**").ConfigureAwait(false); await e.Channel.SendMessage(":ok: **New channel name set.**").ConfigureAwait(false);
}); });
cgb.CreateCommand(Prefix + "userid").Alias(Prefix + "uid")
.Description("Shows user ID.")
.Parameter("user", ParameterType.Unparsed)
.Do(async e =>
{
var usr = e.User;
if (!string.IsNullOrWhiteSpace(e.GetArg("user"))) usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr == null)
return;
await e.Channel.SendMessage($"Id of the user { usr.Name } is { usr.Id }").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "channelid").Alias(Prefix + "cid")
.Description("Shows current channel ID.")
.Do(async e => await e.Channel.SendMessage("This channel's ID is " + e.Channel.Id).ConfigureAwait(false));
cgb.CreateCommand(Prefix + "serverid").Alias(Prefix + "sid")
.Description("Shows current server ID.")
.Do(async e => await e.Channel.SendMessage("This server's ID is " + e.Server.Id).ConfigureAwait(false));
cgb.CreateCommand(Prefix + "stats")
.Description("Shows some basic stats for Nadeko.")
.Do(async e =>
{
await e.Channel.SendMessage(await NadekoStats.Instance.GetStats());
});
cgb.CreateCommand(Prefix + "dysyd")
.Description("Shows some basic stats for Nadeko.")
.Do(async e =>
{
await e.Channel.SendMessage((await NadekoStats.Instance.GetStats()).Matrix().TrimTo(1990)).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "heap") cgb.CreateCommand(Prefix + "heap")
.Description("Shows allocated memory - **Bot Owner Only!**") .Description("Shows allocated memory - **Bot Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.OwnerOnly())
@ -736,7 +719,7 @@ namespace NadekoBot.Modules.Administration
cgb.CreateCommand(Prefix + "newavatar") cgb.CreateCommand(Prefix + "newavatar")
.Alias(Prefix + "setavatar") .Alias(Prefix + "setavatar")
.Description("Sets a new avatar image for the NadekoBot. **Bot Owner Only!**") .Description("Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!**\n**Usage**: `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`")
.Parameter("img", ParameterType.Unparsed) .Parameter("img", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e => .Do(async e =>
@ -766,80 +749,51 @@ namespace NadekoBot.Modules.Administration
client.SetGame(e.GetArg("set_game")); client.SetGame(e.GetArg("set_game"));
}); });
cgb.CreateCommand(Prefix + "checkmyperms")
.Description("Checks your userspecific permissions on this channel.")
.Do(async e =>
{
var output = "```\n";
foreach (var p in e.User.ServerPermissions.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
{
output += p.Name + ": " + p.GetValue(e.User.ServerPermissions, null).ToString() + "\n";
}
output += "```";
await e.User.SendMessage(output).ConfigureAwait(false);
});
Server commsServer = null;
User commsUser = null;
Channel commsChannel = null;
cgb.CreateCommand(Prefix + "commsuser")
.Description("Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Bot Owner Only!**")
.Parameter("name", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e =>
{
commsUser = commsServer?.FindUsers(e.GetArg("name")).FirstOrDefault();
if (commsUser != null)
{
commsChannel = null;
await e.Channel.SendMessage("User for comms set.").ConfigureAwait(false);
}
else
await e.Channel.SendMessage("No server specified or user.").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "commsserver")
.Description("Sets a server for through-bot communication. **Bot Owner Only!**")
.Parameter("server", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e =>
{
commsServer = client.FindServers(e.GetArg("server")).FirstOrDefault();
if (commsServer != null)
await e.Channel.SendMessage("Server for comms set.").ConfigureAwait(false);
else
await e.Channel.SendMessage("No such server.").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "commschannel")
.Description("Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Bot Owner Only!**")
.Parameter("ch", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e =>
{
commsChannel = commsServer?.FindChannels(e.GetArg("ch"), ChannelType.Text).FirstOrDefault();
if (commsChannel != null)
{
commsUser = null;
await e.Channel.SendMessage("Server for comms set.").ConfigureAwait(false);
}
else
await e.Channel.SendMessage("No server specified or channel is invalid.").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "send") cgb.CreateCommand(Prefix + "send")
.Description("Send a message to someone on a different server through the bot. **Bot Owner Only!**\n**Usage**: .send Message text multi word!") .Description("Send a message to someone on a different server through the bot. **Bot Owner Only!**\n**Usage**: `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!`")
.Parameter("ids", ParameterType.Required)
.Parameter("msg", ParameterType.Unparsed) .Parameter("msg", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e => .Do(async e =>
{ {
if (commsUser != null) var msg = e.GetArg("msg")?.Trim();
await commsUser.SendMessage(e.GetArg("msg")).ConfigureAwait(false);
else if (commsChannel != null) if (string.IsNullOrWhiteSpace(msg))
await commsChannel.SendMessage(e.GetArg("msg")).ConfigureAwait(false); return;
var ids = e.GetArg("ids").Split('-');
if (ids.Length != 2)
return;
var sid = ulong.Parse(ids[0]);
var server = NadekoBot.Client.Servers.Where(s => s.Id == sid).FirstOrDefault();
if (server == null)
return;
if (ids[1].ToUpperInvariant().StartsWith("C:"))
{
var cid = ulong.Parse(ids[1].Substring(2));
var channel = server.TextChannels.Where(c => c.Id == cid).FirstOrDefault();
if (channel == null)
{
return;
}
await channel.SendMessage(msg);
}
else if (ids[1].ToUpperInvariant().StartsWith("U:"))
{
var uid = ulong.Parse(ids[1].Substring(2));
var user = server.Users.Where(u => u.Id == uid).FirstOrDefault();
if (user == null)
{
return;
}
await user.SendMessage(msg);
}
else else
await e.Channel.SendMessage("Failed. Make sure you've specified server and [channel or user]").ConfigureAwait(false); {
await e.Channel.SendMessage("`Invalid format.`");
}
}); });
cgb.CreateCommand(Prefix + "mentionrole") cgb.CreateCommand(Prefix + "mentionrole")
@ -874,37 +828,6 @@ namespace NadekoBot.Modules.Administration
}).ConfigureAwait(false); }).ConfigureAwait(false);
}); });
cgb.CreateCommand(Prefix + "inrole")
.Description("Lists every person from the provided role or roles (separated by a ',') on this server.")
.Parameter("roles", ParameterType.Unparsed)
.Do(async e =>
{
await Task.Run(async () =>
{
if (!e.User.ServerPermissions.MentionEveryone) return;
var arg = e.GetArg("roles").Split(',').Select(r => r.Trim());
string send = $"`Here is a list of users in a specfic role:`";
foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str)))
{
var role = e.Server.FindRoles(roleStr).FirstOrDefault();
if (role == null) continue;
send += $"\n`{role.Name}`\n";
send += string.Join(", ", role.Members.Select(r => "**" + r.Name + "**#" + r.Discriminator));
}
while (send.Length > 2000)
{
var curstr = send.Substring(0, 2000);
await
e.Channel.Send(curstr.Substring(0,
curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false);
send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) +
send.Substring(2000);
}
await e.Channel.Send(send).ConfigureAwait(false);
}).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "unstuck") cgb.CreateCommand(Prefix + "unstuck")
.Description("Clears the message queue. **Bot Owner Only!**") .Description("Clears the message queue. **Bot Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.OwnerOnly())
@ -967,27 +890,6 @@ namespace NadekoBot.Modules.Administration
await e.Channel.SendMessage(":ok:").ConfigureAwait(false); await e.Channel.SendMessage(":ok:").ConfigureAwait(false);
}); });
cgb.CreateCommand(Prefix + "whoplays")
.Description("Shows a list of users who are playing the specified game.")
.Parameter("game", ParameterType.Unparsed)
.Do(async e =>
{
var game = e.GetArg("game")?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(game))
return;
var en = e.Server.Users
.Where(u => u.CurrentGame?.Name?.ToUpperInvariant() == game)
.Select(u => u.Name);
var arr = en as string[] ?? en.ToArray();
int i = 0;
if (arr.Length == 0)
await e.Channel.SendMessage("Nobody. (not 100% sure)").ConfigureAwait(false);
else
await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Join("", ig.Select(el => $"• {el,-35}")))) + "\n```").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "leave") cgb.CreateCommand(Prefix + "leave")
.Description("Leaves a server with a supplied ID.\n**Usage**: `.leave 493243292839`") .Description("Leaves a server with a supplied ID.\n**Usage**: `.leave 493243292839`")
.Parameter("num", ParameterType.Required) .Parameter("num", ParameterType.Required)
@ -1003,6 +905,33 @@ namespace NadekoBot.Modules.Administration
await e.Channel.SendMessage("`Done.`").ConfigureAwait(false); 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<Message>(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());
});
}); });
} }
} }

View File

@ -2,6 +2,7 @@
using Discord.Commands; using Discord.Commands;
using NadekoBot.Classes; using NadekoBot.Classes;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -45,14 +46,88 @@ namespace NadekoBot.Modules.Administration.Commands
cgb.CreateCommand(Prefix + "listcustreact") cgb.CreateCommand(Prefix + "listcustreact")
.Alias(Prefix + "lcr") .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) .Parameter("num", ParameterType.Required)
.Do(async e => .Do(async e =>
{ {
int num; int num;
if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) return; if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) num = 1;
string result = GetCustomsOnPage(num - 1); //People prefer starting with 1 var cmds = GetCustomsOnPage(num - 1);
await e.Channel.SendMessage(result).ConfigureAwait(false); if (!cmds.Any())
{
await e.Channel.SendMessage("");
}
else
{
string result = SearchHelper.ShowInPrettyCode<string>(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") 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<string> GetCustomsOnPage(int page)
{ {
var items = NadekoBot.Config.CustomReactions.Skip(page * ItemsPerPage).Take(ItemsPerPage); var items = NadekoBot.Config.CustomReactions.Skip(page * ItemsPerPage).Take(ItemsPerPage);
if (!items.Any()) if (!items.Any())
{ {
return $"No items on page {page + 1}."; return Enumerable.Empty<string>();
} }
return items.Select(kvp => kvp.Key);
/*
var message = new StringBuilder($"--- Custom reactions - page {page + 1} ---\n"); var message = new StringBuilder($"--- Custom reactions - page {page + 1} ---\n");
foreach (var cr in items) foreach (var cr in items)
{ {
message.Append($"{ Format.Code(cr.Key)}\n"); message.Append($"{Format.Code(cr.Key)}\n");
int i = 1; int i = 1;
var last = cr.Value.Last(); var last = cr.Value.Last();
foreach (var reaction in cr.Value) foreach (var reaction in cr.Value)
@ -123,6 +200,7 @@ namespace NadekoBot.Modules.Administration.Commands
} }
} }
return message.ToString() + "\n"; return message.ToString() + "\n";
*/
} }
} }
} }

View File

@ -0,0 +1,47 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.DataModels;
using NadekoBot.Modules.Permissions.Classes;
using System.IO;
using System.Linq;
namespace NadekoBot.Modules.Administration.Commands
{
internal class IncidentsCommands : DiscordCommand
{
public IncidentsCommands(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "listincidents")
.Alias(Prefix + "lin")
.Description("List all UNREAD incidents and flags them as read.")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e =>
{
var sid = (long)e.Server.Id;
var incs = DbHandler.Instance.FindAll<Incident>(i => i.ServerId == sid && i.Read == false);
DbHandler.Instance.UpdateAll<Incident>(incs.Select(i => { i.Read = true; return i; }));
await e.User.SendMessage(string.Join("\n----------------------", incs.Select(i => i.Text)));
});
cgb.CreateCommand(Module.Prefix + "listallincidents")
.Alias(Prefix + "lain")
.Description("Sends you a file containing all incidents and flags them as read.")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e =>
{
var sid = (long)e.Server.Id;
var incs = DbHandler.Instance.FindAll<Incident>(i => i.ServerId == sid);
DbHandler.Instance.UpdateAll<Incident>(incs.Select(i => { i.Read = true; return i; }));
var data = string.Join("\n----------------------\n", incs.Select(i => i.Text));
MemoryStream ms = new MemoryStream();
var sw = new StreamWriter(ms);
sw.WriteLine(data);
sw.Flush();
sw.BaseStream.Position = 0;
await e.User.SendFile("incidents.txt", sw.BaseStream);
});
}
}
}

View File

@ -4,19 +4,12 @@ using NadekoBot.Classes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
using System; using System;
using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Commands namespace NadekoBot.Modules.Administration.Commands
{ {
internal class LogCommand : DiscordCommand internal class LogCommand : DiscordCommand
{ {
private readonly ConcurrentDictionary<Server, Channel> logs = new ConcurrentDictionary<Server, Channel>();
private readonly ConcurrentDictionary<Server, Channel> loggingPresences = new ConcurrentDictionary<Server, Channel>();
private readonly ConcurrentDictionary<Channel, Channel> voiceChannelLog = new ConcurrentDictionary<Channel, Channel>();
private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
public LogCommand(DiscordModule module) : base(module) public LogCommand(DiscordModule module) : base(module)
@ -58,8 +51,11 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
if (e.Before.Name != e.After.Name) if (e.Before.Name != e.After.Name)
await ch.SendMessage($@"`{prettyCurrentTime}` **Channel Name Changed** `#{e.Before.Name}` (*{e.After.Id}*) await ch.SendMessage($@"`{prettyCurrentTime}` **Channel Name Changed** `#{e.Before.Name}` (*{e.After.Id}*)
@ -76,8 +72,11 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
await ch.SendMessage($"❗`{prettyCurrentTime}`❗`Channel Deleted:` #{e.Channel.Name} (*{e.Channel.Id}*)").ConfigureAwait(false); await ch.SendMessage($"❗`{prettyCurrentTime}`❗`Channel Deleted:` #{e.Channel.Name} (*{e.Channel.Id}*)").ConfigureAwait(false);
} }
@ -88,8 +87,11 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
await ch.SendMessage($"`{prettyCurrentTime}`🆕`Channel Created:` #{e.Channel.Mention} (*{e.Channel.Id}*)").ConfigureAwait(false); await ch.SendMessage($"`{prettyCurrentTime}`🆕`Channel Created:` #{e.Channel.Mention} (*{e.Channel.Id}*)").ConfigureAwait(false);
} }
@ -100,8 +102,11 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
await ch.SendMessage($"`{prettyCurrentTime}`♻`User was unbanned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); 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 try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
await ch.SendMessage($"`{prettyCurrentTime}`✅`User joined:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); await ch.SendMessage($"`{prettyCurrentTime}`✅`User joined:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false);
} }
@ -124,8 +132,11 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
await ch.SendMessage($"`{prettyCurrentTime}`❗`User left:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); await ch.SendMessage($"`{prettyCurrentTime}`❗`User left:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false);
} }
@ -136,35 +147,28 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
await ch.SendMessage($"❗`{prettyCurrentTime}`❌`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); await ch.SendMessage($"❗`{prettyCurrentTime}`❌`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false);
} }
catch { } catch { }
} }
public Func<CommandEventArgs, Task> 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) private async void MsgRecivd(object sender, MessageEventArgs e)
{ {
try try
{ {
if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return; return;
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null || e.Channel.Id == chId)
return;
Channel ch; 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; return;
if (!string.IsNullOrWhiteSpace(e.Message.Text)) 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) if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id)
return; return;
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null || e.Channel.Id == chId)
return;
Channel ch; 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; return;
if (!string.IsNullOrWhiteSpace(e.Message.Text)) 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) if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id)
return; return;
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null || e.Channel.Id == chId)
return;
Channel ch; 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; return;
await ch.SendMessage( await ch.SendMessage(
$@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
@ -225,19 +235,28 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
} }
private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) private async void UsrUpdtd(object sender, UserUpdatedEventArgs e)
{ {
var config = SpecificConfigurations.Default.Of(e.Server.Id);
try try
{ {
Channel ch; var chId = config.LogServerChannel;
if (loggingPresences.TryGetValue(e.Server, out ch)) if (chId != null)
if (e.Before.Status != e.After.Status) {
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 { } catch { }
try try
{ {
ulong notifyChBeforeId;
ulong notifyChAfterId;
Channel notifyChBefore = null; Channel notifyChBefore = null;
Channel notifyChAfter = null; Channel notifyChAfter = null;
var beforeVch = e.Before.VoiceChannel; var beforeVch = e.Before.VoiceChannel;
@ -246,11 +265,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
var notifyJoin = false; var notifyJoin = false;
if ((beforeVch != null || afterVch != null) && (beforeVch != afterVch)) // this means we need to notify for sure. 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; 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; notifyJoin = true;
} }
@ -272,8 +291,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
try try
{ {
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch; Channel ch;
if (!logs.TryGetValue(e.Server, out ch)) if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return; return;
string str = $"🕔`{prettyCurrentTime}`"; string str = $"🕔`{prettyCurrentTime}`";
if (e.Before.Name != e.After.Name) if (e.Before.Name != e.After.Name)
@ -331,21 +353,34 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
.Description("Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!**") .Description("Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.OwnerOnly())
.AddCheck(SimpleCheckers.ManageServer()) .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") cgb.CreateCommand(Module.Prefix + "userpresence")
.Description("Starts logging to this channel when someone from the server goes online/offline/idle.") .Description("Starts logging to this channel when someone from the server goes online/offline/idle.")
.AddCheck(SimpleCheckers.ManageServer()) .AddCheck(SimpleCheckers.ManageServer())
.Do(async e => .Do(async e =>
{ {
Channel ch; var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (!loggingPresences.TryRemove(e.Server, out ch)) if (chId == null)
{ {
loggingPresences.TryAdd(e.Server, e.Channel); SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id;
await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false);
return; return;
} }
SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = null;
await e.Channel.SendMessage($"**User presence notifications disabled.**").ConfigureAwait(false); await e.Channel.SendMessage($"**User presence notifications disabled.**").ConfigureAwait(false);
}); });
@ -356,11 +391,12 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
.Do(async e => .Do(async e =>
{ {
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (e.GetArg("all")?.ToLower() == "all") if (e.GetArg("all")?.ToLower() == "all")
{ {
foreach (var voiceChannel in e.Server.VoiceChannels) 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); await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false);
return; return;
@ -371,10 +407,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); await e.Channel.SendMessage("💢 You are not in a voice channel right now. If you are, please rejoin it.").ConfigureAwait(false);
return; return;
} }
Channel throwaway; ulong throwaway;
if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out 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); await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false);
} }
else else

View File

@ -51,11 +51,9 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
if (PlayingPlaceholders.Count == 0 if (PlayingPlaceholders.Count == 0
|| NadekoBot.Config.RotatingStatuses.Count == 0 || NadekoBot.Config.RotatingStatuses.Count == 0
|| i >= PlayingPlaceholders.Count
|| i >= NadekoBot.Config.RotatingStatuses.Count) || i >= NadekoBot.Config.RotatingStatuses.Count)
{ {
i = -1; i = 0;
return;
} }
status = NadekoBot.Config.RotatingStatuses[i]; status = NadekoBot.Config.RotatingStatuses[i];
status = PlayingPlaceholders.Aggregate(status, status = PlayingPlaceholders.Aggregate(status,

View File

@ -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<ulong> CopiedUsers = new HashSet<ulong>();
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<CommandEventArgs, Task> 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<CommandEventArgs, Task> 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);
};
}
}

View File

@ -1,7 +1,6 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.Modules; using Discord.Modules;
using NadekoBot.Classes.Conversations.Commands;
using NadekoBot.DataModels; using NadekoBot.DataModels;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Conversations.Commands; using NadekoBot.Modules.Conversations.Commands;
@ -19,7 +18,6 @@ namespace NadekoBot.Modules.Conversations
private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥"; private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥";
public Conversations() public Conversations()
{ {
commands.Add(new CopyCommand(this));
commands.Add(new RipCommand(this)); commands.Add(new RipCommand(this));
} }
@ -255,21 +253,6 @@ namespace NadekoBot.Modules.Conversations
await e.Channel.SendMessage(construct).ConfigureAwait(false); 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);
});
}); });
} }

View File

@ -15,6 +15,11 @@ namespace NadekoBot.Classes
/// </summary> /// </summary>
protected DiscordModule Module { get; } protected DiscordModule Module { get; }
/// <summary>
/// Parent module's prefix
/// </summary>
protected string Prefix => Module.Prefix;
/// <summary> /// <summary>
/// Creates a new instance of discord command, /// Creates a new instance of discord command,
/// use ": base(module)" in the derived class' /// use ": base(module)" in the derived class'

View File

@ -79,7 +79,7 @@ namespace NadekoBot.Modules.Gambling
await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream()).ConfigureAwait(false); await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream()).ConfigureAwait(false);
if (cardObjects.Count == 5) 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) catch (Exception ex)

View File

@ -1,10 +1,12 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Classes; using NadekoBot.Classes;
using NadekoBot.Modules.Permissions.Classes;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands namespace NadekoBot.Modules.Games.Commands
@ -18,11 +20,31 @@ namespace NadekoBot.Modules.Games.Commands
/// </summary> /// </summary>
class PlantPick : DiscordCommand class PlantPick : DiscordCommand
{ {
private Random rng;
public PlantPick(DiscordModule module) : base(module) 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 //channelid/messageid pair
ConcurrentDictionary<ulong, Message> plantedFlowerChannels = new ConcurrentDictionary<ulong, Message>(); ConcurrentDictionary<ulong, Message> plantedFlowerChannels = new ConcurrentDictionary<ulong, Message>();
@ -65,8 +87,7 @@ namespace NadekoBot.Modules.Games.Commands
return; return;
} }
var rng = new Random(); var file = GetRandomCurrencyImagePath();
var file = Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault();
Message msg; Message msg;
//todo send message after, not in lock //todo send message after, not in lock
if (file == null) if (file == null)
@ -80,6 +101,38 @@ namespace NadekoBot.Modules.Games.Commands
await Task.Delay(20000).ConfigureAwait(false); await Task.Delay(20000).ConfigureAwait(false);
await msg2.Delete().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;
}
} }
} }
} }

View File

@ -86,7 +86,7 @@ Version: `{NadekoStats.Instance.BotVersion}`";
.Description("Sends a readme and a guide links to the channel.") .Description("Sends a readme and a guide links to the channel.")
.Do(async e => .Do(async e =>
await e.Channel.SendMessage( await e.Channel.SendMessage(
@"**FULL README**: <https://github.com/Kwoth/NadekoBot/blob/master/README.md> @"**Wiki with all info**: <https://github.com/Kwoth/NadekoBot/wiki>
**WINDOWS SETUP GUIDE**: <https://github.com/Kwoth/NadekoBot/blob/master/ComprehensiveGuide.md> **WINDOWS SETUP GUIDE**: <https://github.com/Kwoth/NadekoBot/blob/master/ComprehensiveGuide.md>

View File

@ -1,5 +1,6 @@
using Discord.Commands; using Discord.Commands;
using Discord.Modules; using Discord.Modules;
using NadekoBot.Classes;
using NadekoBot.Classes.Help.Commands; using NadekoBot.Classes.Help.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
@ -54,10 +55,8 @@ namespace NadekoBot.Modules.Help
} }
var i = 0; var i = 0;
if (module != "customreactions" && module != "conversations") if (module != "customreactions" && module != "conversations")
await e.Channel.SendMessage("`List Of Commands:`\n```xl\n" + await e.Channel.SendMessage("`List Of Commands:`\n" + SearchHelper.ShowInPrettyCode<Command>(cmdsArray,
string.Join("\n", cmdsArray.GroupBy(item => (i++) / 3) el => $"{el.Text,-15}{"[" + el.Aliases.FirstOrDefault() + "]",-8}"))
.Select(ig => string.Join("", ig.Select(el => $"{el.Text,-15}" + $"{"[" + el.Aliases.FirstOrDefault() + "]",-8}"))))
+ $"\n```")
.ConfigureAwait(false); .ConfigureAwait(false);
else else
await e.Channel.SendMessage("`List Of Commands:`\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}"))); await e.Channel.SendMessage("`List Of Commands:`\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}")));

View File

@ -50,6 +50,7 @@ namespace NadekoBot.Modules.Music.Classes
private bool Destroyed { get; set; } = false; private bool Destroyed { get; set; } = false;
public bool RepeatSong { get; private set; } = false; public bool RepeatSong { get; private set; } = false;
public bool RepeatPlaylist { get; private set; } = false; public bool RepeatPlaylist { get; private set; } = false;
public bool Autoplay { get; private set; } = false;
public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
{ {
@ -173,6 +174,7 @@ namespace NadekoBot.Modules.Music.Classes
throw new ArgumentNullException(nameof(s)); throw new ArgumentNullException(nameof(s));
lock (playlistLock) lock (playlistLock)
{ {
s.MusicPlayer = this;
playlist.Add(s); playlist.Add(s);
} }
} }
@ -239,5 +241,7 @@ namespace NadekoBot.Modules.Music.Classes
internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;
internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay;
} }
} }

View File

@ -54,7 +54,7 @@ namespace NadekoBot.Modules.Music.Classes
} }
} }
private Song(SongInfo songInfo) public Song(SongInfo songInfo)
{ {
this.SongInfo = songInfo; this.SongInfo = songInfo;
} }
@ -67,6 +67,12 @@ namespace NadekoBot.Modules.Music.Classes
return s; return s;
} }
public Song SetMusicPlayer(MusicPlayer mp)
{
this.MusicPlayer = mp;
return this;
}
private Task BufferSong(CancellationToken cancelToken) => private Task BufferSong(CancellationToken cancelToken) =>
Task.Factory.StartNew(async () => Task.Factory.StartNew(async () =>
{ {

View File

@ -1,4 +1,5 @@
using NadekoBot.Classes; using NadekoBot.Classes;
using Newtonsoft.Json;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -34,18 +35,22 @@ namespace NadekoBot.Modules.Music.Classes
public class SoundCloudVideo public class SoundCloudVideo
{ {
public string Kind = ""; public string Kind { get; set; } = "";
public long Id = 0; public long Id { get; set; } = 0;
public SoundCloudUser User = new SoundCloudUser(); public SoundCloudUser User { get; set; } = new SoundCloudUser();
public string Title = ""; public string Title { get; set; } = "";
[JsonIgnore]
public string FullName => User.Name + " - " + Title; 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 string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Creds.SoundCloudClientID}";
} }
public class SoundCloudUser public class SoundCloudUser
{ {
[Newtonsoft.Json.JsonProperty("username")] [Newtonsoft.Json.JsonProperty("username")]
public string Name; public string Name { get; set; }
} }
/* /*
{"kind":"track", {"kind":"track",

View File

@ -6,6 +6,7 @@ using NadekoBot.DataModels;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Music.Classes; using NadekoBot.Modules.Music.Classes;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,7 +20,6 @@ namespace NadekoBot.Modules.Music
{ {
public static ConcurrentDictionary<Server, MusicPlayer> MusicPlayers = new ConcurrentDictionary<Server, MusicPlayer>(); public static ConcurrentDictionary<Server, MusicPlayer> MusicPlayers = new ConcurrentDictionary<Server, MusicPlayer>();
public static ConcurrentDictionary<ulong, float> DefaultMusicVolumes = new ConcurrentDictionary<ulong, float>();
public MusicModule() public MusicModule()
{ {
@ -53,30 +53,24 @@ namespace NadekoBot.Modules.Music
cgb.CreateCommand("stop") cgb.CreateCommand("stop")
.Alias("s") .Alias("s")
.Description("Stops the music and clears the playlist. Stays in the channel.\n**Usage**: `!m 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;
MusicPlayer musicPlayer; if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; musicPlayer.Stop();
if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
musicPlayer.Stop();
}).ConfigureAwait(false);
}); });
cgb.CreateCommand("destroy") cgb.CreateCommand("destroy")
.Alias("d") .Alias("d")
.Description("Completely stops the music and unbinds the bot from the channel. " + .Description("Completely stops the music and unbinds the bot from the channel. " +
"(may cause weird behaviour)\n**Usage**: `!m d`") "(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;
MusicPlayer musicPlayer; if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; musicPlayer.Destroy();
if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
musicPlayer.Destroy();
}).ConfigureAwait(false);
}); });
cgb.CreateCommand("pause") cgb.CreateCommand("pause")
@ -111,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") cgb.CreateCommand("listqueue")
.Alias("lq") .Alias("lq")
.Description("Lists 15 currently queued songs per page. Default page is 1.\n**Usage**: `!m lq` or `!m lq 2`") .Description("Lists 15 currently queued songs per page. Default page is 1.\n**Usage**: `!m lq` or `!m lq 2`")
@ -200,7 +209,8 @@ namespace NadekoBot.Modules.Music
await e.Channel.SendMessage("Volume number invalid.").ConfigureAwait(false); await e.Channel.SendMessage("Volume number invalid.").ConfigureAwait(false);
return; return;
} }
DefaultMusicVolumes.AddOrUpdate(e.Server.Id, volume / 100, (key, newval) => volume / 100); var conf = SpecificConfigurations.Default.Of(e.Server.Id);
conf.DefaultMusicVolume = volume / 100;
await e.Channel.SendMessage($"🎵 `Default volume set to {volume}%`").ConfigureAwait(false); await e.Channel.SendMessage($"🎵 `Default volume set to {volume}%`").ConfigureAwait(false);
}); });
@ -302,6 +312,37 @@ namespace NadekoBot.Modules.Music
await msg.Edit("🎵 `Playlist queue complete.`").ConfigureAwait(false); 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<SoundCloudVideo[]>();
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") cgb.CreateCommand("localplaylst")
.Alias("lopl") .Alias("lopl")
.Description("Queues all songs from a directory. **Bot Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") .Description("Queues all songs from a directory. **Bot Owner Only!**\n**Usage**: `!m lopl C:/music/classical`")
@ -430,7 +471,8 @@ namespace NadekoBot.Modules.Music
var s = playlist[n1 - 1]; var s = playlist[n1 - 1];
playlist.Insert(n2 - 1, s); playlist.Insert(n2 - 1, s);
playlist.RemoveAt(n1 - 1); var nn1 = n2 < n1 ? n1 : n1 - 1;
playlist.RemoveAt(nn1);
await e.Channel.SendMessage($"🎵`Moved` {s.PrettyName} `from #{n1} to #{n2}`"); await e.Channel.SendMessage($"🎵`Moved` {s.PrettyName} `from #{n1} to #{n2}`");
@ -609,6 +651,23 @@ namespace NadekoBot.Modules.Music
e.Channel.SendMessage($"```js\n--- List of saved playlists ---\n\n" + string.Join("\n", result.Select(r => $"'{r.Name}-{r.Id}' by {r.Creator} ({r.SongCnt} songs)")) + $"\n\n --- Page {num} ---```"); e.Channel.SendMessage($"```js\n--- List of saved playlists ---\n\n" + string.Join("\n", result.Select(r => $"'{r.Name}-{r.Id}' by {r.Creator} ({r.SongCnt} songs)")) + $"\n\n --- Page {num} ---```");
}); });
cgb.CreateCommand("deleteplaylist")
.Alias("delpls")
.Description("Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m delpls animu-5`")
.Parameter("pl", ParameterType.Required)
.Do(async e =>
{
var pl = e.GetArg("pl").Trim().Split('-')[1];
if (string.IsNullOrWhiteSpace(pl))
return;
var plnum = int.Parse(pl);
if (NadekoBot.IsOwner(e.User.Id))
DbHandler.Instance.Delete<MusicPlaylist>(plnum);
else
DbHandler.Instance.DeleteWhere<MusicPlaylist>(mp => mp.Id == plnum && (long)e.User.Id == mp.CreatorId);
await e.Channel.SendMessage("`Ok.` :ok:");
});
cgb.CreateCommand("goto") cgb.CreateCommand("goto")
.Description("Goes to a specific time in seconds in a song.") .Description("Goes to a specific time in seconds in a song.")
.Parameter("time") .Parameter("time")
@ -659,10 +718,26 @@ namespace NadekoBot.Modules.Music
return; return;
await e.Channel.SendMessage($"🎶`Current song:` <{curSong.SongInfo.Query}>"); await e.Channel.SendMessage($"🎶`Current song:` <{curSong.SongInfo.Query}>");
}); });
cgb.CreateCommand("autoplay")
.Alias("ap")
.Description("Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty)")
.Do(async e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
if (!musicPlayer.ToggleAutoplay())
await e.Channel.SendMessage("🎶`Autoplay disabled.`");
else
await e.Channel.SendMessage("🎶`Autoplay enabled.`");
});
}); });
} }
private async Task QueueSong(Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) public static async Task QueueSong(Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
{ {
if (voiceCh == null || voiceCh.Server != textCh.Server) if (voiceCh == null || voiceCh.Server != textCh.Server)
{ {
@ -675,10 +750,7 @@ namespace NadekoBot.Modules.Music
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Server, server => var musicPlayer = MusicPlayers.GetOrAdd(textCh.Server, server =>
{ {
float? vol = null; float vol = SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
float throwAway;
if (DefaultMusicVolumes.TryGetValue(server.Id, out throwAway))
vol = throwAway;
var mp = new MusicPlayer(voiceCh, vol); var mp = new MusicPlayer(voiceCh, vol);
@ -695,8 +767,15 @@ namespace NadekoBot.Modules.Music
if (playingMessage != null) if (playingMessage != null)
await playingMessage.Delete().ConfigureAwait(false); await playingMessage.Delete().ConfigureAwait(false);
lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false); lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false);
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube")
{
await QueueSong(textCh, voiceCh, await SearchHelper.GetRelatedVideoId(song.SongInfo.Query), silent, musicType).ConfigureAwait(false);
}
}
catch (Exception e)
{
Console.WriteLine(e);
} }
catch { }
} }
}; };
mp.OnStarted += async (s, song) => mp.OnStarted += async (s, song) =>
@ -719,22 +798,20 @@ namespace NadekoBot.Modules.Music
return mp; return mp;
}); });
var resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); var resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false);
resolvedSong.MusicPlayer = musicPlayer;
musicPlayer.AddSong(resolvedSong); musicPlayer.AddSong(resolvedSong);
if (!silent) if (!silent)
{ {
var queuedMessage = await textCh.SendMessage($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count}`").ConfigureAwait(false); var queuedMessage = await textCh.SendMessage($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count}`").ConfigureAwait(false);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => Task.Run(async () =>
{ {
await Task.Delay(10000).ConfigureAwait(false); await Task.Delay(10000).ConfigureAwait(false);
try try
{ {
await queuedMessage.Delete().ConfigureAwait(false); await queuedMessage.Delete().ConfigureAwait(false);
} }
catch { } catch { }
}).ConfigureAwait(false); }).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
} }

View File

@ -25,9 +25,9 @@ namespace NadekoBot.Modules.Permissions.Commands
if (filterRegex.IsMatch(args.Message.RawText)) if (filterRegex.IsMatch(args.Message.RawText))
{ {
await args.Message.Delete().ConfigureAwait(false); await args.Message.Delete().ConfigureAwait(false);
IncidentsHandler.Add(args.Server.Id, $"User [{args.User.Name}/{args.User.Id}] posted " + IncidentsHandler.Add(args.Server.Id, args.Channel.Id, $"User [{args.User.Name}/{args.User.Id}] posted " +
$"INVITE LINK in [{args.Channel.Name}/{args.Channel.Id}] channel. " + $"INVITE LINK in [{args.Channel.Name}/{args.Channel.Id}] channel.\n" +
$"Full message: [[{args.Message.Text}]]"); $"`Full message:` {args.Message.Text}");
if (serverPerms.Verbose) if (serverPerms.Verbose)
await args.Channel.SendMessage($"{args.User.Mention} Invite links are not " + await args.Channel.SendMessage($"{args.User.Mention} Invite links are not " +
$"allowed on this channel.") $"allowed on this channel.")

View File

@ -23,9 +23,9 @@ namespace NadekoBot.Modules.Permissions.Commands
if (serverPerms.Words.Any(w => wordsInMessage.Contains(w))) if (serverPerms.Words.Any(w => wordsInMessage.Contains(w)))
{ {
await args.Message.Delete().ConfigureAwait(false); await args.Message.Delete().ConfigureAwait(false);
IncidentsHandler.Add(args.Server.Id, $"User [{args.User.Name}/{args.User.Id}] posted " + IncidentsHandler.Add(args.Server.Id, args.Channel.Id, $"User [{args.User.Name}/{args.User.Id}] posted " +
$"BANNED WORD in [{args.Channel.Name}/{args.Channel.Id}] channel. " + $"BANNED WORD in [{args.Channel.Name}/{args.Channel.Id}] channel.\n" +
$"Full message: [[{args.Message.Text}]]"); $"`Full message:` {args.Message.Text}");
if (serverPerms.Verbose) if (serverPerms.Verbose)
await args.Channel.SendMessage($"{args.User.Mention} One or more of the words you used " + await args.Channel.SendMessage($"{args.User.Mention} One or more of the words you used " +
$"in that sentence are not allowed here.") $"in that sentence are not allowed here.")

View File

@ -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<Dictionary<string, string>>(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");
});
}
}
}

View File

@ -0,0 +1,268 @@
using Discord.Commands;
using NadekoBot.Classes;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
namespace NadekoBot.Modules.Searches.Commands
{
internal class OsuCommands : DiscordCommand
{
public OsuCommands(DiscordModule module) : base(module)
{
}
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 taiko`")
.Parameter("usr", ParameterType.Required)
.Parameter("mode", ParameterType.Unparsed)
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(e.GetArg("usr")))
return;
using (WebClient cl = new WebClient())
{
try
{
var m = 0;
if (!string.IsNullOrWhiteSpace(e.GetArg("mode")))
{
m = ResolveGameMode(e.GetArg("mode"));
}
cl.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
cl.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/5.0 (Windows NT 6.2; Win64; x64)");
cl.DownloadDataAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ e.GetArg("usr") }&flagshadow&xpbar&xpbarhex&pp=2&mode={m}"));
cl.DownloadDataCompleted += async (s, cle) =>
{
try
{
await e.Channel.SendFile($"{e.GetArg("usr")}.png", new MemoryStream(cle.Result)).ConfigureAwait(false);
await e.Channel.SendMessage($"`Profile Link:`https://osu.ppy.sh/u/{Uri.EscapeDataString(e.GetArg("usr"))}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
}
catch { }
};
}
catch
{
await e.Channel.SendMessage("💢 Failed retrieving osu signature :\\").ConfigureAwait(false);
}
}
});
cgb.CreateCommand(Module.Prefix + "osu b")
.Description("Shows information about an osu beatmap.\n**Usage**:~osu b https://osu.ppy.sh/s/127712")
.Parameter("map", ParameterType.Unparsed)
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(NadekoBot.Creds.OsuAPIKey))
{
await e.Channel.SendMessage("💢 An osu! API key is required.").ConfigureAwait(false);
return;
}
if (string.IsNullOrWhiteSpace(e.GetArg("map")))
return;
try
{
var mapId = ResolveMap(e.GetArg("map"));
var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Creds.OsuAPIKey}&{mapId}";
var obj = JArray.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false))[0];
var sb = new System.Text.StringBuilder();
var starRating = Math.Round(Double.Parse($"{obj["difficultyrating"]}"), 2);
var time = TimeSpan.FromSeconds(Double.Parse($"{obj["total_length"]}")).ToString(@"mm\:ss");
sb.AppendLine($"{obj["artist"]} - {obj["title"]}, mapped by {obj["creator"]}. https://osu.ppy.sh/s/{obj["beatmapset_id"]}");
sb.AppendLine($"{starRating} stars, {obj["bpm"]} BPM | AR{obj["diff_approach"]}, CS{obj["diff_size"]}, OD{obj["diff_overall"]} | Length: {time}");
await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false);
}
catch
{
await e.Channel.SendMessage("Something went wrong.");
}
});
cgb.CreateCommand(Module.Prefix + "osu top5")
.Description("Displays a user's top 5 plays. \n**Usage**:~osu top5 Name")
.Parameter("usr", ParameterType.Required)
.Parameter("mode", ParameterType.Unparsed)
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(NadekoBot.Creds.OsuAPIKey))
{
await e.Channel.SendMessage("💢 An osu! API key is required.").ConfigureAwait(false);
return;
}
if (string.IsNullOrWhiteSpace(e.GetArg("usr")))
{
await e.Channel.SendMessage("💢 Please provide a username.").ConfigureAwait(false);
return;
}
try
{
var m = 0;
if (!string.IsNullOrWhiteSpace(e.GetArg("mode")))
{
m = ResolveGameMode(e.GetArg("mode"));
}
var reqString = $"https://osu.ppy.sh/api/get_user_best?k={NadekoBot.Creds.OsuAPIKey}&u={Uri.EscapeDataString(e.GetArg("usr"))}&type=string&limit=5&m={m}";
var obj = JArray.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false));
var sb = new System.Text.StringBuilder($"`Top 5 plays for {e.GetArg("usr")}:`\n```xl" + Environment.NewLine);
foreach (var item in obj)
{
var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Creds.OsuAPIKey}&b={item["beatmap_id"]}";
var map = JArray.Parse(await SearchHelper.GetResponseStringAsync(mapReqString).ConfigureAwait(false))[0];
var pp = Math.Round(Double.Parse($"{item["pp"]}"), 2);
var acc = CalculateAcc(item, m);
var mods = ResolveMods(Int32.Parse($"{item["enabled_mods"]}"));
if (mods != "+")
sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"],-40}) | **{mods,-10}** | /b/{item["beatmap_id"]}");
else
sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"],-40}) | /b/{item["beatmap_id"]}");
}
sb.Append("```");
await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false);
}
catch
{
await e.Channel.SendMessage("Something went wrong.");
}
});
}
//https://osu.ppy.sh/wiki/Accuracy
private static Double CalculateAcc(JToken play, int mode)
{
if (mode == 0)
{
var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["count300"]}") * 300;
var totalHits = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countmiss"]}");
totalHits *= 300;
return Math.Round(hitPoints / totalHits * 100, 2);
}
else if (mode == 1)
{
var hitPoints = Double.Parse($"{play["countmiss"]}") * 0 + Double.Parse($"{play["count100"]}") * 0.5 + Double.Parse($"{play["count300"]}") * 1;
var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}");
hitPoints *= 300;
totalHits *= 300;
return Math.Round(hitPoints / totalHits * 100, 2);
}
else if (mode == 2)
{
var fruitsCaught = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}");
var totalFruits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countkatu"]}");
return Math.Round(fruitsCaught / totalFruits * 100, 2);
}
else
{
var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["countkatu"]}") * 200 + (Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}")) * 300;
var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["countkatu"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}");
totalHits *= 300;
return Math.Round(hitPoints / totalHits * 100, 2);
}
}
private static string ResolveMap(string mapLink)
{
Match s = new Regex(@"osu.ppy.sh\/s\/", RegexOptions.IgnoreCase).Match(mapLink);
Match b = new Regex(@"osu.ppy.sh\/b\/", RegexOptions.IgnoreCase).Match(mapLink);
Match p = new Regex(@"osu.ppy.sh\/p\/", RegexOptions.IgnoreCase).Match(mapLink);
Match m = new Regex(@"&m=", RegexOptions.IgnoreCase).Match(mapLink);
if (s.Success)
{
var mapId = mapLink.Substring(mapLink.IndexOf("/s/") + 3);
return $"s={mapId}";
}
else if (b.Success)
{
if (m.Success)
return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("/b/") + 3))}";
else
return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3)}";
}
else if (p.Success)
{
if (m.Success)
return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("?b=") + 3))}";
else
return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3)}";
}
else
{
return $"s={mapLink}"; //just a default incase an ID number was provided by itself (non-url)?
}
}
private static int ResolveGameMode(string mode)
{
switch (mode.ToLower())
{
case "std":
case "standard":
return 0;
case "taiko":
return 1;
case "ctb":
case "catchthebeat":
return 2;
case "mania":
case "osu!mania":
return 3;
default:
return 0;
}
}
//https://github.com/ppy/osu-api/wiki#mods
private static string ResolveMods(int mods)
{
var modString = $"+";
if (IsBitSet(mods, 0))
modString += "NF";
if (IsBitSet(mods, 1))
modString += "EZ";
if (IsBitSet(mods, 8))
modString += "HT";
if (IsBitSet(mods, 3))
modString += "HD";
if (IsBitSet(mods, 4))
modString += "HR";
if (IsBitSet(mods, 6) && !IsBitSet(mods, 9))
modString += "DT";
if (IsBitSet(mods, 9))
modString += "NC";
if (IsBitSet(mods, 10))
modString += "FL";
if (IsBitSet(mods, 5))
modString += "SD";
if (IsBitSet(mods, 14))
modString += "PF";
if (IsBitSet(mods, 7))
modString += "RX";
if (IsBitSet(mods, 11))
modString += "AT";
if (IsBitSet(mods, 12))
modString += "SO";
return modString;
}
private static bool IsBitSet(int mods, int pos)
{
return (mods & (1 << pos)) != 0;
}
}
}

View File

@ -0,0 +1,116 @@
using Discord.Commands;
using NadekoBot.Classes;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
namespace NadekoBot.Modules.Searches.Commands
{
class PokemonSearchCommands : DiscordCommand
{
private static Dictionary<string, SearchPokemon> pokemons;
private static Dictionary<string, SearchPokemonAbility> pokemonAbilities;
public PokemonSearchCommands(DiscordModule module) : base(module)
{
pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText("data/pokemon/pokemon_list.json"));
pokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText("data/pokemon/pokemon_abilities.json"));
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Prefix + "pokemon")
.Alias(Prefix + "poke")
.Description("Searches for a pokemon.")
.Parameter("pokemon", ParameterType.Unparsed)
.Do(async e =>
{
var pok = e.GetArg("pokemon")?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(pok))
return;
foreach (var kvp in pokemons)
{
if (kvp.Key.ToUpperInvariant() == pok.ToUpperInvariant())
{
await e.Channel.SendMessage($"`Stats for \"{kvp.Key}\" pokemon:`\n{kvp.Value}");
return;
}
}
await e.Channel.SendMessage("`No pokemon found.`");
});
cgb.CreateCommand(Prefix + "pokemonability")
.Alias(Prefix + "pokab")
.Description("Searches for a pokemon ability.")
.Parameter("abil", ParameterType.Unparsed)
.Do(async e =>
{
var ab = e.GetArg("abil")?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(ab))
return;
foreach (var kvp in pokemonAbilities)
{
if (kvp.Key.ToUpperInvariant() == ab)
{
await e.Channel.SendMessage($"`Info for \"{kvp.Key}\" ability:`\n{kvp.Value}");
return;
}
}
await e.Channel.SendMessage("`No ability found.`");
});
}
}
public class SearchPokemon
{
public class GenderRatioClass
{
public float M { get; set; }
public float F { get; set; }
}
public class BaseStatsClass
{
public int HP { get; set; }
public int ATK { get; set; }
public int DEF { get; set; }
public int SPA { get; set; }
public int SPD { get; set; }
public int SPE { get; set; }
public override string ToString() => $@"
**HP:** {HP,-4} **ATK:** {ATK,-4} **DEF:** {DEF,-4}
**SPA:** {SPA,-4} **SPD:** {SPD,-4} **SPE:** {SPE,-4}";
}
public int Id { get; set; }
public string Species { get; set; }
public string[] Types { get; set; }
public GenderRatioClass GenderRatio { get; set; }
public BaseStatsClass BaseStats { get; set; }
public Dictionary<string, string> Abilities { get; set; }
public float HeightM { get; set; }
public float WeightKg { get; set; }
public string Color { get; set; }
public string[] Evos { get; set; }
public string[] EggGroups { get; set; }
public override string ToString() => $@"`Name:` {Species}
`Types:` {string.Join(", ", Types)}
`Stats:` {BaseStats}
`Height:` {HeightM,4}m `Weight:` {WeightKg}kg
`Abilities:` {string.Join(", ", Abilities.Values)}";
}
public class SearchPokemonAbility
{
public string Desc { get; set; }
public string Name { get; set; }
public float Rating { get; set; }
public override string ToString() => $@"`Name:` : {Name}
`Rating:` {Rating}
`Description:` {Desc}";
}
}

View File

@ -73,7 +73,7 @@ namespace NadekoBot.Modules.Searches.Commands
checkTimer.Start(); checkTimer.Start();
} }
private async Task<Tuple<bool, string>> GetStreamStatus(StreamNotificationConfig stream) private async Task<Tuple<bool, string>> GetStreamStatus(StreamNotificationConfig stream, bool checkCache = true)
{ {
bool isLive; bool isLive;
string response; string response;
@ -83,7 +83,7 @@ namespace NadekoBot.Modules.Searches.Commands
{ {
case StreamNotificationConfig.StreamType.Hitbox: case StreamNotificationConfig.StreamType.Hitbox:
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}"; 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; return result;
response = await SearchHelper.GetResponseStringAsync(hitboxUrl).ConfigureAwait(false); response = await SearchHelper.GetResponseStringAsync(hitboxUrl).ConfigureAwait(false);
data = JObject.Parse(response); data = JObject.Parse(response);
@ -93,7 +93,7 @@ namespace NadekoBot.Modules.Searches.Commands
return result; return result;
case StreamNotificationConfig.StreamType.Twitch: case StreamNotificationConfig.StreamType.Twitch:
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}"; 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; return result;
response = await SearchHelper.GetResponseStringAsync(twitchUrl).ConfigureAwait(false); response = await SearchHelper.GetResponseStringAsync(twitchUrl).ConfigureAwait(false);
data = JObject.Parse(response); data = JObject.Parse(response);
@ -103,7 +103,7 @@ namespace NadekoBot.Modules.Searches.Commands
return result; return result;
case StreamNotificationConfig.StreamType.Beam: case StreamNotificationConfig.StreamType.Beam:
var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}"; 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; return result;
response = await SearchHelper.GetResponseStringAsync(beamUrl).ConfigureAwait(false); response = await SearchHelper.GetResponseStringAsync(beamUrl).ConfigureAwait(false);
data = JObject.Parse(response); data = JObject.Parse(response);
@ -143,6 +143,93 @@ namespace NadekoBot.Modules.Searches.Commands
.Parameter("username", ParameterType.Unparsed) .Parameter("username", ParameterType.Unparsed)
.Do(TrackStream(StreamNotificationConfig.StreamType.Beam)); .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") cgb.CreateCommand(Module.Prefix + "removestream")
.Alias(Module.Prefix + "rms") .Alias(Module.Prefix + "rms")
.Description("Removes notifications of a certain streamer on this channel." + .Description("Removes notifications of a certain streamer on this channel." +

View File

@ -14,7 +14,6 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
namespace NadekoBot.Modules.Searches namespace NadekoBot.Modules.Searches
@ -30,6 +29,9 @@ namespace NadekoBot.Modules.Searches
commands.Add(new RedditCommand(this)); commands.Add(new RedditCommand(this));
commands.Add(new WowJokeCommand(this)); commands.Add(new WowJokeCommand(this));
commands.Add(new CalcCommand(this)); commands.Add(new CalcCommand(this));
commands.Add(new OsuCommands(this));
commands.Add(new PokemonSearchCommands(this));
commands.Add(new MemegenCommands(this));
rng = new Random(); rng = new Random();
} }
@ -183,29 +185,30 @@ $@"🌍 **Weather for** 【{obj["target"]}】
cgb.CreateCommand(Prefix + "ir") cgb.CreateCommand(Prefix + "ir")
.Description("Pulls a random image using a search parameter.\n**Usage**: ~ir cute kitten") .Description("Pulls a random image using a search parameter.\n**Usage**: ~ir cute kitten")
.Parameter("query", ParameterType.Unparsed) .Parameter("query", ParameterType.Unparsed)
.Do(async e => .Do(async e =>
{ {
if (string.IsNullOrWhiteSpace(e.GetArg("query"))) if (string.IsNullOrWhiteSpace(e.GetArg("query")))
return; return;
try 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 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 obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false));
var items = obj["items"] as JArray; var items = obj["items"] as JArray;
await e.Channel.SendMessage(items[rng.Next(0, items.Count)]["link"].ToString()).ConfigureAwait(false); await e.Channel.SendMessage(items[0]["link"].ToString()).ConfigureAwait(false);
} }
catch (HttpRequestException exception) catch (HttpRequestException exception)
{ {
if (exception.Message.Contains("403 (Forbidden)")) if (exception.Message.Contains("403 (Forbidden)"))
{ {
await e.Channel.SendMessage("Daily limit reached!"); await e.Channel.SendMessage("Daily limit reached!");
} }
else else
{ {
await e.Channel.SendMessage("Something went wrong."); await e.Channel.SendMessage("Something went wrong.");
} }
} }
}); });
cgb.CreateCommand(Prefix + "lmgtfy") cgb.CreateCommand(Prefix + "lmgtfy")
.Description("Google something for an idiot.") .Description("Google something for an idiot.")
.Parameter("ffs", ParameterType.Unparsed) .Parameter("ffs", ParameterType.Unparsed)
@ -257,38 +260,6 @@ $@"🌍 **Weather for** 【{obj["target"]}】
} }
}); });
cgb.CreateCommand(Prefix + "osu")
.Description("Shows osu stats for a player.\n**Usage**:~osu Name")
.Parameter("usr", ParameterType.Unparsed)
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(e.GetArg("usr")))
return;
using (WebClient cl = new WebClient())
{
try
{
cl.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
cl.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/5.0 (Windows NT 6.2; Win64; x64)");
cl.DownloadDataAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ e.GetArg("usr") }&flagshadow&xpbar&xpbarhex&pp=2"));
cl.DownloadDataCompleted += async (s, cle) =>
{
try
{
await e.Channel.SendFile($"{e.GetArg("usr")}.png", new MemoryStream(cle.Result)).ConfigureAwait(false);
await e.Channel.SendMessage($"`Profile Link:`https://osu.ppy.sh/u/{Uri.EscapeDataString(e.GetArg("usr"))}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
}
catch { }
};
}
catch
{
await e.Channel.SendMessage("💢 Failed retrieving osu signature :\\").ConfigureAwait(false);
}
}
});
cgb.CreateCommand(Prefix + "ud") cgb.CreateCommand(Prefix + "ud")
.Description("Searches Urban Dictionary for a word.\n**Usage**:~ud Pineapple") .Description("Searches Urban Dictionary for a word.\n**Usage**:~ud Pineapple")
.Parameter("query", ParameterType.Unparsed) .Parameter("query", ParameterType.Unparsed)
@ -505,6 +476,22 @@ $@"🌍 **Weather for** 【{obj["target"]}】
Console.WriteLine(ex); Console.WriteLine(ex);
} }
}); });
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);
});
}); });
} }
} }

View File

@ -2,10 +2,12 @@
// License: Code Project Open License // License: Code Project Open License
// http://www.codeproject.com/info/cpol10.aspx // http://www.codeproject.com/info/cpol10.aspx
using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net.Http;
using System.Threading.Tasks;
using System.Web; using System.Web;
namespace NadekoBot.Modules.Translator.Helpers namespace NadekoBot.Modules.Translator.Helpers
@ -26,32 +28,6 @@ namespace NadekoBot.Modules.Translator.Helpers
return GoogleTranslator._languageModeMap.Keys.OrderBy(p => p); return GoogleTranslator._languageModeMap.Keys.OrderBy(p => p);
} }
} }
/// <summary>
/// Gets the time taken to perform the translation.
/// </summary>
public TimeSpan TranslationTime {
get;
private set;
}
/// <summary>
/// Gets the url used to speak the translation.
/// </summary>
/// <value>The url used to speak the translation.</value>
public string TranslationSpeechUrl {
get;
private set;
}
/// <summary>
/// Gets the error.
/// </summary>
public Exception Error {
get;
private set;
}
#endregion #endregion
#region Public methods #region Public methods
@ -63,92 +39,28 @@ namespace NadekoBot.Modules.Translator.Helpers
/// <param name="sourceLanguage">The source language.</param> /// <param name="sourceLanguage">The source language.</param>
/// <param name="targetLanguage">The target language.</param> /// <param name="targetLanguage">The target language.</param>
/// <returns>The translation.</returns> /// <returns>The translation.</returns>
public string Translate public async Task<string> Translate
(string sourceText, (string sourceText,
string sourceLanguage, string sourceLanguage,
string targetLanguage) string targetLanguage)
{ {
// Initialize // Initialize
this.Error = null;
this.TranslationSpeechUrl = null;
this.TranslationTime = TimeSpan.Zero;
DateTime tmStart = DateTime.Now; DateTime tmStart = DateTime.Now;
string translation = string.Empty;
string text = 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 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");
string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", text = await http.GetStringAsync(url).ConfigureAwait(false);
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;
} }
// Return result return JArray.Parse(text)[0][0][0].ToString();
this.TranslationTime = DateTime.Now - tmStart;
return translation;
} }
#endregion #endregion

View File

@ -27,13 +27,17 @@ namespace NadekoBot.Modules.Translator
await e.Channel.SendIsTyping().ConfigureAwait(false); await e.Channel.SendIsTyping().ConfigureAwait(false);
string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0]; string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0];
string to = e.GetArg("langs").ToLowerInvariant().Split('>')[1]; 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); 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);
} }
}; };

View File

@ -6,7 +6,7 @@ using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace NadekoBot.Modules.Administration.Commands namespace NadekoBot.Modules.Utility.Commands
{ {
class InfoCommands : DiscordCommand class InfoCommands : DiscordCommand
{ {

View File

@ -9,7 +9,7 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Timers; using System.Timers;
namespace NadekoBot.Modules.Administration.Commands namespace NadekoBot.Modules.Utility.Commands
{ {
class Remind : DiscordCommand class Remind : DiscordCommand
{ {

View File

@ -0,0 +1,151 @@
using Discord.Commands;
using Discord.Modules;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Classes;
using NadekoBot.Modules.Utility.Commands;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility
{
internal class UtilityModule : DiscordModule
{
public UtilityModule()
{
commands.Add(new Remind(this));
commands.Add(new InfoCommands(this));
}
public override string Prefix => NadekoBot.Config.CommandPrefixes.Utility;
public override void Install(ModuleManager manager)
{
manager.CreateCommands("", cgb =>
{
cgb.AddCheck(PermissionChecker.Instance);
var client = manager.Client;
commands.ForEach(cmd => cmd.Init(cgb));
cgb.CreateCommand(Prefix + "whoplays")
.Description("Shows a list of users who are playing the specified game.")
.Parameter("game", ParameterType.Unparsed)
.Do(async e =>
{
var game = e.GetArg("game")?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(game))
return;
var en = e.Server.Users
.Where(u => u.CurrentGame?.Name?.ToUpperInvariant() == game)
.Select(u => u.Name);
var arr = en as string[] ?? en.ToArray();
int i = 0;
if (arr.Length == 0)
await e.Channel.SendMessage("Nobody. (not 100% sure)").ConfigureAwait(false);
else
await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Join("", ig.Select(el => $"• {el,-35}")))) + "\n```").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "inrole")
.Description("Lists every person from the provided role or roles (separated by a ',') on this server.")
.Parameter("roles", ParameterType.Unparsed)
.Do(async e =>
{
await Task.Run(async () =>
{
if (!e.User.ServerPermissions.MentionEveryone) return;
var arg = e.GetArg("roles").Split(',').Select(r => r.Trim());
string send = $"`Here is a list of users in a specfic role:`";
foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str)))
{
var role = e.Server.FindRoles(roleStr).FirstOrDefault();
if (role == null) continue;
send += $"\n`{role.Name}`\n";
send += string.Join(", ", role.Members.Select(r => "**" + r.Name + "**#" + r.Discriminator));
}
while (send.Length > 2000)
{
var curstr = send.Substring(0, 2000);
await
e.Channel.Send(curstr.Substring(0,
curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false);
send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) +
send.Substring(2000);
}
await e.Channel.Send(send).ConfigureAwait(false);
}).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "checkmyperms")
.Description("Checks your userspecific permissions on this channel.")
.Do(async e =>
{
var output = "```\n";
foreach (var p in e.User.ServerPermissions.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
{
output += p.Name + ": " + p.GetValue(e.User.ServerPermissions, null).ToString() + "\n";
}
output += "```";
await e.User.SendMessage(output).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "stats")
.Description("Shows some basic stats for Nadeko.")
.Do(async e =>
{
await e.Channel.SendMessage(await NadekoStats.Instance.GetStats());
});
cgb.CreateCommand(Prefix + "dysyd")
.Description("Shows some basic stats for Nadeko.")
.Do(async e =>
{
await e.Channel.SendMessage((await NadekoStats.Instance.GetStats()).Matrix().TrimTo(1990)).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "userid").Alias(Prefix + "uid")
.Description("Shows user ID.")
.Parameter("user", ParameterType.Unparsed)
.Do(async e =>
{
var usr = e.User;
if (!string.IsNullOrWhiteSpace(e.GetArg("user"))) usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr == null)
return;
await e.Channel.SendMessage($"Id of the user { usr.Name } is { usr.Id }").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "channelid").Alias(Prefix + "cid")
.Description("Shows current channel ID.")
.Do(async e => await e.Channel.SendMessage("This channel's ID is " + e.Channel.Id).ConfigureAwait(false));
cgb.CreateCommand(Prefix + "serverid").Alias(Prefix + "sid")
.Description("Shows current server ID.")
.Do(async e => await e.Channel.SendMessage("This server's ID is " + e.Server.Id).ConfigureAwait(false));
cgb.CreateCommand(Prefix + "roles")
.Description("List all roles on this server or a single user if specified.")
.Parameter("user", ParameterType.Unparsed)
.Do(async e =>
{
if (!string.IsNullOrWhiteSpace(e.GetArg("user")))
{
var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr == null) return;
await e.Channel.SendMessage($"`List of roles for **{usr.Name}**:` \n• " + string.Join("\n• ", usr.Roles)).ConfigureAwait(false);
return;
}
await e.Channel.SendMessage("`List of roles:` \n• " + string.Join("\n• ", e.Server.Roles)).ConfigureAwait(false);
});
});
}
}
}

View File

@ -22,6 +22,7 @@ using NadekoBot.Modules.Pokemon;
using NadekoBot.Modules.Searches; using NadekoBot.Modules.Searches;
using NadekoBot.Modules.Translator; using NadekoBot.Modules.Translator;
using NadekoBot.Modules.Trello; using NadekoBot.Modules.Trello;
using NadekoBot.Modules.Utility;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -105,6 +106,9 @@ namespace NadekoBot
Console.WriteLine(string.IsNullOrWhiteSpace(Creds.SoundCloudClientID) Console.WriteLine(string.IsNullOrWhiteSpace(Creds.SoundCloudClientID)
? "No soundcloud Client ID found. Soundcloud streaming is disabled." ? "No soundcloud Client ID found. Soundcloud streaming is disabled."
: "SoundCloud streaming enabled."); : "SoundCloud streaming enabled.");
Console.WriteLine(string.IsNullOrWhiteSpace(Creds.OsuAPIKey)
? "No osu! api key found. Song & top score lookups will not work. User lookups still available."
: "osu! API key provided.");
BotMention = $"<@{Creds.BotId}>"; BotMention = $"<@{Creds.BotId}>";
@ -160,6 +164,7 @@ namespace NadekoBot
//install modules //install modules
modules.Add(new HelpModule(), "Help", ModuleFilter.None); modules.Add(new HelpModule(), "Help", ModuleFilter.None);
modules.Add(new AdministrationModule(), "Administration", ModuleFilter.None); modules.Add(new AdministrationModule(), "Administration", ModuleFilter.None);
modules.Add(new UtilityModule(), "Utility", ModuleFilter.None);
modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None); modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None);
modules.Add(new Conversations(), "Conversations", ModuleFilter.None); modules.Add(new Conversations(), "Conversations", ModuleFilter.None);
modules.Add(new GamblingModule(), "Gambling", ModuleFilter.None); modules.Add(new GamblingModule(), "Gambling", ModuleFilter.None);

View File

@ -134,8 +134,10 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Classes\ObservableConcurrentDictionary.cs" />
<Compile Include="Modules\Administration\Commands\AutoAssignRole.cs" /> <Compile Include="Modules\Administration\Commands\AutoAssignRole.cs" />
<Compile Include="Modules\Administration\Commands\CustomReactionsCommands.cs" /> <Compile Include="Modules\Administration\Commands\CustomReactionsCommands.cs" />
<Compile Include="Modules\Administration\Commands\SelfAssignedRolesCommand.cs" />
<Compile Include="Modules\Administration\Commands\SelfCommands.cs" /> <Compile Include="Modules\Administration\Commands\SelfCommands.cs" />
<Compile Include="Modules\ClashOfClans\ClashOfClans.cs" /> <Compile Include="Modules\ClashOfClans\ClashOfClans.cs" />
<Compile Include="Classes\DBHandler.cs" /> <Compile Include="Classes\DBHandler.cs" />
@ -148,6 +150,11 @@
<Compile Include="Modules\Searches\Commands\IMDB\ImdbMovie.cs" /> <Compile Include="Modules\Searches\Commands\IMDB\ImdbMovie.cs" />
<Compile Include="Modules\Searches\Commands\IMDB\ImdbScraper.cs" /> <Compile Include="Modules\Searches\Commands\IMDB\ImdbScraper.cs" />
<Compile Include="Classes\IncidentsHandler.cs" /> <Compile Include="Classes\IncidentsHandler.cs" />
<Compile Include="Modules\Searches\Commands\MemegenCommands.cs" />
<Compile Include="Modules\Searches\Commands\OsuCommands.cs" />
<Compile Include="Modules\Searches\Commands\PokemonSearchCommands.cs" />
<Compile Include="Modules\Utility\UtilityModule.cs" />
<Compile Include="_Models\DataModels\Incident.cs" />
<Compile Include="_Models\JSONModels\AnimeResult.cs" /> <Compile Include="_Models\JSONModels\AnimeResult.cs" />
<Compile Include="_Models\JSONModels\Configuration.cs" /> <Compile Include="_Models\JSONModels\Configuration.cs" />
<Compile Include="Modules\Searches\Commands\WowJokes.cs" /> <Compile Include="Modules\Searches\Commands\WowJokes.cs" />
@ -186,9 +193,9 @@
<Compile Include="Modules\DiscordCommand.cs" /> <Compile Include="Modules\DiscordCommand.cs" />
<Compile Include="Modules\Games\Commands\PlantPick.cs" /> <Compile Include="Modules\Games\Commands\PlantPick.cs" />
<Compile Include="Modules\Administration\Commands\CrossServerTextChannel.cs" /> <Compile Include="Modules\Administration\Commands\CrossServerTextChannel.cs" />
<Compile Include="Modules\Administration\Commands\InfoCommands.cs" /> <Compile Include="Modules\Utility\Commands\InfoCommands.cs" />
<Compile Include="Modules\Administration\Commands\Remind.cs" /> <Compile Include="Modules\Utility\Commands\Remind.cs" />
<Compile Include="Modules\Administration\Commands\SelfAssignedRolesCommand.cs" /> <Compile Include="Modules\Administration\Commands\IncidentsCommands.cs" />
<Compile Include="Modules\ClashOfClans\ClashOfClansModule.cs" /> <Compile Include="Modules\ClashOfClans\ClashOfClansModule.cs" />
<Compile Include="Modules\Permissions\Commands\FilterWordsCommand.cs" /> <Compile Include="Modules\Permissions\Commands\FilterWordsCommand.cs" />
<Compile Include="Modules\Permissions\Commands\FilterInvitesCommand.cs" /> <Compile Include="Modules\Permissions\Commands\FilterInvitesCommand.cs" />
@ -205,7 +212,6 @@
<Compile Include="Modules\Games\Commands\SpeedTyping.cs" /> <Compile Include="Modules\Games\Commands\SpeedTyping.cs" />
<Compile Include="Modules\Gambling\Helpers\Cards.cs" /> <Compile Include="Modules\Gambling\Helpers\Cards.cs" />
<Compile Include="Classes\Extensions.cs" /> <Compile Include="Classes\Extensions.cs" />
<Compile Include="Modules\Conversations\Commands\CopyCommand.cs" />
<Compile Include="Modules\Gambling\DiceRollCommand.cs" /> <Compile Include="Modules\Gambling\DiceRollCommand.cs" />
<Compile Include="Modules\Gambling\DrawCommand.cs" /> <Compile Include="Modules\Gambling\DrawCommand.cs" />
<Compile Include="Modules\Gambling\FlipCoinCommand.cs" /> <Compile Include="Modules\Gambling\FlipCoinCommand.cs" />
@ -491,9 +497,10 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="lib\ScaredFingers.UnitsConversion.dll" /> <Content Include="Classes\lib\ScaredFingers.UnitsConversion.dll" />
<None Include="resources\images\rose_overlay.png" /> <None Include="resources\images\rose_overlay.png" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,10 @@
namespace NadekoBot.DataModels
{
class Incident : IDataModel
{
public long ServerId { get; set; }
public long ChannelId { get; set; }
public string Text { get; set; }
public bool Read { get; set; } = false;
}
}

View File

@ -161,6 +161,7 @@ Nadeko Support Server: <https://discord.gg/0ehQwTK2RBjAxzEY>";
public string Permissions { get; set; } = ";"; public string Permissions { get; set; } = ";";
public string Programming { get; set; } = "%"; public string Programming { get; set; } = "%";
public string Pokemon { get; set; } = ">"; public string Pokemon { get; set; } = ">";
public string Utility { get; set; } = ".";
} }
public static class ConfigHandler public static class ConfigHandler

View File

@ -16,6 +16,7 @@ namespace NadekoBot.Classes.JSONModels
public string LOLAPIKey { get; set; } = ""; public string LOLAPIKey { get; set; } = "";
public string TrelloAppKey { get; set; } = ""; public string TrelloAppKey { get; set; } = "";
public string CarbonKey { get; set; } = ""; public string CarbonKey { get; set; } = "";
public string OsuAPIKey { get; set; } = "";
} }
[DebuggerDisplay("{items[0].id.playlistId}")] [DebuggerDisplay("{items[0].id.playlistId}")]
public class YoutubePlaylistSearch public class YoutubePlaylistSearch

View File

@ -11,5 +11,6 @@
"MashapeKey": "", "MashapeKey": "",
"LOLAPIKey": "", "LOLAPIKey": "",
"TrelloAppKey": "", "TrelloAppKey": "",
"CarbonKey": "" "CarbonKey": "",
"OsuAPIKey": ""
} }

View File

@ -87,7 +87,8 @@
"Gambling": "$", "Gambling": "$",
"Permissions": ";", "Permissions": ";",
"Programming": "%", "Programming": "%",
"Pokemon": ">" "Pokemon": ">",
"Utility": "."
}, },
"ServerBlacklist": [], "ServerBlacklist": [],
"ChannelBlacklist": [], "ChannelBlacklist": [],

View File

@ -2,7 +2,7 @@
######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` ######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa`
#NadekoBot List Of Commands #NadekoBot List Of Commands
Version: `NadekoBot v0.9.6015.37609` Version: `NadekoBot v0.9.6029.16666`
### Help ### Help
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
@ -44,16 +44,16 @@ Command and aliases | Description | Usage
`.lsar` | Lists all self-assignable roles. `.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 `.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 `.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: <https://github.com/Kwoth/NadekoBot/wiki/Custom-Reactions> **Bot Owner Only!** | .acr "hello" I love saying hello to %user% `.addcustreact`, `.acr` | Add a custom reaction. Guide here: <https://github.com/Kwoth/NadekoBot/wiki/Custom-Reactions> **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) `.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 `.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` `.leave` | Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331`
`.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. `.restart` | Restarts the bot. Might not work.
`.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest `.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest
`.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin `.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin
@ -61,7 +61,6 @@ Command and aliases | Description | Usage
`.removeallroles`, `.rar` | Removes all roles from a mentioned user. | .rar @User `.removeallroles`, `.rar` | Removes all roles from a mentioned user. | .rar @User
`.createrole`, `.cr` | Creates a role with a given name. | `.r Awesome Role` `.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` `.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. `.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. `.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. `.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. `.creatxtchanl`, `.ctch` | Creates a new text channel with a given name.
`.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic` `.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic`
`.setchanlname`, `.schn` | Changed the name of the current channel. `.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!** `.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` `.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!** `.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!** `.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!** `.setgame` | Sets the bots game. **Bot Owner Only!**
`.checkmyperms` | Checks your userspecific permissions on this channel. `.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!`
`.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!
`.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. `.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!** `.unstuck` | Clears the message queue. **Bot Owner Only!**
`.donators` | List of lovely people who donated to keep this project alive. `.donators` | List of lovely people who donated to keep this project alive.
`.donadd` | Add a donator to the database. `.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 `.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` `.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 ### Permissions
Command and aliases | Description | Usage 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 `..` | 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 `...` | 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` `..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 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 uptime` | Shows how long Nadeko has been running for.
`@BotName die` | Works only for the owner. Shuts the bot down. `@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 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 dump` | Dumps all of the invites it can to dump.txt.** Owner Only.**
`@BotName ab` | Try to get 'abalabahaha' `@BotName ab` | Try to get 'abalabahaha'
`@BotName av`, `@BotName avatar` | Shows a mentioned person's avatar. | ~av @X
### Gambling ### Gambling
Command and aliases | Description | Usage 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. `>pollend` | Stops active poll on this server and prints the results in this channel.
`>pick` | Picks a flower planted 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) `>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 `>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 `>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more
`>8ball` | Ask the 8ball a yes/no question. `>8ball` | Ask the 8ball a yes/no question.
@ -195,34 +200,37 @@ Command and aliases | Description | Usage
### Music ### Music
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`!m next`, `!m n`, `!m skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!m n` `! next`, `! n`, `! skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!m n`
`!m stop`, `!m s` | Stops the music and clears the playlist. Stays in the channel. | `!m s` `! stop`, `! s` | Stops the music and clears the playlist. Stays in the channel. | `!m s`
`!m destroy`, `!m d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d` `! destroy`, `! 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` `! pause`, `! 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` `! queue`, `! q`, `! 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` `! listqueue`, `! 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` `! nowplaying`, `! np` | Shows the song currently playing. | `!m np`
`!m volume`, `!m vol` | Sets the music volume 0-100% | `!m vol 50` `! volume`, `! 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` `! defvol`, `! dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80`
`!m mute`, `!m min` | Sets the music volume to 0% | `!m min` `! mute`, `! min` | Sets the music volume to 0% | `!m min`
`!m max` | Sets the music volume to 100% (real max is actually 150%). | `!m max` `! max` | Sets the music volume to 100% (real max is actually 150%). | `!m max`
`!m half` | Sets the music volume to 50%. | `!m half` `! half` | Sets the music volume to 50%. | `!m half`
`!m shuffle`, `!m sh` | Shuffles the current playlist. | `!m sh` `! shuffle`, `! 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` `! playlist`, `! pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name`
`!m localplaylst`, `!m lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m lopl C:/music/classical` `! soundcloudpl`, `! scpl` | Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony`
`!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` `! localplaylst`, `! lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m lopl C:/music/classical`
`!m local`, `!m lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3` `! radio`, `! 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 move`, `!m mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv` `! local`, `! lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3`
`!m remove`, `!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5` `! move`, `! mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv`
`!m movesong`, `!m ms` | Moves a song from one position to another. | `!m ms` 5>3 `! remove`, `! rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5`
`!m cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup` `! movesong`, `! ms` | Moves a song from one position to another. | `! ms` 5>3
`!m reptcursong`, `!m rcs` | Toggles repeat of current song. | `!m rcs` `! cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup`
`!m rpeatplaylst`, `!m rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl` `! reptcursong`, `! rcs` | Toggles repeat of current song. | `!m rcs`
`!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` `! rpeatplaylst`, `! rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl`
`!m load` | Loads a playlist under a certain name. | `!m load classical-1` `! 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 playlists`, `!m pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1` `! load` | Loads a playlist under a certain name. | `!m load classical-1`
`!m goto` | Goes to a specific time in seconds in a song. `! playlists`, `! pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1`
`!m getlink`, `!m gl` | Shows a link to the currently playing song. `! deleteplaylist`, `! delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m delpls animu-5`
`! goto` | Goes to a specific time in seconds in a song.
`! getlink`, `! gl` | Shows a link to the currently playing song.
`! autoplay`, `! 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 ### Searches
Command and aliases | Description | Usage 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 `~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 `~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 `~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 `~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 `~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls
`~convert` | Convert quantities from>to. Like `~convert m>km 1000` `~convert` | Convert quantities from>to. Like `~convert m>km 1000`
`~convertlist` | List of the convertable dimensions and currencies. `~convertlist` | List of the convertable dimensions and currencies.
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. `~wowjoke` | Get one of Kwoth's penultimate WoW jokes.
`~calculate`, `~calc` | Evaluate a mathematical expression. | ~calc 1+1 `~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 `~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 `~yt` | Searches youtubes and shows the first result
`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result.
@ -249,7 +266,6 @@ Command and aliases | Description | Usage
`~ir` | Pulls a random image using a search parameter. | ~ir cute kitten `~ir` | Pulls a random image using a search parameter. | ~ir cute kitten
`~lmgtfy` | Google something for an idiot. `~lmgtfy` | Google something for an idiot.
`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera
`~osu` | Shows osu stats for a player. | ~osu Name
`~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple `~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple
`~#` | Searches Tagdef.com for a hashtag. | ~# ff `~#` | Searches Tagdef.com for a hashtag. | ~# ff
`~quote` | Shows a random quote. `~quote` | Shows a random quote.
@ -264,6 +280,7 @@ Command and aliases | Description | Usage
`~wiki` | Gives you back a wikipedia link `~wiki` | Gives you back a wikipedia link
`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00`
`~videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. `~videocall` | Creates a private <http://www.appear.in> 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 ### NSFW
Command and aliases | Description | Usage Command and aliases | Description | Usage