Applied codestyle to all .cs files

This commit is contained in:
Kwoth
2021-12-29 06:07:16 +01:00
parent 723447c7d4
commit 82000c97a4
543 changed files with 13221 additions and 14059 deletions

View File

@@ -1,6 +1,6 @@
#nullable disable
using NadekoBot.Common.ModuleBehaviors;
using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.ModuleBehaviors;
namespace NadekoBot.Services;
@@ -19,20 +19,15 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
{
_lateExecutors = _services.GetServices<ILateExecutor>();
_lateBlockers = _services.GetServices<ILateBlocker>();
_earlyBehaviors = _services.GetServices<IEarlyBehavior>()
.OrderByDescending(x => x.Priority);
_earlyBehaviors = _services.GetServices<IEarlyBehavior>().OrderByDescending(x => x.Priority);
_transformers = _services.GetServices<IInputTransformer>();
}
public async Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg)
{
foreach (var beh in _earlyBehaviors)
{
if (await beh.RunBehavior(guild, usrMsg))
{
return true;
}
}
return false;
}
@@ -43,7 +38,7 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
foreach (var exec in _transformers)
{
string newContent;
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
!= messageContent.ToLowerInvariant())
{
messageContent = newContent;
@@ -57,16 +52,14 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
public async Task<bool> RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd)
{
foreach (var exec in _lateBlockers)
{
if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd))
{
Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]",
Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]",
ctx.User,
cmd.Aliases[0],
exec.GetType().Name);
return true;
}
}
return false;
}
@@ -74,17 +67,13 @@ public sealed class BehaviorExecutor : IBehaviourExecutor, INService
public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg)
{
foreach (var exec in _lateExecutors)
{
try
{
await exec.LateExecute(guild, usrMsg);
}
catch (Exception ex)
{
Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}",
exec.GetType().Name,
ex.Message);
Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}", exec.GetType().Name, ex.Message);
}
}
}
}
}

View File

@@ -12,63 +12,81 @@ public interface IBotCredsProvider
public IBotCredentials GetCreds();
public void ModifyCredsFile(Action<Creds> func);
}
public sealed class BotCredsProvider : IBotCredsProvider
{
private readonly int? _totalShards;
private const string _credsFileName = "creds.yml";
private const string _credsExampleFileName = "creds_example.yml";
private string CredsPath => Path.Combine(Directory.GetCurrentDirectory(), _credsFileName);
private string CredsExamplePath => Path.Combine(Directory.GetCurrentDirectory(), _credsExampleFileName);
private string OldCredsJsonPath => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
private string OldCredsJsonBackupPath => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak");
private string CredsPath
=> Path.Combine(Directory.GetCurrentDirectory(), _credsFileName);
private string CredsExamplePath
=> Path.Combine(Directory.GetCurrentDirectory(), _credsExampleFileName);
private string OldCredsJsonPath
=> Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
private string OldCredsJsonBackupPath
=> Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak");
private readonly int? _totalShards;
private readonly Creds _creds = new();
private readonly IConfigurationRoot _config;
private readonly object reloadLock = new();
public BotCredsProvider(int? totalShards = null)
{
_totalShards = totalShards;
if (!File.Exists(CredsExamplePath)) File.WriteAllText(CredsExamplePath, Yaml.Serializer.Serialize(_creds));
MigrateCredentials();
if (!File.Exists(CredsPath))
Log.Warning($"{CredsPath} is missing. "
+ "Attempting to load creds from environment variables prefixed with 'NadekoBot_'. "
+ $"Example is in {CredsExamplePath}");
_config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true)
.AddEnvironmentVariables("NadekoBot_")
.Build();
ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
Reload();
}
public void Reload()
{
lock (reloadLock)
{
_creds.OwnerIds.Clear();
_config.Bind(_creds);
if (string.IsNullOrWhiteSpace(_creds.Token))
{
Log.Error("Token is missing from creds.yml or Environment variables.\n" +
"Add it and restart the program.");
Log.Error("Token is missing from creds.yml or Environment variables.\n"
+ "Add it and restart the program.");
Helpers.ReadErrorAndExit(5);
return;
}
if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd)
|| string.IsNullOrWhiteSpace(_creds.RestartCommand?.Args))
{
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
_creds.RestartCommand = new()
{
Args = "dotnet",
Cmd = "NadekoBot.dll -- {0}",
};
}
_creds.RestartCommand = new() { Args = "dotnet", Cmd = "NadekoBot.dll -- {0}" };
else
{
_creds.RestartCommand = new()
{
Args = "NadekoBot.exe",
Cmd = "{0}",
};
}
_creds.RestartCommand = new() { Args = "NadekoBot.exe", Cmd = "{0}" };
}
if (string.IsNullOrWhiteSpace(_creds.RedisOptions))
_creds.RedisOptions = "127.0.0.1,syncTimeout=3000";
if (string.IsNullOrWhiteSpace(_creds.CoinmarketcapApiKey))
_creds.CoinmarketcapApiKey = "e79ec505-0913-439d-ae07-069e296a6079";
@@ -76,35 +94,6 @@ public sealed class BotCredsProvider : IBotCredsProvider
}
}
public BotCredsProvider(int? totalShards = null)
{
_totalShards = totalShards;
if (!File.Exists(CredsExamplePath))
{
File.WriteAllText(CredsExamplePath, Yaml.Serializer.Serialize(_creds));
}
MigrateCredentials();
if (!File.Exists(CredsPath))
{
Log.Warning($"{CredsPath} is missing. " +
$"Attempting to load creds from environment variables prefixed with 'NadekoBot_'. " +
$"Example is in {CredsExamplePath}");
}
_config = new ConfigurationBuilder()
.AddYamlFile(CredsPath, false, true)
.AddEnvironmentVariables("NadekoBot_")
.Build();
ChangeToken.OnChange(
() => _config.GetReloadToken(),
Reload);
Reload();
}
public void ModifyCredsFile(Action<Creds> func)
{
var ymlData = File.ReadAllText(_credsFileName);
@@ -114,13 +103,13 @@ public sealed class BotCredsProvider : IBotCredsProvider
ymlData = Yaml.Serializer.Serialize(creds);
File.WriteAllText(_credsFileName, ymlData);
Reload();
}
/// <summary>
/// Checks if there's a V2 credentials file present, loads it if it exists,
/// converts it to new model, and saves it to YAML. Also backs up old credentials to credentials.json.bak
/// Checks if there's a V2 credentials file present, loads it if it exists,
/// converts it to new model, and saves it to YAML. Also backs up old credentials to credentials.json.bak
/// </summary>
private void MigrateCredentials()
{
@@ -140,25 +129,20 @@ public sealed class BotCredsProvider : IBotCredsProvider
OsuApiKey = oldCreds.OsuApiKey,
CleverbotApiKey = oldCreds.CleverbotApiKey,
TotalShards = oldCreds.TotalShards <= 1 ? 1 : oldCreds.TotalShards,
Patreon = new(oldCreds.PatreonAccessToken,
null,
null,
oldCreds.PatreonCampaignId),
Votes = new(oldCreds.VotesUrl,
oldCreds.VotesToken,
string.Empty,
string.Empty),
Patreon = new(oldCreds.PatreonAccessToken, null, null, oldCreds.PatreonCampaignId),
Votes = new(oldCreds.VotesUrl, oldCreds.VotesToken, string.Empty, string.Empty),
BotListToken = oldCreds.BotListToken,
RedisOptions = oldCreds.RedisOptions,
LocationIqApiKey = oldCreds.LocationIqApiKey,
TimezoneDbApiKey = oldCreds.TimezoneDbApiKey,
CoinmarketcapApiKey = oldCreds.CoinmarketcapApiKey,
CoinmarketcapApiKey = oldCreds.CoinmarketcapApiKey
};
File.Move(OldCredsJsonPath, OldCredsJsonBackupPath, true);
File.WriteAllText(CredsPath, Yaml.Serializer.Serialize(creds));
Log.Warning("Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness");
Log.Warning(
"Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness");
}
if (File.Exists(_credsFileName))
@@ -170,8 +154,8 @@ public sealed class BotCredsProvider : IBotCredsProvider
File.WriteAllText(_credsFileName, Yaml.Serializer.Serialize(creds));
}
}
}
public IBotCredentials GetCreds() => _creds;
}
public IBotCredentials GetCreds()
=> _creds;
}

View File

@@ -1,8 +1,8 @@
#nullable disable
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services;
@@ -13,7 +13,11 @@ public class CurrencyService : ICurrencyService, INService
private readonly IEmbedBuilderService _eb;
private readonly IUser _bot;
public CurrencyService(DbService db, DiscordSocketClient c, GamblingConfigService gss, IEmbedBuilderService eb)
public CurrencyService(
DbService db,
DiscordSocketClient c,
GamblingConfigService gss,
IEmbedBuilderService eb)
{
_db = db;
_gss = gss;
@@ -21,16 +25,18 @@ public class CurrencyService : ICurrencyService, INService
_bot = c.CurrentUser;
}
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount) =>
new()
{
Amount = amount,
UserId = userId,
Reason = reason ?? "-",
};
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount)
=> new() { Amount = amount, UserId = userId, Reason = reason ?? "-" };
private bool InternalChange(ulong userId, string userName, string discrim, string avatar,
string reason, long amount, bool gamble, NadekoContext uow)
private bool InternalChange(
ulong userId,
string userName,
string discrim,
string avatar,
string reason,
long amount,
bool gamble,
NadekoContext uow)
{
var result = uow.TryUpdateCurrencyState(userId, userName, discrim, avatar, amount);
if (result)
@@ -45,47 +51,64 @@ public class CurrencyService : ICurrencyService, INService
uow.TryUpdateCurrencyState(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId, -amount, true);
}
}
return result;
}
private async Task InternalAddAsync(ulong userId, string userName, string discrim, string avatar, string reason, long amount, bool gamble)
private async Task InternalAddAsync(
ulong userId,
string userName,
string discrim,
string avatar,
string reason,
long amount,
bool gamble)
{
if (amount < 0)
{
throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.", nameof(amount));
}
throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.",
nameof(amount));
await using var uow = _db.GetDbContext();
InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
await uow.SaveChangesAsync();
}
public Task AddAsync(ulong userId, string reason, long amount, bool gamble = false)
public Task AddAsync(
ulong userId,
string reason,
long amount,
bool gamble = false)
=> InternalAddAsync(userId, null, null, null, reason, amount, gamble);
public async Task AddAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false)
public async Task AddAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false)
{
await InternalAddAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
if (sendMessage)
{
try
{
var sign = _gss.Data.Currency.Sign;
await user
.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle($"Received Currency")
.AddField("Amount", amount + sign)
.AddField("Reason", reason));
await user.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("Received Currency")
.AddField("Amount", amount + sign)
.AddField("Reason", reason));
}
catch
{
// ignored
}
}
}
public async Task AddBulkAsync(IEnumerable<ulong> userIds, IEnumerable<string> reasons, IEnumerable<long> amounts, bool gamble = false)
public async Task AddBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
@@ -97,15 +120,17 @@ public class CurrencyService : ICurrencyService, INService
var userIdHashSet = new HashSet<ulong>(idArray.Length);
await using var uow = _db.GetDbContext();
for (var i = 0; i < idArray.Length; i++)
{
// i have to prevent same user changing more than once as it will cause db error
if (userIdHashSet.Add(idArray[i]))
InternalChange(idArray[i], null, null, null, reasonArray[i], amountArray[i], gamble, uow);
}
await uow.SaveChangesAsync();
}
public async Task RemoveBulkAsync(IEnumerable<ulong> userIds, IEnumerable<string> reasons, IEnumerable<long> amounts, bool gamble = false)
public async Task RemoveBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
@@ -117,20 +142,24 @@ public class CurrencyService : ICurrencyService, INService
var userIdHashSet = new HashSet<ulong>(idArray.Length);
await using var uow = _db.GetDbContext();
for (var i = 0; i < idArray.Length; i++)
{
// i have to prevent same user changing more than once as it will cause db error
if (userIdHashSet.Add(idArray[i]))
InternalChange(idArray[i], null, null, null, reasonArray[i], -amountArray[i], gamble, uow);
}
await uow.SaveChangesAsync();
}
private async Task<bool> InternalRemoveAsync(ulong userId, string userName, string userDiscrim, string avatar, string reason, long amount, bool gamble = false)
private async Task<bool> InternalRemoveAsync(
ulong userId,
string userName,
string userDiscrim,
string avatar,
string reason,
long amount,
bool gamble = false)
{
if (amount < 0)
{
throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.", nameof(amount));
}
throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.",
nameof(amount));
bool result;
await using var uow = _db.GetDbContext();
@@ -139,9 +168,18 @@ public class CurrencyService : ICurrencyService, INService
return result;
}
public Task<bool> RemoveAsync(ulong userId, string reason, long amount, bool gamble = false)
public Task<bool> RemoveAsync(
ulong userId,
string reason,
long amount,
bool gamble = false)
=> InternalRemoveAsync(userId, null, null, null, reason, amount, gamble);
public Task<bool> RemoveAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false)
public Task<bool> RemoveAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false)
=> InternalRemoveAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
}
}

View File

@@ -5,6 +5,19 @@ namespace NadekoBot.Services;
public class FontProvider : INService
{
public FontFamily DottyFont { get; }
public FontFamily UniSans { get; }
public FontFamily NotoSans { get; }
//public FontFamily Emojis { get; }
/// <summary>
/// Font used for .rip command
/// </summary>
public Font RipFont { get; }
public List<FontFamily> FallBackFonts { get; }
private readonly FontCollection _fonts;
public FontProvider()
@@ -20,44 +33,23 @@ public class FontProvider : INService
// try loading some emoji and jap fonts on windows as fallback fonts
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
try
{
var fontsfolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts);
var fontsfolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
FallBackFonts.Add(_fonts.Install(Path.Combine(fontsfolder, "seguiemj.ttf")));
FallBackFonts.AddRange(_fonts.InstallCollection(Path.Combine(fontsfolder, "msgothic.ttc")));
FallBackFonts.AddRange(_fonts.InstallCollection(Path.Combine(fontsfolder, "segoe.ttc")));
}
catch { }
}
// any fonts present in data/fonts should be added as fallback fonts
// this will allow support for special characters when drawing text
foreach (var font in Directory.GetFiles(@"data/fonts"))
{
if (font.EndsWith(".ttf"))
{
FallBackFonts.Add(_fonts.Install(font));
}
else if (font.EndsWith(".ttc"))
{
FallBackFonts.AddRange(_fonts.InstallCollection(font));
}
}
else if (font.EndsWith(".ttc")) FallBackFonts.AddRange(_fonts.InstallCollection(font));
RipFont = NotoSans.CreateFont(20, FontStyle.Bold);
DottyFont = FallBackFonts.First(x => x.Name == "dotty");
}
public FontFamily DottyFont { get; }
public FontFamily UniSans { get; }
public FontFamily NotoSans { get; }
//public FontFamily Emojis { get; }
/// <summary>
/// Font used for .rip command
/// </summary>
public Font RipFont { get; }
public List<FontFamily> FallBackFonts { get; }
}
}

View File

@@ -7,6 +7,7 @@ using Google.Apis.YouTube.v3;
using Newtonsoft.Json.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml;
namespace NadekoBot.Services;
@@ -14,26 +15,162 @@ public class GoogleApiService : IGoogleApiService, INService
{
private const string SearchEngineId = "018084019232060951019:hs5piey28-e";
private static readonly Regex
plRegex = new("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)", RegexOptions.Compiled);
public IReadOnlyDictionary<string, string> Languages { get; } = 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" }
};
private readonly YouTubeService yt;
private readonly UrlshortenerService sh;
private readonly CustomsearchService cs;
//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;
private readonly IHttpClientFactory _httpFactory;
public GoogleApiService(IBotCredentials creds, IHttpClientFactory factory)
{
_creds = creds;
_httpFactory = factory;
var bcs = new BaseClientService.Initializer
{
ApplicationName = "Nadeko Bot",
ApiKey = _creds.GoogleApiKey,
};
var bcs = new BaseClientService.Initializer { ApplicationName = "Nadeko Bot", ApiKey = _creds.GoogleApiKey };
yt = new(bcs);
sh = new(bcs);
cs = new(bcs);
}
private static readonly Regex plRegex = new("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)", RegexOptions.Compiled);
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
{
await Task.Yield();
@@ -44,10 +181,7 @@ public class GoogleApiService : IGoogleApiService, INService
throw new ArgumentOutOfRangeException(nameof(count));
var match = plRegex.Match(keywords);
if (match.Length > 1)
{
return new[] { match.Groups["id"].Value.ToString() };
}
if (match.Length > 1) return new[] { match.Groups["id"].Value };
var query = yt.Search.List("snippet");
query.MaxResults = count;
query.Type = "playlist";
@@ -56,10 +190,6 @@ public class GoogleApiService : IGoogleApiService, INService
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;
private readonly IHttpClientFactory _httpFactory;
// todo future add quota users
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
{
@@ -93,7 +223,9 @@ public class GoogleApiService : IGoogleApiService, INService
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)
public async Task<IEnumerable<(string Name, string Id, string Url)>> GetVideoInfosByKeywordAsync(
string keywords,
int count = 1)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(keywords))
@@ -106,10 +238,12 @@ public class GoogleApiService : IGoogleApiService, INService
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));
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 Task<string> ShortenUrl(Uri url) => ShortenUrl(url.ToString());
public Task<string> ShortenUrl(Uri url)
=> ShortenUrl(url.ToString());
public async Task<string> ShortenUrl(string url)
{
@@ -163,8 +297,7 @@ public class GoogleApiService : IGoogleApiService, INService
toReturn.AddRange(data.Items.Select(i => i.ContentDetails.VideoId));
nextPageToken = data.NextPageToken;
}
while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken));
} while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken));
return toReturn;
}
@@ -189,12 +322,8 @@ public class GoogleApiService : IGoogleApiService, INService
q.Id = string.Join(",", videoIdsList.Take(toGet));
videoIdsList = videoIdsList.Skip(toGet).ToList();
var items = (await q.ExecuteAsync()).Items;
foreach (var i in items)
{
toReturn.Add(i.Id, System.Xml.XmlConvert.ToTimeSpan(i.ContentDetails.Duration));
}
}
while (remaining > 0);
foreach (var i in items) toReturn.Add(i.Id, XmlConvert.ToTimeSpan(i.ContentDetails.Duration));
} while (remaining > 0);
return toReturn;
}
@@ -218,156 +347,24 @@ public class GoogleApiService : IGoogleApiService, INService
return new(search.Items[0].Image, search.Items[0].Link);
}
public IReadOnlyDictionary<string, string> Languages { get; } = 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 (!Languages.ContainsKey(sourceLanguage) ||
!Languages.ContainsKey(targetLanguage))
if (!Languages.ContainsKey(sourceLanguage) || !Languages.ContainsKey(targetLanguage))
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
var url = new Uri(string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
var url = new Uri(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 = _httpFactory.CreateClient())
{
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");
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);
}
@@ -379,4 +376,4 @@ public class GoogleApiService : IGoogleApiService, INService
Languages.TryGetValue(language, out var mode);
return mode;
}
}
}

View File

@@ -1,45 +1,50 @@
#nullable disable
using System.Globalization;
using Newtonsoft.Json;
using NadekoBot.Db;
using Newtonsoft.Json;
using System.Globalization;
namespace NadekoBot.Services;
public class Localization : ILocalization, INService
{
private readonly BotConfigService _bss;
private readonly DbService _db;
private static readonly Dictionary<string, CommandData> _commandData =
JsonConvert.DeserializeObject<Dictionary<string, CommandData>>(
File.ReadAllText("./data/strings/commands/commands.en-US.json"));
public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
public CultureInfo DefaultCultureInfo => _bss.Data.DefaultLocale;
private static readonly Dictionary<string, CommandData> _commandData = JsonConvert.DeserializeObject<Dictionary<string, CommandData>>(
File.ReadAllText("./data/strings/commands/commands.en-US.json"));
public CultureInfo DefaultCultureInfo
=> _bss.Data.DefaultLocale;
private readonly BotConfigService _bss;
private readonly DbService _db;
public Localization(BotConfigService bss, Bot bot, DbService db)
{
_bss = bss;
_db = db;
var cultureInfoNames = bot.AllGuildConfigs
.ToDictionary(x => x.GuildId, x => x.Locale);
GuildCultureInfos = new(cultureInfoNames.ToDictionary(x => x.Key, x =>
{
CultureInfo cultureInfo = null;
try
{
if (x.Value is null)
return null;
cultureInfo = new(x.Value);
}
catch { }
return cultureInfo;
}).Where(x => x.Value != null));
var cultureInfoNames = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale);
GuildCultureInfos = new(cultureInfoNames.ToDictionary(x => x.Key,
x =>
{
CultureInfo cultureInfo = null;
try
{
if (x.Value is null)
return null;
cultureInfo = new(x.Value);
}
catch { }
return cultureInfo;
})
.Where(x => x.Value != null));
}
public void SetGuildCulture(IGuild guild, CultureInfo ci) =>
SetGuildCulture(guild.Id, ci);
public void SetGuildCulture(IGuild guild, CultureInfo ci)
=> SetGuildCulture(guild.Id, ci);
public void SetGuildCulture(ulong guildId, CultureInfo ci)
{
@@ -59,12 +64,11 @@ public class Localization : ILocalization, INService
GuildCultureInfos.AddOrUpdate(guildId, ci, (id, old) => ci);
}
public void RemoveGuildCulture(IGuild guild) =>
RemoveGuildCulture(guild.Id);
public void RemoveGuildCulture(IGuild guild)
=> RemoveGuildCulture(guild.Id);
public void RemoveGuildCulture(ulong guildId)
{
if (GuildCultureInfos.TryRemove(guildId, out var _))
{
using var uow = _db.GetDbContext();
@@ -80,17 +84,17 @@ public class Localization : ILocalization, INService
bs.DefaultLocale = ci;
});
public void ResetDefaultCulture() =>
SetDefaultCulture(CultureInfo.CurrentCulture);
public void ResetDefaultCulture()
=> SetDefaultCulture(CultureInfo.CurrentCulture);
public CultureInfo GetCultureInfo(IGuild guild) =>
GetCultureInfo(guild?.Id);
public CultureInfo GetCultureInfo(IGuild guild)
=> GetCultureInfo(guild?.Id);
public CultureInfo GetCultureInfo(ulong? guildId)
{
if (guildId is null || !GuildCultureInfos.TryGetValue(guildId.Value, out var info) || info is null)
return _bss.Data.DefaultLocale;
return info;
}
@@ -99,13 +103,8 @@ public class Localization : ILocalization, INService
_commandData.TryGetValue(key, out var toReturn);
if (toReturn is null)
return new()
{
Cmd = key,
Desc = key,
Usage = new[] { key },
};
return new() { Cmd = key, Desc = key, Usage = new[] { key } };
return toReturn;
}
}
}

View File

@@ -15,8 +15,13 @@ public class RedisCache : IDataCache
private readonly string _redisKey;
private readonly EndPoint _redisEndpoint;
public RedisCache(ConnectionMultiplexer redis, IBotCredentials creds,
IImageCache imageCache, ILocalDataCache dataCache)
private readonly object timelyLock = new();
public RedisCache(
ConnectionMultiplexer redis,
IBotCredentials creds,
IImageCache imageCache,
ILocalDataCache dataCache)
{
Redis = redis;
_redisEndpoint = Redis.GetEndPoints().First();
@@ -52,7 +57,7 @@ public class RedisCache : IDataCache
public Task SetAnimeDataAsync(string key, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("anime_" + key, data, expiry: TimeSpan.FromHours(3));
return _db.StringSetAsync("anime_" + key, data, TimeSpan.FromHours(3));
}
public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key)
@@ -65,10 +70,9 @@ public class RedisCache : IDataCache
public Task SetNovelDataAsync(string key, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("novel_" + key, data, expiry: TimeSpan.FromHours(3));
return _db.StringSetAsync("novel_" + key, data, TimeSpan.FromHours(3));
}
private readonly object timelyLock = new();
public TimeSpan? AddTimelyClaim(ulong id, int period)
{
if (period == 0)
@@ -82,6 +86,7 @@ public class RedisCache : IDataCache
_db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time);
return null;
}
return _db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}");
}
}
@@ -91,9 +96,7 @@ public class RedisCache : IDataCache
var server = Redis.GetServer(_redisEndpoint);
var _db = Redis.GetDatabase();
foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*"))
{
_db.KeyDelete(k, CommandFlags.FireAndForget);
}
}
public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time)
@@ -106,6 +109,7 @@ public class RedisCache : IDataCache
_db.StringSet($"{_redisKey}_affinity_{userId}", true, time);
return true;
}
return false;
}
@@ -119,13 +123,14 @@ public class RedisCache : IDataCache
_db.StringSet($"{_redisKey}_divorce_{userId}", true, time);
return true;
}
return false;
}
public Task SetStreamDataAsync(string url, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync($"{_redisKey}_stream_{url}", data, expiry: TimeSpan.FromHours(6));
return _db.StringSetAsync($"{_redisKey}_stream_{url}", data, TimeSpan.FromHours(6));
}
public bool TryGetStreamData(string url, out string dataStr)
@@ -143,9 +148,7 @@ public class RedisCache : IDataCache
0, // i don't use the value
TimeSpan.FromSeconds(expireIn),
When.NotExists))
{
return null;
}
return _db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}");
}
@@ -153,10 +156,7 @@ public class RedisCache : IDataCache
public bool TryGetEconomy(out string data)
{
var _db = Redis.GetDatabase();
if ((data = _db.StringGet($"{_redisKey}_economy")) != null)
{
return true;
}
if ((data = _db.StringGet($"{_redisKey}_economy")) != null) return true;
return false;
}
@@ -164,12 +164,15 @@ public class RedisCache : IDataCache
public void SetEconomy(string data)
{
var _db = Redis.GetDatabase();
_db.StringSet($"{_redisKey}_economy",
data,
expiry: TimeSpan.FromMinutes(3));
_db.StringSet($"{_redisKey}_economy", data, TimeSpan.FromMinutes(3));
}
public async Task<TOut> GetOrAddCachedDataAsync<TParam, TOut>(string key, Func<TParam, Task<TOut>> factory, TParam param, TimeSpan expiry) where TOut : class
public async Task<TOut> GetOrAddCachedDataAsync<TParam, TOut>(
string key,
Func<TParam, Task<TOut>> factory,
TParam param,
TimeSpan expiry)
where TOut : class
{
var _db = Redis.GetDatabase();
@@ -179,13 +182,13 @@ public class RedisCache : IDataCache
var obj = await factory(param);
if (obj is null)
return default(TOut);
return default;
await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj),
expiry: expiry);
await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj), expiry);
return obj;
}
return (TOut)JsonConvert.DeserializeObject(data, typeof(TOut));
}
@@ -194,7 +197,7 @@ public class RedisCache : IDataCache
var db = Redis.GetDatabase();
var str = (string)db.StringGet($"{_redisKey}_last_currency_decay");
if(string.IsNullOrEmpty(str))
if (string.IsNullOrEmpty(str))
return DateTime.MinValue;
return JsonConvert.DeserializeObject<DateTime>(str);
@@ -206,4 +209,4 @@ public class RedisCache : IDataCache
db.StringSet($"{_redisKey}_last_currency_decay", JsonConvert.SerializeObject(DateTime.UtcNow));
}
}
}

View File

@@ -5,7 +5,7 @@ public static class RedisImageExtensions
{
private const string OldCdnUrl = "nadeko-pictures.nyc3.digitaloceanspaces.com";
private const string NewCdnUrl = "cdn.nadeko.bot";
public static Uri ToNewCdn(this Uri uri)
=> new(uri.ToString().Replace(OldCdnUrl, NewCdnUrl));
}
}

View File

@@ -1,26 +1,13 @@
#nullable disable
using Newtonsoft.Json;
using StackExchange.Redis;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Common.Yml;
using Newtonsoft.Json;
using StackExchange.Redis;
namespace NadekoBot.Services;
public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly HttpClient _http;
private readonly string _imagesPath;
private IDatabase Db
=> _con.GetDatabase();
private const string BASE_PATH = "data/";
private const string CARDS_PATH = $"{BASE_PATH}images/cards";
public ImageUrls ImageUrls { get; private set; }
public enum ImageKeys
{
CoinHeads,
@@ -36,6 +23,14 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
XpBg
}
private const string BASE_PATH = "data/";
private const string CARDS_PATH = $"{BASE_PATH}images/cards";
private IDatabase Db
=> _con.GetDatabase();
public ImageUrls ImageUrls { get; private set; }
public IReadOnlyList<byte[]> Heads
=> GetByteArrayData(ImageKeys.CoinHeads);
@@ -69,17 +64,10 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
public byte[] RipOverlay
=> GetByteData(ImageKeys.RipOverlay);
public byte[] GetCard(string key)
// since cards are always local for now, don't cache them
=> File.ReadAllBytes(Path.Join(CARDS_PATH, key + ".jpg"));
public async Task OnReadyAsync()
{
if (await AllKeysExist())
return;
await Reload();
}
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly HttpClient _http;
private readonly string _imagesPath;
public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
{
@@ -93,6 +81,18 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
}
public byte[] GetCard(string key)
// since cards are always local for now, don't cache them
=> File.ReadAllBytes(Path.Join(CARDS_PATH, key + ".jpg"));
public async Task OnReadyAsync()
{
if (await AllKeysExist())
return;
await Reload();
}
private void Migrate()
{
// migrate to yml
@@ -105,21 +105,22 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
if (oldData is not null)
{
var newData = new ImageUrls()
var newData = new ImageUrls
{
Coins =
new()
{
Heads = oldData.Coins.Heads.Length == 1 &&
oldData.Coins.Heads[0].ToString() ==
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") }
: oldData.Coins.Heads,
Tails = oldData.Coins.Tails.Length == 1 &&
oldData.Coins.Tails[0].ToString() ==
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
Heads =
oldData.Coins.Heads.Length == 1
&& oldData.Coins.Heads[0].ToString()
== "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") }
: oldData.Coins.Heads,
Tails = oldData.Coins.Tails.Length == 1
&& oldData.Coins.Tails[0].ToString()
== "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
? new[] { new Uri("https://cdn.nadeko.bot/coins/tails3.png") }
: oldData.Coins.Tails,
: oldData.Coins.Tails
},
Dice = oldData.Dice.Map(x => x.ToNewCdn()),
Currency = oldData.Currency.Map(x => x.ToNewCdn()),
@@ -128,7 +129,7 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
Dot = oldData.Rategirl.Dot.ToNewCdn(), Matrix = oldData.Rategirl.Matrix.ToNewCdn()
},
Rip = new() { Bg = oldData.Rip.Bg.ToNewCdn(), Overlay = oldData.Rip.Overlay.ToNewCdn(), },
Rip = new() { Bg = oldData.Rip.Bg.ToNewCdn(), Overlay = oldData.Rip.Overlay.ToNewCdn() },
Slots = new()
{
Bg = new("https://cdn.nadeko.bot/slots/slots_bg.png"),
@@ -139,8 +140,8 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
"https://cdn.nadeko.bot/slots/4.png", "https://cdn.nadeko.bot/slots/5.png"
}.Map(x => new Uri(x))
},
Xp = new() { Bg = oldData.Xp.Bg.ToNewCdn(), },
Version = 2,
Xp = new() { Bg = oldData.Xp.Bg.ToNewCdn() },
Version = 2
};
File.Move(oldFilePath, backupFilePath, true);
@@ -161,7 +162,6 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(await File.ReadAllTextAsync(_imagesPath));
foreach (var key in GetAllKeys())
{
switch (key)
{
case ImageKeys.CoinHeads:
@@ -200,7 +200,6 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
default:
throw new ArgumentOutOfRangeException();
}
}
}
private async Task Load(ImageKeys key, Uri uri)
@@ -221,20 +220,17 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
await Db.ListRightPushAsync(GetRedisKey(key), vals);
if (uris.Length != vals.Length)
{
Log.Information("{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n" +
"Some of the supplied URIs are either unavailable or invalid",
Log.Information(
"{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n"
+ "Some of the supplied URIs are either unavailable or invalid",
vals.Length,
uris.Length,
key
);
}
key);
}
private async Task<byte[]> GetImageData(Uri uri)
{
if (uri.IsFile)
{
try
{
var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
@@ -245,7 +241,6 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
Log.Warning(ex, "Failed reading image bytes from uri: {Uri}", uri.ToString());
return null;
}
}
try
{
@@ -260,9 +255,7 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
private async Task<bool> AllKeysExist()
{
var tasks = await GetAllKeys()
.Select(x => Db.KeyExistsAsync(GetRedisKey(x)))
.WhenAll();
var tasks = await GetAllKeys().Select(x => Db.KeyExistsAsync(GetRedisKey(x))).WhenAll();
return tasks.All(exist => exist);
}
@@ -278,4 +271,4 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
private RedisKey GetRedisKey(ImageKeys key)
=> _creds.RedisKey() + "_image_" + key;
}
}

View File

@@ -8,9 +8,6 @@ namespace NadekoBot.Services;
public class RedisLocalDataCache : ILocalDataCache
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private const string POKEMON_ABILITIES_FILE = "data/pokemon/pokemon_abilities.json";
private const string POKEMON_LIST_FILE = "data/pokemon/pokemon_list.json";
private const string POKEMON_MAP_PATH = "data/pokemon/name-id_map.json";
@@ -40,6 +37,9 @@ public class RedisLocalDataCache : ILocalDataCache
private init => Set("pokemon_map", value);
}
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client)
{
_con = con;
@@ -49,32 +49,25 @@ public class RedisLocalDataCache : ILocalDataCache
if (shardId == 0)
{
if (!File.Exists(POKEMON_LIST_FILE))
{
Log.Warning($"{POKEMON_LIST_FILE} is missing. Pokemon abilities not loaded");
}
else
{
Pokemons =
JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(POKEMON_LIST_FILE));
}
JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(
File.ReadAllText(POKEMON_LIST_FILE));
if (!File.Exists(POKEMON_ABILITIES_FILE))
{
Log.Warning($"{POKEMON_ABILITIES_FILE} is missing. Pokemon abilities not loaded.");
}
else
{
PokemonAbilities =
JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(
File.ReadAllText(POKEMON_ABILITIES_FILE)
);
}
File.ReadAllText(POKEMON_ABILITIES_FILE));
try
{
TriviaQuestions = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(QUESTIONS_FILE));
PokemonMap = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(POKEMON_MAP_PATH))
?.ToDictionary(x => x.Id, x => x.Name) ?? new();
?.ToDictionary(x => x.Id, x => x.Name)
?? new();
}
catch (Exception ex)
{
@@ -90,4 +83,4 @@ public class RedisLocalDataCache : ILocalDataCache
private void Set(string key, object obj)
=> _con.GetDatabase().StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj));
}
}

View File

@@ -1,5 +1,6 @@
#nullable disable
using Grpc.Core;
using Grpc.Net.Client;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Coordinator;
@@ -12,37 +13,26 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
public RemoteGrpcCoordinator(IBotCredentials creds, DiscordSocketClient client)
{
var coordUrl = string.IsNullOrWhiteSpace(creds.CoordinatorUrl)
? "http://localhost:3442"
: creds.CoordinatorUrl;
var channel = Grpc.Net.Client.GrpcChannel.ForAddress(coordUrl);
var coordUrl = string.IsNullOrWhiteSpace(creds.CoordinatorUrl) ? "http://localhost:3442" : creds.CoordinatorUrl;
var channel = GrpcChannel.ForAddress(coordUrl);
_coordClient = new(channel);
_client = client;
}
public bool RestartBot()
{
_coordClient.RestartAllShards(new()
{
});
_coordClient.RestartAllShards(new());
return true;
}
public void Die(bool graceful)
=> _coordClient.Die(new()
{
Graceful = graceful
});
=> _coordClient.Die(new() { Graceful = graceful });
public bool RestartShard(int shardId)
{
_coordClient.RestartShard(new()
{
ShardId = shardId,
});
_coordClient.RestartShard(new() { ShardId = shardId });
return true;
}
@@ -51,15 +41,14 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
{
var res = _coordClient.GetAllStatuses(new());
return res.Statuses
.ToArray()
.Map(s => new ShardStatus()
{
ConnectionState = FromCoordConnState(s.State),
GuildCount = s.GuildCount,
ShardId = s.ShardId,
LastUpdate = s.LastUpdate.ToDateTime(),
});
return res.Statuses.ToArray()
.Map(s => new ShardStatus
{
ConnectionState = FromCoordConnState(s.State),
GuildCount = s.GuildCount,
ShardId = s.ShardId,
LastUpdate = s.LastUpdate.ToDateTime()
});
}
public int GetGuildCount()
@@ -82,18 +71,21 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
try
{
var reply = await _coordClient.HeartbeatAsync(new()
{
State = ToCoordConnState(_client.ConnectionState),
GuildCount = _client.ConnectionState == ConnectionState.Connected ? _client.Guilds.Count : 0,
ShardId = _client.ShardId,
}, deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10));
{
State = ToCoordConnState(_client.ConnectionState),
GuildCount =
_client.ConnectionState == ConnectionState.Connected ? _client.Guilds.Count : 0,
ShardId = _client.ShardId
},
deadline: DateTime.UtcNow + TimeSpan.FromSeconds(10));
gracefulImminent = reply.GracefulImminent;
}
catch (RpcException ex)
{
if (!gracefulImminent)
{
Log.Warning(ex, "Hearbeat failed and graceful shutdown was not expected: {Message}",
Log.Warning(ex,
"Hearbeat failed and graceful shutdown was not expected: {Message}",
ex.Message);
break;
}
@@ -130,4 +122,4 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
ConnState.Connected => ConnectionState.Connected,
_ => ConnectionState.Disconnected
};
}
}

View File

@@ -13,6 +13,7 @@ public class SingleProcessCoordinator : ICoordinator
_creds = creds;
_client = client;
}
public bool RestartBot()
{
if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd)
@@ -21,7 +22,7 @@ public class SingleProcessCoordinator : ICoordinator
Log.Error("You must set RestartCommand.Cmd and RestartCommand.Args in creds.yml");
return false;
}
Process.Start(_creds.RestartCommand.Cmd, _creds.RestartCommand.Args);
_ = Task.Run(async () =>
{
@@ -40,7 +41,7 @@ public class SingleProcessCoordinator : ICoordinator
public IList<ShardStatus> GetAllShardStatuses()
=> new[]
{
new ShardStatus()
new ShardStatus
{
ConnectionState = _client.ConnectionState,
GuildCount = _client.Guilds.Count,
@@ -54,4 +55,4 @@ public class SingleProcessCoordinator : ICoordinator
public Task Reload()
=> Task.CompletedTask;
}
}

View File

@@ -21,7 +21,7 @@ public class SoundCloudApiService : INService
{
response = await http.GetStringAsync($"https://scapi.nadeko.bot/resolve?url={url}");
}
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo>(response);
if (responseObj?.Kind != "track")
throw new InvalidOperationException("Url is either not a track, or it doesn't exist.");
@@ -37,12 +37,13 @@ public class SoundCloudApiService : INService
var response = string.Empty;
using (var http = _httpFactory.CreateClient())
{
response = await http.GetStringAsync(new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}"));
response = await http.GetStringAsync(
new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}"));
}
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response)
.FirstOrDefault(s => s.Streamable is true);
.FirstOrDefault(s => s.Streamable is true);
if (responseObj?.Kind != "track")
throw new InvalidOperationException("Query yielded no results.");
@@ -56,17 +57,22 @@ public class SoundCloudVideo
public long Id { get; set; } = 0;
public SoundCloudUser User { get; set; } = new();
public string Title { get; set; } = string.Empty;
public string FullName => User.Name + " - " + Title;
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; } = string.Empty;
[JsonProperty("artwork_url")]
public string ArtworkUrl { get; set; } = string.Empty;
}
public class SoundCloudUser
{
[JsonProperty("username")]
public string Name { get; set; }
}
}

View File

@@ -1,6 +1,6 @@
#nullable disable
using System.Collections.Immutable;
using System.Collections;
using System.Collections.Immutable;
namespace NadekoBot.Services;
@@ -9,11 +9,11 @@ public class StartingGuildsService : IEnumerable<ulong>, INService
private readonly ImmutableList<ulong> _guilds;
public StartingGuildsService(DiscordSocketClient client)
=> this._guilds = client.Guilds.Select(x => x.Id).ToImmutableList();
=> _guilds = client.Guilds.Select(x => x.Id).ToImmutableList();
public IEnumerator<ulong> GetEnumerator() =>
_guilds.GetEnumerator();
public IEnumerator<ulong> GetEnumerator()
=> _guilds.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
_guilds.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
=> _guilds.GetEnumerator();
}

View File

@@ -6,30 +6,47 @@ namespace NadekoBot.Services;
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
{
public const string BotVersion = "4.0.0";
public string Author
=> "Kwoth#2452";
public string Library
=> "Discord.Net";
public double MessagesPerSecond
=> MessageCounter / GetUptime().TotalSeconds;
public long TextChannels
=> Interlocked.Read(ref _textChannels);
public long VoiceChannels
=> Interlocked.Read(ref _voiceChannels);
public long MessageCounter
=> Interlocked.Read(ref _messageCounter);
public long CommandsRan
=> Interlocked.Read(ref _commandsRan);
private readonly Process _currentProcess = Process.GetCurrentProcess();
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "4.0.0";
public string Author => "Kwoth#2452";
public string Library => "Discord.Net";
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 _botlistTimer;
private readonly IHttpClientFactory _httpFactory;
public StatsService(DiscordSocketClient client, CommandHandler cmdHandler,
IBotCredentials creds, IHttpClientFactory factory)
public StatsService(
DiscordSocketClient client,
CommandHandler cmdHandler,
IBotCredentials creds,
IHttpClientFactory factory)
{
_client = client;
_creds = creds;
@@ -116,34 +133,39 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
};
_botlistTimer = new(async state =>
{
if (string.IsNullOrWhiteSpace(_creds.BotListToken))
return;
try
{
using var http = _httpFactory.CreateClient();
using var content = new FormUrlEncodedContent(
new Dictionary<string, string> {
{ "shard_count", _creds.TotalShards.ToString()},
if (string.IsNullOrWhiteSpace(_creds.BotListToken))
return;
try
{
using var http = _httpFactory.CreateClient();
using var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "shard_count", _creds.TotalShards.ToString() },
{ "shard_id", client.ShardId.ToString() },
{ "server_count", client.Guilds.Count().ToString() }
});
content.Headers.Clear();
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken);
content.Headers.Clear();
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken);
using (await http.PostAsync(new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), content)) { }
}
catch (Exception ex)
{
Log.Error(ex, "Error ");
// ignored
}
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromHours(1));
using (await http.PostAsync(
new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"),
content)) { }
}
catch (Exception ex)
{
Log.Error(ex, "Error ");
// ignored
}
},
null,
TimeSpan.FromMinutes(5),
TimeSpan.FromHours(1));
}
public TimeSpan GetUptime() =>
DateTime.UtcNow - _started;
public TimeSpan GetUptime()
=> DateTime.UtcNow - _started;
public string GetUptimeString(string separator = ", ")
{
@@ -170,4 +192,4 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
_currentProcess.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@@ -26,11 +26,11 @@ public class YtdlOperation
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
CreateNoWindow = true,
},
CreateNoWindow = true
}
};
}
public async Task<string> GetDataAsync(params string[] args)
{
try
@@ -49,13 +49,12 @@ public class YtdlOperation
}
catch (Win32Exception)
{
Log.Error("youtube-dl is likely not installed. " +
"Please install it before running the command again");
Log.Error("youtube-dl is likely not installed. " + "Please install it before running the command again");
return default;
}
catch (Exception ex)
{
Log.Error(ex , "Exception running youtube-dl: {ErrorMessage}", ex.Message);
Log.Error(ex, "Exception running youtube-dl: {ErrorMessage}", ex.Message);
return default;
}
}
@@ -66,9 +65,9 @@ public class YtdlOperation
Log.Debug($"Executing {process.StartInfo.FileName} {process.StartInfo.Arguments}");
process.Start();
string line;
while((line = await process.StandardOutput.ReadLineAsync()) != null)
while ((line = await process.StandardOutput.ReadLineAsync()) != null)
yield return line;
}
}
}