Global usings and file scoped namespaces

This commit is contained in:
Kwoth
2021-12-19 05:14:11 +01:00
parent bc31dae965
commit ee33313519
548 changed files with 47528 additions and 49115 deletions

View File

@@ -1,100 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using Serilog;
using Microsoft.Extensions.DependencyInjection;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public sealed class BehaviorExecutor : IBehaviourExecutor, INService
{
public sealed class BehaviorExecutor : IBehaviourExecutor, INService
private readonly IServiceProvider _services;
private IEnumerable<ILateExecutor> _lateExecutors;
private IEnumerable<ILateBlocker> _lateBlockers;
private IEnumerable<IEarlyBehavior> _earlyBehaviors;
private IEnumerable<IInputTransformer> _transformers;
public BehaviorExecutor(IServiceProvider services)
{
private readonly IServiceProvider _services;
private IEnumerable<ILateExecutor> _lateExecutors;
private IEnumerable<ILateBlocker> _lateBlockers;
private IEnumerable<IEarlyBehavior> _earlyBehaviors;
private IEnumerable<IInputTransformer> _transformers;
_services = services;
}
public BehaviorExecutor(IServiceProvider services)
{
_services = services;
}
public void Initialize()
{
_lateExecutors = _services.GetServices<ILateExecutor>();
_lateBlockers = _services.GetServices<ILateBlocker>();
_earlyBehaviors = _services.GetServices<IEarlyBehavior>()
.OrderByDescending(x => x.Priority);
_transformers = _services.GetServices<IInputTransformer>();
}
public void Initialize()
public async Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg)
{
foreach (var beh in _earlyBehaviors)
{
_lateExecutors = _services.GetServices<ILateExecutor>();
_lateBlockers = _services.GetServices<ILateBlocker>();
_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))
{
if (await beh.RunBehavior(guild, usrMsg))
{
return true;
}
return true;
}
return false;
}
public async Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg)
return false;
}
public async Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg)
{
var messageContent = usrMsg.Content;
foreach (var exec in _transformers)
{
var messageContent = usrMsg.Content;
foreach (var exec in _transformers)
string newContent;
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
!= messageContent.ToLowerInvariant())
{
string newContent;
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
!= messageContent.ToLowerInvariant())
{
messageContent = newContent;
break;
}
messageContent = newContent;
break;
}
return messageContent;
}
public async Task<bool> RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd)
return messageContent;
}
public async Task<bool> RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd)
{
foreach (var exec in _lateBlockers)
{
foreach (var exec in _lateBlockers)
if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd))
{
if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd))
{
Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]",
ctx.User,
cmd.Aliases[0],
exec.GetType().Name);
return true;
}
Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]",
ctx.User,
cmd.Aliases[0],
exec.GetType().Name);
return true;
}
return false;
}
public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg)
return false;
}
public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg)
{
foreach (var exec in _lateExecutors)
{
foreach (var exec in _lateExecutors)
try
{
try
{
await exec.LateExecute(guild, usrMsg).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}",
exec.GetType().Name,
ex.Message);
}
await exec.LateExecute(guild, usrMsg).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}",
exec.GetType().Name,
ex.Message);
}
}
}

View File

@@ -1,182 +1,178 @@
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Primitives;
using NadekoBot.Common;
using NadekoBot.Common.Yml;
using Newtonsoft.Json;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public interface IBotCredsProvider
{
public interface IBotCredsProvider
{
public void Reload();
public IBotCredentials GetCreds();
public void ModifyCredsFile(Action<Creds> func);
}
public void Reload();
public IBotCredentials GetCreds();
public void ModifyCredsFile(Action<Creds> func);
}
public sealed class BotCredsProvider : IBotCredsProvider
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 Creds _creds = new Creds();
private IConfigurationRoot _config;
private readonly object reloadLock = new object();
public void Reload()
{
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 Creds _creds = new Creds();
private IConfigurationRoot _config;
private readonly object reloadLock = new object();
public void Reload()
lock (reloadLock)
{
lock (reloadLock)
_creds.OwnerIds.Clear();
_config.Bind(_creds);
if (string.IsNullOrWhiteSpace(_creds.Token))
{
_creds.OwnerIds.Clear();
_config.Bind(_creds);
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.Token))
if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd)
|| string.IsNullOrWhiteSpace(_creds.RestartCommand?.Args))
{
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
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 RestartConfig()
{
_creds.RestartCommand = new RestartConfig()
{
Args = "dotnet",
Cmd = "NadekoBot.dll -- {0}",
};
}
else
Args = "dotnet",
Cmd = "NadekoBot.dll -- {0}",
};
}
else
{
_creds.RestartCommand = new RestartConfig()
{
_creds.RestartCommand = new RestartConfig()
{
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";
_creds.TotalShards = _totalShards ?? _creds.TotalShards;
}
}
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);
var creds = Yaml.Deserializer.Deserialize<Creds>(ymlData);
func(creds);
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
/// </summary>
private void MigrateCredentials()
{
if (File.Exists(OldCredsJsonPath))
{
Log.Information("Migrating old creds...");
var jsonCredentialsFileText = File.ReadAllText(OldCredsJsonPath);
var oldCreds = JsonConvert.DeserializeObject<Creds.Old>(jsonCredentialsFileText);
var creds = new Creds
{
Version = 1,
Token = oldCreds.Token,
OwnerIds = oldCreds.OwnerIds.Distinct().ToHashSet(),
GoogleApiKey = oldCreds.GoogleApiKey,
RapidApiKey = oldCreds.MashapeKey,
OsuApiKey = oldCreds.OsuApiKey,
CleverbotApiKey = oldCreds.CleverbotApiKey,
TotalShards = oldCreds.TotalShards <= 1 ? 1 : oldCreds.TotalShards,
Patreon = new Creds.PatreonSettings(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,
};
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");
}
if (File.Exists(_credsFileName))
{
var creds = Yaml.Deserializer.Deserialize<Creds>(File.ReadAllText(_credsFileName));
if (creds.Version <= 1)
{
creds.Version = 2;
File.WriteAllText(_credsFileName, Yaml.Serializer.Serialize(creds));
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";
_creds.TotalShards = _totalShards ?? _creds.TotalShards;
}
public IBotCredentials GetCreds() => _creds;
}
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);
var creds = Yaml.Deserializer.Deserialize<Creds>(ymlData);
func(creds);
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
/// </summary>
private void MigrateCredentials()
{
if (File.Exists(OldCredsJsonPath))
{
Log.Information("Migrating old creds...");
var jsonCredentialsFileText = File.ReadAllText(OldCredsJsonPath);
var oldCreds = JsonConvert.DeserializeObject<Creds.Old>(jsonCredentialsFileText);
var creds = new Creds
{
Version = 1,
Token = oldCreds.Token,
OwnerIds = oldCreds.OwnerIds.Distinct().ToHashSet(),
GoogleApiKey = oldCreds.GoogleApiKey,
RapidApiKey = oldCreds.MashapeKey,
OsuApiKey = oldCreds.OsuApiKey,
CleverbotApiKey = oldCreds.CleverbotApiKey,
TotalShards = oldCreds.TotalShards <= 1 ? 1 : oldCreds.TotalShards,
Patreon = new Creds.PatreonSettings(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,
};
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");
}
if (File.Exists(_credsFileName))
{
var creds = Yaml.Deserializer.Deserialize<Creds>(File.ReadAllText(_credsFileName));
if (creds.Version <= 1)
{
creds.Version = 2;
File.WriteAllText(_credsFileName, Yaml.Serializer.Serialize(creds));
}
}
}
public IBotCredentials GetCreds() => _creds;
}

View File

@@ -3,166 +3,162 @@ using Discord.WebSocket;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService
{
public class CurrencyService : ICurrencyService, INService
private readonly DbService _db;
private readonly GamblingConfigService _gss;
private readonly IEmbedBuilderService _eb;
private readonly IUser _bot;
public CurrencyService(DbService db, DiscordSocketClient c, GamblingConfigService gss, IEmbedBuilderService eb)
{
private readonly DbService _db;
private readonly GamblingConfigService _gss;
private readonly IEmbedBuilderService _eb;
private readonly IUser _bot;
_db = db;
_gss = gss;
_eb = eb;
_bot = c.CurrentUser;
}
public CurrencyService(DbService db, DiscordSocketClient c, GamblingConfigService gss, IEmbedBuilderService eb)
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount) =>
new CurrencyTransaction
{
_db = db;
_gss = gss;
_eb = eb;
_bot = c.CurrentUser;
}
Amount = amount,
UserId = userId,
Reason = reason ?? "-",
};
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount) =>
new CurrencyTransaction
{
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)
{
var result = uow.TryUpdateCurrencyState(userId, userName, discrim, avatar, amount);
if (result)
{
var t = GetCurrencyTransaction(userId, reason, amount);
uow.CurrencyTransactions.Add(t);
var t = GetCurrencyTransaction(userId, reason, amount);
uow.CurrencyTransactions.Add(t);
if (gamble)
{
var t2 = GetCurrencyTransaction(_bot.Id, reason, -amount);
uow.CurrencyTransactions.Add(t2);
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)
{
if (amount < 0)
if (gamble)
{
throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.", nameof(amount));
}
using (var uow = _db.GetDbContext())
{
InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
await uow.SaveChangesAsync();
var t2 = GetCurrencyTransaction(_bot.Id, reason, -amount);
uow.CurrencyTransactions.Add(t2);
uow.TryUpdateCurrencyState(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId, -amount, true);
}
}
return result;
}
public Task AddAsync(ulong userId, string reason, long amount, bool gamble = false)
private async Task InternalAddAsync(ulong userId, string userName, string discrim, string avatar, string reason, long amount, bool gamble)
{
if (amount < 0)
{
return InternalAddAsync(userId, null, null, null, reason, amount, gamble);
throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.", nameof(amount));
}
public async Task AddAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false)
using (var uow = _db.GetDbContext())
{
await InternalAddAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
if (sendMessage)
{
try
{
var sign = _gss.Data.Currency.Sign;
await (await user.GetOrCreateDMChannelAsync())
.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)
{
ulong[] idArray = userIds as ulong[] ?? userIds.ToArray();
string[] reasonArray = reasons as string[] ?? reasons.ToArray();
long[] amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
using (var uow = _db.GetDbContext())
{
for (int 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)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
var amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
using (var uow = _db.GetDbContext())
{
for (int 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)
{
if (amount < 0)
{
throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.", nameof(amount));
}
bool result;
using (var uow = _db.GetDbContext())
{
result = InternalChange(userId, userName, userDiscrim, avatar, reason, -amount, gamble, uow);
await uow.SaveChangesAsync();
}
return result;
}
public Task<bool> RemoveAsync(ulong userId, string reason, long amount, bool gamble = false)
{
return InternalRemoveAsync(userId, null, null, null, reason, amount, gamble);
}
public Task<bool> RemoveAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false)
{
return InternalRemoveAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
await uow.SaveChangesAsync();
}
}
}
public Task AddAsync(ulong userId, string reason, long amount, bool gamble = false)
{
return InternalAddAsync(userId, null, null, null, reason, amount, gamble);
}
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 (await user.GetOrCreateDMChannelAsync())
.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)
{
ulong[] idArray = userIds as ulong[] ?? userIds.ToArray();
string[] reasonArray = reasons as string[] ?? reasons.ToArray();
long[] amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
using (var uow = _db.GetDbContext())
{
for (int 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)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
var amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
using (var uow = _db.GetDbContext())
{
for (int 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)
{
if (amount < 0)
{
throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.", nameof(amount));
}
bool result;
using (var uow = _db.GetDbContext())
{
result = InternalChange(userId, userName, userDiscrim, avatar, reason, -amount, gamble, uow);
await uow.SaveChangesAsync();
}
return result;
}
public Task<bool> RemoveAsync(ulong userId, string reason, long amount, bool gamble = false)
{
return InternalRemoveAsync(userId, null, null, null, reason, amount, gamble);
}
public Task<bool> RemoveAsync(IUser user, string reason, long amount, bool sendMessage = false, bool gamble = false)
{
return InternalRemoveAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
}
}

View File

@@ -1,67 +1,63 @@
using SixLabors.Fonts;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class FontProvider : INService
{
public class FontProvider : INService
private readonly FontCollection _fonts;
public FontProvider()
{
private readonly FontCollection _fonts;
_fonts = new FontCollection();
public FontProvider()
NotoSans = _fonts.Install("data/fonts/NotoSans-Bold.ttf");
UniSans = _fonts.Install("data/fonts/Uni Sans.ttf");
FallBackFonts = new List<FontFamily>();
//FallBackFonts.Add(_fonts.Install("data/fonts/OpenSansEmoji.ttf"));
// try loading some emoji and jap fonts on windows as fallback fonts
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
_fonts = new FontCollection();
NotoSans = _fonts.Install("data/fonts/NotoSans-Bold.ttf");
UniSans = _fonts.Install("data/fonts/Uni Sans.ttf");
FallBackFonts = new List<FontFamily>();
//FallBackFonts.Add(_fonts.Install("data/fonts/OpenSansEmoji.ttf"));
// try loading some emoji and jap fonts on windows as fallback fonts
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
try
{
try
{
string fontsfolder = System.Environment.GetFolderPath(System.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 { }
string fontsfolder = System.Environment.GetFolderPath(System.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")));
}
// 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));
}
}
RipFont = NotoSans.CreateFont(20, FontStyle.Bold);
DottyFont = FallBackFonts.First(x => x.Name == "dotty");
catch { }
}
public FontFamily DottyFont { get; }
// 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));
}
}
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; }
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,385 +7,380 @@ using Google.Apis.YouTube.v3;
using NadekoBot.Common;
using NadekoBot.Extensions;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class GoogleApiService : IGoogleApiService, INService
{
public class GoogleApiService : IGoogleApiService, INService
{
private const string SearchEngineId = "018084019232060951019:hs5piey28-e";
private const string SearchEngineId = "018084019232060951019:hs5piey28-e";
private YouTubeService yt;
private UrlshortenerService sh;
private CustomsearchService cs;
private YouTubeService yt;
private UrlshortenerService sh;
private CustomsearchService cs;
public GoogleApiService(IBotCredentials creds, IHttpClientFactory factory)
{
_creds = creds;
_httpFactory = factory;
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 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)
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)
{
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().ConfigureAwait(false)).Items.Select(i => i.Id.PlaylistId);
return new[] { match.Groups["id"].Value.ToString() };
}
var query = yt.Search.List("snippet");
query.MaxResults = count;
query.Type = "playlist";
query.Q = keywords;
//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;
return (await query.ExecuteAsync().ConfigureAwait(false)).Items.Select(i => i.Id.PlaylistId);
}
// todo future add quota users
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
//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)
{
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().ConfigureAwait(false)).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";
query.SafeSearch = SearchResource.ListRequest.SafeSearchEnum.Strict;
return (await query.ExecuteAsync().ConfigureAwait(false)).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().ConfigureAwait(false)).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 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
{
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().ConfigureAwait(false)).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
var response = await sh.Url.Insert(new Url { LongUrl = url }).ExecuteAsync().ConfigureAwait(false);
return response.Id;
}
public async Task<IEnumerable<string>> GetVideoLinksByKeywordAsync(string keywords, int count = 1)
catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.Forbidden)
{
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";
query.SafeSearch = SearchResource.ListRequest.SafeSearchEnum.Strict;
return (await query.ExecuteAsync().ConfigureAwait(false)).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
return url;
}
public async Task<IEnumerable<(string Name, string Id, string Url)>> GetVideoInfosByKeywordAsync(string keywords, int count = 1)
catch (Exception ex)
{
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().ConfigureAwait(false)).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 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().ConfigureAwait(false);
return response.Id;
}
catch (GoogleApiException ex) when (ex.HttpStatusCode == HttpStatusCode.Forbidden)
{
return url;
}
catch (Exception ex)
{
Log.Warning(ex, "Error shortening URL");
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().ConfigureAwait(false);
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 remaining = videoIdsList.Count;
do
{
var 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)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
var req = cs.Cse.List();
req.Q = query;
req.Cx = SearchEngineId;
req.Num = 1;
req.Fields = "items(image(contextLink,thumbnailLink),link)";
req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image;
req.Start = new NadekoRandom().Next(0, 20);
var search = await req.ExecuteAsync().ConfigureAwait(false);
return new ImageResult(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))
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}",
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");
text = await http.GetStringAsync(url).ConfigureAwait(false);
}
return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
}
private string ConvertToLanguageCode(string language)
{
Languages.TryGetValue(language, out var mode);
return mode;
Log.Warning(ex, "Error shortening URL");
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().ConfigureAwait(false);
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 remaining = videoIdsList.Count;
do
{
var 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)
{
await Task.Yield();
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
var req = cs.Cse.List();
req.Q = query;
req.Cx = SearchEngineId;
req.Num = 1;
req.Fields = "items(image(contextLink,thumbnailLink),link)";
req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image;
req.Start = new NadekoRandom().Next(0, 20);
var search = await req.ExecuteAsync().ConfigureAwait(false);
return new ImageResult(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))
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}",
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");
text = await http.GetStringAsync(url).ConfigureAwait(false);
}
return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
}
private string ConvertToLanguageCode(string language)
{
Languages.TryGetValue(language, out var mode);
return mode;
}
}

View File

@@ -1,123 +1,119 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Discord;
using NadekoBot.Common;
using Newtonsoft.Json;
using System.IO;
using NadekoBot.Db;
using NadekoBot.Modules.Administration;
namespace NadekoBot.Services
namespace NadekoBot.Services;
// todo future use guild locale more in the code (from guild settings) (for dates, currency, etc?)
public class Localization : ILocalization, INService
{
// todo future use guild locale more in the code (from guild settings) (for dates, currency, etc?)
public class Localization : ILocalization, INService
private readonly BotConfigService _bss;
private readonly DbService _db;
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 Localization(BotConfigService bss, Bot bot, DbService db)
{
private readonly BotConfigService _bss;
private readonly DbService _db;
_bss = bss;
_db = db;
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 Localization(BotConfigService bss, Bot bot, DbService db)
{
_bss = bss;
_db = db;
var cultureInfoNames = bot.AllGuildConfigs
.ToDictionary(x => x.GuildId, x => x.Locale);
var cultureInfoNames = bot.AllGuildConfigs
.ToDictionary(x => x.GuildId, x => x.Locale);
GuildCultureInfos = new ConcurrentDictionary<ulong, CultureInfo>(cultureInfoNames.ToDictionary(x => x.Key, x =>
{
CultureInfo cultureInfo = null;
try
{
if (x.Value is null)
return null;
cultureInfo = new CultureInfo(x.Value);
}
catch { }
return cultureInfo;
}).Where(x => x.Value != null));
GuildCultureInfos = new ConcurrentDictionary<ulong, CultureInfo>(cultureInfoNames.ToDictionary(x => x.Key, x =>
{
CultureInfo cultureInfo = null;
try
{
if (x.Value is 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.Name == _bss.Data.DefaultLocale.Name)
{
RemoveGuildCulture(guildId);
return;
}
public void SetGuildCulture(IGuild guild, CultureInfo ci) =>
SetGuildCulture(guild.Id, ci);
public void SetGuildCulture(ulong guildId, CultureInfo ci)
using (var uow = _db.GetDbContext())
{
if (ci.Name == _bss.Data.DefaultLocale.Name)
{
RemoveGuildCulture(guildId);
return;
}
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.Locale = ci.Name;
uow.SaveChanges();
}
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.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.Locale = ci.Name;
gc.Locale = null;
uow.SaveChanges();
}
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.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.Locale = null;
uow.SaveChanges();
}
}
}
public void SetDefaultCulture(CultureInfo ci)
{
_bss.ModifyConfig(bs =>
{
bs.DefaultLocale = ci;
});
}
public void ResetDefaultCulture() =>
SetDefaultCulture(CultureInfo.CurrentCulture);
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;
}
public static CommandData LoadCommand(string key)
{
_commandData.TryGetValue(key, out var toReturn);
if (toReturn is null)
return new CommandData
{
Cmd = key,
Desc = key,
Usage = new[] { key },
};
return toReturn;
}
}
}
public void SetDefaultCulture(CultureInfo ci)
{
_bss.ModifyConfig(bs =>
{
bs.DefaultLocale = ci;
});
}
public void ResetDefaultCulture() =>
SetDefaultCulture(CultureInfo.CurrentCulture);
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;
}
public static CommandData LoadCommand(string key)
{
_commandData.TryGetValue(key, out var toReturn);
if (toReturn is null)
return new CommandData
{
Cmd = key,
Desc = key,
Usage = new[] { key },
};
return toReturn;
}
}

View File

@@ -1,214 +1,210 @@
using NadekoBot.Extensions;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Discord.WebSocket;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class RedisCache : IDataCache
{
public class RedisCache : IDataCache
public ConnectionMultiplexer Redis { get; }
public IImageCache LocalImages { get; }
public ILocalDataCache LocalData { get; }
private readonly string _redisKey;
private readonly EndPoint _redisEndpoint;
public RedisCache(ConnectionMultiplexer redis, IBotCredentials creds,
IImageCache imageCache, ILocalDataCache dataCache)
{
public ConnectionMultiplexer Redis { get; }
Redis = redis;
_redisEndpoint = Redis.GetEndPoints().First();
LocalImages = imageCache;
LocalData = dataCache;
_redisKey = creds.RedisKey();
}
public IImageCache LocalImages { get; }
public ILocalDataCache LocalData { get; }
// things here so far don't need the bot id
// because it's a good thing if different bots
// which are hosted on the same PC
// can re-use the same image/anime data
public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(Uri key)
{
var _db = Redis.GetDatabase();
byte[] x = await _db.StringGetAsync("image_" + key).ConfigureAwait(false);
return (x != null, x);
}
private readonly string _redisKey;
private readonly EndPoint _redisEndpoint;
public Task SetImageDataAsync(Uri key, byte[] data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("image_" + key, data);
}
public RedisCache(ConnectionMultiplexer redis, IBotCredentials creds,
IImageCache imageCache, ILocalDataCache dataCache)
{
Redis = redis;
_redisEndpoint = Redis.GetEndPoints().First();
LocalImages = imageCache;
LocalData = dataCache;
_redisKey = creds.RedisKey();
}
// things here so far don't need the bot id
// because it's a good thing if different bots
// which are hosted on the same PC
// can re-use the same image/anime data
public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(Uri key)
public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key)
{
var _db = Redis.GetDatabase();
string x = await _db.StringGetAsync("anime_" + key).ConfigureAwait(false);
return (x != null, x);
}
public Task SetAnimeDataAsync(string key, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("anime_" + key, data, expiry: TimeSpan.FromHours(3));
}
public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key)
{
var _db = Redis.GetDatabase();
string x = await _db.StringGetAsync("novel_" + key).ConfigureAwait(false);
return (x != null, x);
}
public Task SetNovelDataAsync(string key, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("novel_" + key, data, expiry: TimeSpan.FromHours(3));
}
private readonly object timelyLock = new object();
public TimeSpan? AddTimelyClaim(ulong id, int period)
{
if (period == 0)
return null;
lock (timelyLock)
{
var time = TimeSpan.FromHours(period);
var _db = Redis.GetDatabase();
byte[] x = await _db.StringGetAsync("image_" + key).ConfigureAwait(false);
return (x != null, x);
}
public Task SetImageDataAsync(Uri key, byte[] data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("image_" + key, data);
}
public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key)
{
var _db = Redis.GetDatabase();
string x = await _db.StringGetAsync("anime_" + key).ConfigureAwait(false);
return (x != null, x);
}
public Task SetAnimeDataAsync(string key, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("anime_" + key, data, expiry: TimeSpan.FromHours(3));
}
public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key)
{
var _db = Redis.GetDatabase();
string x = await _db.StringGetAsync("novel_" + key).ConfigureAwait(false);
return (x != null, x);
}
public Task SetNovelDataAsync(string key, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync("novel_" + key, data, expiry: TimeSpan.FromHours(3));
}
private readonly object timelyLock = new object();
public TimeSpan? AddTimelyClaim(ulong id, int period)
{
if (period == 0)
if ((bool?)_db.StringGet($"{_redisKey}_timelyclaim_{id}") is null)
{
_db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time);
return null;
lock (timelyLock)
{
var time = TimeSpan.FromHours(period);
var _db = Redis.GetDatabase();
if ((bool?)_db.StringGet($"{_redisKey}_timelyclaim_{id}") is null)
{
_db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time);
return null;
}
return _db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}");
}
return _db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}");
}
}
public void RemoveAllTimelyClaims()
public void RemoveAllTimelyClaims()
{
var server = Redis.GetServer(_redisEndpoint);
var _db = Redis.GetDatabase();
foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*"))
{
var server = Redis.GetServer(_redisEndpoint);
var _db = Redis.GetDatabase();
foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*"))
{
_db.KeyDelete(k, CommandFlags.FireAndForget);
}
_db.KeyDelete(k, CommandFlags.FireAndForget);
}
}
public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time)
public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time)
{
var _db = Redis.GetDatabase();
time = _db.KeyTimeToLive($"{_redisKey}_affinity_{userId}");
if (time is null)
{
var _db = Redis.GetDatabase();
time = _db.KeyTimeToLive($"{_redisKey}_affinity_{userId}");
if (time is null)
{
time = TimeSpan.FromMinutes(30);
_db.StringSet($"{_redisKey}_affinity_{userId}", true, time);
return true;
}
return false;
time = TimeSpan.FromMinutes(30);
_db.StringSet($"{_redisKey}_affinity_{userId}", true, time);
return true;
}
return false;
}
public bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time)
public bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time)
{
var _db = Redis.GetDatabase();
time = _db.KeyTimeToLive($"{_redisKey}_divorce_{userId}");
if (time is null)
{
var _db = Redis.GetDatabase();
time = _db.KeyTimeToLive($"{_redisKey}_divorce_{userId}");
if (time is null)
{
time = TimeSpan.FromHours(6);
_db.StringSet($"{_redisKey}_divorce_{userId}", true, time);
return true;
}
return false;
time = TimeSpan.FromHours(6);
_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));
}
public Task SetStreamDataAsync(string url, string data)
{
var _db = Redis.GetDatabase();
return _db.StringSetAsync($"{_redisKey}_stream_{url}", data, expiry: TimeSpan.FromHours(6));
}
public bool TryGetStreamData(string url, out string dataStr)
{
var _db = Redis.GetDatabase();
dataStr = _db.StringGet($"{_redisKey}_stream_{url}");
public bool TryGetStreamData(string url, out string dataStr)
{
var _db = Redis.GetDatabase();
dataStr = _db.StringGet($"{_redisKey}_stream_{url}");
return !string.IsNullOrWhiteSpace(dataStr);
}
return !string.IsNullOrWhiteSpace(dataStr);
}
public TimeSpan? TryAddRatelimit(ulong id, string name, int expireIn)
{
var _db = Redis.GetDatabase();
if (_db.StringSet($"{_redisKey}_ratelimit_{id}_{name}",
public TimeSpan? TryAddRatelimit(ulong id, string name, int expireIn)
{
var _db = Redis.GetDatabase();
if (_db.StringSet($"{_redisKey}_ratelimit_{id}_{name}",
0, // i don't use the value
TimeSpan.FromSeconds(expireIn),
When.NotExists))
{
return null;
}
return _db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}");
}
public bool TryGetEconomy(out string data)
{
var _db = Redis.GetDatabase();
if ((data = _db.StringGet($"{_redisKey}_economy")) != null)
{
return true;
}
return false;
return null;
}
public void SetEconomy(string data)
{
var _db = Redis.GetDatabase();
_db.StringSet($"{_redisKey}_economy",
data,
expiry: TimeSpan.FromMinutes(3));
}
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();
RedisValue data = await _db.StringGetAsync(key).ConfigureAwait(false);
if (!data.HasValue)
{
var obj = await factory(param).ConfigureAwait(false);
if (obj is null)
return default(TOut);
await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj),
expiry: expiry).ConfigureAwait(false);
return obj;
}
return (TOut)JsonConvert.DeserializeObject(data, typeof(TOut));
}
public DateTime GetLastCurrencyDecay()
{
var db = Redis.GetDatabase();
var str = (string)db.StringGet($"{_redisKey}_last_currency_decay");
if(string.IsNullOrEmpty(str))
return DateTime.MinValue;
return JsonConvert.DeserializeObject<DateTime>(str);
}
public void SetLastCurrencyDecay()
{
var db = Redis.GetDatabase();
db.StringSet($"{_redisKey}_last_currency_decay", JsonConvert.SerializeObject(DateTime.UtcNow));
}
return _db.KeyTimeToLive($"{_redisKey}_ratelimit_{id}_{name}");
}
}
public bool TryGetEconomy(out string data)
{
var _db = Redis.GetDatabase();
if ((data = _db.StringGet($"{_redisKey}_economy")) != null)
{
return true;
}
return false;
}
public void SetEconomy(string data)
{
var _db = Redis.GetDatabase();
_db.StringSet($"{_redisKey}_economy",
data,
expiry: TimeSpan.FromMinutes(3));
}
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();
RedisValue data = await _db.StringGetAsync(key).ConfigureAwait(false);
if (!data.HasValue)
{
var obj = await factory(param).ConfigureAwait(false);
if (obj is null)
return default(TOut);
await _db.StringSetAsync(key, JsonConvert.SerializeObject(obj),
expiry: expiry).ConfigureAwait(false);
return obj;
}
return (TOut)JsonConvert.DeserializeObject(data, typeof(TOut));
}
public DateTime GetLastCurrencyDecay()
{
var db = Redis.GetDatabase();
var str = (string)db.StringGet($"{_redisKey}_last_currency_decay");
if(string.IsNullOrEmpty(str))
return DateTime.MinValue;
return JsonConvert.DeserializeObject<DateTime>(str);
}
public void SetLastCurrencyDecay()
{
var db = Redis.GetDatabase();
db.StringSet($"{_redisKey}_last_currency_decay", JsonConvert.SerializeObject(DateTime.UtcNow));
}
}

View File

@@ -1,13 +1,10 @@
using System;
namespace NadekoBot.Services;
namespace NadekoBot.Services
public static class RedisImageExtensions
{
public static class RedisImageExtensions
{
private const string OldCdnUrl = "nadeko-pictures.nyc3.digitaloceanspaces.com";
private const string NewCdnUrl = "cdn.nadeko.bot";
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));
}
public static Uri ToNewCdn(this Uri uri)
=> new(uri.ToString().Replace(OldCdnUrl, NewCdnUrl));
}

View File

@@ -2,299 +2,294 @@
using NadekoBot.Extensions;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Common.Yml;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly HttpClient _http;
private readonly string _imagesPath;
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly HttpClient _http;
private readonly string _imagesPath;
private IDatabase _db => _con.GetDatabase();
private IDatabase _db => _con.GetDatabase();
private const string _basePath = "data/";
private const string _cardsPath = "data/images/cards";
private const string _basePath = "data/";
private const string _cardsPath = "data/images/cards";
public ImageUrls ImageUrls { get; private set; }
public ImageUrls ImageUrls { get; private set; }
public enum ImageKeys
public enum ImageKeys
{
CoinHeads,
CoinTails,
Dice,
SlotBg,
SlotEmojis,
Currency,
RategirlMatrix,
RategirlDot,
RipOverlay,
RipBg,
XpBg
}
public IReadOnlyList<byte[]> Heads
=> GetByteArrayData(ImageKeys.CoinHeads);
public IReadOnlyList<byte[]> Tails
=> GetByteArrayData(ImageKeys.CoinTails);
public IReadOnlyList<byte[]> Dice
=> GetByteArrayData(ImageKeys.Dice);
public IReadOnlyList<byte[]> SlotEmojis
=> GetByteArrayData(ImageKeys.SlotEmojis);
public IReadOnlyList<byte[]> Currency
=> GetByteArrayData(ImageKeys.Currency);
public byte[] SlotBackground
=> GetByteData(ImageKeys.SlotBg);
public byte[] RategirlMatrix
=> GetByteData(ImageKeys.RategirlMatrix);
public byte[] RategirlDot
=> GetByteData(ImageKeys.RategirlDot);
public byte[] XpBackground
=> GetByteData(ImageKeys.XpBg);
public byte[] Rip
=> GetByteData(ImageKeys.RipBg);
public byte[] RipOverlay
=> GetByteData(ImageKeys.RipOverlay);
public byte[] GetCard(string key)
{
// since cards are always local for now, don't cache them
return File.ReadAllBytes(Path.Join(_cardsPath, key + ".jpg"));
}
public async Task OnReadyAsync()
{
if (await AllKeysExist())
return;
await Reload();
}
public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
{
_con = con;
_creds = creds;
_http = new HttpClient();
_imagesPath = Path.Combine(_basePath, "images.yml");
Migrate();
ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
}
private void Migrate()
{
// migrate to yml
if (File.Exists(Path.Combine(_basePath, "images.json")))
{
CoinHeads,
CoinTails,
Dice,
SlotBg,
SlotEmojis,
Currency,
RategirlMatrix,
RategirlDot,
RipOverlay,
RipBg,
XpBg
}
public IReadOnlyList<byte[]> Heads
=> GetByteArrayData(ImageKeys.CoinHeads);
public IReadOnlyList<byte[]> Tails
=> GetByteArrayData(ImageKeys.CoinTails);
public IReadOnlyList<byte[]> Dice
=> GetByteArrayData(ImageKeys.Dice);
public IReadOnlyList<byte[]> SlotEmojis
=> GetByteArrayData(ImageKeys.SlotEmojis);
public IReadOnlyList<byte[]> Currency
=> GetByteArrayData(ImageKeys.Currency);
public byte[] SlotBackground
=> GetByteData(ImageKeys.SlotBg);
public byte[] RategirlMatrix
=> GetByteData(ImageKeys.RategirlMatrix);
public byte[] RategirlDot
=> GetByteData(ImageKeys.RategirlDot);
public byte[] XpBackground
=> GetByteData(ImageKeys.XpBg);
public byte[] Rip
=> GetByteData(ImageKeys.RipBg);
public byte[] RipOverlay
=> GetByteData(ImageKeys.RipOverlay);
public byte[] GetCard(string key)
{
// since cards are always local for now, don't cache them
return File.ReadAllBytes(Path.Join(_cardsPath, key + ".jpg"));
}
public async Task OnReadyAsync()
{
if (await AllKeysExist())
return;
await Reload();
}
public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
{
_con = con;
_creds = creds;
_http = new HttpClient();
_imagesPath = Path.Combine(_basePath, "images.yml");
Migrate();
ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
}
private void Migrate()
{
// migrate to yml
if (File.Exists(Path.Combine(_basePath, "images.json")))
{
var oldFilePath = Path.Combine(_basePath, "images.json");
var backupFilePath = Path.Combine(_basePath, "images.json.backup");
var oldFilePath = Path.Combine(_basePath, "images.json");
var backupFilePath = Path.Combine(_basePath, "images.json.backup");
var oldData = JsonConvert.DeserializeObject<OldImageUrls>(
File.ReadAllText(oldFilePath));
var oldData = JsonConvert.DeserializeObject<OldImageUrls>(
File.ReadAllText(oldFilePath));
if (oldData is not null)
if (oldData is not null)
{
var newData = new ImageUrls()
{
var newData = new ImageUrls()
Coins = new ImageUrls.CoinData()
{
Coins = new ImageUrls.CoinData()
{
Heads = oldData.Coins.Heads.Length == 1 &&
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,
},
Dice = oldData.Dice.Map(x => x.ToNewCdn()),
Currency = oldData.Currency.Map(x => x.ToNewCdn()),
Rategirl = new ImageUrls.RategirlData()
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,
},
Dice = oldData.Dice.Map(x => x.ToNewCdn()),
Currency = oldData.Currency.Map(x => x.ToNewCdn()),
Rategirl = new ImageUrls.RategirlData()
{
Dot = oldData.Rategirl.Dot.ToNewCdn(),
Matrix = oldData.Rategirl.Matrix.ToNewCdn()
},
Rip = new ImageUrls.RipData()
{
Bg = oldData.Rip.Bg.ToNewCdn(),
Overlay = oldData.Rip.Overlay.ToNewCdn(),
},
Slots = new ImageUrls.SlotData()
{
Bg = new Uri("https://cdn.nadeko.bot/slots/slots_bg.png"),
Emojis = new[]
{
Dot = oldData.Rategirl.Dot.ToNewCdn(),
Matrix = oldData.Rategirl.Matrix.ToNewCdn()
},
Rip = new ImageUrls.RipData()
{
Bg = oldData.Rip.Bg.ToNewCdn(),
Overlay = oldData.Rip.Overlay.ToNewCdn(),
},
Slots = new ImageUrls.SlotData()
{
Bg = new Uri("https://cdn.nadeko.bot/slots/slots_bg.png"),
Emojis = new[]
{
"https://cdn.nadeko.bot/slots/0.png",
"https://cdn.nadeko.bot/slots/1.png",
"https://cdn.nadeko.bot/slots/2.png",
"https://cdn.nadeko.bot/slots/3.png",
"https://cdn.nadeko.bot/slots/4.png",
"https://cdn.nadeko.bot/slots/5.png"
}.Map(x => new Uri(x))
},
Xp = new ImageUrls.XpData()
{
Bg = oldData.Xp.Bg.ToNewCdn(),
},
Version = 2,
};
"https://cdn.nadeko.bot/slots/0.png",
"https://cdn.nadeko.bot/slots/1.png",
"https://cdn.nadeko.bot/slots/2.png",
"https://cdn.nadeko.bot/slots/3.png",
"https://cdn.nadeko.bot/slots/4.png",
"https://cdn.nadeko.bot/slots/5.png"
}.Map(x => new Uri(x))
},
Xp = new ImageUrls.XpData()
{
Bg = oldData.Xp.Bg.ToNewCdn(),
},
Version = 2,
};
File.Move(oldFilePath, backupFilePath, true);
File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(newData));
}
File.Move(oldFilePath, backupFilePath, true);
File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(newData));
}
}
// removed numbers from slots
var localImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
if (localImageUrls.Version == 2)
// removed numbers from slots
var localImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
if (localImageUrls.Version == 2)
{
localImageUrls.Version = 3;
File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(localImageUrls));
}
}
public async Task Reload()
{
ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(await File.ReadAllTextAsync(_imagesPath));
foreach (var key in GetAllKeys())
{
switch (key)
{
localImageUrls.Version = 3;
File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(localImageUrls));
case ImageKeys.CoinHeads:
await Load(key, ImageUrls.Coins.Heads);
break;
case ImageKeys.CoinTails:
await Load(key, ImageUrls.Coins.Tails);
break;
case ImageKeys.Dice:
await Load(key, ImageUrls.Dice);
break;
case ImageKeys.SlotBg:
await Load(key, ImageUrls.Slots.Bg);
break;
case ImageKeys.SlotEmojis:
await Load(key, ImageUrls.Slots.Emojis);
break;
case ImageKeys.Currency:
await Load(key, ImageUrls.Currency);
break;
case ImageKeys.RategirlMatrix:
await Load(key, ImageUrls.Rategirl.Matrix);
break;
case ImageKeys.RategirlDot:
await Load(key, ImageUrls.Rategirl.Dot);
break;
case ImageKeys.RipOverlay:
await Load(key, ImageUrls.Rip.Overlay);
break;
case ImageKeys.RipBg:
await Load(key, ImageUrls.Rip.Bg);
break;
case ImageKeys.XpBg:
await Load(key, ImageUrls.Xp.Bg);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
public async Task Reload()
{
ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(await File.ReadAllTextAsync(_imagesPath));
foreach (var key in GetAllKeys())
{
switch (key)
{
case ImageKeys.CoinHeads:
await Load(key, ImageUrls.Coins.Heads);
break;
case ImageKeys.CoinTails:
await Load(key, ImageUrls.Coins.Tails);
break;
case ImageKeys.Dice:
await Load(key, ImageUrls.Dice);
break;
case ImageKeys.SlotBg:
await Load(key, ImageUrls.Slots.Bg);
break;
case ImageKeys.SlotEmojis:
await Load(key, ImageUrls.Slots.Emojis);
break;
case ImageKeys.Currency:
await Load(key, ImageUrls.Currency);
break;
case ImageKeys.RategirlMatrix:
await Load(key, ImageUrls.Rategirl.Matrix);
break;
case ImageKeys.RategirlDot:
await Load(key, ImageUrls.Rategirl.Dot);
break;
case ImageKeys.RipOverlay:
await Load(key, ImageUrls.Rip.Overlay);
break;
case ImageKeys.RipBg:
await Load(key, ImageUrls.Rip.Bg);
break;
case ImageKeys.XpBg:
await Load(key, ImageUrls.Xp.Bg);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
private async Task Load(ImageKeys key, Uri uri)
{
var data = await GetImageData(uri);
if (data is null)
return;
private async Task Load(ImageKeys key, Uri uri)
{
var data = await GetImageData(uri);
if (data is null)
return;
await _db.StringSetAsync(GetRedisKey(key), data);
}
await _db.StringSetAsync(GetRedisKey(key), data);
}
private async Task Load(ImageKeys key, Uri[] uris)
{
await _db.KeyDeleteAsync(GetRedisKey(key));
var imageData = await Task.WhenAll(uris.Select(GetImageData));
var vals = imageData
.Where(x => x is not null)
.Select(x => (RedisValue)x)
.ToArray();
private async Task Load(ImageKeys key, Uri[] uris)
{
await _db.KeyDeleteAsync(GetRedisKey(key));
var imageData = await Task.WhenAll(uris.Select(GetImageData));
var vals = imageData
.Where(x => x is not null)
.Select(x => (RedisValue)x)
.ToArray();
await _db.ListRightPushAsync(GetRedisKey(key), vals);
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.",
vals.Length, uris.Length, key);
}
}
private async Task<byte[]> GetImageData(Uri uri)
if (uris.Length != vals.Length)
{
if (uri.IsFile)
{
try
{
var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
return bytes;
}
catch (Exception ex)
{
Log.Warning(ex, "Failed reading image bytes from uri: {Uri}", uri.ToString());
return null;
}
}
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);
}
}
private async Task<byte[]> GetImageData(Uri uri)
{
if (uri.IsFile)
{
try
{
return await _http.GetByteArrayAsync(uri);
var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
return bytes;
}
catch (Exception ex)
{
Log.Warning(ex, "Image url you provided is not a valid image: {Uri}", uri.ToString());
Log.Warning(ex, "Failed reading image bytes from uri: {Uri}", uri.ToString());
return null;
}
}
private async Task<bool> AllKeysExist()
try
{
var tasks = await Task.WhenAll(GetAllKeys()
.Select(x => _db.KeyExistsAsync(GetRedisKey(x))));
return tasks.All(exist => exist);
return await _http.GetByteArrayAsync(uri);
}
catch (Exception ex)
{
Log.Warning(ex, "Image url you provided is not a valid image: {Uri}", uri.ToString());
return null;
}
private IEnumerable<ImageKeys> GetAllKeys() =>
Enum.GetValues<ImageKeys>();
private byte[][] GetByteArrayData(ImageKeys key)
=> _db.ListRange(GetRedisKey(key)).Map(x => (byte[])x);
private byte[] GetByteData(ImageKeys key)
=> _db.StringGet(GetRedisKey(key));
private RedisKey GetRedisKey(ImageKeys key)
=> _creds.RedisKey() + "_image_" + key;
}
private async Task<bool> AllKeysExist()
{
var tasks = await Task.WhenAll(GetAllKeys()
.Select(x => _db.KeyExistsAsync(GetRedisKey(x))));
return tasks.All(exist => exist);
}
private IEnumerable<ImageKeys> GetAllKeys() =>
Enum.GetValues<ImageKeys>();
private byte[][] GetByteArrayData(ImageKeys key)
=> _db.ListRange(GetRedisKey(key)).Map(x => (byte[])x);
private byte[] GetByteData(ImageKeys key)
=> _db.StringGet(GetRedisKey(key));
private RedisKey GetRedisKey(ImageKeys key)
=> _creds.RedisKey() + "_image_" + key;
}

View File

@@ -3,100 +3,94 @@ using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Common.Trivia;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Discord;
using Discord.WebSocket;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class RedisLocalDataCache : ILocalDataCache
{
public class RedisLocalDataCache : ILocalDataCache
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private IDatabase _db => _con.GetDatabase();
private const string pokemonAbilitiesFile = "data/pokemon/pokemon_abilities.json";
private const string pokemonListFile = "data/pokemon/pokemon_list.json";
private const string pokemonMapPath = "data/pokemon/name-id_map.json";
private const string questionsFile = "data/trivia_questions.json";
public IReadOnlyDictionary<string, SearchPokemon> Pokemons
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
get => Get<Dictionary<string, SearchPokemon>>("pokemon_list");
private set => Set("pokemon_list", value);
}
private IDatabase _db => _con.GetDatabase();
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities
{
get => Get<Dictionary<string, SearchPokemonAbility>>("pokemon_abilities");
private set => Set("pokemon_abilities", value);
}
private const string pokemonAbilitiesFile = "data/pokemon/pokemon_abilities.json";
private const string pokemonListFile = "data/pokemon/pokemon_list.json";
private const string pokemonMapPath = "data/pokemon/name-id_map.json";
private const string questionsFile = "data/trivia_questions.json";
public TriviaQuestion[] TriviaQuestions
{
get => Get<TriviaQuestion[]>("trivia_questions");
private set => Set("trivia_questions", value);
}
public IReadOnlyDictionary<string, SearchPokemon> Pokemons
public IReadOnlyDictionary<int, string> PokemonMap
{
get => Get<Dictionary<int, string>>("pokemon_map");
private set => Set("pokemon_map", value);
}
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client)
{
_con = con;
_creds = creds;
var shardId = client.ShardId;
if (shardId == 0)
{
get => Get<Dictionary<string, SearchPokemon>>("pokemon_list");
private set => Set("pokemon_list", value);
}
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities
{
get => Get<Dictionary<string, SearchPokemonAbility>>("pokemon_abilities");
private set => Set("pokemon_abilities", value);
}
public TriviaQuestion[] TriviaQuestions
{
get => Get<TriviaQuestion[]>("trivia_questions");
private set => Set("trivia_questions", value);
}
public IReadOnlyDictionary<int, string> PokemonMap
{
get => Get<Dictionary<int, string>>("pokemon_map");
private set => Set("pokemon_map", value);
}
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client)
{
_con = con;
_creds = creds;
var shardId = client.ShardId;
if (shardId == 0)
if (!File.Exists(pokemonListFile))
{
if (!File.Exists(pokemonListFile))
{
Log.Warning($"{pokemonListFile} is missing. Pokemon abilities not loaded");
}
else
{
Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(pokemonListFile));
}
Log.Warning($"{pokemonListFile} is missing. Pokemon abilities not loaded");
}
else
{
Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(pokemonListFile));
}
if (!File.Exists(pokemonAbilitiesFile))
{
Log.Warning($"{pokemonAbilitiesFile} is missing. Pokemon abilities not loaded.");
}
else
{
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(pokemonAbilitiesFile));
}
if (!File.Exists(pokemonAbilitiesFile))
{
Log.Warning($"{pokemonAbilitiesFile} is missing. Pokemon abilities not loaded.");
}
else
{
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(pokemonAbilitiesFile));
}
try
{
TriviaQuestions = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(questionsFile));
PokemonMap = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(pokemonMapPath))
.ToDictionary(x => x.Id, x => x.Name);
}
catch (Exception ex)
{
Log.Error(ex, "Error loading local data");
throw;
}
try
{
TriviaQuestions = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(questionsFile));
PokemonMap = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(pokemonMapPath))
.ToDictionary(x => x.Id, x => x.Name);
}
catch (Exception ex)
{
Log.Error(ex, "Error loading local data");
throw;
}
}
private T Get<T>(string key) where T : class
{
return JsonConvert.DeserializeObject<T>(_db.StringGet($"{_creds.RedisKey()}_localdata_{key}"));
}
private void Set(string key, object obj)
{
_db.StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj));
}
}
}
private T Get<T>(string key) where T : class
{
return JsonConvert.DeserializeObject<T>(_db.StringGet($"{_creds.RedisKey()}_localdata_{key}"));
}
private void Set(string key, object obj)
{
_db.StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj));
}
}

View File

@@ -1,146 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Grpc.Core;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Coordinator;
using NadekoBot.Services;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
{
public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
private readonly Coordinator.Coordinator.CoordinatorClient _coordClient;
private readonly DiscordSocketClient _client;
public RemoteGrpcCoordinator(IBotCredentials creds, DiscordSocketClient client)
{
private readonly Coordinator.Coordinator.CoordinatorClient _coordClient;
private readonly DiscordSocketClient _client;
public RemoteGrpcCoordinator(IBotCredentials creds, DiscordSocketClient client)
{
var coordUrl = string.IsNullOrWhiteSpace(creds.CoordinatorUrl)
? "http://localhost:3442"
: creds.CoordinatorUrl;
var coordUrl = string.IsNullOrWhiteSpace(creds.CoordinatorUrl)
? "http://localhost:3442"
: creds.CoordinatorUrl;
var channel = Grpc.Net.Client.GrpcChannel.ForAddress(coordUrl);
_coordClient = new(channel);
_client = client;
}
var channel = Grpc.Net.Client.GrpcChannel.ForAddress(coordUrl);
_coordClient = new(channel);
_client = client;
}
public bool RestartBot()
public bool RestartBot()
{
_coordClient.RestartAllShards(new RestartAllRequest
{
_coordClient.RestartAllShards(new RestartAllRequest
{
});
});
return true;
}
return true;
}
public void Die(bool graceful)
public void Die(bool graceful)
{
_coordClient.Die(new DieRequest()
{
_coordClient.Die(new DieRequest()
Graceful = graceful
});
}
public bool RestartShard(int shardId)
{
_coordClient.RestartShard(new RestartShardRequest
{
ShardId = shardId,
});
return true;
}
public IList<ShardStatus> GetAllShardStatuses()
{
var res = _coordClient.GetAllStatuses(new GetAllStatusesRequest());
return res.Statuses
.ToArray()
.Map(s => new ShardStatus()
{
Graceful = graceful
ConnectionState = FromCoordConnState(s.State),
GuildCount = s.GuildCount,
ShardId = s.ShardId,
LastUpdate = s.LastUpdate.ToDateTime(),
});
}
}
public bool RestartShard(int shardId)
public int GetGuildCount()
{
var res = _coordClient.GetAllStatuses(new GetAllStatusesRequest());
return res.Statuses.Sum(x => x.GuildCount);
}
public async Task Reload()
{
await _coordClient.ReloadAsync(new());
}
public Task OnReadyAsync()
{
Task.Run(async () =>
{
_coordClient.RestartShard(new RestartShardRequest
var gracefulImminent = false;
while (true)
{
ShardId = shardId,
});
return true;
}
public IList<ShardStatus> GetAllShardStatuses()
{
var res = _coordClient.GetAllStatuses(new GetAllStatusesRequest());
return res.Statuses
.ToArray()
.Map(s => new ShardStatus()
try
{
ConnectionState = FromCoordConnState(s.State),
GuildCount = s.GuildCount,
ShardId = s.ShardId,
LastUpdate = s.LastUpdate.ToDateTime(),
});
}
public int GetGuildCount()
{
var res = _coordClient.GetAllStatuses(new GetAllStatusesRequest());
return res.Statuses.Sum(x => x.GuildCount);
}
public async Task Reload()
{
await _coordClient.ReloadAsync(new());
}
public Task OnReadyAsync()
{
Task.Run(async () =>
{
var gracefulImminent = false;
while (true)
var reply = await _coordClient.HeartbeatAsync(new HeartbeatRequest
{
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)
{
try
if (!gracefulImminent)
{
var reply = await _coordClient.HeartbeatAsync(new HeartbeatRequest
{
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}",
ex.Message);
break;
}
await Task.Delay(22500).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, "Unexpected heartbeat exception: {Message}", ex.Message);
Log.Warning(ex, "Hearbeat failed and graceful shutdown was not expected: {Message}",
ex.Message);
break;
}
await Task.Delay(7500).ConfigureAwait(false);
await Task.Delay(22500).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, "Unexpected heartbeat exception: {Message}", ex.Message);
break;
}
Environment.Exit(5);
});
await Task.Delay(7500).ConfigureAwait(false);
}
return Task.CompletedTask;
}
Environment.Exit(5);
});
private ConnState ToCoordConnState(ConnectionState state)
=> state switch
{
ConnectionState.Connecting => ConnState.Connecting,
ConnectionState.Connected => ConnState.Connected,
_ => ConnState.Disconnected
};
private ConnectionState FromCoordConnState(ConnState state)
=> state switch
{
ConnState.Connecting => ConnectionState.Connecting,
ConnState.Connected => ConnectionState.Connected,
_ => ConnectionState.Disconnected
};
return Task.CompletedTask;
}
private ConnState ToCoordConnState(ConnectionState state)
=> state switch
{
ConnectionState.Connecting => ConnState.Connecting,
ConnectionState.Connected => ConnState.Connected,
_ => ConnState.Disconnected
};
private ConnectionState FromCoordConnState(ConnState state)
=> state switch
{
ConnState.Connecting => ConnectionState.Connecting,
ConnState.Connected => ConnectionState.Connected,
_ => ConnectionState.Disconnected
};
}

View File

@@ -1,73 +1,68 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Services;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class SingleProcessCoordinator : ICoordinator
{
public class SingleProcessCoordinator : ICoordinator
private readonly IBotCredentials _creds;
private readonly DiscordSocketClient _client;
public SingleProcessCoordinator(IBotCredentials creds, DiscordSocketClient client)
{
private readonly IBotCredentials _creds;
private readonly DiscordSocketClient _client;
public SingleProcessCoordinator(IBotCredentials creds, DiscordSocketClient client)
_creds = creds;
_client = client;
}
public bool RestartBot()
{
if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd)
|| string.IsNullOrWhiteSpace(_creds.RestartCommand?.Args))
{
_creds = creds;
_client = client;
Log.Error("You must set RestartCommand.Cmd and RestartCommand.Args in creds.yml");
return false;
}
public bool RestartBot()
{
if (string.IsNullOrWhiteSpace(_creds.RestartCommand?.Cmd)
|| string.IsNullOrWhiteSpace(_creds.RestartCommand?.Args))
{
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 () =>
Process.Start(_creds.RestartCommand.Cmd, _creds.RestartCommand.Args);
_ = Task.Run(async () =>
{
await Task.Delay(2000);
Die();
});
return true;
}
public void Die(bool graceful = false)
{
Environment.Exit(5);
}
public bool RestartShard(int shardId)
{
return RestartBot();
}
public IList<ShardStatus> GetAllShardStatuses()
{
return new[]
{
new ShardStatus()
{
await Task.Delay(2000);
Die();
});
return true;
}
ConnectionState = _client.ConnectionState,
GuildCount = _client.Guilds.Count,
LastUpdate = DateTime.UtcNow,
ShardId = _client.ShardId
}
};
}
public void Die(bool graceful = false)
{
Environment.Exit(5);
}
public int GetGuildCount()
{
return _client.Guilds.Count;
}
public bool RestartShard(int shardId)
{
return RestartBot();
}
public IList<ShardStatus> GetAllShardStatuses()
{
return new[]
{
new ShardStatus()
{
ConnectionState = _client.ConnectionState,
GuildCount = _client.Guilds.Count,
LastUpdate = DateTime.UtcNow,
ShardId = _client.ShardId
}
};
}
public int GetGuildCount()
{
return _client.Guilds.Count;
}
public Task Reload()
{
return Task.CompletedTask;
}
public Task Reload()
{
return Task.CompletedTask;
}
}

View File

@@ -1,78 +1,75 @@
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class SoundCloudApiService : INService
{
public class SoundCloudApiService : INService
private readonly IHttpClientFactory _httpFactory;
public SoundCloudApiService(IHttpClientFactory factory)
{
private readonly IHttpClientFactory _httpFactory;
public SoundCloudApiService(IHttpClientFactory factory)
{
_httpFactory = factory;
}
public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
{
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url));
string response = "";
using (var http = _httpFactory.CreateClient())
{
response = await http.GetStringAsync($"https://scapi.nadeko.bot/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 async Task<SoundCloudVideo> GetVideoByQueryAsync(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
var response = "";
using (var http = _httpFactory.CreateClient())
{
response = await http.GetStringAsync(new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}")).ConfigureAwait(false);
}
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response)
.FirstOrDefault(s => s.Streamable is true);
if (responseObj?.Kind != "track")
throw new InvalidOperationException("Query yielded no results.");
return responseObj;
}
_httpFactory = factory;
}
public class SoundCloudVideo
public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
{
public string Kind { get; set; } = "";
public long Id { get; set; } = 0;
public SoundCloudUser User { get; set; } = new SoundCloudUser();
public string Title { get; set; } = "";
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; } = "";
[JsonProperty("artwork_url")]
public string ArtworkUrl { get; set; } = "";
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url));
string response = "";
using (var http = _httpFactory.CreateClient())
{
response = await http.GetStringAsync($"https://scapi.nadeko.bot/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 class SoundCloudUser
public async Task<SoundCloudVideo> GetVideoByQueryAsync(string query)
{
[JsonProperty("username")]
public string Name { get; set; }
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
var response = "";
using (var http = _httpFactory.CreateClient())
{
response = await http.GetStringAsync(new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}")).ConfigureAwait(false);
}
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response)
.FirstOrDefault(s => s.Streamable is true);
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; } = "";
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; } = "";
[JsonProperty("artwork_url")]
public string ArtworkUrl { get; set; } = "";
}
public class SoundCloudUser
{
[JsonProperty("username")]
public string Name { get; set; }
}

View File

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

View File

@@ -2,186 +2,181 @@
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
{
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
private readonly Process _currentProcess = Process.GetCurrentProcess();
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "3.0.11";
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)
{
private readonly Process _currentProcess = Process.GetCurrentProcess();
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private readonly DateTime _started;
_client = client;
_creds = creds;
_httpFactory = factory;
public const string BotVersion = "3.0.11";
public string Author => "Kwoth#2452";
public string Library => "Discord.Net";
public double MessagesPerSecond => MessageCounter / GetUptime().TotalSeconds;
_started = DateTime.UtcNow;
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
cmdHandler.CommandExecuted += (_, e) => Task.FromResult(Interlocked.Increment(ref _commandsRan));
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)
_client.ChannelCreated += (c) =>
{
_client = client;
_creds = creds;
_httpFactory = factory;
_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(() =>
{
var _ = Task.Run(() =>
{
if (c is ITextChannel)
Interlocked.Increment(ref _textChannels);
else if (c is IVoiceChannel)
Interlocked.Increment(ref _voiceChannels);
});
if (c is ITextChannel)
Interlocked.Increment(ref _textChannels);
else if (c is IVoiceChannel)
Interlocked.Increment(ref _voiceChannels);
});
return Task.CompletedTask;
};
return Task.CompletedTask;
};
_client.ChannelDestroyed += (c) =>
_client.ChannelDestroyed += (c) =>
{
var _ = Task.Run(() =>
{
var _ = Task.Run(() =>
{
if (c is ITextChannel)
Interlocked.Decrement(ref _textChannels);
else if (c is IVoiceChannel)
Interlocked.Decrement(ref _voiceChannels);
});
if (c is ITextChannel)
Interlocked.Decrement(ref _textChannels);
else if (c is IVoiceChannel)
Interlocked.Decrement(ref _voiceChannels);
});
return Task.CompletedTask;
};
return Task.CompletedTask;
};
_client.GuildAvailable += (g) =>
_client.GuildAvailable += (g) =>
{
var _ = Task.Run(() =>
{
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;
};
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) =>
_client.JoinedGuild += (g) =>
{
var _ = Task.Run(() =>
{
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;
};
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) =>
_client.GuildUnavailable += (g) =>
{
var _ = Task.Run(() =>
{
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);
});
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;
};
return Task.CompletedTask;
};
_client.LeftGuild += (g) =>
_client.LeftGuild += (g) =>
{
var _ = Task.Run(() =>
{
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);
});
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;
};
return Task.CompletedTask;
};
_botlistTimer = new Timer(async (state) =>
_botlistTimer = new Timer(async (state) =>
{
if (string.IsNullOrWhiteSpace(_creds.BotListToken))
return;
try
{
if (string.IsNullOrWhiteSpace(_creds.BotListToken))
return;
try
using (var http = _httpFactory.CreateClient())
{
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() }
}))
{
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).ConfigureAwait(false)) { }
}
using (await http.PostAsync(new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), content).ConfigureAwait(false)) { }
}
}
catch (Exception ex)
{
Log.Error(ex, "Error ");
// ignored
}
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromHours(1));
}
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";
}
public Task OnReadyAsync()
{
var guilds = _client.Guilds;
_textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel));
_voiceChannels = guilds.Sum(g => g.Channels.Count(cx => cx is IVoiceChannel));
return Task.CompletedTask;
}
public double GetPrivateMemory()
{
_currentProcess.Refresh();
return _currentProcess.PrivateMemorySize64 / (double)1.MiB();
}
public void Dispose()
{
_currentProcess.Dispose();
GC.SuppressFinalize(this);
}
}
catch (Exception ex)
{
Log.Error(ex, "Error ");
// ignored
}
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromHours(1));
}
}
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";
}
public Task OnReadyAsync()
{
var guilds = _client.Guilds;
_textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel));
_voiceChannels = guilds.Sum(g => g.Channels.Count(cx => cx is IVoiceChannel));
return Task.CompletedTask;
}
public double GetPrivateMemory()
{
_currentProcess.Refresh();
return _currentProcess.PrivateMemorySize64 / (double)1.MiB();
}
public void Dispose()
{
_currentProcess.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,81 +1,77 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Services
namespace NadekoBot.Services;
public class YtdlOperation
{
public class YtdlOperation
private readonly string _baseArgString;
public YtdlOperation(string baseArgString)
{
private readonly string _baseArgString;
_baseArgString = baseArgString;
}
public YtdlOperation(string baseArgString)
private Process CreateProcess(string[] args)
{
args = args.Map(arg => arg.Replace("\"", ""));
return new Process()
{
_baseArgString = baseArgString;
}
private Process CreateProcess(string[] args)
{
args = args.Map(arg => arg.Replace("\"", ""));
return new Process()
StartInfo = new ProcessStartInfo()
{
StartInfo = new ProcessStartInfo()
{
FileName = "youtube-dl",
Arguments = string.Format(_baseArgString, args),
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
CreateNoWindow = true,
},
};
}
FileName = "youtube-dl",
Arguments = string.Format(_baseArgString, args),
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
CreateNoWindow = true,
},
};
}
public async Task<string> GetDataAsync(params string[] args)
{
try
{
using var process = CreateProcess(args);
Log.Debug($"Executing {process.StartInfo.FileName} {process.StartInfo.Arguments}");
process.Start();
var str = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
var err = await process.StandardError.ReadToEndAsync().ConfigureAwait(false);
if (!string.IsNullOrEmpty(err))
Log.Warning("YTDL warning: {YtdlWarning}", err);
return str;
}
catch (Win32Exception)
{
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);
return default;
}
}
public async IAsyncEnumerable<string> EnumerateDataAsync(params string[] args)
public async Task<string> GetDataAsync(params string[] args)
{
try
{
using var process = CreateProcess(args);
Log.Debug($"Executing {process.StartInfo.FileName} {process.StartInfo.Arguments}");
process.Start();
string line;
while((line = await process.StandardOutput.ReadLineAsync()) != null)
yield return line;
var str = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
var err = await process.StandardError.ReadToEndAsync().ConfigureAwait(false);
if (!string.IsNullOrEmpty(err))
Log.Warning("YTDL warning: {YtdlWarning}", err);
return str;
}
catch (Win32Exception)
{
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);
return default;
}
}
}
public async IAsyncEnumerable<string> EnumerateDataAsync(params string[] args)
{
using var process = CreateProcess(args);
Log.Debug($"Executing {process.StartInfo.FileName} {process.StartInfo.Arguments}");
process.Start();
string line;
while((line = await process.StandardOutput.ReadLineAsync()) != null)
yield return line;
}
}