Initial split of the modules

This commit is contained in:
Master Kwoth
2017-09-30 00:46:33 +02:00
parent cdc2c43913
commit 599245b1ca
499 changed files with 469 additions and 256 deletions

View File

@ -0,0 +1,147 @@
using System;
using NadekoBot.Common;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services;
namespace NadekoBot.Services.Impl
{
public class BotConfigProvider : IBotConfigProvider
{
private readonly DbService _db;
public BotConfig BotConfig { get; private set; }
public BotConfigProvider(DbService db, BotConfig bc)
{
_db = db;
BotConfig = bc;
}
public void Reload()
{
using (var uow = _db.UnitOfWork)
{
BotConfig = uow.BotConfig.GetOrCreate();
}
}
public bool Edit(BotConfigEditType type, string newValue)
{
using (var uow = _db.UnitOfWork)
{
var bc = uow.BotConfig.GetOrCreate();
switch (type)
{
case BotConfigEditType.CurrencyGenerationChance:
if (float.TryParse(newValue, out var chance)
&& chance >= 0
&& chance <= 1)
{
bc.CurrencyGenerationChance = chance;
}
else
{
return false;
}
break;
case BotConfigEditType.CurrencyGenerationCooldown:
if (int.TryParse(newValue, out var cd) && cd >= 1)
{
bc.CurrencyGenerationCooldown = cd;
}
else
{
return false;
}
break;
case BotConfigEditType.CurrencyName:
bc.CurrencyName = newValue ?? "-";
break;
case BotConfigEditType.CurrencyPluralName:
bc.CurrencyPluralName = newValue ?? bc.CurrencyName + "s";
break;
case BotConfigEditType.CurrencySign:
bc.CurrencySign = newValue ?? "-";
break;
case BotConfigEditType.DmHelpString:
bc.DMHelpString = string.IsNullOrWhiteSpace(newValue)
? "-"
: newValue;
break;
case BotConfigEditType.HelpString:
bc.HelpString = string.IsNullOrWhiteSpace(newValue)
? "-"
: newValue;
break;
case BotConfigEditType.CurrencyDropAmount:
if (int.TryParse(newValue, out var amount) && amount > 0)
bc.CurrencyDropAmount = amount;
else
return false;
break;
case BotConfigEditType.CurrencyDropAmountMax:
if (newValue == null)
bc.CurrencyDropAmountMax = null;
else if (int.TryParse(newValue, out var maxAmount) && maxAmount > 0)
bc.CurrencyDropAmountMax = maxAmount;
else
return false;
break;
case BotConfigEditType.MinimumBetAmount:
if (int.TryParse(newValue, out var minBetAmount) && minBetAmount > 0)
bc.MinimumBetAmount = minBetAmount;
else
return false;
break;
case BotConfigEditType.TriviaCurrencyReward:
if (int.TryParse(newValue, out var triviaReward) && triviaReward > 0)
bc.TriviaCurrencyReward = triviaReward;
else
return false;
break;
case BotConfigEditType.Betroll100Multiplier:
if (float.TryParse(newValue, out var br100) && br100 > 0)
bc.Betroll100Multiplier = br100;
else
return false;
break;
case BotConfigEditType.Betroll91Multiplier:
if (int.TryParse(newValue, out var br91) && br91 > 0)
bc.Betroll91Multiplier = br91;
else
return false;
break;
case BotConfigEditType.Betroll67Multiplier:
if (int.TryParse(newValue, out var br67) && br67 > 0)
bc.Betroll67Multiplier = br67;
else
return false;
break;
case BotConfigEditType.BetflipMultiplier:
if (int.TryParse(newValue, out var bf) && bf > 0)
bc.BetflipMultiplier = bf;
else
return false;
break;
case BotConfigEditType.XpPerMessage:
if (int.TryParse(newValue, out var xp) && xp > 0)
bc.XpPerMessage = xp;
else
return false;
break;
case BotConfigEditType.XpMinutesTimeout:
if (int.TryParse(newValue, out var min) && min > 0)
bc.XpMinutesTimeout = min;
else
return false;
break;
default:
return false;
}
BotConfig = bc;
uow.Complete();
}
return true;
}
}
}

View File

@ -0,0 +1,149 @@
using Newtonsoft.Json;
using System;
using System.IO;
using Discord;
using System.Linq;
using NLog;
using Microsoft.Extensions.Configuration;
using System.Collections.Immutable;
using NadekoBot.Common;
namespace NadekoBot.Services.Impl
{
public class BotCredentials : IBotCredentials
{
private Logger _log;
public ulong ClientId { get; }
public string GoogleApiKey { get; }
public string MashapeKey { get; }
public string Token { get; }
public ImmutableArray<ulong> OwnerIds { get; }
public string LoLApiKey { get; }
public string OsuApiKey { get; }
public string CleverbotApiKey { get; }
public RestartConfig RestartCommand { get; }
public DBConfig Db { get; }
public int TotalShards { get; }
public string CarbonKey { get; }
private readonly string _credsFileName = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
public string PatreonAccessToken { get; }
public string ShardRunCommand { get; }
public string ShardRunArguments { get; }
public int ShardRunPort { get; }
public string PatreonCampaignId { get; }
public BotCredentials()
{
_log = LogManager.GetCurrentClassLogger();
try { File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); } catch { }
if(!File.Exists(_credsFileName))
_log.Warn($"credentials.json is missing. Attempting to load creds from environment variables prefixed with 'NadekoBot_'. Example is in {Path.GetFullPath("./credentials_example.json")}");
try
{
var configBuilder = new ConfigurationBuilder();
configBuilder.AddJsonFile(_credsFileName, true)
.AddEnvironmentVariables("NadekoBot_");
var data = configBuilder.Build();
Token = data[nameof(Token)];
if (string.IsNullOrWhiteSpace(Token))
{
_log.Error("Token is missing from credentials.json or Environment varibles. Add it and restart the program.");
Console.ReadKey();
Environment.Exit(3);
}
OwnerIds = data.GetSection("OwnerIds").GetChildren().Select(c => ulong.Parse(c.Value)).ToImmutableArray();
LoLApiKey = data[nameof(LoLApiKey)];
GoogleApiKey = data[nameof(GoogleApiKey)];
MashapeKey = data[nameof(MashapeKey)];
OsuApiKey = data[nameof(OsuApiKey)];
PatreonAccessToken = data[nameof(PatreonAccessToken)];
PatreonCampaignId = data[nameof(PatreonCampaignId)] ?? "334038";
ShardRunCommand = data[nameof(ShardRunCommand)];
ShardRunArguments = data[nameof(ShardRunArguments)];
CleverbotApiKey = data[nameof(CleverbotApiKey)];
var restartSection = data.GetSection(nameof(RestartCommand));
var cmd = restartSection["cmd"];
var args = restartSection["args"];
if (!string.IsNullOrWhiteSpace(cmd))
RestartCommand = new RestartConfig(cmd, args);
if (string.IsNullOrWhiteSpace(ShardRunCommand))
ShardRunCommand = "dotnet";
if (string.IsNullOrWhiteSpace(ShardRunArguments))
ShardRunArguments = "run -c Release -- {0} {1} {2}";
var portStr = data[nameof(ShardRunPort)];
if (string.IsNullOrWhiteSpace(portStr))
ShardRunPort = new NadekoRandom().Next(5000, 6000);
else
ShardRunPort = int.Parse(portStr);
int ts = 1;
int.TryParse(data[nameof(TotalShards)], out ts);
TotalShards = ts < 1 ? 1 : ts;
ulong.TryParse(data[nameof(ClientId)], out ulong clId);
ClientId = clId;
CarbonKey = data[nameof(CarbonKey)];
var dbSection = data.GetSection("db");
Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"])
? "sqlite"
: dbSection["Type"],
string.IsNullOrWhiteSpace(dbSection["ConnectionString"])
? "Data Source=data/NadekoBot.db"
: dbSection["ConnectionString"]);
}
catch (Exception ex)
{
_log.Fatal(ex.Message);
_log.Fatal(ex);
throw;
}
}
private class CredentialsModel
{
public ulong ClientId { get; set; } = 123123123;
public string Token { get; set; } = "";
public ulong[] OwnerIds { get; set; } = new ulong[1];
public string LoLApiKey { get; set; } = "";
public string GoogleApiKey { get; set; } = "";
public string MashapeKey { get; set; } = "";
public string OsuApiKey { get; set; } = "";
public string SoundCloudClientId { get; set; } = "";
public string CleverbotApiKey { get; } = "";
public string CarbonKey { get; set; } = "";
public DBConfig Db { get; set; } = new DBConfig("sqlite", "Data Source=data/NadekoBot.db");
public int TotalShards { get; set; } = 1;
public string PatreonAccessToken { get; set; } = "";
public string PatreonCampaignId { get; set; } = "334038";
public string RestartCommand { get; set; } = null;
public string ShardRunCommand { get; set; } = "";
public string ShardRunArguments { get; set; } = "";
public int? ShardRunPort { get; set; } = null;
}
private class DbModel
{
public string Type { get; set; }
public string ConnectionString { get; set; }
}
public bool IsOwner(IUser u) => OwnerIds.Contains(u.Id);
}
}

View File

@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Google.Apis.YouTube.v3;
using Google.Apis.Services;
using System.Text.RegularExpressions;
using Google.Apis.Urlshortener.v1;
using Google.Apis.Urlshortener.v1.Data;
using NLog;
using Google.Apis.Customsearch.v1;
using System.Net.Http;
using System.Net;
using Newtonsoft.Json.Linq;
using NadekoBot.Extensions;
namespace NadekoBot.Services.Impl
{
public class GoogleApiService : IGoogleApiService
{
const string search_engine_id = "018084019232060951019:hs5piey28-e";
private YouTubeService yt;
private UrlshortenerService sh;
private CustomsearchService cs;
private Logger _log { get; }
public GoogleApiService(IBotCredentials creds)
{
_creds = creds;
var bcs = new BaseClientService.Initializer
{
ApplicationName = "Nadeko Bot",
ApiKey = _creds.GoogleApiKey,
};
_log = LogManager.GetCurrentClassLogger();
yt = new YouTubeService(bcs);
sh = new UrlshortenerService(bcs);
cs = new CustomsearchService(bcs);
}
private static readonly Regex plRegex = new Regex("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)", RegexOptions.Compiled);
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(keywords))
throw new ArgumentNullException(nameof(keywords));
if (count <= 0)
throw new ArgumentOutOfRangeException(nameof(count));
var match = plRegex.Match(keywords);
if (match.Length > 1)
{
return new[] { match.Groups["id"].Value.ToString() };
}
var query = yt.Search.List("snippet");
query.MaxResults = count;
query.Type = "playlist";
query.Q = keywords;
return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId);
}
//private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?<id>[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled);
private readonly IBotCredentials _creds;
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id));
if (count <= 0)
throw new ArgumentOutOfRangeException(nameof(count));
var query = yt.Search.List("snippet");
query.MaxResults = count;
query.RelatedToVideoId = id;
query.Type = "video";
return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
}
public async Task<IEnumerable<string>> GetVideoLinksByKeywordAsync(string keywords, int count = 1)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(keywords))
throw new ArgumentNullException(nameof(keywords));
if (count <= 0)
throw new ArgumentOutOfRangeException(nameof(count));
var query = yt.Search.List("snippet");
query.MaxResults = count;
query.Q = keywords;
query.Type = "video";
return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
}
public async Task<IEnumerable<(string Name, string Id, string Url)>> GetVideoInfosByKeywordAsync(string keywords, int count = 1)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(keywords))
throw new ArgumentNullException(nameof(keywords));
if (count <= 0)
throw new ArgumentOutOfRangeException(nameof(count));
var query = yt.Search.List("snippet");
query.MaxResults = count;
query.Q = keywords;
query.Type = "video";
return (await query.ExecuteAsync()).Items.Select(i => (i.Snippet.Title.TrimTo(50), i.Id.VideoId, "http://www.youtube.com/watch?v=" + i.Id.VideoId));
}
public async Task<string> ShortenUrl(string url)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url));
if (string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
return url;
try
{
var response = await sh.Url.Insert(new Url { LongUrl = url }).ExecuteAsync();
return response.Id;
}
catch (Exception ex)
{
_log.Warn(ex);
return url;
}
}
public async Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(playlistId))
throw new ArgumentNullException(nameof(playlistId));
if (count <= 0)
throw new ArgumentOutOfRangeException(nameof(count));
string nextPageToken = null;
List<string> toReturn = new List<string>(count);
do
{
var toGet = count > 50 ? 50 : count;
count -= toGet;
var query = yt.PlaylistItems.List("contentDetails");
query.MaxResults = toGet;
query.PlaylistId = playlistId;
query.PageToken = nextPageToken;
var data = await query.ExecuteAsync();
toReturn.AddRange(data.Items.Select(i => i.ContentDetails.VideoId));
nextPageToken = data.NextPageToken;
}
while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken));
return toReturn;
}
public async Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds)
{
await Task.Yield();
var videoIdsList = videoIds as List<string> ?? videoIds.ToList();
Dictionary<string, TimeSpan> toReturn = new Dictionary<string, TimeSpan>();
if (!videoIdsList.Any())
return toReturn;
var toGet = 0;
var remaining = videoIdsList.Count;
do
{
toGet = remaining > 50 ? 50 : remaining;
remaining -= toGet;
var q = yt.Videos.List("contentDetails");
q.Id = string.Join(",", videoIdsList.Take(toGet));
videoIdsList = videoIdsList.Skip(toGet).ToList();
var items = (await q.ExecuteAsync().ConfigureAwait(false)).Items;
foreach (var i in items)
{
toReturn.Add(i.Id, System.Xml.XmlConvert.ToTimeSpan(i.ContentDetails.Duration));
}
}
while (remaining > 0);
return toReturn;
}
public async Task<ImageResult> GetImageAsync(string query, int start = 1)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
var req = cs.Cse.List(query);
req.Cx = search_engine_id;
req.Num = 1;
req.Fields = "items(image(contextLink,thumbnailLink),link)";
req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image;
req.Start = start;
var search = await req.ExecuteAsync().ConfigureAwait(false);
return new ImageResult(search.Items[0].Image, search.Items[0].Link);
}
public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
private readonly Dictionary<string, string> _languageDictionary = 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-traditional", "zh-TW"},
{ "chinese-simplified", "zh-CN"},
{ "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-tw", "zh-TW"},
{ "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"},
};
public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
{
await Task.Yield();
string text;
if (!_languageDictionary.ContainsKey(sourceLanguage) ||
!_languageDictionary.ContainsKey(targetLanguage))
throw new ArgumentException();
var url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
ConvertToLanguageCode(sourceLanguage),
ConvertToLanguageCode(targetLanguage),
WebUtility.UrlEncode(sourceText));
using (var 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])));
}
private string ConvertToLanguageCode(string language)
{
_languageDictionary.TryGetValue(language, out var mode);
return mode;
}
}
}

View File

@ -0,0 +1,93 @@
using NLog;
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
namespace NadekoBot.Services.Impl
{
public class ImagesService : IImagesService
{
private readonly Logger _log;
private const string _basePath = "data/images/";
private const string _headsPath = _basePath + "coins/heads.png";
private const string _tailsPath = _basePath + "coins/tails.png";
private const string _currencyImagesPath = _basePath + "currency";
private const string _diceImagesPath = _basePath + "dice";
private const string _slotBackgroundPath = _basePath + "slots/background2.png";
private const string _slotNumbersPath = _basePath + "slots/numbers/";
private const string _slotEmojisPath = _basePath + "slots/emojis/";
private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png";
private const string _rategirlDot = _basePath + "rategirl/dot.png";
private const string _xpCardPath = _basePath + "xp/xp.png";
public ImmutableArray<byte> Heads { get; private set; }
public ImmutableArray<byte> Tails { get; private set; }
public ImmutableArray<(string, ImmutableArray<byte>)> Currency { get; private set; }
public ImmutableArray<ImmutableArray<byte>> Dice { get; private set; }
public ImmutableArray<byte> SlotBackground { get; private set; }
public ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; private set; }
public ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; private set; }
public ImmutableArray<byte> WifeMatrix { get; private set; }
public ImmutableArray<byte> RategirlDot { get; private set; }
public ImmutableArray<byte> XpCard { get; private set; }
public ImagesService()
{
_log = LogManager.GetCurrentClassLogger();
this.Reload();
}
public void Reload()
{
try
{
Heads = File.ReadAllBytes(_headsPath).ToImmutableArray();
Tails = File.ReadAllBytes(_tailsPath).ToImmutableArray();
Currency = Directory.GetFiles(_currencyImagesPath)
.Select(x => (Path.GetFileName(x), File.ReadAllBytes(x).ToImmutableArray()))
.ToImmutableArray();
Dice = Directory.GetFiles(_diceImagesPath)
.OrderBy(x => int.Parse(Path.GetFileNameWithoutExtension(x)))
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
SlotBackground = File.ReadAllBytes(_slotBackgroundPath).ToImmutableArray();
SlotNumbers = Directory.GetFiles(_slotNumbersPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
SlotEmojis = Directory.GetFiles(_slotEmojisPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray();
RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray();
XpCard = File.ReadAllBytes(_xpCardPath).ToImmutableArray();
}
catch (Exception ex)
{
_log.Error(ex);
throw;
}
}
}
}

View File

@ -0,0 +1,146 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Discord;
using NLog;
using NadekoBot.Services.Database.Models;
using NadekoBot.Common;
using Newtonsoft.Json;
using System.IO;
namespace NadekoBot.Services.Impl
{
public class Localization : ILocalization
{
private readonly Logger _log;
private readonly DbService _db;
public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture;
private static readonly Dictionary<string, CommandData> _commandData;
static Localization()
{
_commandData = JsonConvert.DeserializeObject<Dictionary<string, CommandData>>(
File.ReadAllText("./data/command_strings.json"));
}
private Localization() { }
public Localization(IBotConfigProvider bcp, IEnumerable<GuildConfig> gcs, DbService db)
{
_log = LogManager.GetCurrentClassLogger();
var cultureInfoNames = gcs.ToDictionary(x => x.GuildId, x => x.Locale);
var defaultCulture = bcp.BotConfig.Locale;
_db = db;
if (string.IsNullOrWhiteSpace(defaultCulture))
DefaultCultureInfo = new CultureInfo("en-US");
else
{
try
{
DefaultCultureInfo = new CultureInfo(defaultCulture);
}
catch
{
_log.Warn("Unable to load default bot's locale/language. Using en-US.");
DefaultCultureInfo = new CultureInfo("en-US");
}
}
GuildCultureInfos = new ConcurrentDictionary<ulong, CultureInfo>(cultureInfoNames.ToDictionary(x => x.Key, x =>
{
CultureInfo cultureInfo = null;
try
{
if (x.Value == null)
return null;
cultureInfo = new CultureInfo(x.Value);
}
catch { }
return cultureInfo;
}).Where(x => x.Value != null));
}
public void SetGuildCulture(IGuild guild, CultureInfo ci) =>
SetGuildCulture(guild.Id, ci);
public void SetGuildCulture(ulong guildId, CultureInfo ci)
{
if (ci == DefaultCultureInfo)
{
RemoveGuildCulture(guildId);
return;
}
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set);
gc.Locale = ci.Name;
uow.Complete();
}
GuildCultureInfos.AddOrUpdate(guildId, ci, (id, old) => ci);
}
public void RemoveGuildCulture(IGuild guild) =>
RemoveGuildCulture(guild.Id);
public void RemoveGuildCulture(ulong guildId) {
if (GuildCultureInfos.TryRemove(guildId, out var _))
{
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set);
gc.Locale = null;
uow.Complete();
}
}
}
public void SetDefaultCulture(CultureInfo ci)
{
using (var uow = _db.UnitOfWork)
{
var bc = uow.BotConfig.GetOrCreate();
bc.Locale = ci.Name;
uow.Complete();
}
DefaultCultureInfo = ci;
}
public void ResetDefaultCulture() =>
SetDefaultCulture(CultureInfo.CurrentCulture);
public CultureInfo GetCultureInfo(IGuild guild) =>
GetCultureInfo(guild?.Id);
public CultureInfo GetCultureInfo(ulong? guildId)
{
if (guildId == null)
return DefaultCultureInfo;
CultureInfo info = null;
GuildCultureInfos.TryGetValue(guildId.Value, out info);
return info ?? DefaultCultureInfo;
}
public static CommandData LoadCommand(string key)
{
_commandData.TryGetValue(key, out var toReturn);
if (toReturn == null)
return new CommandData
{
Cmd = key,
Desc = key,
Usage = new[] { key },
};
return toReturn;
}
}
}

View File

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using NLog;
namespace NadekoBot.Services.Impl
{
public class NadekoStrings : INService
{
public const string stringsPath = @"_strings/";
private readonly ImmutableDictionary<string, ImmutableDictionary<string, string>> responseStrings;
private readonly Logger _log;
/// <summary>
/// Used as failsafe in case response key doesn't exist in the selected or default language.
/// </summary>
private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
private readonly ILocalization _localization;
private readonly Regex formatFinder = new Regex(@"{\d}", RegexOptions.Compiled);
public NadekoStrings(ILocalization loc)
{
_log = LogManager.GetCurrentClassLogger();
_localization = loc;
var sw = Stopwatch.StartNew();
var allLangsDict = new Dictionary<string, ImmutableDictionary<string, string>>(); // lang:(name:value)
foreach (var file in Directory.GetFiles(stringsPath))
{
var langDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(file));
allLangsDict.Add(GetLocaleName(file).ToLowerInvariant(), langDict.ToImmutableDictionary());
}
responseStrings = allLangsDict.ToImmutableDictionary();
sw.Stop();
_log.Info("Loaded {0} languages in {1:F2}s",
responseStrings.Count,
//string.Join(",", responseStrings.Keys),
sw.Elapsed.TotalSeconds);
////improper string format checks
//var compareTo = responseStrings["en-us"]
// .Select(x =>
// {
// return (StringKey: x.Key, Placeholders: formatFinder.Matches(x.Value).Cast<Match>().Select(y => y.Value).ToArray());
// })
// .ToDictionary(x => x.StringKey, x => x.Placeholders);
//var errors = responseStrings
// .Select(a => (a.Key, a.Value.Select(x =>
// {
// if (!compareTo.ContainsKey(x.Key))
// return (StringKey: x.Key, Placeholders: new HashSet<string>(), Missing: true);
// var hs = new HashSet<string>(compareTo[x.Key]);
// hs.SymmetricExceptWith(formatFinder.Matches(x.Value).Cast<Match>().Select(y => y.Value).ToArray());
// return (StringKey: x.Key, Placeholders: hs, Missing: false);
// })
// .Where(x => x.Placeholders.Any() || x.Missing)))
// .Where(x => x.Item2.Any());
//var str = string.Join("\n", errors.Select(x => $"------{x.Item1}------\n" +
// string.Join("\n", x.Item2.Select(y =>
// y.StringKey + ": " + (y.Missing ? "MISSING" : string.Join(", ", y.Placeholders))))));
//if (!string.IsNullOrWhiteSpace(str))
// _log.Warn($"Improperly Formatted strings:\n{str}");
}
private string GetLocaleName(string fileName)
{
var dotIndex = fileName.IndexOf('.') + 1;
var secondDotINdex = fileName.LastIndexOf('.');
return fileName.Substring(dotIndex, secondDotINdex - dotIndex);
}
private string GetString(string text, CultureInfo cultureInfo)
{
if (!responseStrings.TryGetValue(cultureInfo.Name.ToLowerInvariant(), out ImmutableDictionary<string, string> strings))
return null;
strings.TryGetValue(text, out string val);
return val;
}
public string GetText(string key, ulong? guildId, string lowerModuleTypeName, params object[] replacements) =>
GetText(key, _localization.GetCultureInfo(guildId), lowerModuleTypeName, replacements);
public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
{
var text = GetString(lowerModuleTypeName + "_" + key, cultureInfo);
if (string.IsNullOrWhiteSpace(text))
{
LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
text = GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
if (string.IsNullOrWhiteSpace(text))
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
}
return text;
}
public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName,
params object[] replacements)
{
try
{
return string.Format(GetText(key, cultureInfo, lowerModuleTypeName), replacements);
}
catch (FormatException)
{
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
}
}
}
}

View File

@ -0,0 +1,43 @@
using StackExchange.Redis;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class RedisCache : IDataCache
{
private ulong _botid;
public ConnectionMultiplexer Redis { get; }
private readonly IDatabase _db;
public RedisCache(ulong botId)
{
_botid = botId;
Redis = ConnectionMultiplexer.Connect("127.0.0.1");
Redis.PreserveAsyncOrder = false;
_db = Redis.GetDatabase();
}
public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key)
{
byte[] x = await _db.StringGetAsync("image_" + key);
return (x != null, x);
}
public Task SetImageDataAsync(string key, byte[] data)
{
return _db.StringSetAsync("image_" + key, data);
}
public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key)
{
string x = await _db.StringGetAsync("anime_" + key);
return (x != null, x);
}
public Task SetAnimeDataAsync(string key, string data)
{
return _db.StringSetAsync("anime_" + key, data);
}
}
}

View File

@ -0,0 +1,143 @@
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class SoundCloudApiService : INService
{
private readonly IBotCredentials _creds;
public SoundCloudApiService(IBotCredentials creds)
{
_creds = creds;
}
public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
{
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url));
string response = "";
using (var http = new HttpClient())
{
response = await http.GetStringAsync($"https://scapi.nadekobot.me/resolve?url={url}").ConfigureAwait(false);
}
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo>(response);
if (responseObj?.Kind != "track")
throw new InvalidOperationException("Url is either not a track, or it doesn't exist.");
return responseObj;
}
public bool IsSoundCloudLink(string url) =>
System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)");
public async Task<SoundCloudVideo> GetVideoByQueryAsync(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
var response = "";
using (var http = new HttpClient())
{
response = await http.GetStringAsync($"https://scapi.nadekobot.me/tracks?q={Uri.EscapeDataString(query)}").ConfigureAwait(false);
}
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response).Where(s => s.Streamable).FirstOrDefault();
if (responseObj?.Kind != "track")
throw new InvalidOperationException("Query yielded no results.");
return responseObj;
}
}
public class SoundCloudVideo
{
public string Kind { get; set; } = "";
public long Id { get; set; } = 0;
public SoundCloudUser User { get; set; } = new SoundCloudUser();
public string Title { get; set; } = "";
[JsonIgnore]
public string FullName => User.Name + " - " + Title;
public bool Streamable { get; set; } = false;
public int Duration { get; set; }
[JsonProperty("permalink_url")]
public string TrackLink { get; set; } = "";
public string artwork_url { get; set; } = "";
public async Task<string> StreamLink()
{
using (var http = new HttpClient())
{
return await http.GetStringAsync($"http://scapi.nadekobot.me/stream/{Id}");
}
}
}
public class SoundCloudUser
{
[JsonProperty("username")]
public string Name { get; set; }
}
/*
{"kind":"track",
"id":238888167,
"created_at":"2015/12/24 01:04:52 +0000",
"user_id":43141975,
"duration":120852,
"commentable":true,
"state":"finished",
"original_content_size":4834829,
"last_modified":"2015/12/24 01:17:59 +0000",
"sharing":"public",
"tag_list":"Funky",
"permalink":"18-fd",
"streamable":true,
"embeddable_by":"all",
"downloadable":false,
"purchase_url":null,
"label_id":null,
"purchase_title":null,
"genre":"Disco",
"title":"18 Ж",
"description":"",
"label_name":null,
"release":null,
"track_type":null,
"key_signature":null,
"isrc":null,
"video_url":null,
"bpm":null,
"release_year":null,
"release_month":null,
"release_day":null,
"original_format":"mp3",
"license":"all-rights-reserved",
"uri":"https://api.soundcloud.com/tracks/238888167",
"user":{
"id":43141975,
"kind":"user",
"permalink":"mrb00gi",
"username":"Mrb00gi",
"last_modified":"2015/12/01 16:06:57 +0000",
"uri":"https://api.soundcloud.com/users/43141975",
"permalink_url":"http://soundcloud.com/mrb00gi",
"avatar_url":"https://a1.sndcdn.com/images/default_avatar_large.png"
},
"permalink_url":"http://soundcloud.com/mrb00gi/18-fd",
"artwork_url":null,
"waveform_url":"https://w1.sndcdn.com/gsdLfvEW1cUK_m.png",
"stream_url":"https://api.soundcloud.com/tracks/238888167/stream",
"playback_count":7,
"download_count":0,
"favoritings_count":1,
"comment_count":0,
"attachments_uri":"https://api.soundcloud.com/tracks/238888167/attachments"}
*/
}

View File

@ -0,0 +1,24 @@
using Discord.WebSocket;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Collections;
namespace NadekoBot.Services.Impl
{
public class StartingGuildsService : IEnumerable<long>, INService
{
private readonly ImmutableList<long> _guilds;
public StartingGuildsService(DiscordSocketClient client)
{
this._guilds = client.Guilds.Select(x => (long)x.Id).ToImmutableList();
}
public IEnumerator<long> GetEnumerator() =>
_guilds.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
_guilds.GetEnumerator();
}
}

View File

@ -0,0 +1,229 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class StatsService : IStatsService
{
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "1.10.2";
public string Author => "Kwoth#2560";
public string Library => "Discord.Net";
public string Heap =>
Math.Round((double)GC.GetTotalMemory(false) / 1.MiB(), 2).ToString(CultureInfo.InvariantCulture);
public double MessagesPerSecond => MessageCounter / GetUptime().TotalSeconds;
private long _textChannels;
public long TextChannels => Interlocked.Read(ref _textChannels);
private long _voiceChannels;
public long VoiceChannels => Interlocked.Read(ref _voiceChannels);
private long _messageCounter;
public long MessageCounter => Interlocked.Read(ref _messageCounter);
private long _commandsRan;
public long CommandsRan => Interlocked.Read(ref _commandsRan);
private readonly Timer _carbonitexTimer;
private readonly Timer _dataTimer;
private readonly ShardsCoordinator _sc;
public int GuildCount =>
_sc?.GuildCount ?? _client.Guilds.Count();
public StatsService(DiscordSocketClient client, CommandHandler cmdHandler, IBotCredentials creds, NadekoBot nadeko)
{
_client = client;
_creds = creds;
_sc = nadeko.ShardCoord;
_started = DateTime.UtcNow;
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
cmdHandler.CommandExecuted += (_, e) => Task.FromResult(Interlocked.Increment(ref _commandsRan));
_client.ChannelCreated += (c) =>
{
var _ = Task.Run(() =>
{
if (c is ITextChannel)
Interlocked.Increment(ref _textChannels);
else if (c is IVoiceChannel)
Interlocked.Increment(ref _voiceChannels);
});
return Task.CompletedTask;
};
_client.ChannelDestroyed += (c) =>
{
var _ = Task.Run(() =>
{
if (c is ITextChannel)
Interlocked.Decrement(ref _textChannels);
else if (c is IVoiceChannel)
Interlocked.Decrement(ref _voiceChannels);
});
return Task.CompletedTask;
};
_client.GuildAvailable += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, tc);
Interlocked.Add(ref _voiceChannels, vc);
});
return Task.CompletedTask;
};
_client.JoinedGuild += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, tc);
Interlocked.Add(ref _voiceChannels, vc);
});
return Task.CompletedTask;
};
_client.GuildUnavailable += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, -tc);
Interlocked.Add(ref _voiceChannels, -vc);
});
return Task.CompletedTask;
};
_client.LeftGuild += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, -tc);
Interlocked.Add(ref _voiceChannels, -vc);
});
return Task.CompletedTask;
};
if (_sc != null)
{
_carbonitexTimer = new Timer(async (state) =>
{
if (string.IsNullOrWhiteSpace(_creds.CarbonKey))
return;
try
{
using (var http = new HttpClient())
{
using (var content = new FormUrlEncodedContent(
new Dictionary<string, string> {
{ "servercount", _sc.GuildCount.ToString() },
{ "key", _creds.CarbonKey }}))
{
content.Headers.Clear();
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false);
}
}
}
catch
{
// ignored
}
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
var platform = "other";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
platform = "linux";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
platform = "osx";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
platform = "windows";
_dataTimer = new Timer(async (state) =>
{
try
{
using (var http = new HttpClient())
{
using (var content = new FormUrlEncodedContent(
new Dictionary<string, string> {
{ "id", string.Concat(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(_creds.ClientId.ToString())).Select(x => x.ToString("X2"))) },
{ "guildCount", _sc.GuildCount.ToString() },
{ "version", BotVersion },
{ "platform", platform }}))
{
content.Headers.Clear();
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
await http.PostAsync("https://selfstats.nadekobot.me/", content).ConfigureAwait(false);
}
}
}
catch
{
// ignored
}
}, null, TimeSpan.FromSeconds(1), TimeSpan.FromHours(1));
}
}
public void Initialize()
{
var guilds = _client.Guilds.ToArray();
_textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel));
_voiceChannels = guilds.Sum(g => g.Channels.Count) - _textChannels;
}
public Task<string> Print()
{
SocketSelfUser curUser;
while ((curUser = _client.CurrentUser) == null) Task.Delay(1000).ConfigureAwait(false);
return Task.FromResult($@"
Author: [{Author}] | Library: [{Library}]
Bot Version: [{BotVersion}]
Bot ID: {curUser.Id}
Owner ID(s): {string.Join(", ", _creds.OwnerIds)}
Uptime: {GetUptimeString()}
Servers: {_client.Guilds.Count} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels}
Commands Ran this session: {CommandsRan}
Messages: {MessageCounter} [{MessagesPerSecond:F2}/sec] Heap: [{Heap} MB]");
}
public TimeSpan GetUptime() =>
DateTime.UtcNow - _started;
public string GetUptimeString(string separator = ", ")
{
var time = GetUptime();
return $"{time.Days} days{separator}{time.Hours} hours{separator}{time.Minutes} minutes";
}
}
}

View File

@ -0,0 +1,7 @@
namespace NadekoBot.Services.Impl
{
public class SyncPreconditionService
{
}
}

View File

@ -0,0 +1,46 @@
using NLog;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class YtdlOperation : IDisposable
{
private readonly Logger _log;
public YtdlOperation()
{
_log = LogManager.GetCurrentClassLogger();
}
public async Task<string> GetDataAsync(string url)
{
using (Process process = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "youtube-dl",
Arguments = $"-f bestaudio -e --get-url --get-id --get-thumbnail --get-duration --no-check-certificate --default-search \"ytsearch:\" \"{url}\"",
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
},
})
{
process.Start();
var str = await process.StandardOutput.ReadToEndAsync();
var err = await process.StandardError.ReadToEndAsync();
if(!string.IsNullOrEmpty(err))
_log.Warn(err);
return str;
}
}
public void Dispose()
{
}
}
}