Merge remote-tracking branch 'refs/remotes/Kwoth/master'

# Conflicts:
#	NadekoBot/Modules/Games.cs
This commit is contained in:
appelemac 2016-03-28 15:27:50 +02:00
commit 4c9b6185aa
11 changed files with 878 additions and 334 deletions

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Classes
var flows = "";
for (var i = 0; i < amount; i++)
{
flows += "🌸";
flows += NadekoBot.Config.CurrencySign;
}
await u.SendMessage("👑Congratulations!👑\nYou received: " + flows);
}

View File

@ -1,11 +1,12 @@
using System;
using Discord;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using Discord;
using Newtonsoft.Json;
namespace NadekoBot.Classes.JSONModels {
public class Configuration {
namespace NadekoBot.Classes.JSONModels
{
public class Configuration
{
public bool DontJoinServers { get; set; } = false;
public bool ForwardMessages { get; set; } = true;
public bool IsRotatingStatus { get; set; } = false;
@ -50,7 +51,7 @@ namespace NadekoBot.Classes.JSONModels {
"NO - It may cause disease contraction"
};
public string[] DisguiseResponses { get; set; } = {
public string[] DisguiseResponses { get; set; } = {
"https://cdn.discordapp.com/attachments/140007341880901632/156721710458994690/Cc5mixjUYAADgBs.jpg",
"https://cdn.discordapp.com/attachments/140007341880901632/156721715831898113/hqdefault.jpg",
"https://cdn.discordapp.com/attachments/140007341880901632/156721724430352385/okawari_01_haruka_weird_mask.jpg",
@ -75,9 +76,13 @@ namespace NadekoBot.Classes.JSONModels {
"http://gallery1.anivide.com/_full/65030_1382582341.gif",
"https://49.media.tumblr.com/8e8a099c4eba22abd3ec0f70fd087cce/tumblr_nxovj9oY861ur1mffo1_500.gif ",
};
public string CurrencySign { get; set; } = "🌸";
public string CurrencyName { get; set; } = "NadekoFlower";
}
public class CommandPrefixesModel {
public class CommandPrefixesModel
{
public string Administration { get; set; } = ".";
public string Searches { get; set; } = "~";
public string NSFW { get; set; } = "~";
@ -93,10 +98,13 @@ namespace NadekoBot.Classes.JSONModels {
public string Pokemon { get; set; } = "poke";
}
public static class ConfigHandler {
public static class ConfigHandler
{
private static readonly object configLock = new object();
public static void SaveConfig() {
lock (configLock) {
public static void SaveConfig()
{
lock (configLock)
{
File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented));
}
}
@ -112,7 +120,8 @@ namespace NadekoBot.Classes.JSONModels {
public static bool IsUserBlacklisted(ulong id) => NadekoBot.Config.UserBlacklist.Contains(id);
}
public class Quote {
public class Quote
{
public string Author { get; set; }
public string Text { get; set; }

View File

@ -1,19 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using System.Drawing;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Extensions;
using NadekoBot.Modules;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Commands {
internal class LoLCommands : DiscordCommand {
namespace NadekoBot.Commands
{
internal class LoLCommands : DiscordCommand
{
private class CachedChampion {
private class CachedChampion
{
public System.IO.Stream ImageStream { get; set; }
public DateTime AddedAt { get; set; }
public string Name { get; set; }
@ -24,16 +27,20 @@ namespace NadekoBot.Commands {
private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer();
public LoLCommands(DiscordModule module) : base(module) {
public LoLCommands(DiscordModule module) : base(module)
{
clearTimer.Interval = new TimeSpan(0, 10, 0).TotalMilliseconds;
clearTimer.Start();
clearTimer.Elapsed += (s, e) => {
try {
clearTimer.Elapsed += (s, e) =>
{
try
{
lock (cacheLock)
CachedChampionImages = CachedChampionImages
.Where(kvp => DateTime.Now - kvp.Value.AddedAt > new TimeSpan(1, 0, 0))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
} catch { }
}
catch { }
};
}
@ -44,11 +51,13 @@ namespace NadekoBot.Commands {
"If you consider playing teemo, do it. If you consider teemo, you deserve him.",
"Doesn't matter what you ban really. Enemy will ban your main and you will lose." };
public Func<CommandEventArgs, Task> DoFunc() {
public Func<CommandEventArgs, Task> DoFunc()
{
throw new NotImplementedException();
}
private class MatchupModel {
private class MatchupModel
{
public int Games { get; set; }
public float WinRate { get; set; }
[Newtonsoft.Json.JsonProperty("key")]
@ -56,48 +65,60 @@ namespace NadekoBot.Commands {
public float StatScore { get; set; }
}
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "lolchamp")
.Description("Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role.\n**Usage**:~lolchamp Riven or ~lolchamp Annie sup")
.Parameter("champ", ParameterType.Required)
.Parameter("position", ParameterType.Unparsed)
.Do(async e => {
try {
.Do(async e =>
{
try
{
//get role
var role = ResolvePos(e.GetArg("position"));
var resolvedRole = role;
var name = e.GetArg("champ").Replace(" ", "").ToLower();
CachedChampion champ = null;
lock (cacheLock) {
lock (cacheLock)
{
CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ);
}
if (champ != null) {
if (champ != null)
{
champ.ImageStream.Position = 0;
await e.Channel.SendFile("champ.png", champ.ImageStream);
return;
}
var allData = JArray.Parse(await Classes.SearchHelper.GetResponseStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Creds.LOLAPIKey}"));
JToken data = null;
if (role != null) {
for (var i = 0; i < allData.Count; i++) {
if (allData[i]["role"].ToString().Equals(role)) {
if (role != null)
{
for (var i = 0; i < allData.Count; i++)
{
if (allData[i]["role"].ToString().Equals(role))
{
data = allData[i];
break;
}
}
if (data == null) {
if (data == null)
{
await e.Channel.SendMessage("💢 Data for that role does not exist.");
return;
}
} else {
}
else {
data = allData[0];
role = allData[0]["role"].ToString();
resolvedRole = ResolvePos(role);
}
lock (cacheLock) {
lock (cacheLock)
{
CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ);
}
if (champ != null) {
if (champ != null)
{
Console.WriteLine("Sending lol image from cache.");
champ.ImageStream.Position = 0;
await e.Channel.SendFile("champ.png", champ.ImageStream);
@ -106,7 +127,8 @@ namespace NadekoBot.Commands {
//name = data["title"].ToString();
// get all possible roles, and "select" the shown one
var roles = new string[allData.Count];
for (var i = 0; i < allData.Count; i++) {
for (var i = 0; i < allData.Count; i++)
{
roles[i] = allData[i]["role"].ToString();
if (roles[i] == role)
roles[i] = ">" + roles[i] + "<";
@ -114,14 +136,16 @@ namespace NadekoBot.Commands {
var general = JArray.Parse(await SearchHelper.GetResponseStringAsync($"http://api.champion.gg/stats/" +
$"champs/{name}?api_key={NadekoBot.Creds.LOLAPIKey}"))
.FirstOrDefault(jt => jt["role"].ToString() == role)?["general"];
if (general == null) {
if (general == null)
{
Console.WriteLine("General is null.");
return;
}
//get build data for this role
var buildData = data["items"]["mostGames"]["items"];
var items = new string[6];
for (var i = 0; i < 6; i++) {
for (var i = 0; i < 6; i++)
{
items[i] = buildData[i]["id"].ToString();
}
@ -146,7 +170,8 @@ namespace NadekoBot.Commands {
var orderArr = (data["skills"]["mostGames"]["order"] as JArray);
var img = Image.FromFile("data/lol/bg.png");
using (var g = Graphics.FromImage(img)) {
using (var g = Graphics.FromImage(img))
{
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
//g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
const int margin = 5;
@ -166,12 +191,14 @@ namespace NadekoBot.Commands {
//draw skill order
float orderFormula = 120 / orderArr.Count;
const float orderVerticalSpacing = 10;
for (var i = 0; i < orderArr.Count; i++) {
for (var i = 0; i < orderArr.Count; i++)
{
var orderX = margin + margin + imageSize + orderFormula * i + i;
float orderY = margin + 35;
var spellName = orderArr[i].ToString().ToLowerInvariant();
switch (spellName) {
switch (spellName)
{
case "w":
orderY += orderVerticalSpacing;
break;
@ -206,7 +233,8 @@ Assists: {general["assists"]} Ban: {general["banRate"]}%
g.DrawString($"Best against", smallFont, Brushes.WhiteSmoke, margin, img.Height - imageSize + margin);
var smallImgSize = 50;
for (var i = 0; i < counters.Length; i++) {
for (var i = 0; i < counters.Length; i++)
{
g.DrawImage(GetImage(counters[i]),
new Rectangle(i * (smallImgSize + margin) + margin, img.Height - smallImgSize - margin,
smallImgSize,
@ -215,7 +243,8 @@ Assists: {general["assists"]} Ban: {general["banRate"]}%
//draw countered by
g.DrawString($"Worst against", smallFont, Brushes.WhiteSmoke, img.Width - 3 * (smallImgSize + margin), img.Height - imageSize + margin);
for (var i = 0; i < countered.Length; i++) {
for (var i = 0; i < countered.Length; i++)
{
var j = countered.Length - i;
g.DrawImage(GetImage(countered[i]),
new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), img.Height - smallImgSize - margin,
@ -225,7 +254,8 @@ Assists: {general["assists"]} Ban: {general["banRate"]}%
//draw item build
g.DrawString("Popular build", normalFont, Brushes.WhiteSmoke, img.Width - (3 * (smallImgSize + margin) + margin), 77);
for (var i = 0; i < 6; i++) {
for (var i = 0; i < 6; i++)
{
var inverseI = 5 - i;
var j = inverseI % 3 + 1;
var k = inverseI / 3;
@ -238,18 +268,23 @@ Assists: {general["assists"]} Ban: {general["banRate"]}%
var cachedChamp = new CachedChampion { AddedAt = DateTime.Now, ImageStream = img.ToStream(System.Drawing.Imaging.ImageFormat.Png), Name = name.ToLower() + "_" + resolvedRole };
CachedChampionImages.Add(cachedChamp.Name, cachedChamp);
await e.Channel.SendFile(data["title"] + "_stats.png", cachedChamp.ImageStream);
} catch {
}
catch (Exception ex)
{
Console.WriteLine(ex);
await e.Channel.SendMessage("💢 Failed retreiving data for that champion.");
}
});
cgb.CreateCommand(Module.Prefix + "lolban")
.Description("Shows top 6 banned champions ordered by ban rate. Ban these champions and you will be Plat 5 in no time.")
.Do(async e => {
.Do(async e =>
{
var showCount = 6;
//http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2
try {
try
{
var data = JObject.Parse(
await Classes
.SearchHelper
@ -260,7 +295,8 @@ Assists: {general["assists"]} Ban: {general["banRate"]}%
var sb = new StringBuilder();
sb.AppendLine($"**Showing {showCount} top banned champions.**");
sb.AppendLine($"`{trashTalk[new Random().Next(0, trashTalk.Length)]}`");
for (var i = 0; i < data.Count; i++) {
for (var i = 0; i < data.Count; i++)
{
if (i % 2 == 0 && i != 0)
sb.AppendLine();
sb.Append($"`{i + 1}.` **{data[i]["name"]}** ");
@ -268,34 +304,44 @@ Assists: {general["assists"]} Ban: {general["banRate"]}%
}
await e.Channel.SendMessage(sb.ToString());
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage($":anger: Fail: Champion.gg didsabled ban data until next patch. Sorry for the inconvenience.");
}
});
}
private enum GetImageType {
private enum GetImageType
{
Champion,
Item
}
private static Image GetImage(string id, GetImageType imageType = GetImageType.Champion) {
try {
switch (imageType) {
private static Image GetImage(string id, GetImageType imageType = GetImageType.Champion)
{
try
{
switch (imageType)
{
case GetImageType.Champion:
return Image.FromFile($"data/lol/champions/{id}.png");
case GetImageType.Item:
default:
return Image.FromFile($"data/lol/items/{id}.png");
}
} catch (Exception) {
}
catch (Exception)
{
return Image.FromFile("data/lol/_ERROR.png");
}
}
private static string ResolvePos(string pos) {
private static string ResolvePos(string pos)
{
if (string.IsNullOrWhiteSpace(pos))
return null;
switch (pos.ToLowerInvariant()) {
switch (pos.ToLowerInvariant())
{
case "m":
case "mid":
case "midorfeed":

View File

@ -1,23 +1,24 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Timers;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord;
using NadekoBot.Classes;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Commands {
internal class LogCommand : DiscordCommand {
namespace NadekoBot.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>();
public LogCommand(DiscordModule module) : base(module) {
public LogCommand(DiscordModule module) : base(module)
{
NadekoBot.Client.MessageReceived += MsgRecivd;
NadekoBot.Client.MessageDeleted += MsgDltd;
NadekoBot.Client.MessageUpdated += MsgUpdtd;
@ -25,11 +26,13 @@ namespace NadekoBot.Commands {
NadekoBot.Client.UserBanned += UsrBanned;
NadekoBot.Client.MessageReceived += async (s, e) => {
NadekoBot.Client.MessageReceived += async (s, e) =>
{
if (e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return;
if (!SpecificConfigurations.Default.Of(e.Server.Id).SendPrivateMessageOnMention) return;
try {
try
{
var usr = e.Message.MentionedUsers.FirstOrDefault(u => u != e.User);
if (usr?.Status != UserStatus.Offline)
return;
@ -38,22 +41,28 @@ namespace NadekoBot.Commands {
$"User `{e.User.Name}` mentioned you on " +
$"`{e.Server.Name}` server while you were offline.\n" +
$"`Message:` {e.Message.Text}");
} catch { }
}
catch { }
};
}
private async void UsrBanned(object sender, UserEventArgs e) {
try {
private async void UsrBanned(object sender, UserEventArgs e)
{
try
{
Channel ch;
if (!logs.TryGetValue(e.Server, out ch))
return;
await ch.SendMessage($"`User banned:` **{e.User.Name}** ({e.User.Id})");
} catch { }
}
catch { }
}
public Func<CommandEventArgs, Task> DoFunc() => async e => {
public Func<CommandEventArgs, Task> DoFunc() => async e =>
{
Channel ch;
if (!logs.TryRemove(e.Server, out 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**");
return;
@ -62,57 +71,75 @@ namespace NadekoBot.Commands {
await e.Channel.SendMessage($"**NO LONGER LOGGING IN {ch.Mention} CHANNEL**");
};
private async void MsgRecivd(object sender, MessageEventArgs e) {
try {
private async void MsgRecivd(object sender, MessageEventArgs e)
{
try
{
if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return;
Channel ch;
if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch)
return;
await ch.SendMessage($"`Type:` **Message received** `Time:` **{DateTime.Now}** `Channel:` **{e.Channel.Name}**\n`{e.User}:` {e.Message.Text}");
} catch { }
}
catch { }
}
private async void MsgDltd(object sender, MessageEventArgs e) {
try {
private async void MsgDltd(object sender, MessageEventArgs e)
{
try
{
if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return;
Channel ch;
if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch)
return;
await ch.SendMessage($"`Type:` **Message deleted** `Time:` **{DateTime.Now}** `Channel:` **{e.Channel.Name}**\n`{e.User}:` {e.Message.Text}");
} catch { }
}
catch { }
}
private async void MsgUpdtd(object sender, MessageUpdatedEventArgs e) {
try {
private async void MsgUpdtd(object sender, MessageUpdatedEventArgs e)
{
try
{
if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return;
Channel ch;
if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch)
return;
await ch.SendMessage($"`Type:` **Message updated** `Time:` **{DateTime.Now}** `Channel:` **{e.Channel.Name}**\n**BEFORE**: `{e.User}:` {e.Before.Text}\n---------------\n**AFTER**: `{e.User}:` {e.After.Text}");
} catch { }
}
catch { }
}
private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) {
try {
private async void UsrUpdtd(object sender, UserUpdatedEventArgs e)
{
try
{
Channel ch;
if (loggingPresences.TryGetValue(e.Server, out ch))
if (e.Before.Status != e.After.Status) {
if (e.Before.Status != e.After.Status)
{
await ch.SendMessage($"**{e.Before.Name}** is now **{e.After.Status}**.");
}
} catch { }
}
catch { }
try {
if (e.Before.VoiceChannel != null && voiceChannelLog.ContainsKey(e.Before.VoiceChannel)) {
try
{
if (e.Before.VoiceChannel != null && voiceChannelLog.ContainsKey(e.Before.VoiceChannel))
{
if (e.After.VoiceChannel != e.Before.VoiceChannel)
await voiceChannelLog[e.Before.VoiceChannel].SendMessage($"🎼`{e.Before.Name} has left the` {e.Before.VoiceChannel.Mention} `voice channel.`");
}
if (e.After.VoiceChannel != null && voiceChannelLog.ContainsKey(e.After.VoiceChannel)) {
if (e.After.VoiceChannel != null && voiceChannelLog.ContainsKey(e.After.VoiceChannel))
{
if (e.After.VoiceChannel != e.Before.VoiceChannel)
await voiceChannelLog[e.After.VoiceChannel].SendMessage($"🎼`{e.After.Name} has joined the`{e.After.VoiceChannel.Mention} `voice channel.`");
}
} catch { }
}
catch { }
try {
try
{
Channel ch;
if (!logs.TryGetValue(e.Server, out ch))
return;
@ -126,15 +153,18 @@ namespace NadekoBot.Commands {
else
return;
await ch.SendMessage(str);
} catch { }
}
catch { }
}
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "spmom")
.Description("Toggles whether mentions of other offline users on your server will send a pm to them.")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e => {
.Do(async e =>
{
var specificConfig = SpecificConfigurations.Default.Of(e.Server.Id);
specificConfig.SendPrivateMessageOnMention =
!specificConfig.SendPrivateMessageOnMention;
@ -156,9 +186,11 @@ namespace NadekoBot.Commands {
.Description("Starts logging to this channel when someone from the server goes online/offline/idle. **Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e => {
.Do(async e =>
{
Channel ch;
if (!loggingPresences.TryRemove(e.Server, out ch)) {
if (!loggingPresences.TryRemove(e.Server, out ch))
{
loggingPresences.TryAdd(e.Server, e.Channel);
await e.Channel.SendMessage($"**User presence notifications enabled.**");
return;
@ -172,25 +204,31 @@ namespace NadekoBot.Commands {
.Parameter("all", ParameterType.Optional)
.AddCheck(SimpleCheckers.OwnerOnly())
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e => {
.Do(async e =>
{
if (e.GetArg("all")?.ToLower() == "all") {
foreach (var voiceChannel in e.Server.VoiceChannels) {
if (e.GetArg("all")?.ToLower() == "all")
{
foreach (var voiceChannel in e.Server.VoiceChannels)
{
voiceChannelLog.TryAdd(voiceChannel, e.Channel);
}
await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!");
return;
}
if (e.User.VoiceChannel == null) {
if (e.User.VoiceChannel == null)
{
await e.Channel.SendMessage("💢 You are not in a voice channel right now. If you are, please rejoin it.");
return;
}
Channel throwaway;
if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out throwaway)) {
if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out throwaway))
{
voiceChannelLog.TryAdd(e.User.VoiceChannel, e.Channel);
await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`");
} else
}
else
await e.Channel.SendMessage($"`Stopped logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`");
});
}

View File

@ -1,21 +1,23 @@
using Discord.Modules;
using Discord.Commands;
using Discord;
using System;
using System.Collections.Generic;
using System.Linq;
using NadekoBot.Extensions;
using System.Threading.Tasks;
using NadekoBot.Commands;
using System.IO;
using Newtonsoft.Json.Linq;
using Discord.Commands;
using Discord.Modules;
using NadekoBot.Classes;
using NadekoBot.Classes.Permissions;
using NadekoBot.Classes._DataModels;
using NadekoBot.Classes.Permissions;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules {
internal class Administration : DiscordModule {
public Administration() {
namespace NadekoBot.Modules
{
internal class Administration : DiscordModule
{
public Administration()
{
commands.Add(new ServerGreetCommand(this));
commands.Add(new LogCommand(this));
commands.Add(new MessageRepeater(this));
@ -28,8 +30,10 @@ namespace NadekoBot.Modules {
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Administration;
public override void Install(ModuleManager manager) {
manager.CreateCommands("", cgb => {
public override void Install(ModuleManager manager)
{
manager.CreateCommands("", cgb =>
{
cgb.AddCheck(PermissionChecker.Instance);
@ -42,32 +46,39 @@ namespace NadekoBot.Modules {
.Parameter("user_name", ParameterType.Required)
.Parameter("role_name", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.CanManageRoles)
.Do(async e => {
.Do(async e =>
{
var userName = e.GetArg("user_name");
var roleName = e.GetArg("role_name");
if (string.IsNullOrWhiteSpace(roleName)) return;
if (!e.User.ServerPermissions.ManageRoles) {
if (!e.User.ServerPermissions.ManageRoles)
{
await e.Channel.SendMessage("You have insufficient permissions.");
}
var usr = e.Server.FindUsers(userName).FirstOrDefault();
if (usr == null) {
if (usr == null)
{
await e.Channel.SendMessage("You failed to supply a valid username");
return;
}
var role = e.Server.FindRoles(roleName).FirstOrDefault();
if (role == null) {
if (role == null)
{
await e.Channel.SendMessage("You failed to supply a valid role");
return;
}
try {
try
{
await usr.AddRoles(role);
await e.Channel.SendMessage($"Successfully added role **{role.Name}** to user **{usr.Name}**");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage("Failed to add roles. Bot has insufficient permissions.\n");
Console.WriteLine(ex.ToString());
}
@ -78,28 +89,34 @@ namespace NadekoBot.Modules {
.Parameter("user_name", ParameterType.Required)
.Parameter("role_name", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.CanManageRoles)
.Do(async e => {
.Do(async e =>
{
var userName = e.GetArg("user_name");
var roleName = e.GetArg("role_name");
if (string.IsNullOrWhiteSpace(roleName)) return;
var usr = e.Server.FindUsers(userName).FirstOrDefault();
if (usr == null) {
if (usr == null)
{
await e.Channel.SendMessage("You failed to supply a valid username");
return;
}
var role = e.Server.FindRoles(roleName).FirstOrDefault();
if (role == null) {
if (role == null)
{
await e.Channel.SendMessage("You failed to supply a valid role");
return;
}
try {
try
{
await usr.RemoveRoles(role);
await e.Channel.SendMessage($"Successfully removed role **{role.Name}** from user **{usr.Name}**");
} catch {
}
catch
{
await e.Channel.SendMessage("Failed to remove roles. Most likely reason: Insufficient permissions.");
}
});
@ -108,13 +125,17 @@ namespace NadekoBot.Modules {
.Description("Creates a role with a given name.**Usage**: .r Awesome Role")
.Parameter("role_name", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.CanManageRoles)
.Do(async e => {
.Do(async e =>
{
if (string.IsNullOrWhiteSpace(e.GetArg("role_name")))
return;
try {
try
{
var r = await e.Server.CreateRole(e.GetArg("role_name"));
await e.Channel.SendMessage($"Successfully created role **{r.Name}**.");
} catch (Exception) {
}
catch (Exception)
{
await e.Channel.SendMessage(":warning: Unspecified error.");
}
});
@ -125,26 +146,31 @@ namespace NadekoBot.Modules {
.Parameter("g", ParameterType.Optional)
.Parameter("b", ParameterType.Optional)
.Description("Set a role's color to the hex or 0-255 rgb color value provided.\n**Usage**: .color Admin 255 200 100 or .color Admin ffba55")
.Do(async e => {
if (!e.User.ServerPermissions.ManageRoles) {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageRoles)
{
await e.Channel.SendMessage("You don't have permission to use this!");
return;
}
var args = e.Args.Where(s => s != string.Empty);
if (args.Count() != 2 && args.Count() != 4) {
if (args.Count() != 2 && args.Count() != 4)
{
await e.Channel.SendMessage("The parameters are invalid.");
return;
}
var role = e.Server.FindRoles(e.Args[0]).FirstOrDefault();
if (role == null) {
if (role == null)
{
await e.Channel.SendMessage("That role does not exist.");
return;
}
try {
try
{
var rgb = args.Count() == 4;
var red = Convert.ToByte(rgb ? int.Parse(e.Args[1]) : Convert.ToInt32(e.Args[1].Substring(0, 2), 16));
@ -153,7 +179,9 @@ namespace NadekoBot.Modules {
await role.Edit(color: new Color(red, green, blue));
await e.Channel.SendMessage($"Role {role.Name}'s color has been changed.");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage("Error occured, most likely invalid parameters or insufficient permissions.");
}
});
@ -161,8 +189,10 @@ namespace NadekoBot.Modules {
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"))) {
.Do(async e =>
{
if (!string.IsNullOrWhiteSpace(e.GetArg("user")))
{
var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr == null) return;
@ -176,24 +206,31 @@ namespace NadekoBot.Modules {
.Parameter("user", ParameterType.Required)
.Parameter("msg", ParameterType.Optional)
.Description("Bans a user by id or name with an optional message.\n**Usage**: .b \"@some Guy\" Your behaviour is toxic.")
.Do(async e => {
.Do(async e =>
{
var msg = e.GetArg("msg");
var user = e.GetArg("user");
if (e.User.ServerPermissions.BanMembers) {
if (e.User.ServerPermissions.BanMembers)
{
var usr = e.Server.FindUsers(user).FirstOrDefault();
if (usr == null) {
if (usr == null)
{
await e.Channel.SendMessage("User not found.");
return;
}
if (!string.IsNullOrWhiteSpace(msg)) {
if (!string.IsNullOrWhiteSpace(msg))
{
await usr.SendMessage($"**You have been BANNED from `{e.Server.Name}` server.**\n" +
$"Reason: {msg}");
await Task.Delay(2000); // temp solution; give time for a message to be send, fu volt
}
try {
try
{
await e.Server.Ban(usr);
await e.Channel.SendMessage("Banned user " + usr.Name + " Id: " + usr.Id);
} catch {
}
catch
{
await e.Channel.SendMessage("Error. Most likely I don't have sufficient permissions.");
}
}
@ -203,24 +240,31 @@ namespace NadekoBot.Modules {
.Parameter("user")
.Parameter("msg", ParameterType.Unparsed)
.Description("Kicks a mentioned user.")
.Do(async e => {
.Do(async e =>
{
var msg = e.GetArg("msg");
var user = e.GetArg("user");
if (e.User.ServerPermissions.KickMembers) {
if (e.User.ServerPermissions.KickMembers)
{
var usr = e.Server.FindUsers(user).FirstOrDefault();
if (usr == null) {
if (usr == null)
{
await e.Channel.SendMessage("User not found.");
return;
}
if (!string.IsNullOrWhiteSpace(msg)) {
if (!string.IsNullOrWhiteSpace(msg))
{
await usr.SendMessage($"**You have been KICKED from `{e.Server.Name}` server.**\n" +
$"Reason: {msg}");
await Task.Delay(2000); // temp solution; give time for a message to be send, fu volt
}
try {
try
{
await usr.Kick();
await e.Channel.SendMessage("Kicked user " + usr.Name + " Id: " + usr.Id);
} catch {
}
catch
{
await e.Channel.SendMessage("Error. Most likely I don't have sufficient permissions.");
}
}
@ -228,19 +272,25 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "mute")
.Description("Mutes mentioned user or users.")
.Parameter("throwaway", ParameterType.Unparsed)
.Do(async e => {
if (!e.User.ServerPermissions.MuteMembers) {
.Do(async e =>
{
if (!e.User.ServerPermissions.MuteMembers)
{
await e.Channel.SendMessage("You do not have permission to do that.");
return;
}
if (!e.Message.MentionedUsers.Any())
return;
try {
foreach (var u in e.Message.MentionedUsers) {
try
{
foreach (var u in e.Message.MentionedUsers)
{
await u.Edit(isMuted: true);
}
await e.Channel.SendMessage("Mute successful");
} catch {
}
catch
{
await e.Channel.SendMessage("I do not have permission to do that most likely.");
}
});
@ -248,19 +298,25 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "unmute")
.Description("Unmutes mentioned user or users.")
.Parameter("throwaway", ParameterType.Unparsed)
.Do(async e => {
if (!e.User.ServerPermissions.MuteMembers) {
.Do(async e =>
{
if (!e.User.ServerPermissions.MuteMembers)
{
await e.Channel.SendMessage("You do not have permission to do that.");
return;
}
if (!e.Message.MentionedUsers.Any())
return;
try {
foreach (var u in e.Message.MentionedUsers) {
try
{
foreach (var u in e.Message.MentionedUsers)
{
await u.Edit(isMuted: false);
}
await e.Channel.SendMessage("Unmute successful");
} catch {
}
catch
{
await e.Channel.SendMessage("I do not have permission to do that most likely.");
}
});
@ -269,19 +325,25 @@ namespace NadekoBot.Modules {
.Alias(Prefix + "deaf")
.Description("Deafens mentioned user or users")
.Parameter("throwaway", ParameterType.Unparsed)
.Do(async e => {
if (!e.User.ServerPermissions.DeafenMembers) {
.Do(async e =>
{
if (!e.User.ServerPermissions.DeafenMembers)
{
await e.Channel.SendMessage("You do not have permission to do that.");
return;
}
if (!e.Message.MentionedUsers.Any())
return;
try {
foreach (var u in e.Message.MentionedUsers) {
try
{
foreach (var u in e.Message.MentionedUsers)
{
await u.Edit(isDeafened: true);
}
await e.Channel.SendMessage("Deafen successful");
} catch {
}
catch
{
await e.Channel.SendMessage("I do not have permission to do that most likely.");
}
});
@ -290,19 +352,25 @@ namespace NadekoBot.Modules {
.Alias(Prefix + "undeaf")
.Description("Undeafens mentioned user or users")
.Parameter("throwaway", ParameterType.Unparsed)
.Do(async e => {
if (!e.User.ServerPermissions.DeafenMembers) {
.Do(async e =>
{
if (!e.User.ServerPermissions.DeafenMembers)
{
await e.Channel.SendMessage("You do not have permission to do that.");
return;
}
if (!e.Message.MentionedUsers.Any())
return;
try {
foreach (var u in e.Message.MentionedUsers) {
try
{
foreach (var u in e.Message.MentionedUsers)
{
await u.Edit(isDeafened: false);
}
await e.Channel.SendMessage("Undeafen successful");
} catch {
}
catch
{
await e.Channel.SendMessage("I do not have permission to do that most likely.");
}
});
@ -310,13 +378,18 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "rvch")
.Description("Removes a voice channel with a given name.")
.Parameter("channel_name", ParameterType.Required)
.Do(async e => {
try {
if (e.User.ServerPermissions.ManageChannels) {
.Do(async e =>
{
try
{
if (e.User.ServerPermissions.ManageChannels)
{
await e.Server.FindChannels(e.GetArg("channel_name"), ChannelType.Voice).FirstOrDefault()?.Delete();
await e.Channel.SendMessage($"Removed channel **{e.GetArg("channel_name")}**.");
}
} catch {
}
catch
{
await e.Channel.SendMessage("Insufficient permissions.");
}
});
@ -324,13 +397,18 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "vch").Alias(Prefix + "cvch")
.Description("Creates a new voice channel with a given name.")
.Parameter("channel_name", ParameterType.Required)
.Do(async e => {
try {
if (e.User.ServerPermissions.ManageChannels) {
.Do(async e =>
{
try
{
if (e.User.ServerPermissions.ManageChannels)
{
await e.Server.CreateChannel(e.GetArg("channel_name"), ChannelType.Voice);
await e.Channel.SendMessage($"Created voice channel **{e.GetArg("channel_name")}**.");
}
} catch {
}
catch
{
await e.Channel.SendMessage("Insufficient permissions.");
}
});
@ -338,15 +416,20 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "rch").Alias(Prefix + "rtch")
.Description("Removes a text channel with a given name.")
.Parameter("channel_name", ParameterType.Required)
.Do(async e => {
try {
if (e.User.ServerPermissions.ManageChannels) {
.Do(async e =>
{
try
{
if (e.User.ServerPermissions.ManageChannels)
{
var channel = e.Server.FindChannels(e.GetArg("channel_name"), ChannelType.Text).FirstOrDefault();
if (channel == null) return;
await channel.Delete();
await e.Channel.SendMessage($"Removed text channel **{e.GetArg("channel_name")}**.");
}
} catch {
}
catch
{
await e.Channel.SendMessage("Insufficient permissions.");
}
});
@ -354,31 +437,40 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "ch").Alias(Prefix + "tch")
.Description("Creates a new text channel with a given name.")
.Parameter("channel_name", ParameterType.Required)
.Do(async e => {
try {
if (e.User.ServerPermissions.ManageChannels) {
.Do(async e =>
{
try
{
if (e.User.ServerPermissions.ManageChannels)
{
await e.Server.CreateChannel(e.GetArg("channel_name"), ChannelType.Text);
await e.Channel.SendMessage($"Added text channel **{e.GetArg("channel_name")}**.");
}
} catch {
}
catch
{
await e.Channel.SendMessage("Insufficient permissions.");
}
});
cgb.CreateCommand(Prefix + "st").Alias(Prefix + "settopic")
.Alias(Prefix + "topic")
.Description("Sets a topic on the current channel.")
.Parameter("topic", ParameterType.Unparsed)
.Do(async e => {
try {
if (e.User.ServerPermissions.ManageChannels)
await e.Channel.Edit(topic: e.GetArg("topic"));
} catch { }
.Do(async e =>
{
var topic = e.GetArg("topic");
if (string.IsNullOrWhiteSpace(topic))
return;
await e.Channel.Edit(topic: topic);
await e.Channel.SendMessage(":ok: **New channel topic set.**");
});
cgb.CreateCommand(Prefix + "uid").Alias(Prefix + "userid")
.Description("Shows user ID.")
.Parameter("user", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
var usr = e.User;
if (!string.IsNullOrWhiteSpace(e.GetArg("user"))) usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr == null)
@ -396,33 +488,38 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "stats")
.Description("Shows some basic stats for Nadeko.")
.Do(async e => {
.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 => {
.Do(async e =>
{
await e.Channel.SendMessage((await NadekoStats.Instance.GetStats()).Matrix().TrimTo(1990));
});
cgb.CreateCommand(Prefix + "heap")
.Description("Shows allocated memory - **Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
var heap = await Task.Run(() => NadekoStats.Instance.Heap());
await e.Channel.SendMessage($"`Heap Size:` {heap}");
});
cgb.CreateCommand(Prefix + "prune")
.Parameter("num", ParameterType.Required)
.Description("Prunes a number of messages from the current channel.\n**Usage**: .prune 5")
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageMessages) return;
int val;
if (string.IsNullOrWhiteSpace(e.GetArg("num")) || !int.TryParse(e.GetArg("num"), out val) || val < 0)
return;
foreach (var msg in await e.Channel.DownloadMessages(val)) {
foreach (var msg in await e.Channel.DownloadMessages(val))
{
await msg.Delete();
await Task.Delay(100);
}
@ -431,8 +528,10 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "die")
.Alias(Prefix + "graceful")
.Description("Shuts the bot down and notifies users about the restart. **Owner Only!**")
.Do(async e => {
if (NadekoBot.IsOwner(e.User.Id)) {
.Do(async e =>
{
if (NadekoBot.IsOwner(e.User.Id))
{
await e.Channel.SendMessage("`Shutting down.`");
await Task.Delay(2000);
Environment.Exit(0);
@ -442,19 +541,25 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "clr")
.Description("Clears some of Nadeko's messages from the current channel. If given a user, will clear the user's messages from the current channel (**Owner Only!**) \n**Usage**: .clr @X")
.Parameter("user", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
var usrId = NadekoBot.Client.CurrentUser.Id;
if (!string.IsNullOrWhiteSpace(e.GetArg("user")) && e.User.ServerPermissions.ManageMessages) {
if (!string.IsNullOrWhiteSpace(e.GetArg("user")) && e.User.ServerPermissions.ManageMessages)
{
var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault();
if (usr != null)
usrId = usr.Id;
}
await Task.Run(async () => {
await Task.Run(async () =>
{
var msgs = (await e.Channel.DownloadMessages(100)).Where(m => m.User.Id == usrId);
foreach (var m in msgs) {
try {
foreach (var m in msgs)
{
try
{
await m.Delete();
} catch { }
}
catch { }
await Task.Delay(200);
}
@ -465,7 +570,8 @@ namespace NadekoBot.Modules {
.Alias(Prefix + "setname")
.Description("Give the bot a new name. **Owner Only!**")
.Parameter("new_name", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id) || e.GetArg("new_name") == null) return;
await client.CurrentUser.Edit(NadekoBot.Creds.Password, e.GetArg("new_name"));
@ -475,7 +581,8 @@ namespace NadekoBot.Modules {
.Alias(Prefix + "setavatar")
.Description("Sets a new avatar image for the NadekoBot. **Owner Only!**")
.Parameter("img", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("img")))
return;
// Gather user provided URL.
@ -492,7 +599,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "setgame")
.Description("Sets the bots game. **Owner Only!**")
.Parameter("set_game", ParameterType.Unparsed)
.Do(e => {
.Do(e =>
{
if (!NadekoBot.IsOwner(e.User.Id) || e.GetArg("set_game") == null) return;
client.SetGame(e.GetArg("set_game"));
@ -500,9 +608,11 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "checkmyperms")
.Description("Checks your userspecific permissions on this channel.")
.Do(async e => {
.Do(async e =>
{
var output = "```\n";
foreach (var p in e.User.ServerPermissions.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any())) {
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 += "```";
@ -516,20 +626,24 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "commsuser")
.Description("Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Owner Only!**")
.Parameter("name", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id)) return;
commsUser = commsServer?.FindUsers(e.GetArg("name")).FirstOrDefault();
if (commsUser != null) {
if (commsUser != null)
{
commsChannel = null;
await e.Channel.SendMessage("User for comms set.");
} else
}
else
await e.Channel.SendMessage("No server specified or user.");
});
cgb.CreateCommand(Prefix + "commsserver")
.Description("Sets a server for through-bot communication. **Owner Only!**")
.Parameter("server", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id)) return;
commsServer = client.FindServers(e.GetArg("server")).FirstOrDefault();
if (commsServer != null)
@ -541,20 +655,24 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "commschannel")
.Description("Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Owner Only!**")
.Parameter("ch", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id)) return;
commsChannel = commsServer?.FindChannels(e.GetArg("ch"), ChannelType.Text).FirstOrDefault();
if (commsChannel != null) {
if (commsChannel != null)
{
commsUser = null;
await e.Channel.SendMessage("Server for comms set.");
} else
}
else
await e.Channel.SendMessage("No server specified or channel is invalid.");
});
cgb.CreateCommand(Prefix + "send")
.Description("Send a message to someone on a different server through the bot. **Owner Only!**\n **Usage**: .send Message text multi word!")
.Parameter("msg", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id)) return;
if (commsUser != null)
await commsUser.SendMessage(e.GetArg("msg"));
@ -568,19 +686,23 @@ namespace NadekoBot.Modules {
.Alias(Prefix + "mentionrole")
.Description("Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission.")
.Parameter("roles", ParameterType.Unparsed)
.Do(async e => {
await Task.Run(async () => {
.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 = $"--{e.User.Mention} has invoked a mention on the following roles--";
foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str))) {
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.Mention));
}
while (send.Length > 2000) {
while (send.Length > 2000)
{
var curstr = send.Substring(0, 2000);
await
e.Channel.Send(curstr.Substring(0,
@ -594,10 +716,12 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "parsetosql")
.Description("Loads exported parsedata from /data/parsedata/ into sqlite database.")
.Do(async e => {
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id))
return;
await Task.Run(() => {
await Task.Run(() =>
{
SaveParseToDb<Announcement>("data/parsedata/Announcements.json");
SaveParseToDb<Classes._DataModels.Command>("data/parsedata/CommandsRan.json");
SaveParseToDb<Request>("data/parsedata/Requests.json");
@ -609,14 +733,17 @@ namespace NadekoBot.Modules {
cgb.CreateCommand(Prefix + "unstuck")
.Description("Clears the message queue. **Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(e => {
.Do(e =>
{
NadekoBot.Client.MessageQueue.Clear();
});
cgb.CreateCommand(Prefix + "donators")
.Description("List of lovely people who donated to keep this project alive.")
.Do(async e => {
await Task.Run(async () => {
.Do(async e =>
{
await Task.Run(async () =>
{
var rows = DbHandler.Instance.GetAllRows<Donator>();
var donatorsOrdered = rows.OrderByDescending(d => d.Amount);
string str = $"**Thanks to the people listed below for making this project happen!**\n";
@ -632,64 +759,65 @@ namespace NadekoBot.Modules {
.Parameter("donator")
.Parameter("amount")
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e => {
await Task.Run(() => {
.Do(async e =>
{
await Task.Run(() =>
{
if (!NadekoBot.IsOwner(e.User.Id))
return;
var donator = e.Server.FindUsers(e.GetArg("donator")).FirstOrDefault();
var amount = int.Parse(e.GetArg("amount"));
if (donator == null) return;
try {
DbHandler.Instance.InsertData(new Donator {
try
{
DbHandler.Instance.InsertData(new Donator
{
Amount = amount,
UserName = donator.Name,
UserId = (long)e.User.Id
});
e.Channel.SendMessage("Successfuly added a new donator. 👑");
} catch { }
}
catch { }
});
});
cgb.CreateCommand(Prefix + "topic")
.Description("Sets current channel's topic.")
.Parameter("topic", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.ManageChannels())
.Do(async e => {
var topic = e.GetArg("topic");
if (string.IsNullOrWhiteSpace(topic))
return;
await e.Channel.Edit(topic: topic);
await e.Channel.SendMessage(":ok: **New channel topic set.**");
});
cgb.CreateCommand(Prefix + "videocall")
.Description("Creates a private appear.in video call link for you and other mentioned people. The link is sent to mentioned people via a private message.")
.Parameter("arg", ParameterType.Unparsed)
.Do(async e => {
try {
.Do(async e =>
{
try
{
var allUsrs = e.Message.MentionedUsers.Union(new User[] { e.User });
var allUsrsArray = allUsrs as User[] ?? allUsrs.ToArray();
var str = allUsrsArray.Aggregate("http://appear.in/", (current, usr) => current + Uri.EscapeUriString(usr.Name[0].ToString()));
str += new Random().Next();
foreach (var usr in allUsrsArray) {
foreach (var usr in allUsrsArray)
{
await usr.SendMessage(str);
}
} catch (Exception ex) {
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
});
});
}
public void SaveParseToDb<T>(string where) where T : IDataModel {
try {
public void SaveParseToDb<T>(string where) where T : IDataModel
{
try
{
var data = File.ReadAllText(where);
var arr = JObject.Parse(data)["results"] as JArray;
if (arr == null)
return;
var objects = arr.Select(x => x.ToObject<T>());
DbHandler.Instance.InsertMany(objects);
} catch { }
}
catch { }
}
}
}

View File

@ -1,18 +1,17 @@
using Discord;
using Discord.Commands;
using Discord.Modules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using NadekoBot.Classes;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using NadekoBot.Properties;
using NadekoBot.Commands;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules
{

View File

@ -34,15 +34,14 @@ namespace NadekoBot.Modules.Gambling
.Parameter("role", ParameterType.Optional)
.Do(RaffleFunc());
cgb.CreateCommand(Prefix + "$$")
.Description("Check how many NadekoFlowers you have.")
.Description(string.Format("Check how much {0}s you have.", NadekoBot.Config.CurrencyName))
.Do(NadekoFlowerCheckFunc());
cgb.CreateCommand(Prefix + "give")
.Description("Give someone a certain amount of flowers")
.Description(string.Format("Give someone a certain amount of {0}s", NadekoBot.Config.CurrencyName))
.Parameter("amount", ParameterType.Required)
.Parameter("receiver", ParameterType.Unparsed)
.Do(async e =>
{
var amountStr = e.GetArg("amount")?.Trim();
long amount;
if (!long.TryParse(amountStr, out amount) || amount < 0)
@ -58,14 +57,14 @@ namespace NadekoBot.Modules.Gambling
if (userFlowers < amount)
{
await e.Channel.SendMessage($"{e.User.Mention} You don't have enough flowers. You have only {userFlowers}🌸.");
await e.Channel.SendMessage($"{e.User.Mention} You don't have enough {NadekoBot.Config.CurrencyName}s. You have only {userFlowers}{NadekoBot.Config.CurrencySign}.");
return;
}
await FlowersHandler.RemoveFlowersAsync(e.User, "Gift", (int)amount);
await FlowersHandler.AddFlowersAsync(mentionedUser, "Gift", (int)amount);
await e.Channel.SendMessage($"{e.User.Mention} successfully sent {amount}🌸 to {mentionedUser.Mention}!");
await e.Channel.SendMessage($"{e.User.Mention} successfully sent {amount} {NadekoBot.Config.CurrencyName}s to {mentionedUser.Mention}!");
});
});
@ -76,10 +75,10 @@ namespace NadekoBot.Modules.Gambling
return async e =>
{
var pts = GetUserFlowers(e.User.Id);
var str = $"`You have {pts} NadekoFlowers".SnPl((int)pts) + "`\n";
var str = $"`You have {pts} {NadekoBot.Config.CurrencyName}s".SnPl((int)pts) + "`\n";
for (var i = 0; i < pts; i++)
{
str += "🌸";
str += NadekoBot.Config.CurrencySign;
}
await e.Channel.SendMessage(str);
};

View File

@ -3,16 +3,18 @@ using Discord.Commands;
using Discord.Modules;
using NadekoBot.Classes;
using NadekoBot.Classes.Music;
using NadekoBot.Classes.Permissions;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Classes.Permissions;
using Timer = System.Timers.Timer;
namespace NadekoBot.Modules {
internal class Music : DiscordModule {
namespace NadekoBot.Modules
{
internal class Music : DiscordModule
{
public static ConcurrentDictionary<Server, MusicPlayer> MusicPlayers = new ConcurrentDictionary<Server, MusicPlayer>();
public static ConcurrentDictionary<ulong, float> DefaultMusicVolumes = new ConcurrentDictionary<ulong, float>();
@ -21,24 +23,46 @@ namespace NadekoBot.Modules {
private bool setgameEnabled = false;
public Music() {
public Music()
{
setgameTimer.Interval = 20000;
setgameTimer.Elapsed += (s, e) => {
try {
setgameTimer.Elapsed += (s, e) =>
{
try
{
var num = MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
NadekoBot.Client.SetGame($"{num} songs".SnPl(num) + $", {MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count())} queued");
} catch { }
}
catch { }
};
// ready for 1.0
//NadekoBot.Client.UserUpdated += (s, e) =>
//{
// try
// {
// if (e.Before.VoiceChannel != e.After.VoiceChannel &&
// e.Before.VoiceChannel.Members.Count() == 0)
// {
// MusicPlayer musicPlayer;
// if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return;
// musicPlayer.Destroy();
// }
// }
// catch { }
//};
}
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Music;
public override void Install(ModuleManager manager) {
public override void Install(ModuleManager manager)
{
var client = NadekoBot.Client;
manager.CreateCommands(Prefix, cgb => {
manager.CreateCommands(Prefix, cgb =>
{
cgb.AddCheck(Classes.Permissions.PermissionChecker.Instance);
@ -47,7 +71,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("n")
.Alias("next")
.Description("Goes to the next song in the queue.")
.Do(e => {
.Do(e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
musicPlayer.Next();
@ -56,8 +81,10 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("s")
.Alias("stop")
.Description("Stops the music and clears the playlist. Stays in the channel.")
.Do(async e => {
await Task.Run(() => {
.Do(async e =>
{
await Task.Run(() =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
musicPlayer.Stop();
@ -67,8 +94,10 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("d")
.Alias("destroy")
.Description("Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour)")
.Do(async e => {
await Task.Run(() => {
.Do(async e =>
{
await Task.Run(() =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return;
musicPlayer.Destroy();
@ -78,7 +107,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("p")
.Alias("pause")
.Description("Pauses or Unpauses the song.")
.Do(async e => {
.Do(async e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
musicPlayer.TogglePause();
@ -92,16 +122,19 @@ namespace NadekoBot.Modules {
.Alias("yq")
.Description("Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`")
.Parameter("query", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("query"));
});
cgb.CreateCommand("lq")
.Alias("ls").Alias("lp")
.Description("Lists up to 15 currently queued songs.")
.Do(async e => {
.Do(async e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) {
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
{
await e.Channel.SendMessage("🎵 No active music player.");
return;
}
@ -117,7 +150,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("np")
.Alias("playing")
.Description("Shows the song currently playing.")
.Do(async e => {
.Do(async e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
@ -130,13 +164,15 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("vol")
.Description("Sets the music volume 0-150%")
.Parameter("val", ParameterType.Required)
.Do(async e => {
.Do(async e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
var arg = e.GetArg("val");
int volume;
if (!int.TryParse(arg, out volume)) {
if (!int.TryParse(arg, out volume))
{
await e.Channel.SendMessage("Volume number invalid.");
return;
}
@ -148,10 +184,12 @@ namespace NadekoBot.Modules {
.Alias("defvol")
.Description("Sets the default music volume when music playback is started (0-100). Does not persist through restarts.\n**Usage**: !m dv 80")
.Parameter("val", ParameterType.Required)
.Do(async e => {
.Do(async e =>
{
var arg = e.GetArg("val");
float volume;
if (!float.TryParse(arg, out volume) || volume < 0 || volume > 100) {
if (!float.TryParse(arg, out volume) || volume < 0 || volume > 100)
{
await e.Channel.SendMessage("Volume number invalid.");
return;
}
@ -161,7 +199,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("min").Alias("mute")
.Description("Sets the music volume to 0%")
.Do(e => {
.Do(e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
@ -170,7 +209,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("max")
.Description("Sets the music volume to 100% (real max is actually 150%).")
.Do(e => {
.Do(e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
@ -179,7 +219,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("half")
.Description("Sets the music volume to 50%.")
.Do(e => {
.Do(e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
@ -188,11 +229,13 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("sh")
.Description("Shuffles the current playlist.")
.Do(async e => {
.Do(async e =>
{
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
return;
if (musicPlayer.Playlist.Count < 2) {
if (musicPlayer.Playlist.Count < 2)
{
await e.Channel.SendMessage("💢 Not enough songs in order to perform the shuffle.");
return;
}
@ -204,7 +247,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("setgame")
.Description("Sets the game of the bot to the number of songs playing. **Owner only**")
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
await e.Channel.SendMessage("❗This command is deprecated. " +
"Use:\n `.ropl`\n `.adpl %playing% songs, %queued% queued.` instead.\n " +
"It even persists through restarts.");
@ -213,8 +257,10 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("pl")
.Description("Queues up to 25 songs from a youtube playlist specified by a link, or keywords.")
.Parameter("playlist", ParameterType.Unparsed)
.Do(async e => {
if (e.User.VoiceChannel?.Server != e.Server) {
.Do(async e =>
{
if (e.User.VoiceChannel?.Server != e.Server)
{
await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.");
return;
}
@ -224,10 +270,13 @@ namespace NadekoBot.Modules {
var count = idArray.Count();
var msg =
await e.Channel.SendMessage($"🎵 `Attempting to queue {count} songs".SnPl(count) + "...`");
foreach (var id in idArray) {
try {
foreach (var id in idArray)
{
try
{
await QueueSong(e.Channel, e.User.VoiceChannel, id, true);
} catch { }
}
catch { }
}
await msg.Edit("🎵 `Playlist queue complete.`");
});
@ -236,24 +285,30 @@ namespace NadekoBot.Modules {
.Description("Queues up to 50 songs from a directory. **Owner Only!**")
.Parameter("directory", ParameterType.Unparsed)
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
var arg = e.GetArg("directory");
if (string.IsNullOrWhiteSpace(e.GetArg("directory")))
return;
try {
try
{
var fileEnum = System.IO.Directory.EnumerateFiles(e.GetArg("directory")).Take(50);
foreach (var file in fileEnum) {
foreach (var file in fileEnum)
{
await QueueSong(e.Channel, e.User.VoiceChannel, file, true, MusicType.Local);
}
await e.Channel.SendMessage("🎵 `Directory queue complete.`");
} catch { }
}
catch { }
});
cgb.CreateCommand("radio").Alias("ra")
.Description("Queues a direct radio stream from a link.")
.Parameter("radio_link", ParameterType.Required)
.Do(async e => {
if (e.User.VoiceChannel?.Server != e.Server) {
.Do(async e =>
{
if (e.User.VoiceChannel?.Server != e.Server)
{
await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.");
return;
}
@ -264,7 +319,8 @@ namespace NadekoBot.Modules {
.Description("Queues a local file by specifying a full path. **Owner Only!**")
.Parameter("path", ParameterType.Unparsed)
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
var arg = e.GetArg("path");
if (string.IsNullOrWhiteSpace(arg))
return;
@ -273,7 +329,8 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("mv")
.Description("Moves the bot to your voice channel. (works only if music is already playing)")
.Do(e => {
.Do(e =>
{
MusicPlayer musicPlayer;
var voiceChannel = e.User.VoiceChannel;
if (voiceChannel == null || voiceChannel.Server != e.Server || !MusicPlayers.TryGetValue(e.Server, out musicPlayer))
@ -284,19 +341,23 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("rm")
.Description("Remove a song by its # in the queue, or 'all' to remove whole queue.")
.Parameter("num", ParameterType.Required)
.Do(async e => {
.Do(async e =>
{
var arg = e.GetArg("num");
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) {
if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer))
{
return;
}
if (arg?.ToLower() == "all") {
if (arg?.ToLower() == "all")
{
musicPlayer.ClearQueue();
await e.Channel.SendMessage($"🎵`Queue cleared!`");
return;
}
int num;
if (!int.TryParse(arg, out num)) {
if (!int.TryParse(arg, out num))
{
return;
}
if (num <= 0 || num > musicPlayer.Playlist.Count)
@ -309,11 +370,14 @@ namespace NadekoBot.Modules {
cgb.CreateCommand("cleanup")
.Description("Cleans up hanging voice connections. **Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(e => {
foreach (var kvp in MusicPlayers) {
.Do(e =>
{
foreach (var kvp in MusicPlayers)
{
var songs = kvp.Value.Playlist;
var currentSong = kvp.Value.CurrentSong;
if (songs.Count == 0 && currentSong == null) {
if (songs.Count == 0 && currentSong == null)
{
MusicPlayer throwaway;
MusicPlayers.TryRemove(kvp.Key, out throwaway);
throwaway.Destroy();
@ -331,8 +395,10 @@ namespace NadekoBot.Modules {
});
}
private async Task QueueSong(Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) {
if (voiceCh == null || voiceCh.Server != textCh.Server) {
private async Task QueueSong(Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
{
if (voiceCh == null || voiceCh.Server != textCh.Server)
{
if (!silent)
await textCh.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining.");
throw new ArgumentNullException(nameof(voiceCh));
@ -340,25 +406,32 @@ namespace NadekoBot.Modules {
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
throw new ArgumentException("💢 Invalid query for queue song.", nameof(query));
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Server, server => {
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Server, server =>
{
float? vol = null;
float throwAway;
if (DefaultMusicVolumes.TryGetValue(server.Id, out throwAway))
vol = throwAway;
var mp = new MusicPlayer(voiceCh, vol);
mp.OnCompleted += async (s, song) => {
try {
mp.OnCompleted += async (s, song) =>
{
try
{
await textCh.SendMessage($"🎵`Finished`{song.PrettyName}");
} catch { }
}
catch { }
};
mp.OnStarted += async (s, song) => {
mp.OnStarted += async (s, song) =>
{
var sender = s as MusicPlayer;
if (sender == null)
return;
try {
try
{
var msgTxt = $"🎵`Playing`{song.PrettyName} `Vol: {(int)(sender.Volume * 100)}%`";
await textCh.SendMessage(msgTxt);
} catch { }
}
catch { }
};
return mp;
});

View File

@ -71,5 +71,7 @@
"https://media.giphy.com/media/12hvLuZ7uzvCvK/giphy.gif",
"http://gallery1.anivide.com/_full/65030_1382582341.gif",
"https://49.media.tumblr.com/8e8a099c4eba22abd3ec0f70fd087cce/tumblr_nxovj9oY861ur1mffo1_500.gif "
]
],
"CurrencySign": "🌸",
"CurrencyName": "NadekoFlower"
}

View File

@ -0,0 +1,248 @@
[{
"Name": "Ace of Spades",
"Description": "An ace of spades from a standard card deck. No matter where you store it on your body, you will always be able to find it in your right sleeve afterwards."
}, {
"Name": "Arrow of Euarere",
"Description": "A silver arrow, suspended on a string. It always points to the person holding the string."
}, {
"Name": "Amulet of Extra Amulet Slot",
"Description": "This amulet allows you to gain the benefit from two magical amulets rather than one. It cannot be further enchanted."
}, {
"Name": "Amulet of Feather Fall",
"Description": "When worn, this amulet turns into a feather and falls to the ground."
}, {
"Name": "Anti-Matches",
"Description": "A box of matches. Striking one will make it begin to drip water from the tip while the match shrivels away. The amount of water a match releases is about enough to fill a tablespoon."
}, {
"Name": "Artist's Bludgeon, The",
"Description": "Inanimate objects hit with this bludgeon will receive no damage; they will however change color."
}, {
"Name":"Attentive Guardsman's Pike",
"Description":"These ornate and deadly-looking ceremonial pikes are reach weapons and appear to eigh at least 20 lbs, not counting the weight of the fluttering banners that can be unfurled for parade use. Constructed of shadowstuff, they weigh one pound, and inflict only a single point of damage on an attack, being almost entirely for show, although they also have the unique property of remaining in place when set (although unable to support more than 20 lbs), allowing a 'resting his eyes' guardsman to prop it up and leave it standing under its own power, while his hand sags off of it."
}, {
"Name": "Attentive Guardsman's Tabard",
"Description": "A dozen of these tabards were fashioned for palace guardsmen in the Empire of Sard, 250 miles from the nearest enemy. The bearer is placed under a glamour that causes him to appear alert and awake, even if his eyes are closed and he is snoring lightly."
}, {
"Name": "Axe of Big Numbers",
"Description": "This axe shouts \"Big numbers baby, come on!\" whenever it is swung, but always deals 1 damage or less."
}, {
"Name": "Axe of Empathy",
"Description": "Every time you hit something with this +5 greataxe, you get dealt an equal amount of damage. Both you and the thing you hit are then healed the amount of damage dealt by the axe, even if either are dead. The Axe hopes you have learned your lesson."
}, {
"Name": "Axe of Pain",
"Description": "The axe is always moaning and groaning with pain."
}, {
"Name": "Baby Oil",
"Description": "An aphrodisiac made from the finest mashed babies. Strangely unpopular in the upper planes, the judgmental prudes."
}, {
"Name": "Bag of Faerie Gold",
"Description": "This sack appears to be full of gold coins and jewels. When one attempts to spend them, however, the glamour on them soon vanishes, revealing them to be nothing but leaves and pebbles. Obviously, most shopkeepers will not be happy about this, and no amount of 'we didn't know, I swear!' will change their mind."
}, {
"Name": "Bag of Holding",
"Description":"This item functions as a normal
backpack, however when attempting to retrieve an item, a calm female
voice tells them there is a wait time of 4d10 minutes before they can
retrieve their item (actual time is stated time plus 6d6 additional
minutes). During this wait, the bag plays either annoying muzak or advertisements
for the bag's creator's other products/services. Upon attempting to
retrieve an item, there is a chance that the wrong item is retrieved, or
that the intended item is simply missing. Obtaining the original item
requires an additional 4d10+6d6 minutes and has only a 5% chance of
success."
}, {
"Name": "Bag of Holding (Alternate)",
"Description": "This sack needs a hug!"
}, {
"Name": "Bag of Trading",
"Description": "You can take one thing out of the bag for each object you put in the bag. However, you have no control over what you get, and there are no trade-backs. Past research seems to imply there's some sort of correlation to what gets you what, but it's extremely convoluted and far from understood."
}, {
"Name": "Bag of Trick",
"Description": "This bag operates like a Bag of Tricks, except it only works once a week and produces a rat each time itis used."
}, {
"Name": "Bag of Unholding",
"Description": "Quite a large backpack but even the smallest item doesn't fit."
}, {
"Name": "Bagpipe of Stealth",
"Description": "Grants the user invisibility as long as it is being played."
}, {
"Name": "Ball of Eyes",
"Description": "A snow-globe filled with miniature eyeballs. When shaken, it grants the user a blurry, jittery vision of some future event."
}, {
"Name": "Banana Walkie-Talkies",
"Description": "There exist two, and only two, of these items in the world. One of which is possessed by a cranky and lonely half-orc. It appears to be an innocuous wooden banana with a coat of faded yellow paint. When an end (doesn't matter which one) is placed against your ear, you can hear a ringing followed by a *click* and a half-orc yelling at you for waking him up at this ungodly hour. If you drop the banana or \"hang up,\" the call ends. If you stay and listen, the half-orc will yell at you, call o...(line truncated)...
}, {
"Name": "Barrel of Holding",
"Description": "This large wooden barrel measuring &#8730;(12/&#960;) feet in diameter and 5 feet in height can hold up to 15 cubic feet of matter."
}, {
"Name": "Beam Sword of Severed Nerves",
"Description": "A beam sword. It cannot cut anything but nerve strings. Will pass through any other material leaving no harm."
}, {
"Name": "Belt of Pants",
"Description": "This belt creates illusory pants on the wearer. The wearer can suppress the illusion at will"
}, {
"Name": "Belt of Tightening",
"Description": "Every time you put this belt on, all of your clothes permanently shrink a fraction of a millimeter. The effect is compound."
}, {
"Name": "Belt of Unbathed Breath",
"Description": "When worn around the waist, allows the user to breathe underwater. Does not function when wet."
}, {
"Name": "Boogie Skeleton",
"Description": "This pile of bones is small, such as one that might be obtained from a bird or a toad, though it can look as though it came from any creature. When a song is sung or played in the vicinity of the skeleton, it begins to dance appropriately. As soon as the music stops, it collapses into the pile of bones again. The skeleton, when dancing, can be no larger than Diminutive."
}, {
"Name": "Book of Canon",
"Description": "A book that automatically transforms into a copy of the sacred text of any religion, translated into the language the user is most familiar with."
}, {
"Name": "Book of Confusion",
"Description": "The letters in this book always appear to be upside down, even if viewed from different directions at the same time. The book is a bad novel about zombies."
}, {
"Name": "Book of Curses",
"Description": "When opened, the book verbally berates anyone in the immediate vicinity, calling into question their combat ability, intellect, personal hygiene, lineage and profession of their mothers, and other delightful insults. Once closed the book continues shouting (although it is muffled) until placed inside a bag or some other similar container for 1d4+1 minutes and ignored. Replying to the book in any other way causes the insults to get louder and more childish the more time you spend replying to...(line truncated)...
}, {
"Name": "Book of Exalted Deeds",
"Description": "Contains a listing of some of the finest houses ever sold and the specifics of the titles to the properties."
}, {
"Name": "Boots of Levitation",
"Description": "These boots levitate a few inches off the ground when not worn."
}, {
"Name": "Boots of Stylishness",
"Description": "Knee high black boots that are always clean and shiny. They never take in water, thus feet are always dry."
}, {
"Name": "Boots of Walking",
"Description": "The wearer of the boots cannot run, nor can he take a double move action, and takes a -5 to Tumble checks. These boots are made for walkin', and that's just what they'll do."
}, {
"Name": "Bottle of Air",
"Description": "It's a bottle. Full of air. Congratulations."
}, {
"Name": "Bottomless Beer Mug",
"Description": "Any liquid poured into this mug treats the bottom as incorporeal, but solid objects don't"
}, {
"Name": "Bowl of Comfortable Warmth",
"Description": "Any liquid in the bowl will feel comfortably warm, so icy cold water will feel like it's a bit over room temperature. Do note, however, that it's still icy cold water, it just feels warmer."
}, {
"Name": "Breastplate of Secret Detection",
"Description": "If the wearer of this breastplate gains a piece of information that is somehow connected to the concealment of a hidden conspiracy or plot, a live and still wet red herring forms on the inside of the armor."
}, {
"Name": "Bullying Gloves",
"Description": "At random intervals, these gloves instil the wearer with a near-irresistible urge to hit themselves."
}, {
"Name": "Bunyan’s Belt",
"Description": "When worn, causes an enormous, bushy black beard to appear on the wearer’s face."
}, {
"Name": "Cape of Resistance",
"Description": "When this item is placed on any living thing it somehow manages to fall off, untie itself, slip past the owner’s neck entirely, or otherwise avoid being worn."
}, {
"Name": "Case of the Litigator",
"Description": "Translates any document placed in the case into legal jargon; non-reversible. Does not confer the ability to understand legal jargon."
}, {
"Name": "Cat of Schrodinger",
"Description": "When this cat is not being observed in any way it is both dead and alive. When something observes it, it suddenly becomes either dead or alive with a 50% chance of either."
}, {
"Name": "Chair of Steadiness",
"Description": "This chair can be moved but cannot be tipped over by anything less than a DC 35 Strength check."
}, {
"Name": "Charles",
"Description": "This small, unremarkable figurine of a gnome refuses to be called anything but Charles. No other name will leave the lips of the speaker. It has no other powers."
}, {
"Name": "Chime of Interruption",
"Description": "This instrument can be struck once every round, which takes a standard action. On any round the chime is activated the user may ready one action without spending an action to do so."
}, {
"Name": "Chime of Opening",
"Description": "Commonly affixed to or near doors, when pressed it emits a sound on the interior of the owner’s home to let them know guests have arrived."
}, {
"Name": "Chime of Opening (Alternate)",
"Description": "When struck against a solid surface, this chime emits a loud click, and opens along its length, to reveal a tiny compartment adequate to conceal a single 'smoke' worth of pipeweed or a blowgun needle. When the compartment is closed, it is seamless and can be detected only with a DC 20 Search check. If hit with an instrument such as a small mallet, it chimes."
}, {
"Name": "Cloak of Billowing",
"Description": "This black and silver cloak will always billow dramatically behind the wearer, it has no other effects."
}, {
"Name": "Cloak of Displacement, Minor",
"Description": "This item appears to be a normal cloak, but when worn by a character its magical properties distort and warp reality. When any attack is made against the wearer the cloak has a 20% chance of falling off, no matter how it is secured."
}, {
"Name": "Compacting hammer",
"Description": "The force imparted by it is multiplied, but is spread around the surface of a struck object facing inward."
}, {
"Name": "Cymbal of Symbols",
"Description": "This musical instrument enables the user to comprehend dead languages, but only while they are deafened by noise."
}, {
"Name": "Dagger of Told Secrets",
"Description": "A simple-looking dagger. If used to backstab someone to death, it will whisper your most embarrassing secret to that person."
}, {
"Name": "Dagger of Untold Secrets",
"Description": "A simple looking dagger. If used to backstab someone to death, it will whisper the most embarrassing secret of that person to you."
}, {
"Name": "Decanter of Endless Sorrow",
"Description": "A pewter flask that produces limitless alcohol when held to their lips by someone who is troubled. It gets them drunk but they never feel any better."
}, {
"Name": "Diadem of Brothaurity",
"Description": "When wearing this headpiece, you are as elegant and well-spoken as a famous diplomat or regent, but you can't stop calling everyone bro."
}, {
"Name": "Enchanted Book of Collected Stories",
"Description": "Opening this will cause miniature creatures/people to pour out and preform a chapter from the book much like a theater."
}, {
"Name": "Fade to Black Belt",
"Description": "The wearer of this belt will be unable to remember any sexual encounter begun while they were wearing the belt."
}, {
"Name": "Focusing Ring",
"Description": "The digit on which this ring is worn can be viewed in extremely high definition from a great distance."
}, {
"Name": "Gloves of Tinkering",
"Description": "Wearing the gloves will make you able to almost repair any broken item. However, you will always end up with pieces from the item that don't seem to fit anywhere."
}, {
"Name": "Greater Staff of Random Summoning",
"Description": "Summons a random creature at a random place. You could be summoning a giant Ogre on the other side of the globe for all you know."
}, {
"Name": "Hoarder's Wand",
"Description": "Does nothing but for some reason you think it might be important later in your quest."
}, {
"Name": "Hood of Offensive Facades",
"Description": "This hood will change your identity in the eyes of others to the appearance of the person they most personally dislike."
}, {
"Name": "Hood Of Worrisome Facades",
"Description": "This hood will change your identity in the eyes of others, however the identity used will be random."
}, {
"Name": "Indestructible Notebook of Memories",
"Description": "This otherwise normal notepad of normal notepad size cannot be damaged or destroyed, and anything written in it cannot be obscured or defaced. It also has unlimited pages despite its finite size. However, the data it holds only lasts as long as the writer independently remembers it, and decays in exact proportion to the relevant memories. Remember who and when, but not where? Then the words describing the location in that particular entry are the only ones gone."
}, {
"Name": "Intransigent Rod",
"Description": "When the button on this artifact is pressed in, the holder's opinions solidify and they become impossible to convince."
}, {
"Name": "Lunchbox of Delicious Unfulfillment",
"Description": "This lunchbox will hold whatever food you desire. However you will never get full and the food will deliver no nourishment."
}, {
"Name": "Mattress of Poverty, The",
"Description": "No matter how you fluff this gorgeous, thick, mattress, you will always sleep on the thin part of it."
}, {
"Name": "Mug O' Dissatisfaction",
"Description": "A mug that always produces a steaming hot cup of coffee or tea when tapped on the bottom. It conjures the opposite of what the tapper prefers, so if you like tea you get coffee and vice versa. Handing the full mug to another person will make the drink in it transform to the opposite of that persons preferences."
}, {
"Name": "Murder Dagger",
"Description": "All damage it would deal is instead replaced by the target being harassed by crows for that many hours."
}, {
"Name": "Needle Of Learned Compromise",
"Description": "This needle will create beautiful tattoos of any design, however they hurt a tiny bit more. When used to sew it is entirely normal."
}, {
"Name": "Portable Dark Tavern Corner",
"Description": "Consisting of two wooden boards connected by a hinge, this artifact draws those nearby into assuming it is a perfect spot to conduct seedy business."
}, {
"Name": "Ring of First Impression",
"Description": "Wearing the ring will make you able to perform a perfect handshake with the hand wearing it."
}, {
"Name": "Sack of Hive Eggs",
"Description": "Crushing one of the numerous tiny eggs will cause the thoughts of everybody in the proximity to merge. Everybody can hear what you think and you can hear everybody."
}, {
"Name": "Shoes of the Restless Traveler",
"Description": "These shoes allow their user to run for miles without feeling fatigue, but if they try to do anything else with it (walk, sit down, jump), they will instantly trip"
}, {
"Name": "Sword of Parrying",
"Description": "Parries every attack, swinging it yourself will force it to \"parry\" your opponents weapon/attack even though he/she/it is defenseless."
}, {
"Name": "Vorpal Grindstone",
"Description": "It can \"sharpen\" any object to become vorpal. Any object."
}, {
"Name": "Water Hat, The",
"Description": "A small red hat, when worn, causes water to pour from the wearer's fingers at the speed and pressure of a kitchen faucet at half power."
},
{ "Name":"Wineskin of the Eternal Primary",
"Description":"This wineskin never runs out of water, but even the tiniest sip makes you have to go, like, super bad. Right now."
},
]

View File

@ -56,6 +56,8 @@ Next to your exe you must also have a data folder in which there is config.json
- For Mashape Api Key you need to create an account on their api marketplace here https://market.mashape.com/. After that you need to go to market.mashape.com/YOURNAMEHERE/applications/default-application and press GET THE KEYS in the right top corner copy paste it into your credentials.json and you are ready to race!
- If you want to have music, you need to download FFMPEG from this link http://ffmpeg.zeranoe.com/builds/ (static build version) and add ffmpeg/bin folder to your PATH environment variable. You do that by opening explorer -> right click 'This PC' -> properties -> advanced system settings -> In the top part, there is a PATH field, add `;` to the end and then your ffmpeg install location /bin (for example ;C:\ffmpeg-5.6.7\bin) and save. Open command prompt and type ffmpeg to see if you added it correctly. If it says "command not found" then you made a mistake somewhere. There are a lot of guides on the internet on how to add stuff to your PATH, check them out if you are stuck.
- **IF YOU HAVE BEEN USING THIS BOT BEFORE AND YOU HAVE DATA FROM PARSE THAT YOU WANT TO KEEP** you should export your parse data and extract it inside /data/parsedata in your bot's folder. Next time you start the bot, type `.parsetosql` and the bot will fill your local sqlite db with data from those .json files.
**Nothing was buffered music error?** make sure to follow the guide on google api key and ffmpeg [here](https://www.youtube.com/watch?v=x7v02MXNLeI)
Enjoy