A lot of work on searches module done

This commit is contained in:
Kwoth
2016-08-16 01:38:28 +02:00
parent a8fe9d6c42
commit 393c4a853e
29 changed files with 674 additions and 680 deletions

View File

@@ -190,22 +190,6 @@ namespace NadekoBot.Modules.NSFW
}
}
public static async Task<string> GetSafebooruImageLink(string tag)
{
var rng = new Random();
var url =
$"http://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}";
using (var http = new HttpClient())
{
var webpage = await http.GetStringAsync(url).ConfigureAwait(false);
var matches = Regex.Matches(webpage, "file_url=\"(?<url>.*?)\"");
if (matches.Count == 0)
return null;
var match = matches[rng.Next(0, matches.Count)];
return matches[rng.Next(0, matches.Count)].Groups["url"].Value;
}
}
public static async Task<string> GetRule34ImageLink(string tag)
{
var rng = new Random();

View File

@@ -0,0 +1,171 @@
using Discord.Commands;
using NadekoBot.Classes;
using ScaredFingers.UnitsConversion;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches.Commands
{
class ConverterCommand : DiscordCommand
{
public ConverterCommand(DiscordModule module) : base(module)
{
if (unitTables == null)
{
CultureInfo ci = new CultureInfo("en-US");
Thread.CurrentThread.CurrentCulture = ci;
unitTables = new List<UnitTable>();
unitTables.Add(UnitTable.LengthTable);
unitTables.Add(UnitTable.TemperatureTable);
unitTables.Add(UnitTable.VolumeTable);
unitTables.Add(UnitTable.WeightTable);
reInitCurrencyConverterTable();
}
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "convert")
.Description($"Convert quantities from>to. | `{Prefix}convert m>km 1000`")
.Parameter("from-to", ParameterType.Required)
.Parameter("quantity", ParameterType.Optional)
.Do(ConvertFunc());
cgb.CreateCommand(Module.Prefix + "convertlist")
.Description("List of the convertable dimensions and currencies.")
.Do(ConvertListFunc());
}
private Func<CommandEventArgs, Task> ConvertListFunc() =>
async e =>
{
reInitCurrencyConverterTable();
string msg = "";
foreach (var tmpTable in unitTables)
{
int i = 1;
while (tmpTable.IsKnownUnit(i))
{
msg += tmpTable.GetUnitName(i) + " (" + tmpTable.GetUnitSymbol(i) + "); ";
i++;
}
msg += "\n";
}
foreach (var curr in exchangeRateProvider.Currencies)
{
msg += curr + "; ";
}
await channel.SendMessageAsync(msg).ConfigureAwait(false);
};
private Func<CommandEventArgs, Task> ConvertFunc() =>
async e =>
{
try
{
await e.Channel.SendIsTyping().ConfigureAwait(false);
string from = e.GetArg("from-to").ToLowerInvariant().Split('>')[0];
string to = e.GetArg("from-to").ToLowerInvariant().Split('>')[1];
float quantity = 1.0f;
if (!float.TryParse(e.GetArg("quantity"), out quantity))
{
quantity = 1.0f;
}
int fromCode, toCode = 0;
UnitTable table = null;
ResolveUnitCodes(from, to, out table, out fromCode, out toCode);
if (table != null)
{
Unit inUnit = new Unit(fromCode, quantity, table);
Unit outUnit = inUnit.Convert(toCode);
await channel.SendMessageAsync(inUnit.ToString() + " = " + outUnit.ToString()).ConfigureAwait(false);
}
else
{
CultureInfo ci = new CultureInfo("en-US");
Thread.CurrentThread.CurrentCulture = ci;
reInitCurrencyConverterTable();
Unit inUnit = currTable.CreateUnit(quantity, from.ToUpperInvariant());
Unit outUnit = inUnit.Convert(currTable.CurrencyCode(to.ToUpperInvariant()));
await channel.SendMessageAsync(inUnit.ToString() + " = " + outUnit.ToString()).ConfigureAwait(false);
}
}
catch //(Exception ex)
{
//Console.WriteLine(ex.ToString());
await channel.SendMessageAsync("Bad input format, or sth went wrong... Try to list them with `" + Module.Prefix + "`convertlist").ConfigureAwait(false);
}
};
private void reInitCurrencyConverterTable()
{
if (lastChanged == null || lastChanged.DayOfYear != DateTime.Now.DayOfYear)
{
try
{
exchangeRateProvider = new WebExchangeRatesProvider();
currTable = new CurrencyExchangeTable(exchangeRateProvider);
lastChanged = DateTime.Now;
}
catch
{
Console.WriteLine("Error with the currency download.");
}
}
}
private void ResolveUnitCodes(string from, string to, out UnitTable table, out int fromCode, out int toCode)
{
foreach (var tmpTable in unitTables)
{
int f = LookupUnit(tmpTable, from);
int t = LookupUnit(tmpTable, to);
if (f > 0 && t > 0)
{
table = tmpTable;
fromCode = f;
toCode = t;
return;
}
}
table = null;
fromCode = 0;
toCode = 0;
}
private int LookupUnit(UnitTable table, string lookup)
{
string wellformedLookup = lookup.ToLowerInvariant().Replace("°", "");
int i = 1;
while (table.IsKnownUnit(i))
{
if (wellformedLookup == table.GetUnitName(i).ToLowerInvariant().Replace("°", "") ||
wellformedLookup == table.GetUnitPlural(i).ToLowerInvariant().Replace("°", "") ||
wellformedLookup == table.GetUnitSymbol(i).ToLowerInvariant().Replace("°", ""))
{
return i;
}
i++;
}
return 0;
}
private static List<UnitTable> unitTables;
private static CurrencyExchangeRatesProvider exchangeRateProvider;
private static CurrencyExchangeTable currTable;
private static DateTime lastChanged;
}
}

View File

@@ -0,0 +1,85 @@
using Discord.Commands;
using Mathos.Parser;
using NadekoBot.Classes;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches.Commands
{
class CalcCommand : DiscordCommand
{
public CalcCommand(DiscordModule module) : base(module)
{
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "calculate")
.Alias(Module.Prefix + "calc")
.Description($"Evaluate a mathematical expression. | `{Prefix}calc 1+1`")
.Parameter("expression", ParameterType.Unparsed)
.Do(EvalFunc());
}
private CustomParser parser = new CustomParser();
private Func<CommandEventArgs, Task> EvalFunc() => async e =>
{
string expression = e.GetArg("expression")?.Trim();
if (string.IsNullOrWhiteSpace(expression))
{
return;
}
string answer = Evaluate(expression);
if (answer == null)
{
await channel.SendMessageAsync($"Expression {expression} failed to evaluate");
return;
}
await channel.SendMessageAsync($"⚙ `{answer}`");
};
private string Evaluate(string expression)
{
//check for factorial
expression = Regex.Replace(expression, @"\d+!", x => x.Value + "0");
try
{
string result = parser.Parse(expression).ToString();
return result;
}
catch (OverflowException)
{
return $"Overflow error on {expression}";
}
catch (FormatException)
{
return $"\"{expression}\" was not formatted correctly";
}
}
class CustomParser : MathParser
{
public CustomParser() : base()
{
OperatorList.Add("!");
OperatorAction.Add("!", (x, y) => Factorial(x));
}
static decimal Factorial(decimal x)
{
decimal y = x - 1;
while (y > 0)
{
x = x * y--;
}
return x;
}
}
}
}

View File

@@ -0,0 +1,63 @@
using NadekoBot.Extensions;
using System.Collections.Generic;
using System.Web;
namespace NadekoBot.Modules.Searches.Commands.IMDB
{
public class ImdbMovie
{
public bool Status { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public string OriginalTitle { get; set; }
public string Year { get; set; }
public string Rating { get; set; }
public string Plot { get; set; }
public string Poster { get; set; }
public List<string> Genres { get; set; }
//public ArrayList Directors { get; set; }
//public ArrayList Writers { get; set; }
//public ArrayList Cast { get; set; }
//public ArrayList Producers { get; set; }
//public ArrayList Musicians { get; set; }
//public ArrayList Cinematographers { get; set; }
//public ArrayList Editors { get; set; }
//public string MpaaRating { get; set; }
//public string ReleaseDate { get; set; }
//public ArrayList PlotKeywords { get; set; }
//public string PosterLarge { get; set; }
//public string PosterFull { get; set; }
//public string Runtime { get; set; }
//public string Top250 { get; set; }
//public string Oscars { get; set; }
//public string Awards { get; set; }
//public string Nominations { get; set; }
//public string Storyline { get; set; }
//public string Tagline { get; set; }
//public string Votes { get; set; }
//public ArrayList Languages { get; set; }
//public ArrayList Countries { get; set; }
//public Dictionary<string, string> ReleaseDates { get; set; }
//public ArrayList MediaImages { get; set; }
//public ArrayList RecommendedTitles { get; set; }
public string ImdbURL { get; set; }
public Dictionary<string, string> Aka { get; set; }
public override string ToString() =>
$@"`Title:` {HttpUtility.HtmlDecode(Title)} {(string.IsNullOrEmpty(OriginalTitle) ? "" : $"({OriginalTitle})")}
`Year:` {Year}
`Rating:` {Rating}
`Genre:` {GenresAsString}
`Link:` <{ImdbURL}>
`Plot:` {System.Net.WebUtility.HtmlDecode(Plot.TrimTo(500))}
`img:` " + Poster.ShortenUrl().Result;
//public string EnglishTitle => Aka.ContainsKey("USA") ? Aka["USA"] :
// (Aka.ContainsKey("UK") ? Aka["UK"] :
// (Aka.ContainsKey("(original title)") ? Aka["(original title)"] :
// (Aka.ContainsKey("(original)") ? Aka["(original)"] : OriginalTitle)));
public string GenresAsString =>
string.Join(", ", Genres);
}
}

View File

@@ -0,0 +1,223 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
/*******************************************************************************
* Free ASP.net IMDb Scraper API for the new IMDb Template.
* Author: Abhinay Rathore
* Website: http://www.AbhinayRathore.com
* Blog: http://web3o.blogspot.com
* More Info: http://web3o.blogspot.com/2010/11/aspnetc-imdb-scraping-api.html
* Updated By: Gergo Torcsvari
* Last Updated: Feb, 2016
*******************************************************************************/
namespace NadekoBot.Modules.Searches.Commands.IMDB
{
public static class ImdbScraper
{
//Search Engine URLs
private static string GoogleSearch = "https://www.google.com/search?q=imdb+";
private static string BingSearch = "http://www.bing.com/search?q=imdb+";
private static string AskSearch = "http://www.ask.com/web?q=imdb+";
//Constructor
public static ImdbMovie ImdbScrape(string MovieName, bool GetExtraInfo = true)
{
ImdbMovie mov = new ImdbMovie();
string imdbUrl = GetIMDbUrl(System.Uri.EscapeUriString(MovieName));
mov.Status = false;
if (!string.IsNullOrWhiteSpace(imdbUrl))
{
ParseIMDbPage(imdbUrl, GetExtraInfo, mov);
}
return mov;
}
public static ImdbMovie ImdbScrapeFromId(string imdbId, bool GetExtraInfo = true)
{
ImdbMovie mov = new ImdbMovie();
string imdbUrl = "http://www.imdb.com/title/" + imdbId + "/";
mov.Status = false;
ParseIMDbPage(imdbUrl, GetExtraInfo, mov);
return mov;
}
public static string GetIMDBId(string MovieName)
{
string imdbUrl = GetIMDbUrl(System.Uri.EscapeUriString(MovieName));
return match(@"http://www.imdb.com/title/(tt\d{7})", imdbUrl);
}
//Get IMDb URL from search results
private static string GetIMDbUrl(string MovieName, string searchEngine = "google")
{
string url = GoogleSearch + MovieName; //default to Google search
if (searchEngine.ToLower().Equals("bing")) url = BingSearch + MovieName;
if (searchEngine.ToLower().Equals("ask")) url = AskSearch + MovieName;
string html = GetUrlData(url);
ArrayList imdbUrls = MatchAll(@"<a href=""(http://www.imdb.com/title/tt\d{7}/)"".*?>.*?</a>", html);
if (imdbUrls.Count > 0)
return (string)imdbUrls[0]; //return first IMDb result
else if (searchEngine.ToLower().Equals("google")) //if Google search fails
return GetIMDbUrl(MovieName, "bing"); //search using Bing
else if (searchEngine.ToLower().Equals("bing")) //if Bing search fails
return GetIMDbUrl(MovieName, "ask"); //search using Ask
else //search fails
return string.Empty;
}
//Parse IMDb page data
private static void ParseIMDbPage(string imdbUrl, bool GetExtraInfo, ImdbMovie mov)
{
string html = GetUrlData(imdbUrl + "combined");
mov.Id = match(@"<link rel=""canonical"" href=""http://www.imdb.com/title/(tt\d{7})/combined"" />", html);
if (!string.IsNullOrEmpty(mov.Id))
{
mov.Status = true;
mov.Title = match(@"<title>(IMDb \- )*(.*?) \(.*?</title>", html, 2);
mov.OriginalTitle = match(@"title-extra"">(.*?)<", html);
mov.Year = match(@"<title>.*?\(.*?(\d{4}).*?\).*?</title>", match(@"(<title>.*?</title>)", html));
mov.Rating = match(@"<b>(\d.\d)/10</b>", html);
mov.Genres = MatchAll(@"<a.*?>(.*?)</a>", match(@"Genre.?:(.*?)(</div>|See more)", html)).Cast<string>().ToList();
mov.Plot = match(@"Plot:</h5>.*?<div class=""info-content"">(.*?)(<a|</div)", html);
//mov.Directors = matchAll(@"<td valign=""top""><a.*?href=""/name/.*?/"">(.*?)</a>", match(@"Directed by</a></h5>(.*?)</table>", html));
//mov.Writers = matchAll(@"<td valign=""top""><a.*?href=""/name/.*?/"">(.*?)</a>", match(@"Writing credits</a></h5>(.*?)</table>", html));
//mov.Producers = matchAll(@"<td valign=""top""><a.*?href=""/name/.*?/"">(.*?)</a>", match(@"Produced by</a></h5>(.*?)</table>", html));
//mov.Musicians = matchAll(@"<td valign=""top""><a.*?href=""/name/.*?/"">(.*?)</a>", match(@"Original Music by</a></h5>(.*?)</table>", html));
//mov.Cinematographers = matchAll(@"<td valign=""top""><a.*?href=""/name/.*?/"">(.*?)</a>", match(@"Cinematography by</a></h5>(.*?)</table>", html));
//mov.Editors = matchAll(@"<td valign=""top""><a.*?href=""/name/.*?/"">(.*?)</a>", match(@"Film Editing by</a></h5>(.*?)</table>", html));
//mov.Cast = matchAll(@"<td class=""nm""><a.*?href=""/name/.*?/"".*?>(.*?)</a>", match(@"<h3>Cast</h3>(.*?)</table>", html));
//mov.PlotKeywords = matchAll(@"<a.*?>(.*?)</a>", match(@"Plot Keywords:</h5>.*?<div class=""info-content"">(.*?)</div", html));
//mov.ReleaseDate = match(@"Release Date:</h5>.*?<div class=""info-content"">.*?(\d{1,2} (January|February|March|April|May|June|July|August|September|October|November|December) (19|20)\d{2})", html);
//mov.Runtime = match(@"Runtime:</h5><div class=""info-content"">(\d{1,4}) min[\s]*.*?</div>", html);
//mov.Top250 = match(@"Top 250: #(\d{1,3})<", html);
//mov.Oscars = match(@"Won (\d+) Oscars?\.", html);
//if (string.IsNullOrEmpty(mov.Oscars) && "Won Oscar.".Equals(match(@"(Won Oscar\.)", html))) mov.Oscars = "1";
//mov.Awards = match(@"(\d{1,4}) wins", html);
//mov.Nominations = match(@"(\d{1,4}) nominations", html);
//mov.Tagline = match(@"Tagline:</h5>.*?<div class=""info-content"">(.*?)(<a|</div)", html);
//mov.MpaaRating = match(@"MPAA</a>:</h5><div class=""info-content"">Rated (G|PG|PG-13|PG-14|R|NC-17|X) ", html);
//mov.Votes = match(@">(\d+,?\d*) votes<", html);
//mov.Languages = matchAll(@"<a.*?>(.*?)</a>", match(@"Language.?:(.*?)(</div>|>.?and )", html));
//mov.Countries = matchAll(@"<a.*?>(.*?)</a>", match(@"Country:(.*?)(</div>|>.?and )", html));
mov.Poster = match(@"<div class=""photo"">.*?<a name=""poster"".*?><img.*?src=""(.*?)"".*?</div>", html);
if (!string.IsNullOrEmpty(mov.Poster) && mov.Poster.IndexOf("media-imdb.com") > 0)
{
mov.Poster = Regex.Replace(mov.Poster, @"_V1.*?.jpg", "_V1._SY200.jpg");
//mov.PosterLarge = Regex.Replace(mov.Poster, @"_V1.*?.jpg", "_V1._SY500.jpg");
//mov.PosterFull = Regex.Replace(mov.Poster, @"_V1.*?.jpg", "_V1._SY0.jpg");
}
else
{
mov.Poster = string.Empty;
//mov.PosterLarge = string.Empty;
//mov.PosterFull = string.Empty;
}
mov.ImdbURL = "http://www.imdb.com/title/" + mov.Id + "/";
if (GetExtraInfo)
{
string plotHtml = GetUrlData(imdbUrl + "plotsummary");
//mov.Storyline = match(@"<p class=""plotpar"">(.*?)(<i>|</p>)", plotHtml);
GetReleaseDatesAndAka(mov);
//mov.MediaImages = getMediaImages(mov);
//mov.RecommendedTitles = getRecommendedTitles(mov);
}
}
}
//Get all release dates and aka-s
private static void GetReleaseDatesAndAka(ImdbMovie mov)
{
Dictionary<string, string> release = new Dictionary<string, string>();
string releasehtml = GetUrlData("http://www.imdb.com/title/" + mov.Id + "/releaseinfo");
foreach (string r in MatchAll(@"<tr class="".*?"">(.*?)</tr>", match(@"<table id=""release_dates"" class=""subpage_data spFirst"">\n*?(.*?)</table>", releasehtml)))
{
Match rd = new Regex(@"<td>(.*?)</td>\n*?.*?<td class=.*?>(.*?)</td>", RegexOptions.Multiline).Match(r);
release[StripHTML(rd.Groups[1].Value.Trim())] = StripHTML(rd.Groups[2].Value.Trim());
}
//mov.ReleaseDates = release;
Dictionary<string, string> aka = new Dictionary<string, string>();
ArrayList list = MatchAll(@".*?<tr class="".*?"">(.*?)</tr>", match(@"<table id=""akas"" class=.*?>\n*?(.*?)</table>", releasehtml));
foreach (string r in list)
{
Match rd = new Regex(@"\n*?.*?<td>(.*?)</td>\n*?.*?<td>(.*?)</td>", RegexOptions.Multiline).Match(r);
aka[StripHTML(rd.Groups[1].Value.Trim())] = StripHTML(rd.Groups[2].Value.Trim());
}
mov.Aka = aka;
}
//Get all media images
private static ArrayList GetMediaImages(ImdbMovie mov)
{
ArrayList list = new ArrayList();
string mediaurl = "http://www.imdb.com/title/" + mov.Id + "/mediaindex";
string mediahtml = GetUrlData(mediaurl);
int pagecount = MatchAll(@"<a href=""\?page=(.*?)"">", match(@"<span style=""padding: 0 1em;"">(.*?)</span>", mediahtml)).Count;
for (int p = 1; p <= pagecount + 1; p++)
{
mediahtml = GetUrlData(mediaurl + "?page=" + p);
foreach (Match m in new Regex(@"src=""(.*?)""", RegexOptions.Multiline).Matches(match(@"<div class=""thumb_list"" style=""font-size: 0px;"">(.*?)</div>", mediahtml)))
{
String image = m.Groups[1].Value;
list.Add(Regex.Replace(image, @"_V1\..*?.jpg", "_V1._SY0.jpg"));
}
}
return list;
}
//Get Recommended Titles
private static ArrayList GetRecommendedTitles(ImdbMovie mov)
{
ArrayList list = new ArrayList();
string recUrl = "http://www.imdb.com/widget/recommendations/_ajax/get_more_recs?specs=p13nsims%3A" + mov.Id;
string json = GetUrlData(recUrl);
list = MatchAll(@"title=\\""(.*?)\\""", json);
HashSet<String> set = new HashSet<string>();
foreach (String rec in list) set.Add(rec);
return new ArrayList(set.ToList());
}
/*******************************[ Helper Methods ]********************************/
//Match single instance
private static string match(string regex, string html, int i = 1)
{
return new Regex(regex, RegexOptions.Multiline).Match(html).Groups[i].Value.Trim();
}
//Match all instances and return as ArrayList
private static ArrayList MatchAll(string regex, string html, int i = 1)
{
ArrayList list = new ArrayList();
foreach (Match m in new Regex(regex, RegexOptions.Multiline).Matches(html))
list.Add(m.Groups[i].Value.Trim());
return list;
}
//Strip HTML Tags
private static string StripHTML(string inputString)
{
return Regex.Replace(inputString, @"<.*?>", string.Empty);
}
//Get URL Data
private static string GetUrlData(string url)
{
WebClient client = new WebClient();
Random r = new Random();
//Random IP Address
//client.Headers["X-Forwarded-For"] = r.Next(0, 255) + "." + r.Next(0, 255) + "." + r.Next(0, 255) + "." + r.Next(0, 255);
//Random User-Agent
client.Headers["User-Agent"] = "Mozilla/" + r.Next(3, 5) + ".0 (Windows NT " + r.Next(3, 5) + "." + r.Next(0, 2) + "; rv:37.0) Gecko/20100101 Firefox/" + r.Next(30, 37) + "." + r.Next(0, 5);
Stream datastream = client.OpenRead(url);
StreamReader reader = new StreamReader(datastream);
StringBuilder sb = new StringBuilder();
//TODO: Coud be reader error must catch and drop!!!
while (!reader.EndOfStream)
sb.Append(reader.ReadLine());
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,383 @@
using Discord.Commands;
using NadekoBot.Classes;
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 class ChampionNameComparer : IEqualityComparer<JToken>
{
public bool Equals(JToken a, JToken b) => a["name"].ToString() == b["name"].ToString();
public int GetHashCode(JToken obj) =>
obj["name"].GetHashCode();
}
private static Dictionary<string, CachedChampion> CachedChampionImages = new Dictionary<string, CachedChampion>();
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
{
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. |`{Prefix}lolchamp Riven` or `{Prefix}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;
if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ))
if (champ != null)
{
champ.ImageStream.Position = 0;
await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
return;
}
var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Creds.LOLAPIKey}").ConfigureAwait(false));
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 channel.SendMessageAsync("💢 Data for that role does not exist.").ConfigureAwait(false);
return;
}
}
else
{
data = allData[0];
role = allData[0]["role"].ToString();
resolvedRole = ResolvePos(role);
}
if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ))
if (champ != null)
{
champ.ImageStream.Position = 0;
await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
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 http.GetStringAsync($"http://api.champion.gg/stats/" +
$"champs/{name}?api_key={NadekoBot.Creds.LOLAPIKey}")
.ConfigureAwait(false))
.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
if (orderArr.Count != 0)
{
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).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine(ex);
await channel.SendMessageAsync("💢 Failed retreiving data for that champion.").ConfigureAwait(false);
}
});
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. | `{Prefix}lolban`")
.Do(async e =>
{
var showCount = 8;
//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}")
.ConfigureAwait(false))["data"] as JArray;
var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList();
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 < dataList.Count; i++)
{
if (i % 2 == 0 && i != 0)
sb.AppendLine();
sb.Append($"`{i + 1}.` **{dataList[i]["name"]}** ");
//sb.AppendLine($" ({dataList[i]["general"]["banRate"]}%)");
}
await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false);
}
catch (Exception)
{
await channel.SendMessageAsync($":anger: Fail: Champion.gg didsabled ban data until next patch. Sorry for the inconvenience.").ConfigureAwait(false);
}
});
}
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,46 @@
using Discord.Commands;
using NadekoBot.Classes;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace NadekoBot.Modules.Searches.Commands
{
class MemegenCommands : DiscordCommand
{
public MemegenCommands(DiscordModule module) : base(module)
{
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Prefix + "memelist")
.Description($"Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `{Prefix}memelist`")
.Do(async e =>
{
int i = 0;
await channel.SendMessageAsync("`List Of Commands:`\n```xl\n" +
string.Join("\n", JsonConvert.DeserializeObject<Dictionary<string, string>>(await http.GetStringAsync("http://memegen.link/templates/"))
.Select(kvp => Path.GetFileName(kvp.Value))
.GroupBy(item => (i++) / 4)
.Select(ig => string.Concat(ig.Select(el => $"{el,-17}"))))
+ $"\n```").ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "memegen")
.Description($"Generates a meme from memelist with top and bottom text. | `{Prefix}memegen biw \"gets iced coffee\" \"in the winter\"`")
.Parameter("meme", ParameterType.Required)
.Parameter("toptext", ParameterType.Required)
.Parameter("bottext", ParameterType.Required)
.Do(async e =>
{
var meme = e.GetArg("meme");
var top = Uri.EscapeDataString(e.GetArg("toptext").Replace(' ', '-'));
var bot = Uri.EscapeDataString(e.GetArg("bottext").Replace(' ', '-'));
await channel.SendMessageAsync($"http://memegen.link/{meme}/{top}/{bot}.jpg");
});
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
using Discord.Commands;
using NadekoBot.Classes;
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,344 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Modules.Permissions.Classes;
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).ConfigureAwait(false);
}
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).ConfigureAwait(false);
}
}
}
catch { }
await ConfigHandler.SaveConfig().ConfigureAwait(false);
};
checkTimer.Start();
}
private async Task<Tuple<bool, string>> GetStreamStatus(StreamNotificationConfig stream, bool checkCache = true)
{
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 (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result))
return result;
response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false);
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 (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result))
return result;
response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false);
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 (checkCache && cachedStatuses.TryGetValue(beamUrl, out result))
return result;
response = await http.GetStringAsync(beamUrl).ConfigureAwait(false);
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." +
$" | `{Prefix}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." +
$" | `{Prefix}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." +
$" | `{Prefix}beam SomeStreamer`")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(TrackStream(StreamNotificationConfig.StreamType.Beam));
cgb.CreateCommand(Module.Prefix + "checkhitbox")
.Alias(Module.Prefix + "chhb")
.Description("Checks if a certain user is streaming on the hitbox platform." +
$" | `{Prefix}chhb SomeStreamer`")
.Parameter("username", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e =>
{
var stream = e.GetArg("username")?.Trim();
if (string.IsNullOrWhiteSpace(stream))
return;
try
{
var streamStatus = (await GetStreamStatus(new StreamNotificationConfig
{
Username = stream,
Type = StreamNotificationConfig.StreamType.Hitbox
}));
if (streamStatus.Item1)
{
await channel.SendMessageAsync($"`Streamer {streamStatus.Item2} is online.`");
}
}
catch
{
await channel.SendMessageAsync("No channel found.");
}
});
cgb.CreateCommand(Module.Prefix + "checktwitch")
.Alias(Module.Prefix + "chtw")
.Description("Checks if a certain user is streaming on the twitch platform." +
$" | `{Prefix}chtw SomeStreamer`")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(async e =>
{
var stream = e.GetArg("username")?.Trim();
if (string.IsNullOrWhiteSpace(stream))
return;
try
{
var streamStatus = (await GetStreamStatus(new StreamNotificationConfig
{
Username = stream,
Type = StreamNotificationConfig.StreamType.Twitch
}));
if (streamStatus.Item1)
{
await channel.SendMessageAsync($"`Streamer {streamStatus.Item2} is online.`");
}
}
catch
{
await channel.SendMessageAsync("No channel found.");
}
});
cgb.CreateCommand(Module.Prefix + "checkbeam")
.Alias(Module.Prefix + "chbm")
.Description("Checks if a certain user is streaming on the beam platform." +
$" | `{Prefix}chbm SomeStreamer`")
.AddCheck(SimpleCheckers.ManageServer())
.Parameter("username", ParameterType.Unparsed)
.Do(async e =>
{
var stream = e.GetArg("username")?.Trim();
if (string.IsNullOrWhiteSpace(stream))
return;
try
{
var streamStatus = (await GetStreamStatus(new StreamNotificationConfig
{
Username = stream,
Type = StreamNotificationConfig.StreamType.Beam
}));
if (streamStatus.Item1)
{
await channel.SendMessageAsync($"`Streamer {streamStatus.Item2} is online.`");
}
}
catch
{
await channel.SendMessageAsync("No channel found.");
}
});
cgb.CreateCommand(Module.Prefix + "removestream")
.Alias(Module.Prefix + "rms")
.Description("Removes notifications of a certain streamer on this channel." +
$" | `{Prefix}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 channel.SendMessageAsync(":anger: No such stream.").ConfigureAwait(false);
return;
}
config.ObservingStreams.Remove(toRemove);
await ConfigHandler.SaveConfig().ConfigureAwait(false);
await channel.SendMessageAsync($":ok: Removed `{toRemove.Username}`'s stream from notifications.").ConfigureAwait(false);
});
cgb.CreateCommand(Module.Prefix + "liststreams")
.Alias(Module.Prefix + "ls")
.Description("Lists all streams you are following on this server." +
$" | `{Prefix}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 channel.SendMessageAsync("You are not following any streams on this server.").ConfigureAwait(false);
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 channel.SendMessageAsync($"You are following **{streamsArray.Length}** streams on this server.\n\n" + text).ConfigureAwait(false);
});
}
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 channel.SendMessageAsync(":anger: I am already notifying that stream on this channel.").ConfigureAwait(false);
return;
}
Tuple<bool, string> data;
try
{
data = await GetStreamStatus(stream).ConfigureAwait(false);
}
catch
{
await channel.SendMessageAsync(":anger: Stream probably doesn't exist.").ConfigureAwait(false);
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 channel.SendMessageAsync(msg).ConfigureAwait(false);
config.ObservingStreams.Add(stream);
};
}
}

View File

@@ -0,0 +1,36 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.JSONModels;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace NadekoBot.Modules.Searches.Commands
{
class WowJokeCommand : DiscordCommand
{
List<WoWJoke> jokes = new List<WoWJoke>();
public WowJokeCommand(DiscordModule module) : base(module)
{
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "wowjoke")
.Description($"Get one of Kwoth's penultimate WoW jokes. | `{Prefix}wowjoke`")
.Do(async e =>
{
if (!jokes.Any())
{
jokes = JsonConvert.DeserializeObject<List<WoWJoke>>(File.ReadAllText("data/wowjokes.json"));
}
await channel.SendMessageAsync(jokes[new Random().Next(0, jokes.Count)].ToString());
});
}
}
}

View File

@@ -0,0 +1,584 @@
using Discord;
using Discord.Commands;
using NadekoBot.Modules.Searches.Commands.IMDB;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using NadekoBot.Services;
using System.Threading.Tasks;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using Discord.API;
using System.Text.RegularExpressions;
namespace NadekoBot.Modules.Searches
{
[Module("~", AppendSpace = false)]
public class SearchesModule : DiscordModule
{
private readonly Random rng;
public SearchesModule(ILocalization loc, CommandService cmds, IBotConfiguration config, IDiscordClient client) : base(loc, cmds, config, client)
{
rng = new Random();
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Weather(IMessage imsg, string city, string country)
{
var channel = imsg.Channel as IGuildChannel;
city = city.Replace(" ", "");
country = city.Replace(" ", "");
string response;
using (var http = new HttpClient())
response = await http.GetStringAsync($"http://api.lawlypopzz.xyz/nadekobot/weather/?city={city}&country={country}").ConfigureAwait(false);
var obj = JObject.Parse(response)["weather"];
await imsg.Channel.SendMessageAsync(
$@"🌍 **Weather for** 【{obj["target"]}】
📏 **Lat,Long:** ({obj["latitude"]}, {obj["longitude"]}) **Condition:** {obj["condition"]}
😓 **Humidity:** {obj["humidity"]}% 💨 **Wind Speed:** {obj["windspeedk"]}km/h / {obj["windspeedm"]}mph
🔆 **Temperature:** {obj["centigrade"]}°C / {obj["fahrenheit"]}°F 🔆 **Feels like:** {obj["feelscentigrade"]}°C / {obj["feelsfahrenheit"]}°F
🌄 **Sunrise:** {obj["sunrise"]} 🌇 **Sunset:** {obj["sunset"]}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Youtube(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
if (!(await ValidateQuery(imsg.Channel as ITextChannel, query).ConfigureAwait(false))) return;
var link = await FindYoutubeUrlByKeywords(query).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link))
{
await imsg.Channel.SendMessageAsync("No results found for that query.");
return;
}
var shortUrl = await link.ShortenUrl().ConfigureAwait(false);
await imsg.Channel.SendMessageAsync(shortUrl).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Anime(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
if (!(await ValidateQuery(imsg.Channel as ITextChannel, query).ConfigureAwait(false))) return;
string result;
try
{
result = (await GetAnimeData(query).ConfigureAwait(false)).ToString();
}
catch
{
await imsg.Channel.SendMessageAsync("Failed to find that anime.").ConfigureAwait(false);
return;
}
await imsg.Channel.SendMessageAsync(result.ToString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Manga(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
if (!(await ValidateQuery(imsg.Channel as ITextChannel, query).ConfigureAwait(false))) return;
string result;
try
{
result = (await GetMangaData(query).ConfigureAwait(false)).ToString();
}
catch
{
await imsg.Channel.SendMessageAsync("Failed to find that manga.").ConfigureAwait(false);
return;
}
await imsg.Channel.SendMessageAsync(result).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Imdb(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
if (!(await ValidateQuery(imsg.Channel as ITextChannel, query).ConfigureAwait(false))) return;
await e.Channel.SendIsTyping().ConfigureAwait(false);
string result;
try
{
var movie = ImdbScraper.ImdbScrape(query, true);
if (movie.Status) result = movie.ToString();
else result = "Failed to find that movie.";
}
catch
{
await imsg.Channel.SendMessageAsync("Failed to find that movie.").ConfigureAwait(false);
return;
}
await imsg.Channel.SendMessageAsync(result.ToString()).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task RandomCat(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
using (var http = new HttpClient())
{
await imsg.Channel.SendMessageAsync(JObject.Parse(
await http.GetStringAsync("http://www.random.cat/meow").ConfigureAwait(false))["file"].ToString())
.ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task RandomDog(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
using (var http = new HttpClient())
{
await imsg.Channel.SendMessageAsync("http://random.dog/" + await http.GetStringAsync("http://random.dog/woof").ConfigureAwait(false)).ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task I(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
if (string.IsNullOrWhiteSpace(query))
return;
try
{
using (var http = new HttpClient())
{
var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(query)}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&fields=items%2Flink&key={NadekoBot.Credentials.GoogleApiKey}";
var obj = JObject.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false));
await imsg.Channel.SendMessageAsync(obj["items"][0]["link"].ToString()).ConfigureAwait(false);
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("403 (Forbidden)"))
{
await imsg.Channel.SendMessageAsync("Daily limit reached!");
}
else
{
await imsg.Channel.SendMessageAsync("Something went wrong.");
}
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Ir(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
if (string.IsNullOrWhiteSpace(query))
return;
try
{
using (var http = new HttpClient())
{
var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(query)}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Credentials.GoogleApiKey}";
var obj = JObject.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false));
var items = obj["items"] as JArray;
await imsg.Channel.SendMessageAsync(items[0]["link"].ToString()).ConfigureAwait(false);
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("403 (Forbidden)"))
{
await imsg.Channel.SendMessageAsync("Daily limit reached!");
}
else
{
await imsg.Channel.SendMessageAsync("Something went wrong.");
}
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Lmgtfy(IMessage imsg, [Remainder] string ffs)
{
var channel = imsg.Channel as IGuildChannel;
if (string.IsNullOrWhiteSpace(ffs))
return;
await imsg.Channel.SendMessageAsync(await $"<http://lmgtfy.com/?q={ Uri.EscapeUriString(ffs) }>".ShortenUrl())
.ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Google(IMessage imsg, [Remainder] string terms)
{
var channel = imsg.Channel as IGuildChannel;
terms = terms?.Trim();
if (string.IsNullOrWhiteSpace(terms))
return;
await imsg.Channel.SendMessageAsync($"https://google.com/search?q={ HttpUtility.UrlEncode(terms).Replace(' ', '+') }")
.ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Hearthstone(IMessage imsg, [Remainder] string name)
{
var channel = imsg.Channel as IGuildChannel;
var arg = e.GetArg("name");
if (string.IsNullOrWhiteSpace(arg))
{
await imsg.Channel.SendMessageAsync("💢 Please enter a card name to search for.").ConfigureAwait(false);
return;
}
await imsg.Channel.TriggerTypingAsync().ConfigureAwait(false);
string response = "";
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}", headers)
.ConfigureAwait(false);
try
{
var items = JArray.Parse(response);
var images = new List<Image>();
if (items == null)
throw new KeyNotFoundException("Cannot find a card by that name");
var cnt = 0;
items.Shuffle();
foreach (var item in items.TakeWhile(item => cnt++ < 4).Where(item => item.HasValues && item["img"] != null))
{
images.Add(
Image.FromStream(await http.GetStreamAsync(item["img"].ToString()).ConfigureAwait(false)));
}
if (items.Count > 4)
{
await imsg.Channel.SendMessageAsync("⚠ Found over 4 images. Showing random 4.").ConfigureAwait(false);
}
await e.Channel.SendFile(arg + ".png", (await images.MergeAsync()).ToStream(System.Drawing.Imaging.ImageFormat.Png))
.ConfigureAwait(false);
}
catch (Exception ex)
{
await imsg.Channel.SendMessageAsync($"💢 Error {ex.Message}").ConfigureAwait(false);
}
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task UrbanDictionary(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await imsg.Channel.SendMessageAsync("💢 Please enter a search term.").ConfigureAwait(false);
return;
}
await imsg.Channel.TriggerTypingAsync().ConfigureAwait(false);
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
var res = await http.GetStringAsync($"https://mashape-community-urban-dictionary.p.mashape.com/define?term={Uri.EscapeUriString(arg)}", headers).ConfigureAwait(false);
try
{
var items = JObject.Parse(res);
var sb = new System.Text.StringBuilder();
sb.AppendLine($"`Term:` {items["list"][0]["word"].ToString()}");
sb.AppendLine($"`Definition:` {items["list"][0]["definition"].ToString()}");
sb.Append($"`Link:` <{await items["list"][0]["permalink"].ToString().ShortenUrl().ConfigureAwait(false)}>");
await imsg.Channel.SendMessageAsync(sb.ToString());
}
catch
{
await imsg.Channel.SendMessageAsync("💢 Failed finding a definition for that term.").ConfigureAwait(false);
}
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Hashtag(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await imsg.Channel.SendMessageAsync("💢 Please enter a search term.").ConfigureAwait(false);
return;
}
await imsg.Channel.TriggerTypingAsync().ConfigureAwait(false);
string res = "";
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(arg)}.json").ConfigureAwait(false);
}
try
{
var items = JObject.Parse(res);
var str = $@"`Hashtag:` {items["defs"]["def"]["hashtag"].ToString()}
`Definition:` {items["defs"]["def"]["text"].ToString()}
`Link:` <{await items["defs"]["def"]["uri"].ToString().ShortenUrl().ConfigureAwait(false)}>");
await imsg.Channel.SendMessageAsync(str);
}
catch
{
await imsg.Channel.SendMessageAsync("💢 Failed finidng a definition for that tag.").ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Quote(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
var quote = NadekoBot.Config.Quotes[rng.Next(0, NadekoBot.Config.Quotes.Count)].ToString();
await imsg.Channel.SendMessageAsync(quote).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Catfact(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
using (var http = new HttpClient())
{
var response = await http.GetStringAsync("http://catfacts-api.appspot.com/api/facts").ConfigureAwait(false);
if (response == null)
return;
await imsg.Channel.SendMessageAsync($"🐈 `{JObject.Parse(response)["facts"][0].ToString()}`").ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Yomama(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
using (var http = new HttpClient())
{
var response = await http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false);
await imsg.Channel.SendMessageAsync("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Randjoke(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
using (var http = new HttpClient())
{
var response = await http.GetStringAsync("http://tambal.azurewebsites.net/joke/random").ConfigureAwait(false);
await imsg.Channel.SendMessageAsync("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task ChuckNorris(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
using (var http = new HttpClient())
{
var response = await http.GetStringAsync("http://tambal.azurewebsites.net/joke/random").ConfigureAwait(false);
await imsg.Channel.SendMessageAsync("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task MagicItem(IMessage imsg)
{
var channel = imsg.Channel as IGuildChannel;
var magicItems = JsonConvert.DeserializeObject<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
var item = magicItems[rng.Next(0, magicItems.Count)].ToString();
await imsg.Channel.SendMessageAsync(item).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Revav(IMessage imsg, [Remainder] string arg)
{
var channel = imsg.Channel as IGuildChannel;
var usrStr = arg?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(usrStr))
return;
var usr = (await channel.Guild.GetUsersAsync()).Where(u=>u.Username.ToUpperInvariant() == usrStr).FirstOrDefault();
if (usr == null || string.IsNullOrWhiteSpace(usr.AvatarUrl))
return;
await imsg.Channel.SendMessageAsync($"https://images.google.com/searchbyimage?image_url={usr.AvatarUrl}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Revimg(IMessage imsg, [Remainder] string imageLink)
{
var channel = imsg.Channel as IGuildChannel;
imageLink = imageLink?.Trim() ?? "";
if (string.IsNullOrWhiteSpace(imageLink))
return;
await imsg.Channel.SendMessageAsync($"https://images.google.com/searchbyimage?image_url={imageLink}").ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Safebooru(IMessage imsg, [Remainder] string tag)
{
var channel = imsg.Channel as IGuildChannel;
tag = tag?.Trim() ?? "";
var link = await GetSafebooruImageLink(tag).ConfigureAwait(false);
if (link == null)
await imsg.Channel.SendMessageAsync("`No results.`");
else
await imsg.Channel.SendMessageAsync(link).ConfigureAwait(false);
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Wiki(IMessage imsg, [Remainder] string query)
{
var channel = imsg.Channel as IGuildChannel;
query = query?.Trim();
if (string.IsNullOrWhiteSpace(query))
return;
using (var http = new HttpClient())
{
var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query));
var data = JsonConvert.DeserializeObject<WikipediaApiModel>(result);
if (data.Query.Pages[0].Missing)
await imsg.Channel.SendMessageAsync("`That page could not be found.`");
else
await imsg.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Clr(IMessage imsg, [Remainder] string color)
{
var channel = imsg.Channel as IGuildChannel;
var arg1 = e.GetArg("color")?.Trim()?.Replace("#", "");
if (string.IsNullOrWhiteSpace(arg1))
return;
var img = new Bitmap(50, 50);
var red = Convert.ToInt32(arg1.Substring(0, 2), 16);
var green = Convert.ToInt32(arg1.Substring(2, 2), 16);
var blue = Convert.ToInt32(arg1.Substring(4, 2), 16);
var brush = new SolidBrush(System.Drawing.Color.FromArgb(red, green, blue));
using (Graphics g = Graphics.FromImage(img))
{
g.FillRectangle(brush, 0, 0, 50, 50);
g.Flush();
}
await imsg.Channel.SendFileAsync("arg1.png", img.ToStream());
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Videocall(IMessage imsg, [Remainder] string arg)
{
var channel = imsg.Channel as IGuildChannel;
try
{
var allUsrs = imsg.MentionedUsers.Append(imsg.Author);
var allUsrsArray = 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)
{
await (await (usr as IGuildUser).CreateDMChannelAsync()).SendMessageAsync(str).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
[RequireContext(ContextType.Guild)]
public async Task Avatar(IMessage imsg, [Remainder] string mention)
{
var channel = imsg.Channel as IGuildChannel;
var usr = imsg.MentionedUsers.FirstOrDefault();
if (usr == null)
{
await imsg.Channel.SendMessageAsync("Invalid user specified.").ConfigureAwait(false);
return;
}
await imsg.Channel.SendMessageAsync(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false);
}
public static async Task<string> GetSafebooruImageLink(string tag)
{
var rng = new Random();
var url =
$"http://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}";
using (var http = new HttpClient())
{
var webpage = await http.GetStringAsync(url).ConfigureAwait(false);
var matches = Regex.Matches(webpage, "file_url=\"(?<url>.*?)\"");
if (matches.Count == 0)
return null;
var match = matches[rng.Next(0, matches.Count)];
return matches[rng.Next(0, matches.Count)].Groups["url"].Value;
}
}
public static async Task<bool> ValidateQuery(ITextChannel ch, string query)
{
if (!string.IsNullOrEmpty(query.Trim())) return true;
await ch.SendMessageAsync("Please specify search parameters.").ConfigureAwait(false);
return false;
}
}
}

View File

@@ -0,0 +1,234 @@
// Copyright (c) 2015 Ravi Bhavnani
// License: Code Project Open License
// http://www.codeproject.com/info/cpol10.aspx
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
namespace NadekoBot.Modules.Translator.Helpers
{
/// <summary>
/// Translates text using Google's online language tools.
/// </summary>
public class GoogleTranslator
{
#region Properties
/// <summary>
/// Gets the supported languages.
/// </summary>
public static IEnumerable<string> Languages {
get {
GoogleTranslator.EnsureInitialized();
return GoogleTranslator._languageModeMap.Keys.OrderBy(p => p);
}
}
#endregion
#region Public methods
/// <summary>
/// Translates the specified source text.
/// </summary>
/// <param name="sourceText">The source text.</param>
/// <param name="sourceLanguage">The source language.</param>
/// <param name="targetLanguage">The target language.</param>
/// <returns>The translation.</returns>
public async Task<string> Translate
(string sourceText,
string sourceLanguage,
string targetLanguage)
{
// Initialize
DateTime tmStart = DateTime.Now;
string text = string.Empty;
// Download translation
string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage),
GoogleTranslator.LanguageEnumToIdentifier(targetLanguage),
HttpUtility.UrlEncode(sourceText));
using (HttpClient http = new HttpClient())
{
http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
text = await http.GetStringAsync(url).ConfigureAwait(false);
}
return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
}
#endregion
#region Private methods
/// <summary>
/// Converts a language to its identifier.
/// </summary>
/// <param name="language">The language."</param>
/// <returns>The identifier or <see cref="string.Empty"/> if none.</returns>
private static string LanguageEnumToIdentifier
(string language)
{
string mode = string.Empty;
GoogleTranslator.EnsureInitialized();
GoogleTranslator._languageModeMap.TryGetValue(language, out mode);
return mode;
}
/// <summary>
/// Ensures the translator has been initialized.
/// </summary>
public static void EnsureInitialized()
{
if (GoogleTranslator._languageModeMap == null)
{
GoogleTranslator._languageModeMap = new Dictionary<string, string>() {
{ "afrikaans", "af"},
{ "albanian", "sq"},
{ "arabic", "ar"},
{ "armenian", "hy"},
{ "azerbaijani", "az"},
{ "basque", "eu"},
{ "belarusian", "be"},
{ "bengali", "bn"},
{ "bulgarian", "bg"},
{ "catalan", "ca"},
{ "chinese", "zh-CN"},
{ "croatian", "hr"},
{ "czech", "cs"},
{ "danish", "da"},
{ "dutch", "nl"},
{ "english", "en"},
{ "esperanto", "eo"},
{ "estonian", "et"},
{ "filipino", "tl"},
{ "finnish", "fi"},
{ "french", "fr"},
{ "galician", "gl"},
{ "german", "de"},
{ "georgian", "ka"},
{ "greek", "el"},
{ "haitian Creole", "ht"},
{ "hebrew", "iw"},
{ "hindi", "hi"},
{ "hungarian", "hu"},
{ "icelandic", "is"},
{ "indonesian", "id"},
{ "irish", "ga"},
{ "italian", "it"},
{ "japanese", "ja"},
{ "korean", "ko"},
{ "lao", "lo"},
{ "latin", "la"},
{ "latvian", "lv"},
{ "lithuanian", "lt"},
{ "macedonian", "mk"},
{ "malay", "ms"},
{ "maltese", "mt"},
{ "norwegian", "no"},
{ "persian", "fa"},
{ "polish", "pl"},
{ "portuguese", "pt"},
{ "romanian", "ro"},
{ "russian", "ru"},
{ "serbian", "sr"},
{ "slovak", "sk"},
{ "slovenian", "sl"},
{ "spanish", "es"},
{ "swahili", "sw"},
{ "swedish", "sv"},
{ "tamil", "ta"},
{ "telugu", "te"},
{ "thai", "th"},
{ "turkish", "tr"},
{ "ukrainian", "uk"},
{ "urdu", "ur"},
{ "vietnamese", "vi"},
{ "welsh", "cy"},
{ "yiddish", "yi"},
{ "af", "af"},
{ "sq", "sq"},
{ "ar", "ar"},
{ "hy", "hy"},
{ "az", "az"},
{ "eu", "eu"},
{ "be", "be"},
{ "bn", "bn"},
{ "bg", "bg"},
{ "ca", "ca"},
{ "zh-CN", "zh-CN"},
{ "hr", "hr"},
{ "cs", "cs"},
{ "da", "da"},
{ "nl", "nl"},
{ "en", "en"},
{ "eo", "eo"},
{ "et", "et"},
{ "tl", "tl"},
{ "fi", "fi"},
{ "fr", "fr"},
{ "gl", "gl"},
{ "de", "de"},
{ "ka", "ka"},
{ "el", "el"},
{ "ht", "ht"},
{ "iw", "iw"},
{ "hi", "hi"},
{ "hu", "hu"},
{ "is", "is"},
{ "id", "id"},
{ "ga", "ga"},
{ "it", "it"},
{ "ja", "ja"},
{ "ko", "ko"},
{ "lo", "lo"},
{ "la", "la"},
{ "lv", "lv"},
{ "lt", "lt"},
{ "mk", "mk"},
{ "ms", "ms"},
{ "mt", "mt"},
{ "no", "no"},
{ "fa", "fa"},
{ "pl", "pl"},
{ "pt", "pt"},
{ "ro", "ro"},
{ "ru", "ru"},
{ "sr", "sr"},
{ "sk", "sk"},
{ "sl", "sl"},
{ "es", "es"},
{ "sw", "sw"},
{ "sv", "sv"},
{ "ta", "ta"},
{ "te", "te"},
{ "th", "th"},
{ "tr", "tr"},
{ "uk", "uk"},
{ "ur", "ur"},
{ "vi", "vi"},
{ "cy", "cy"},
{ "yi", "yi"},
};
}
}
#endregion
#region Fields
/// <summary>
/// The language to translation mode map.
/// </summary>
public static Dictionary<string, string> _languageModeMap;
#endregion
}
}

View File

@@ -0,0 +1,45 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Modules.Translator.Helpers;
using System;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Translator
{
class TranslateCommand : DiscordCommand
{
public TranslateCommand(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "translate")
.Alias(Module.Prefix + "trans")
.Description($"Translates from>to text. From the given language to the destiation language. | `{Module.Prefix}trans en>fr Hello`")
.Parameter("langs", ParameterType.Required)
.Parameter("text", ParameterType.Unparsed)
.Do(TranslateFunc());
}
private GoogleTranslator t = new GoogleTranslator();
private Func<CommandEventArgs, Task> TranslateFunc() => async e =>
{
try
{
await e.Channel.SendIsTyping().ConfigureAwait(false);
string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0];
string to = e.GetArg("langs").ToLowerInvariant().Split('>')[1];
var text = e.GetArg("text")?.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
string translation = await t.Translate(text, from, to).ConfigureAwait(false);
await channel.SendMessageAsync(translation).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine(ex);
await channel.SendMessageAsync("Bad input format, or something went wrong...").ConfigureAwait(false);
}
};
}
}

View File

@@ -0,0 +1,27 @@
using Discord.Modules;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Classes;
namespace NadekoBot.Modules.Translator
{
internal class TranslatorModule : DiscordModule
{
public TranslatorModule()
{
commands.Add(new TranslateCommand(this));
commands.Add(new ValidLanguagesCommand(this));
}
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Searches;
public override void Install(ModuleManager manager)
{
manager.CreateCommands("", cgb =>
{
cgb.AddCheck(PermissionChecker.Instance);
commands.ForEach(cmd => cmd.Init(cgb));
});
}
}
}

View File

@@ -0,0 +1,50 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Modules.Translator.Helpers;
using System;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Translator
{
class ValidLanguagesCommand : DiscordCommand
{
public ValidLanguagesCommand(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "translangs")
.Description($"List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language`")
.Parameter("search", ParameterType.Optional)
.Do(ListLanguagesFunc());
}
private Func<CommandEventArgs, Task> ListLanguagesFunc() => async e =>
{
try
{
GoogleTranslator.EnsureInitialized();
string s = e.GetArg("search");
string ret = "";
foreach (string key in GoogleTranslator._languageModeMap.Keys)
{
if (!s.Equals(""))
{
if (key.ToLower().Contains(s))
{
ret += " " + key + ";";
}
}
else
{
ret += " " + key + ";";
}
}
await channel.SendMessageAsync(ret).ConfigureAwait(false);
}
catch
{
await channel.SendMessageAsync("Bad input format, or sth went wrong...").ConfigureAwait(false);
}
};
}
}

View File

@@ -19,6 +19,7 @@ namespace NadekoBot.Modules.Utility
{
public UtilityModule(ILocalization loc, CommandService cmds, IBotConfiguration config, IDiscordClient client) : base(loc, cmds, config, client)
{
}
[LocalizedCommand, LocalizedDescription, LocalizedSummary]