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

6
.gitignore vendored
View File

@ -36,4 +36,8 @@ Tests/bin
#!**/packages/repositories.config
NadekoBot/bin/Debug/data/nadekobot.sqlite
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}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU
{45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

View File

@ -32,7 +32,16 @@ namespace NadekoBot.Classes
conn.CreateTable<SongInfo>();
conn.CreateTable<PlaylistSongInfo>();
conn.CreateTable<MusicPlaylist>();
conn.CreateTable<Incident>();
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()
{
using (var conn = new SQLiteConnection(FilePath))
@ -181,7 +198,7 @@ Limit 20 OFFSET ?", num * 20);
{
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 string TransactionTriggerQuery = @"
public const string TransactionTriggerQuery = @"
CREATE TRIGGER IF NOT EXISTS OnTransactionAdded
AFTER INSERT ON CurrencyTransaction
BEGIN
@ -208,4 +225,11 @@ INSERT OR REPLACE INTO CurrencyState (Id, UserId, Value, DateAdded)
NEW.DateAdded);
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

@ -215,7 +215,7 @@ namespace NadekoBot.Extensions
sb.Append($"{ Format.Code(item.Key)}\n");
sb.AppendLine(" `└─`" + Format.Bold(item.Value));
}
}
else
{
@ -224,7 +224,7 @@ namespace NadekoBot.Extensions
sb.Append($"{ Format.Code(item.ToString())} \n");
}
}
return sb.ToString();
}
/// <summary>
@ -352,5 +352,15 @@ namespace NadekoBot.Extensions
await Task.Run(() => images.Merge(reverseScaleFactor)).ConfigureAwait(false);
public static string Unmention(this string str) => str.Replace("@", "ම");
public static Stream ToStream(this string str)
{
var sw = new StreamWriter(new MemoryStream());
sw.Write(str);
sw.Flush();
sw.BaseStream.Position = 0;
return sw.BaseStream;
}
}
}

View File

@ -1,15 +1,25 @@
using System;
using System.IO;
using NadekoBot.DataModels;
using System;
namespace NadekoBot.Classes {
internal static class IncidentsHandler {
public static void Add(ulong serverId, string text) {
Directory.CreateDirectory("data/incidents");
File.AppendAllText($"data/incidents/{serverId}.txt", text + "\n--------------------------\n");
namespace NadekoBot.Classes
{
internal static class IncidentsHandler
{
public static void Add(ulong serverId, ulong channelId, string text)
{
var def = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"INCIDENT: {text}");
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;
}
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)
{
if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey))
@ -350,5 +377,13 @@ namespace NadekoBot.Classes
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; }
set {
voicePlusTextEnabled = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
}
}
@ -76,10 +77,50 @@ namespace NadekoBot.Classes
get { return sendPrivateMessageOnMention; }
set {
sendPrivateMessageOnMention = value;
if (!SpecificConfigurations.Instantiated) return;
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]
private 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]
private 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()
{
ListOfSelfAssignableRoles = new ObservableCollection<ulong>();
ObservingStreams = new ObservableCollection<StreamNotificationConfig>();
GenerateCurrencyChannels = new ObservableCollection<ulong>();
VoiceChannelLog = new ObservableConcurrentDictionary<ulong, ulong>();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); };

View File

@ -6,8 +6,11 @@ using NadekoBot.DataModels;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Commands;
using NadekoBot.Modules.Permissions.Classes;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
@ -24,11 +27,26 @@ namespace NadekoBot.Modules.Administration
commands.Add(new VoicePlusTextCommand(this));
commands.Add(new CrossServerTextChannel(this));
commands.Add(new SelfAssignedRolesCommand(this));
commands.Add(new Remind(this));
commands.Add(new InfoCommands(this));
commands.Add(new CustomReactionsCommands(this));
commands.Add(new AutoAssignRole(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;
@ -45,6 +63,21 @@ namespace NadekoBot.Modules.Administration
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")
.Description("Restarts the bot. Might not work.")
.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")
.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.")
.Do(async e =>
{
@ -315,7 +332,7 @@ namespace NadekoBot.Modules.Administration
cgb.CreateCommand(Prefix + "softban").Alias(Prefix + "sb")
.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.")
.Do(async e =>
{
@ -599,40 +616,6 @@ namespace NadekoBot.Modules.Administration
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")
.Description("Shows allocated memory - **Bot Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
@ -736,7 +719,7 @@ namespace NadekoBot.Modules.Administration
cgb.CreateCommand(Prefix + "newavatar")
.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)
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e =>
@ -766,80 +749,51 @@ namespace NadekoBot.Modules.Administration
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")
.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)
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e =>
{
if (commsUser != null)
await commsUser.SendMessage(e.GetArg("msg")).ConfigureAwait(false);
else if (commsChannel != null)
await commsChannel.SendMessage(e.GetArg("msg")).ConfigureAwait(false);
var msg = e.GetArg("msg")?.Trim();
if (string.IsNullOrWhiteSpace(msg))
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
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")
@ -874,37 +828,6 @@ namespace NadekoBot.Modules.Administration
}).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")
.Description("Clears the message queue. **Bot Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
@ -967,27 +890,6 @@ namespace NadekoBot.Modules.Administration
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")
.Description("Leaves a server with a supplied ID.\n**Usage**: `.leave 493243292839`")
.Parameter("num", ParameterType.Required)
@ -1003,6 +905,33 @@ namespace NadekoBot.Modules.Administration
await e.Channel.SendMessage("`Done.`").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "savechat")
.Description("Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150`")
.Parameter("cnt", ParameterType.Required)
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e =>
{
var cntstr = e.GetArg("cnt")?.Trim();
int cnt;
if (!int.TryParse(cntstr, out cnt))
return;
ulong? lastmsgId = null;
var sb = new StringBuilder();
var msgs = new List<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 NadekoBot.Classes;
using NadekoBot.Modules.Permissions.Classes;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -45,14 +46,88 @@ namespace NadekoBot.Modules.Administration.Commands
cgb.CreateCommand(Prefix + "listcustreact")
.Alias(Prefix + "lcr")
.Description($"Lists all current custom reactions (paginated with 5 commands per page).\n**Usage**:{Prefix}lcr 1")
.Description($"Lists all current custom reactions (paginated with 30 commands per page).\n**Usage**:{Prefix}lcr 1")
.Parameter("num", ParameterType.Required)
.Do(async e =>
{
int num;
if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) return;
string result = GetCustomsOnPage(num - 1); //People prefer starting with 1
await e.Channel.SendMessage(result).ConfigureAwait(false);
if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) num = 1;
var cmds = GetCustomsOnPage(num - 1);
if (!cmds.Any())
{
await e.Channel.SendMessage("");
}
else
{
string result = SearchHelper.ShowInPrettyCode<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")
@ -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);
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");
foreach (var cr in items)
{
message.Append($"{ Format.Code(cr.Key)}\n");
message.Append($"{Format.Code(cr.Key)}\n");
int i = 1;
var last = cr.Value.Last();
foreach (var reaction in cr.Value)
@ -123,6 +200,7 @@ namespace NadekoBot.Modules.Administration.Commands
}
}
return message.ToString() + "\n";
*/
}
}
}

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

View File

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

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.Commands;
using Discord.Modules;
using NadekoBot.Classes.Conversations.Commands;
using NadekoBot.DataModels;
using NadekoBot.Extensions;
using NadekoBot.Modules.Conversations.Commands;
@ -19,7 +18,6 @@ namespace NadekoBot.Modules.Conversations
private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥";
public Conversations()
{
commands.Add(new CopyCommand(this));
commands.Add(new RipCommand(this));
}
@ -255,21 +253,6 @@ namespace NadekoBot.Modules.Conversations
await e.Channel.SendMessage(construct).ConfigureAwait(false);
});
cgb.CreateCommand("av")
.Alias("avatar")
.Parameter("mention", ParameterType.Required)
.Description("Shows a mentioned person's avatar.\n**Usage**: ~av @X")
.Do(async e =>
{
var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault();
if (usr == null)
{
await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false);
return;
}
await e.Channel.SendMessage(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false);
});
});
}

View File

@ -15,6 +15,11 @@ namespace NadekoBot.Classes
/// </summary>
protected DiscordModule Module { get; }
/// <summary>
/// Parent module's prefix
/// </summary>
protected string Prefix => Module.Prefix;
/// <summary>
/// Creates a new instance of discord command,
/// 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);
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)

View File

@ -1,10 +1,12 @@
using Discord;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Modules.Permissions.Classes;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands
@ -18,11 +20,31 @@ namespace NadekoBot.Modules.Games.Commands
/// </summary>
class PlantPick : DiscordCommand
{
private Random rng;
public PlantPick(DiscordModule module) : base(module)
{
NadekoBot.Client.MessageReceived += PotentialFlowerGeneration;
rng = new Random();
}
private async void PotentialFlowerGeneration(object sender, Discord.MessageEventArgs e)
{
if (e.Server == null || e.Channel.IsPrivate)
return;
var config = Classes.SpecificConfigurations.Default.Of(e.Server.Id);
if (config.GenerateCurrencyChannels.Contains(e.Channel.Id))
{
var rnd = Math.Abs(GetRandomNumber());
if ((rnd % 50) == 0)
{
var msg = await e.Channel.SendFile(GetRandomCurrencyImagePath());
await e.Channel.SendMessage($"❗ A random {NadekoBot.Config.CurrencyName} appeared! Pick it up by typing `>pick`");
plantedFlowerChannels.AddOrUpdate(e.Channel.Id, msg, (u, m) => { m.Delete().GetAwaiter().GetResult(); return msg; });
}
}
}
//channelid/messageid pair
ConcurrentDictionary<ulong, Message> plantedFlowerChannels = new ConcurrentDictionary<ulong, Message>();
@ -65,8 +87,7 @@ namespace NadekoBot.Modules.Games.Commands
return;
}
var rng = new Random();
var file = Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault();
var file = GetRandomCurrencyImagePath();
Message msg;
//todo send message after, not in lock
if (file == null)
@ -80,6 +101,38 @@ namespace NadekoBot.Modules.Games.Commands
await Task.Delay(20000).ConfigureAwait(false);
await msg2.Delete().ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "gencurrency")
.Alias(Prefix + "gc")
.Description($"Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a {NadekoBot.Config.CurrencyName}. Requires Manage Messages permission. | `>gc`")
.AddCheck(SimpleCheckers.ManageMessages())
.Do(async e =>
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (config.GenerateCurrencyChannels.Remove(e.Channel.Id))
{
await e.Channel.SendMessage("`Currency generation disabled on this channel.`");
}
else
{
config.GenerateCurrencyChannels.Add(e.Channel.Id);
await e.Channel.SendMessage("`Currency generation enabled on this channel.`");
}
});
}
private string GetRandomCurrencyImagePath() =>
Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault();
int GetRandomNumber()
{
using (RNGCryptoServiceProvider rg = new RNGCryptoServiceProvider())
{
byte[] rno = new byte[4];
rg.GetBytes(rno);
int randomvalue = BitConverter.ToInt32(rno, 0);
return randomvalue;
}
}
}
}

View File

@ -86,7 +86,7 @@ Version: `{NadekoStats.Instance.BotVersion}`";
.Description("Sends a readme and a guide links to the channel.")
.Do(async e =>
await e.Channel.SendMessage(
@"**FULL README**: <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>

View File

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

View File

@ -50,6 +50,7 @@ namespace NadekoBot.Modules.Music.Classes
private bool Destroyed { get; set; } = false;
public bool RepeatSong { get; private set; } = false;
public bool RepeatPlaylist { get; private set; } = false;
public bool Autoplay { get; private set; } = false;
public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
{
@ -173,6 +174,7 @@ namespace NadekoBot.Modules.Music.Classes
throw new ArgumentNullException(nameof(s));
lock (playlistLock)
{
s.MusicPlayer = this;
playlist.Add(s);
}
}
@ -239,5 +241,7 @@ namespace NadekoBot.Modules.Music.Classes
internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
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;
}
@ -67,6 +67,12 @@ namespace NadekoBot.Modules.Music.Classes
return s;
}
public Song SetMusicPlayer(MusicPlayer mp)
{
this.MusicPlayer = mp;
return this;
}
private Task BufferSong(CancellationToken cancelToken) =>
Task.Factory.StartNew(async () =>
{

View File

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

View File

@ -6,6 +6,7 @@ using NadekoBot.DataModels;
using NadekoBot.Extensions;
using NadekoBot.Modules.Music.Classes;
using NadekoBot.Modules.Permissions.Classes;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -19,7 +20,6 @@ namespace NadekoBot.Modules.Music
{
public static ConcurrentDictionary<Server, MusicPlayer> MusicPlayers = new ConcurrentDictionary<Server, MusicPlayer>();
public static ConcurrentDictionary<ulong, float> DefaultMusicVolumes = new ConcurrentDictionary<ulong, float>();
public MusicModule()
{
@ -53,30 +53,24 @@ namespace NadekoBot.Modules.Music
cgb.CreateCommand("stop")
.Alias("s")
.Description("Stops the music and clears the playlist. Stays in the channel.\n**Usage**: `!m s`")
.Do(async e =>
.Do(e =>
{
await Task.Run(() =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
musicPlayer.Stop();
}).ConfigureAwait(false);
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
musicPlayer.Stop();
});
cgb.CreateCommand("destroy")
.Alias("d")
.Description("Completely stops the music and unbinds the bot from the channel. " +
"(may cause weird behaviour)\n**Usage**: `!m d`")
.Do(async e =>
.Do(e =>
{
await Task.Run(() =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return;
if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
musicPlayer.Destroy();
}).ConfigureAwait(false);
MusicPlayer musicPlayer;
if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return;
if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel)
musicPlayer.Destroy();
});
cgb.CreateCommand("pause")
@ -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")
.Alias("lq")
.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);
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);
});
@ -302,6 +312,37 @@ namespace NadekoBot.Modules.Music
await msg.Edit("🎵 `Playlist queue complete.`").ConfigureAwait(false);
});
cgb.CreateCommand("soundcloudpl")
.Alias("scpl")
.Description("Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony`")
.Parameter("pl", ParameterType.Unparsed)
.Do(async e =>
{
var pl = e.GetArg("pl")?.Trim();
if (string.IsNullOrWhiteSpace(pl))
return;
var scvids = JObject.Parse(await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Creds.SoundCloudClientID}"))["tracks"].ToObject<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")
.Alias("lopl")
.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];
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}`");
@ -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} ---```");
});
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")
.Description("Goes to a specific time in seconds in a song.")
.Parameter("time")
@ -659,10 +718,26 @@ namespace NadekoBot.Modules.Music
return;
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)
{
@ -675,10 +750,7 @@ namespace NadekoBot.Modules.Music
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Server, server =>
{
float? vol = null;
float throwAway;
if (DefaultMusicVolumes.TryGetValue(server.Id, out throwAway))
vol = throwAway;
float vol = SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
var mp = new MusicPlayer(voiceCh, vol);
@ -695,8 +767,15 @@ namespace NadekoBot.Modules.Music
if (playingMessage != null)
await playingMessage.Delete().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) =>
@ -719,22 +798,20 @@ namespace NadekoBot.Modules.Music
return mp;
});
var resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false);
resolvedSong.MusicPlayer = musicPlayer;
musicPlayer.AddSong(resolvedSong);
if (!silent)
{
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
Task.Run(async () =>
{
await Task.Delay(10000).ConfigureAwait(false);
try
{
await queuedMessage.Delete().ConfigureAwait(false);
}
catch { }
}).ConfigureAwait(false);
{
await Task.Delay(10000).ConfigureAwait(false);
try
{
await queuedMessage.Delete().ConfigureAwait(false);
}
catch { }
}).ConfigureAwait(false);
#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))
{
await args.Message.Delete().ConfigureAwait(false);
IncidentsHandler.Add(args.Server.Id, $"User [{args.User.Name}/{args.User.Id}] posted " +
$"INVITE LINK in [{args.Channel.Name}/{args.Channel.Id}] channel. " +
$"Full message: [[{args.Message.Text}]]");
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.\n" +
$"`Full message:` {args.Message.Text}");
if (serverPerms.Verbose)
await args.Channel.SendMessage($"{args.User.Mention} Invite links are not " +
$"allowed on this channel.")

View File

@ -23,9 +23,9 @@ namespace NadekoBot.Modules.Permissions.Commands
if (serverPerms.Words.Any(w => wordsInMessage.Contains(w)))
{
await args.Message.Delete().ConfigureAwait(false);
IncidentsHandler.Add(args.Server.Id, $"User [{args.User.Name}/{args.User.Id}] posted " +
$"BANNED WORD in [{args.Channel.Name}/{args.Channel.Id}] channel. " +
$"Full message: [[{args.Message.Text}]]");
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.\n" +
$"`Full message:` {args.Message.Text}");
if (serverPerms.Verbose)
await args.Channel.SendMessage($"{args.User.Mention} One or more of the words you used " +
$"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();
}
private async Task<Tuple<bool, string>> GetStreamStatus(StreamNotificationConfig stream)
private async Task<Tuple<bool, string>> GetStreamStatus(StreamNotificationConfig stream, bool checkCache = true)
{
bool isLive;
string response;
@ -83,7 +83,7 @@ namespace NadekoBot.Modules.Searches.Commands
{
case StreamNotificationConfig.StreamType.Hitbox:
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}";
if (cachedStatuses.TryGetValue(hitboxUrl, out result))
if (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result))
return result;
response = await SearchHelper.GetResponseStringAsync(hitboxUrl).ConfigureAwait(false);
data = JObject.Parse(response);
@ -93,7 +93,7 @@ namespace NadekoBot.Modules.Searches.Commands
return result;
case StreamNotificationConfig.StreamType.Twitch:
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}";
if (cachedStatuses.TryGetValue(twitchUrl, out result))
if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result))
return result;
response = await SearchHelper.GetResponseStringAsync(twitchUrl).ConfigureAwait(false);
data = JObject.Parse(response);
@ -103,7 +103,7 @@ namespace NadekoBot.Modules.Searches.Commands
return result;
case StreamNotificationConfig.StreamType.Beam:
var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}";
if (cachedStatuses.TryGetValue(beamUrl, out result))
if (checkCache && cachedStatuses.TryGetValue(beamUrl, out result))
return result;
response = await SearchHelper.GetResponseStringAsync(beamUrl).ConfigureAwait(false);
data = JObject.Parse(response);
@ -143,6 +143,93 @@ namespace NadekoBot.Modules.Searches.Commands
.Parameter("username", ParameterType.Unparsed)
.Do(TrackStream(StreamNotificationConfig.StreamType.Beam));
cgb.CreateCommand(Module.Prefix + "checkhitbox")
.Alias(Module.Prefix + "chhb")
.Description("Checks if a certain user is streaming on the hitbox platform." +
"\n**Usage**: ~chhb SomeStreamer")
.Parameter("username", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e =>
{
var stream = e.GetArg("username")?.Trim();
if (string.IsNullOrWhiteSpace(stream))
return;
try
{
var streamStatus = (await GetStreamStatus(new StreamNotificationConfig
{
Username = stream,
Type = StreamNotificationConfig.StreamType.Hitbox
}));
if (streamStatus.Item1)
{
await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`");
}
}
catch
{
await e.Channel.SendMessage("No channel found.");
}
});
cgb.CreateCommand(Module.Prefix + "checktwitch")
.Alias(Module.Prefix + "chtw")
.Description("Checks if a certain user is streaming on the twitch platform." +
"\n**Usage**: ~chtw SomeStreamer")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(async e =>
{
var stream = e.GetArg("username")?.Trim();
if (string.IsNullOrWhiteSpace(stream))
return;
try
{
var streamStatus = (await GetStreamStatus(new StreamNotificationConfig
{
Username = stream,
Type = StreamNotificationConfig.StreamType.Twitch
}));
if (streamStatus.Item1)
{
await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`");
}
}
catch
{
await e.Channel.SendMessage("No channel found.");
}
});
cgb.CreateCommand(Module.Prefix + "checkbeam")
.Alias(Module.Prefix + "chbm")
.Description("Checks if a certain user is streaming on the beam platform." +
"\n**Usage**: ~chbm SomeStreamer")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(async e =>
{
var stream = e.GetArg("username")?.Trim();
if (string.IsNullOrWhiteSpace(stream))
return;
try
{
var streamStatus = (await GetStreamStatus(new StreamNotificationConfig
{
Username = stream,
Type = StreamNotificationConfig.StreamType.Beam
}));
if (streamStatus.Item1)
{
await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`");
}
}
catch
{
await e.Channel.SendMessage("No channel found.");
}
});
cgb.CreateCommand(Module.Prefix + "removestream")
.Alias(Module.Prefix + "rms")
.Description("Removes notifications of a certain streamer on this channel." +

View File

@ -14,7 +14,6 @@ using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
namespace NadekoBot.Modules.Searches
@ -30,6 +29,9 @@ namespace NadekoBot.Modules.Searches
commands.Add(new RedditCommand(this));
commands.Add(new WowJokeCommand(this));
commands.Add(new CalcCommand(this));
commands.Add(new OsuCommands(this));
commands.Add(new PokemonSearchCommands(this));
commands.Add(new MemegenCommands(this));
rng = new Random();
}
@ -183,29 +185,30 @@ $@"🌍 **Weather for** 【{obj["target"]}】
cgb.CreateCommand(Prefix + "ir")
.Description("Pulls a random image using a search parameter.\n**Usage**: ~ir cute kitten")
.Parameter("query", ParameterType.Unparsed)
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(e.GetArg("query")))
return;
try
{
var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=50&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}";
var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false));
var items = obj["items"] as JArray;
await e.Channel.SendMessage(items[rng.Next(0, items.Count)]["link"].ToString()).ConfigureAwait(false);
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("403 (Forbidden)"))
{
await e.Channel.SendMessage("Daily limit reached!");
}
else
{
await e.Channel.SendMessage("Something went wrong.");
}
}
});
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(e.GetArg("query")))
return;
try
{
var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}";
var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false));
var items = obj["items"] as JArray;
await e.Channel.SendMessage(items[0]["link"].ToString()).ConfigureAwait(false);
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("403 (Forbidden)"))
{
await e.Channel.SendMessage("Daily limit reached!");
}
else
{
await e.Channel.SendMessage("Something went wrong.");
}
}
});
cgb.CreateCommand(Prefix + "lmgtfy")
.Description("Google something for an idiot.")
.Parameter("ffs", ParameterType.Unparsed)
@ -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")
.Description("Searches Urban Dictionary for a word.\n**Usage**:~ud Pineapple")
.Parameter("query", ParameterType.Unparsed)
@ -505,6 +476,22 @@ $@"🌍 **Weather for** 【{obj["target"]}】
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
// http://www.codeproject.com/info/cpol10.aspx
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
namespace NadekoBot.Modules.Translator.Helpers
@ -26,32 +28,6 @@ namespace NadekoBot.Modules.Translator.Helpers
return GoogleTranslator._languageModeMap.Keys.OrderBy(p => p);
}
}
/// <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
#region Public methods
@ -63,92 +39,28 @@ namespace NadekoBot.Modules.Translator.Helpers
/// <param name="sourceLanguage">The source language.</param>
/// <param name="targetLanguage">The target language.</param>
/// <returns>The translation.</returns>
public string Translate
public async Task<string> Translate
(string sourceText,
string sourceLanguage,
string targetLanguage)
{
// Initialize
this.Error = null;
this.TranslationSpeechUrl = null;
this.TranslationTime = TimeSpan.Zero;
DateTime tmStart = DateTime.Now;
string translation = string.Empty;
string text = string.Empty;
try
// Download translation
string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage),
GoogleTranslator.LanguageEnumToIdentifier(targetLanguage),
HttpUtility.UrlEncode(sourceText));
using (HttpClient http = new HttpClient())
{
// Download translation
string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage),
GoogleTranslator.LanguageEnumToIdentifier(targetLanguage),
HttpUtility.UrlEncode(sourceText));
using (WebClient wc = new WebClient())
{
wc.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
text = wc.DownloadString(url);
}
// Get translated text
// Get phrase collection
// string text = File.ReadAllText(outputFile);
int index = text.IndexOf(string.Format(",,\"{0}\"", GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage)));
if (index == -1)
{
// Translation of single word
int startQuote = text.IndexOf('\"');
if (startQuote != -1)
{
int endQuote = text.IndexOf('\"', startQuote + 1);
if (endQuote != -1)
{
translation = text.Substring(startQuote + 1, endQuote - startQuote - 1);
}
}
else
{
// Translation of phrase
text = text.Substring(0, index);
text = text.Replace("],[", ",");
text = text.Replace("]", string.Empty);
text = text.Replace("[", string.Empty);
text = text.Replace("\",\"", "\"");
// Get translated phrases
string[] phrases = text.Split(new[] { '\"' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; (i < phrases.Count()); i += 2)
{
string translatedPhrase = phrases[i];
if (translatedPhrase.StartsWith(",,"))
{
i--;
continue;
}
translation += translatedPhrase + " ";
}
}
// Fix up translation
translation = translation.Trim();
translation = translation.Replace(" ?", "?");
translation = translation.Replace(" !", "!");
translation = translation.Replace(" ,", ",");
translation = translation.Replace(" .", ".");
translation = translation.Replace(" ;", ";");
// And translation speech URL
this.TranslationSpeechUrl = string.Format("https://translate.googleapis.com/translate_tts?ie=UTF-8&q={0}&tl={1}&total=1&idx=0&textlen={2}&client=gtx",
HttpUtility.UrlEncode(translation), GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), translation.Length);
}
}
catch (Exception ex)
{
this.Error = ex;
http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
text = await http.GetStringAsync(url).ConfigureAwait(false);
}
// Return result
this.TranslationTime = DateTime.Now - tmStart;
return translation;
return JArray.Parse(text)[0][0][0].ToString();
}
#endregion

View File

@ -27,13 +27,17 @@ namespace NadekoBot.Modules.Translator
await e.Channel.SendIsTyping().ConfigureAwait(false);
string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0];
string to = e.GetArg("langs").ToLowerInvariant().Split('>')[1];
var text = e.GetArg("text")?.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
string translation = t.Translate(e.GetArg("text"), from, to);
string translation = await t.Translate(text, from, to).ConfigureAwait(false);
await e.Channel.SendMessage(translation).ConfigureAwait(false);
}
catch
catch (Exception ex)
{
await e.Channel.SendMessage("Bad input format, or sth went wrong...").ConfigureAwait(false);
Console.WriteLine(ex);
await e.Channel.SendMessage("Bad input format, or something went wrong...").ConfigureAwait(false);
}
};

View File

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

View File

@ -9,7 +9,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Timers;
namespace NadekoBot.Modules.Administration.Commands
namespace NadekoBot.Modules.Utility.Commands
{
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.Translator;
using NadekoBot.Modules.Trello;
using NadekoBot.Modules.Utility;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@ -105,6 +106,9 @@ namespace NadekoBot
Console.WriteLine(string.IsNullOrWhiteSpace(Creds.SoundCloudClientID)
? "No soundcloud Client ID found. Soundcloud streaming is disabled."
: "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}>";
@ -160,6 +164,7 @@ namespace NadekoBot
//install modules
modules.Add(new HelpModule(), "Help", 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 Conversations(), "Conversations", ModuleFilter.None);
modules.Add(new GamblingModule(), "Gambling", ModuleFilter.None);

View File

@ -134,8 +134,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Classes\ObservableConcurrentDictionary.cs" />
<Compile Include="Modules\Administration\Commands\AutoAssignRole.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\ClashOfClans\ClashOfClans.cs" />
<Compile Include="Classes\DBHandler.cs" />
@ -148,6 +150,11 @@
<Compile Include="Modules\Searches\Commands\IMDB\ImdbMovie.cs" />
<Compile Include="Modules\Searches\Commands\IMDB\ImdbScraper.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\Configuration.cs" />
<Compile Include="Modules\Searches\Commands\WowJokes.cs" />
@ -186,9 +193,9 @@
<Compile Include="Modules\DiscordCommand.cs" />
<Compile Include="Modules\Games\Commands\PlantPick.cs" />
<Compile Include="Modules\Administration\Commands\CrossServerTextChannel.cs" />
<Compile Include="Modules\Administration\Commands\InfoCommands.cs" />
<Compile Include="Modules\Administration\Commands\Remind.cs" />
<Compile Include="Modules\Administration\Commands\SelfAssignedRolesCommand.cs" />
<Compile Include="Modules\Utility\Commands\InfoCommands.cs" />
<Compile Include="Modules\Utility\Commands\Remind.cs" />
<Compile Include="Modules\Administration\Commands\IncidentsCommands.cs" />
<Compile Include="Modules\ClashOfClans\ClashOfClansModule.cs" />
<Compile Include="Modules\Permissions\Commands\FilterWordsCommand.cs" />
<Compile Include="Modules\Permissions\Commands\FilterInvitesCommand.cs" />
@ -205,7 +212,6 @@
<Compile Include="Modules\Games\Commands\SpeedTyping.cs" />
<Compile Include="Modules\Gambling\Helpers\Cards.cs" />
<Compile Include="Classes\Extensions.cs" />
<Compile Include="Modules\Conversations\Commands\CopyCommand.cs" />
<Compile Include="Modules\Gambling\DiceRollCommand.cs" />
<Compile Include="Modules\Gambling\DrawCommand.cs" />
<Compile Include="Modules\Gambling\FlipCoinCommand.cs" />
@ -491,9 +497,10 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="lib\ScaredFingers.UnitsConversion.dll" />
<Content Include="Classes\lib\ScaredFingers.UnitsConversion.dll" />
<None Include="resources\images\rose_overlay.png" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.

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 Programming { get; set; } = "%";
public string Pokemon { get; set; } = ">";
public string Utility { get; set; } = ".";
}
public static class ConfigHandler

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa`
#NadekoBot List Of Commands
Version: `NadekoBot v0.9.6015.37609`
Version: `NadekoBot v0.9.6029.16666`
### Help
Command and aliases | Description | Usage
----------------|--------------|-------
@ -44,16 +44,16 @@ Command and aliases | Description | Usage
`.lsar` | Lists all self-assignable roles.
`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer
`.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer
`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!`
`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!**
`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server
`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel
`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser
`.addcustreact`, `.acr` | Add a custom reaction. Guide here: <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)
`.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`
`.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.
`.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest
`.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
`.createrole`, `.cr` | Creates a role with a given name. | `.r Awesome Role`
`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55`
`.roles` | List all roles on this server or a single user if specified.
`.ban`, `.b` | Bans a user by id or name with an optional message. | .b "@some Guy" Your behaviour is toxic.
`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | .sb "@some Guy" Your behaviour is toxic.
`.kick`, `.k` | Kicks a mentioned user.
@ -75,30 +74,38 @@ Command and aliases | Description | Usage
`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name.
`.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic`
`.setchanlname`, `.schn` | Changed the name of the current channel.
`.userid`, `.uid` | Shows user ID.
`.channelid`, `.cid` | Shows current channel ID.
`.serverid`, `.sid` | Shows current server ID.
`.stats` | Shows some basic stats for Nadeko.
`.dysyd` | Shows some basic stats for Nadeko.
`.heap` | Shows allocated memory - **Bot Owner Only!**
`.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!**
`.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!**
`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. **Bot Owner Only!**
`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`
`.setgame` | Sets the bots game. **Bot Owner Only!**
`.checkmyperms` | Checks your userspecific permissions on this channel.
`.commsuser` | Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Bot Owner Only!**
`.commsserver` | Sets a server for through-bot communication. **Bot Owner Only!**
`.commschannel` | Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Bot Owner Only!**
`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | .send Message text multi word!
`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!`
`.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission.
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server.
`.unstuck` | Clears the message queue. **Bot Owner Only!**
`.donators` | List of lovely people who donated to keep this project alive.
`.donadd` | Add a donator to the database.
`.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | .announce Useless spam
`.whoplays` | Shows a list of users who are playing the specified game.
`.leave` | Leaves a server with a supplied ID. | `.leave 493243292839`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150`
### Utility
Command and aliases | Description | Usage
----------------|--------------|-------
`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!`
`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!**
`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server
`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel
`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser
`.whoplays` | Shows a list of users who are playing the specified game.
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server.
`.checkmyperms` | Checks your userspecific permissions on this channel.
`.stats` | Shows some basic stats for Nadeko.
`.dysyd` | Shows some basic stats for Nadeko.
`.userid`, `.uid` | Shows user ID.
`.channelid`, `.cid` | Shows current channel ID.
`.serverid`, `.sid` | Shows current server ID.
`.roles` | List all roles on this server or a single user if specified.
### Permissions
Command and aliases | Description | Usage
@ -145,8 +152,6 @@ Command and aliases | Description | Usage
`..` | Adds a new quote with the specified name (single word) and message (no limit). | .. abc My message
`...` | Shows a random quote with a specified name. | .. abc
`..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc`
`@BotName copyme`, `@BotName cm` | Nadeko starts copying everything you say. Disable with cs
`@BotName cs`, `@BotName copystop` | Nadeko stops copying you
`@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000
`@BotName uptime` | Shows how long Nadeko has been running for.
`@BotName die` | Works only for the owner. Shuts the bot down.
@ -156,7 +161,6 @@ Command and aliases | Description | Usage
`@BotName slm` | Shows the message where you were last mentioned in this channel (checks last 10k messages)
`@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.**
`@BotName ab` | Try to get 'abalabahaha'
`@BotName av`, `@BotName avatar` | Shows a mentioned person's avatar. | ~av @X
### Gambling
Command and aliases | Description | Usage
@ -186,6 +190,7 @@ Command and aliases | Description | Usage
`>pollend` | Stops active poll on this server and prints the results in this channel.
`>pick` | Picks a flower planted in this channel.
`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)
`>gencurrency`, `>gc` | Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a NadekoFlower. Requires Manage Messages permission. | `>gc`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | >leet 3 Hello
`>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more
`>8ball` | Ask the 8ball a yes/no question.
@ -195,34 +200,37 @@ Command and aliases | Description | Usage
### Music
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`
`!m stop`, `!m 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`
`!m pause`, `!m p` | Pauses or Unpauses the song. | `!m p`
`!m queue`, `!m q`, `!m yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!m q Dream Of Venice`
`!m listqueue`, `!m lq` | Lists up to 15 currently queued songs. | `!m lq`
`!m nowplaying`, `!m np` | Shows the song currently playing. | `!m np`
`!m volume`, `!m vol` | Sets the music volume 0-100% | `!m vol 50`
`!m defvol`, `!m dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80`
`!m mute`, `!m min` | Sets the music volume to 0% | `!m min`
`!m max` | Sets the music volume to 100% (real max is actually 150%). | `!m max`
`!m half` | Sets the music volume to 50%. | `!m half`
`!m shuffle`, `!m sh` | Shuffles the current playlist. | `!m sh`
`!m playlist`, `!m pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name`
`!m localplaylst`, `!m lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m lopl C:/music/classical`
`!m radio`, `!m ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf | `!m ra radio link here`
`!m local`, `!m lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3`
`!m move`, `!m mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv`
`!m remove`, `!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5`
`!m movesong`, `!m ms` | Moves a song from one position to another. | `!m ms` 5>3
`!m cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup`
`!m reptcursong`, `!m rcs` | Toggles repeat of current song. | `!m rcs`
`!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`
`!m save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1`
`!m load` | Loads a playlist under a certain name. | `!m load classical-1`
`!m playlists`, `!m pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1`
`!m goto` | Goes to a specific time in seconds in a song.
`!m getlink`, `!m gl` | Shows a link to the currently playing song.
`! 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`
`! stop`, `! s` | Stops the music and clears the playlist. Stays in the channel. | `!m s`
`! destroy`, `! d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d`
`! pause`, `! p` | Pauses or Unpauses the song. | `!m p`
`! 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`
`! listqueue`, `! lq` | Lists 15 currently queued songs per page. Default page is 1. | `!m lq` or `!m lq 2`
`! nowplaying`, `! np` | Shows the song currently playing. | `!m np`
`! volume`, `! vol` | Sets the music volume 0-100% | `!m vol 50`
`! defvol`, `! dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80`
`! mute`, `! min` | Sets the music volume to 0% | `!m min`
`! max` | Sets the music volume to 100% (real max is actually 150%). | `!m max`
`! half` | Sets the music volume to 50%. | `!m half`
`! shuffle`, `! sh` | Shuffles the current playlist. | `!m sh`
`! playlist`, `! pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name`
`! soundcloudpl`, `! scpl` | Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony`
`! localplaylst`, `! lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m lopl C:/music/classical`
`! 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`
`! local`, `! lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3`
`! move`, `! mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv`
`! remove`, `! rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5`
`! movesong`, `! ms` | Moves a song from one position to another. | `! ms` 5>3
`! cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup`
`! reptcursong`, `! rcs` | Toggles repeat of current song. | `!m rcs`
`! rpeatplaylst`, `! rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl`
`! save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1`
`! load` | Loads a playlist under a certain name. | `!m load classical-1`
`! playlists`, `! pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1`
`! 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
Command and aliases | Description | Usage
@ -232,13 +240,22 @@ Command and aliases | Description | Usage
`~hitbox`, `~hb` | Notifies this channel when a certain user starts streaming. | ~hitbox SomeStreamer
`~twitch`, `~tw` | Notifies this channel when a certain user starts streaming. | ~twitch SomeStreamer
`~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | ~beam SomeStreamer
`~checkhitbox`, `~chhb` | Checks if a certain user is streaming on the hitbox platform. | ~chhb SomeStreamer
`~checktwitch`, `~chtw` | Checks if a certain user is streaming on the twitch platform. | ~chtw SomeStreamer
`~checkbeam`, `~chbm` | Checks if a certain user is streaming on the beam platform. | ~chbm SomeStreamer
`~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | ~rms SomeGuy
`~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls
`~convert` | Convert quantities from>to. Like `~convert m>km 1000`
`~convertlist` | List of the convertable dimensions and currencies.
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes.
`~calculate`, `~calc` | Evaluate a mathematical expression. | ~calc 1+1
`~wowjoke` | Get one of Kwoth's penultimate WoW jokes.
`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`
`~osu b` | Shows information about an osu beatmap. | ~osu b https://osu.ppy.sh/s/127712
`~osu top5` | Displays a user's top 5 plays. | ~osu top5 Name
`~pokemon`, `~poke` | Searches for a pokemon.
`~pokemonability`, `~pokab` | Searches for a pokemon ability.
`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/
`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | ~we Moscow RF
`~yt` | Searches youtubes and shows the first result
`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result.
@ -249,7 +266,6 @@ Command and aliases | Description | Usage
`~ir` | Pulls a random image using a search parameter. | ~ir cute kitten
`~lmgtfy` | Google something for an idiot.
`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera
`~osu` | Shows osu stats for a player. | ~osu Name
`~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple
`~#` | Searches Tagdef.com for a hashtag. | ~# ff
`~quote` | Shows a random quote.
@ -264,6 +280,7 @@ Command and aliases | Description | Usage
`~wiki` | Gives you back a wikipedia link
`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00`
`~videocall` | Creates a private <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
Command and aliases | Description | Usage