Refactored Searches

This commit is contained in:
Master Kwoth
2016-04-14 23:19:29 +02:00
parent 73cda7336a
commit eeaf4c4920
8 changed files with 48 additions and 64 deletions

View File

@ -7,7 +7,7 @@ using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Search.Commands
namespace NadekoBot.Modules.Searches.Commands
{
class ConverterCommand : DiscordCommand
{

View File

@ -0,0 +1,380 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Commands;
using NadekoBot.Extensions;
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.Modules.Searches.Commands
{
internal class LoLCommands : DiscordCommand
{
private class CachedChampion
{
public System.IO.Stream ImageStream { get; set; }
public DateTime AddedAt { get; set; }
public string Name { get; set; }
}
private static Dictionary<string, CachedChampion> CachedChampionImages = new Dictionary<string, CachedChampion>();
private readonly object cacheLock = new object();
private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer();
public LoLCommands(DiscordModule module) : base(module)
{
clearTimer.Interval = new TimeSpan(0, 10, 0).TotalMilliseconds;
clearTimer.Start();
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 { }
};
}
private readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.",
"Go with the flow. Don't think. Just ban one of these.",
"DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.",
"Ask your teammates what would they like to play, and ban that.",
"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()
{
throw new NotImplementedException();
}
private class MatchupModel
{
public int Games { get; set; }
public float WinRate { get; set; }
[Newtonsoft.Json.JsonProperty("key")]
public string Name { get; set; }
public float StatScore { get; set; }
}
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
{
//get role
var role = ResolvePos(e.GetArg("position"));
var resolvedRole = role;
var name = e.GetArg("champ").Replace(" ", "").ToLower();
CachedChampion champ = null;
lock (cacheLock)
{
CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ);
}
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))
{
data = allData[i];
break;
}
}
if (data == null)
{
await e.Channel.SendMessage("💢 Data for that role does not exist.");
return;
}
}
else
{
data = allData[0];
role = allData[0]["role"].ToString();
resolvedRole = ResolvePos(role);
}
lock (cacheLock)
{
CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ);
}
if (champ != null)
{
Console.WriteLine("Sending lol image from cache.");
champ.ImageStream.Position = 0;
await e.Channel.SendFile("champ.png", champ.ImageStream);
return;
}
//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++)
{
roles[i] = allData[i]["role"].ToString();
if (roles[i] == role)
roles[i] = ">" + roles[i] + "<";
}
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)
{
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++)
{
items[i] = buildData[i]["id"].ToString();
}
//get matchup data to show counters and countered champions
var matchupDataIE = data["matchups"].ToObject<List<MatchupModel>>();
var matchupData = matchupDataIE.OrderBy(m => m.StatScore).ToArray();
var countered = new[] { matchupData[0].Name, matchupData[1].Name, matchupData[2].Name };
var counters = new[] { matchupData[matchupData.Length - 1].Name, matchupData[matchupData.Length - 2].Name, matchupData[matchupData.Length - 3].Name };
//get runes data
var runesJArray = data["runes"]["mostGames"]["runes"] as JArray;
var runes = string.Join("\n", runesJArray.OrderBy(jt => int.Parse(jt["number"].ToString())).Select(jt => jt["number"].ToString() + "x" + jt["name"]));
// get masteries data
var masteries = (data["masteries"]["mostGames"]["masteries"] as JArray);
//get skill order data<API_KEY>
var orderArr = (data["skills"]["mostGames"]["order"] as JArray);
var img = Image.FromFile("data/lol/bg.png");
using (var g = Graphics.FromImage(img))
{
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
//g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
const int margin = 5;
const int imageSize = 75;
var normalFont = new Font("Monaco", 8, FontStyle.Regular);
var smallFont = new Font("Monaco", 7, FontStyle.Regular);
//draw champ image
var champName = data["key"].ToString().Replace(" ", "");
g.DrawImage(GetImage(champName), new Rectangle(margin, margin, imageSize, imageSize));
//draw champ name
if (champName == "MonkeyKing")
champName = "Wukong";
g.DrawString($"{champName}", new Font("Times New Roman", 24, FontStyle.Regular), Brushes.WhiteSmoke, margin + imageSize + margin, margin);
//draw champ surname
//draw skill order
float orderFormula = 120 / orderArr.Count;
const float orderVerticalSpacing = 10;
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)
{
case "w":
orderY += orderVerticalSpacing;
break;
case "e":
orderY += orderVerticalSpacing * 2;
break;
case "r":
orderY += orderVerticalSpacing * 3;
break;
default:
break;
}
g.DrawString(spellName.ToUpperInvariant(), new Font("Monaco", 7), Brushes.LimeGreen, orderX, orderY);
}
//draw roles
g.DrawString("Roles: " + string.Join(", ", roles), normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin);
//draw average stats
g.DrawString(
$@" Average Stats
Kills: {general["kills"]} CS: {general["minionsKilled"]}
Deaths: {general["deaths"]} Win: {general["winPercent"]}%
Assists: {general["assists"]} Ban: {general["banRate"]}%
", normalFont, Brushes.WhiteSmoke, img.Width - 150, margin);
//draw masteries
g.DrawString($"Masteries: {string.Join(" / ", masteries?.Select(jt => jt["total"]))}", normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 20);
//draw runes
g.DrawString($"{runes}", smallFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 40);
//draw counters
g.DrawString($"Best against", smallFont, Brushes.WhiteSmoke, margin, img.Height - imageSize + margin);
var smallImgSize = 50;
for (var i = 0; i < counters.Length; i++)
{
g.DrawImage(GetImage(counters[i]),
new Rectangle(i * (smallImgSize + margin) + margin, img.Height - smallImgSize - margin,
smallImgSize,
smallImgSize));
}
//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++)
{
var j = countered.Length - i;
g.DrawImage(GetImage(countered[i]),
new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), img.Height - smallImgSize - margin,
smallImgSize,
smallImgSize));
}
//draw item build
g.DrawString("Popular build", normalFont, Brushes.WhiteSmoke, img.Width - (3 * (smallImgSize + margin) + margin), 77);
for (var i = 0; i < 6; i++)
{
var inverseI = 5 - i;
var j = inverseI % 3 + 1;
var k = inverseI / 3;
g.DrawImage(GetImage(items[i], GetImageType.Item),
new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), 92 + k * (smallImgSize + margin),
smallImgSize,
smallImgSize));
}
}
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 (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 =>
{
var showCount = 6;
//http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2
try
{
var data = JObject.Parse(
await Classes
.SearchHelper
.GetResponseStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" +
$"api_key={NadekoBot.Creds.LOLAPIKey}&page=1&" +
$"limit={showCount}"))["data"] as JArray;
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++)
{
if (i % 2 == 0 && i != 0)
sb.AppendLine();
sb.Append($"`{i + 1}.` **{data[i]["name"]}** ");
//sb.AppendLine($" ({data[i]["general"]["banRate"]}%)");
}
await e.Channel.SendMessage(sb.ToString());
}
catch (Exception ex)
{
await e.Channel.SendMessage($":anger: Fail: Champion.gg didsabled ban data until next patch. Sorry for the inconvenience.");
}
});
}
private enum GetImageType
{
Champion,
Item
}
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)
{
return Image.FromFile("data/lol/_ERROR.png");
}
}
private static string ResolvePos(string pos)
{
if (string.IsNullOrWhiteSpace(pos))
return null;
switch (pos.ToLowerInvariant())
{
case "m":
case "mid":
case "midorfeed":
case "midd":
case "middle":
return "Middle";
case "top":
case "topp":
case "t":
case "toporfeed":
return "Top";
case "j":
case "jun":
case "jungl":
case "jungle":
return "Jungle";
case "a":
case "ad":
case "adc":
case "carry":
case "ad carry":
case "adcarry":
case "c":
return "ADC";
case "s":
case "sup":
case "supp":
case "support":
return "Support";
default:
return pos;
}
}
}
}

View File

@ -0,0 +1,18 @@
using Discord.Commands;
using NadekoBot.Commands;
using System;
namespace NadekoBot.Modules.Searches.Commands
{
class RedditCommand : DiscordCommand
{
public RedditCommand(DiscordModule module) : base(module)
{
}
internal override void Init(CommandGroupBuilder cgb)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,258 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Classes.Permissions;
using NadekoBot.Commands;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
namespace NadekoBot.Modules.Searches.Commands
{
internal class StreamNotifications : DiscordCommand
{
private readonly Timer checkTimer = new Timer
{
Interval = new TimeSpan(0, 0, 15).TotalMilliseconds,
};
private ConcurrentDictionary<string, Tuple<bool, string>> cachedStatuses = new ConcurrentDictionary<string, Tuple<bool, string>>();
public StreamNotifications(DiscordModule module) : base(module)
{
checkTimer.Elapsed += async (s, e) =>
{
cachedStatuses.Clear();
try
{
var streams = SpecificConfigurations.Default.AllConfigs.SelectMany(c => c.ObservingStreams);
if (!streams.Any()) return;
foreach (var stream in streams)
{
Tuple<bool, string> data;
try
{
data = await GetStreamStatus(stream);
}
catch
{
continue;
}
if (data.Item1 != stream.LastStatus)
{
stream.LastStatus = data.Item1;
var server = NadekoBot.Client.GetServer(stream.ServerId);
var channel = server?.GetChannel(stream.ChannelId);
if (channel == null)
continue;
var msg = $"`{stream.Username}`'s stream is now " +
$"**{(data.Item1 ? "ONLINE" : "OFFLINE")}** with " +
$"**{data.Item2}** viewers.";
if (stream.LastStatus)
if (stream.Type == StreamNotificationConfig.StreamType.Hitbox)
msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】";
else if (stream.Type == StreamNotificationConfig.StreamType.Twitch)
msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】";
else if (stream.Type == StreamNotificationConfig.StreamType.Beam)
msg += $"\n`Here is the Link:`【 http://www.beam.pro/{stream.Username}/ 】";
else if (stream.Type == StreamNotificationConfig.StreamType.YoutubeGaming)
msg += $"\n`Here is the Link:`【 not implemented yet - {stream.Username} 】";
await channel.SendMessage(msg);
}
}
}
catch { }
ConfigHandler.SaveConfig();
};
checkTimer.Start();
}
private async Task<Tuple<bool, string>> GetStreamStatus(StreamNotificationConfig stream)
{
bool isLive;
string response;
JObject data;
Tuple<bool, string> result;
switch (stream.Type)
{
case StreamNotificationConfig.StreamType.Hitbox:
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}";
if (cachedStatuses.TryGetValue(hitboxUrl, out result))
return result;
response = await SearchHelper.GetResponseStringAsync(hitboxUrl);
data = JObject.Parse(response);
isLive = data["media_is_live"].ToString() == "1";
result = new Tuple<bool, string>(isLive, data["media_views"].ToString());
cachedStatuses.TryAdd(hitboxUrl, result);
return result;
case StreamNotificationConfig.StreamType.Twitch:
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}";
if (cachedStatuses.TryGetValue(twitchUrl, out result))
return result;
response = await SearchHelper.GetResponseStringAsync(twitchUrl);
data = JObject.Parse(response);
isLive = !string.IsNullOrWhiteSpace(data["stream"].ToString());
result = new Tuple<bool, string>(isLive, isLive ? data["stream"]["viewers"].ToString() : "0");
cachedStatuses.TryAdd(twitchUrl, result);
return result;
case StreamNotificationConfig.StreamType.Beam:
var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}";
if (cachedStatuses.TryGetValue(beamUrl, out result))
return result;
response = await SearchHelper.GetResponseStringAsync(beamUrl);
data = JObject.Parse(response);
isLive = data["online"].ToObject<bool>() == true;
result = new Tuple<bool, string>(isLive, data["viewersCurrent"].ToString());
cachedStatuses.TryAdd(beamUrl, result);
return result;
default:
break;
}
return new Tuple<bool, string>(false, "0");
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "hitbox")
.Alias(Module.Prefix + "hb")
.Description("Notifies this channel when a certain user starts streaming." +
"\n**Usage**: ~hitbox SomeStreamer")
.Parameter("username", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.ManageServer())
.Do(TrackStream(StreamNotificationConfig.StreamType.Hitbox));
cgb.CreateCommand(Module.Prefix + "twitch")
.Alias(Module.Prefix + "tw")
.Description("Notifies this channel when a certain user starts streaming." +
"\n**Usage**: ~twitch SomeStreamer")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(TrackStream(StreamNotificationConfig.StreamType.Twitch));
cgb.CreateCommand(Module.Prefix + "beam")
.Alias(Module.Prefix + "bm")
.Description("Notifies this channel when a certain user starts streaming." +
"\n**Usage**: ~beam SomeStreamer")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(TrackStream(StreamNotificationConfig.StreamType.Beam));
cgb.CreateCommand(Module.Prefix + "removestream")
.Alias(Module.Prefix + "rms")
.Description("Removes notifications of a certain streamer on this channel." +
"\n**Usage**: ~rms SomeGuy")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(async e =>
{
var username = e.GetArg("username")?.ToLower().Trim();
if (string.IsNullOrWhiteSpace(username))
return;
var config = SpecificConfigurations.Default.Of(e.Server.Id);
var toRemove = config.ObservingStreams
.FirstOrDefault(snc => snc.ChannelId == e.Channel.Id &&
snc.Username.ToLower().Trim() == username);
if (toRemove == null)
{
await e.Channel.SendMessage(":anger: No such stream.");
return;
}
config.ObservingStreams.Remove(toRemove);
ConfigHandler.SaveConfig();
await e.Channel.SendMessage($":ok: Removed `{toRemove.Username}`'s stream from notifications.");
});
cgb.CreateCommand(Module.Prefix + "liststreams")
.Alias(Module.Prefix + "ls")
.Description("Lists all streams you are following on this server." +
"\n**Usage**: ~ls")
.Do(async e =>
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
var streams = config.ObservingStreams.Where(snc =>
snc.ServerId == e.Server.Id);
var streamsArray = streams as StreamNotificationConfig[] ?? streams.ToArray();
if (streamsArray.Length == 0)
{
await e.Channel.SendMessage("You are not following any streams on this server.");
return;
}
var text = string.Join("\n", streamsArray.Select(snc =>
{
try
{
return $"`{snc.Username}`'s stream on **{e.Server.GetChannel(e.Channel.Id).Name}** channel. 【`{snc.Type.ToString()}`】";
}
catch { }
return "";
}));
await e.Channel.SendMessage($"You are following **{streamsArray.Length}** streams on this server.\n\n" + text);
});
}
private Func<CommandEventArgs, Task> TrackStream(StreamNotificationConfig.StreamType type) =>
async e =>
{
var username = e.GetArg("username")?.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(username))
return;
var config = SpecificConfigurations.Default.Of(e.Server.Id);
var stream = new StreamNotificationConfig
{
ServerId = e.Server.Id,
ChannelId = e.Channel.Id,
Username = username,
Type = type,
};
var exists = config.ObservingStreams.Contains(stream);
if (exists)
{
await e.Channel.SendMessage(":anger: I am already notifying that stream on this channel.");
return;
}
Tuple<bool, string> data;
try
{
data = await GetStreamStatus(stream);
}
catch
{
await e.Channel.SendMessage(":anger: Stream probably doesn't exist.");
return;
}
var msg = $"Stream is currently **{(data.Item1 ? "ONLINE" : "OFFLINE")}** with **{data.Item2}** viewers";
if (data.Item1)
if (type == StreamNotificationConfig.StreamType.Hitbox)
msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】";
else if (type == StreamNotificationConfig.StreamType.Twitch)
msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】";
else if (type == StreamNotificationConfig.StreamType.Beam)
msg += $"\n`Here is the Link:`【 https://beam.pro/{stream.Username}/ 】";
else if (type == StreamNotificationConfig.StreamType.YoutubeGaming)
msg += $"\n`Here is the Link:` not implemented yet - {stream.Username}";
stream.LastStatus = data.Item1;
if (!exists)
msg = $":ok: I will notify this channel when status changes.\n{msg}";
await e.Channel.SendMessage(msg);
config.ObservingStreams.Add(stream);
};
}
}

View File

@ -3,9 +3,8 @@ using Discord.Modules;
using NadekoBot.Classes;
using NadekoBot.Classes.IMDB;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using NadekoBot.Modules.Search.Commands;
using NadekoBot.Modules.Searches.Commands;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@ -15,7 +14,7 @@ using System.IO;
using System.Linq;
using System.Net;
namespace NadekoBot.Modules
namespace NadekoBot.Modules.Searches
{
internal class Searches : DiscordModule
{
@ -25,6 +24,7 @@ namespace NadekoBot.Modules
commands.Add(new LoLCommands(this));
commands.Add(new StreamNotifications(this));
commands.Add(new ConverterCommand(this));
commands.Add(new RedditCommand(this));
rng = new Random();
}