mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 02:08:27 -04:00
Global usings and file scoped namespaces
This commit is contained in:
@@ -3,159 +3,155 @@ using AngleSharp.Html.Dom;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class AnimeSearchService : INService
|
||||
{
|
||||
public class AnimeSearchService : INService
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public AnimeSearchService(IDataCache cache, IHttpClientFactory httpFactory)
|
||||
{
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
public AnimeSearchService(IDataCache cache, IHttpClientFactory httpFactory)
|
||||
public async Task<AnimeResult> GetAnimeData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
{
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
public async Task<AnimeResult> GetAnimeData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
var link = "https://aniapi.nadeko.bot/anime/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
|
||||
var link = "https://aniapi.nadeko.bot/anime/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<AnimeResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonConvert.DeserializeObject<AnimeResult>(data);
|
||||
}
|
||||
|
||||
public async Task<NovelResult> GetNovelData(string query)
|
||||
catch
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
query = query.Replace(" ", "-", StringComparison.InvariantCulture);
|
||||
try
|
||||
{
|
||||
var link = "https://www.novelupdates.com/series/" + Uri.EscapeDataString(query
|
||||
.Replace(" ", "-")
|
||||
.Replace("/", " ")
|
||||
);
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetNovelDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using (var document = await BrowsingContext.New(config).OpenAsync(link).ConfigureAwait(false))
|
||||
{
|
||||
var imageElem = document.QuerySelector("div.seriesimg > img");
|
||||
if (imageElem is null)
|
||||
return null;
|
||||
var imageUrl = ((IHtmlImageElement)imageElem).Source;
|
||||
|
||||
var descElem = document.QuerySelector("div#editdescription > p");
|
||||
var desc = descElem.InnerHtml;
|
||||
|
||||
var genres = document.QuerySelector("div#seriesgenre").Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var authors = document
|
||||
.QuerySelector("div#showauthors")
|
||||
.Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var score = ((IHtmlSpanElement)document
|
||||
.QuerySelector("h5.seriesother > span.uvotes"))
|
||||
.InnerHtml;
|
||||
|
||||
var status = document
|
||||
.QuerySelector("div#editstatus")
|
||||
.InnerHtml;
|
||||
var title = document
|
||||
.QuerySelector("div.w-blog-content > div.seriestitlenu")
|
||||
.InnerHtml;
|
||||
|
||||
var obj = new NovelResult()
|
||||
{
|
||||
Description = desc,
|
||||
Authors = authors,
|
||||
Genres = genres,
|
||||
ImageUrl = imageUrl,
|
||||
Link = link,
|
||||
Score = score,
|
||||
Status = status,
|
||||
Title = title,
|
||||
};
|
||||
|
||||
await _cache.SetNovelDataAsync(link,
|
||||
JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<NovelResult>(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting novel data");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MangaResult> GetMangaData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
{
|
||||
|
||||
var link = "https://aniapi.nadeko.bot/manga/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<MangaResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<NovelResult> GetNovelData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
query = query.Replace(" ", "-", StringComparison.InvariantCulture);
|
||||
try
|
||||
{
|
||||
var link = "https://www.novelupdates.com/series/" + Uri.EscapeDataString(query
|
||||
.Replace(" ", "-")
|
||||
.Replace("/", " ")
|
||||
);
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetNovelDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using (var document = await BrowsingContext.New(config).OpenAsync(link).ConfigureAwait(false))
|
||||
{
|
||||
var imageElem = document.QuerySelector("div.seriesimg > img");
|
||||
if (imageElem is null)
|
||||
return null;
|
||||
var imageUrl = ((IHtmlImageElement)imageElem).Source;
|
||||
|
||||
var descElem = document.QuerySelector("div#editdescription > p");
|
||||
var desc = descElem.InnerHtml;
|
||||
|
||||
var genres = document.QuerySelector("div#seriesgenre").Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var authors = document
|
||||
.QuerySelector("div#showauthors")
|
||||
.Children
|
||||
.Select(x => x as IHtmlAnchorElement)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.InnerHtml}]({x.Href})")
|
||||
.ToArray();
|
||||
|
||||
var score = ((IHtmlSpanElement)document
|
||||
.QuerySelector("h5.seriesother > span.uvotes"))
|
||||
.InnerHtml;
|
||||
|
||||
var status = document
|
||||
.QuerySelector("div#editstatus")
|
||||
.InnerHtml;
|
||||
var title = document
|
||||
.QuerySelector("div.w-blog-content > div.seriestitlenu")
|
||||
.InnerHtml;
|
||||
|
||||
var obj = new NovelResult()
|
||||
{
|
||||
Description = desc,
|
||||
Authors = authors,
|
||||
Genres = genres,
|
||||
ImageUrl = imageUrl,
|
||||
Link = link,
|
||||
Score = score,
|
||||
Status = status,
|
||||
Title = title,
|
||||
};
|
||||
|
||||
await _cache.SetNovelDataAsync(link,
|
||||
JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<NovelResult>(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting novel data");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MangaResult> GetMangaData(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
try
|
||||
{
|
||||
|
||||
var link = "https://aniapi.nadeko.bot/manga/" + Uri.EscapeDataString(query.Replace("/", " ", StringComparison.InvariantCulture));
|
||||
link = link.ToLowerInvariant();
|
||||
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
|
||||
if (!ok)
|
||||
{
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
data = await http.GetStringAsync(link).ConfigureAwait(false);
|
||||
}
|
||||
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
return JsonConvert.DeserializeObject<MangaResult>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,14 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public static class AtlExtensions
|
||||
{
|
||||
public static class AtlExtensions
|
||||
{
|
||||
public static Task<AutoTranslateChannel> GetByChannelId(this IQueryable<AutoTranslateChannel> set, ulong channelId)
|
||||
=> set
|
||||
.Include(x => x.Users)
|
||||
.FirstOrDefaultAsyncEF(x => x.ChannelId == channelId);
|
||||
}
|
||||
public static Task<AutoTranslateChannel> GetByChannelId(this IQueryable<AutoTranslateChannel> set, ulong channelId)
|
||||
=> set
|
||||
.Include(x => x.Users)
|
||||
.FirstOrDefaultAsyncEF(x => x.ChannelId == channelId);
|
||||
}
|
@@ -2,107 +2,102 @@
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class CryptoService : INService
|
||||
{
|
||||
public class CryptoService : INService
|
||||
{
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IBotCredentials _creds;
|
||||
|
||||
public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds)
|
||||
public CryptoService(IDataCache cache, IHttpClientFactory httpFactory, IBotCredentials creds)
|
||||
{
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task<(CryptoResponseData Data, CryptoResponseData Nearest)> GetCryptoData(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
_cache = cache;
|
||||
_httpFactory = httpFactory;
|
||||
_creds = creds;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
public async Task<(CryptoResponseData Data, CryptoResponseData Nearest)> GetCryptoData(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
name = name.ToUpperInvariant();
|
||||
var cryptos = await CryptoData().ConfigureAwait(false);
|
||||
|
||||
name = name.ToUpperInvariant();
|
||||
var cryptos = await CryptoData().ConfigureAwait(false);
|
||||
|
||||
if (cryptos is null)
|
||||
return (null, null);
|
||||
if (cryptos is null)
|
||||
return (null, null);
|
||||
|
||||
var crypto = cryptos
|
||||
?.FirstOrDefault(x => x.Id.ToUpperInvariant() == name || x.Name.ToUpperInvariant() == name
|
||||
|| x.Symbol.ToUpperInvariant() == name);
|
||||
var crypto = cryptos
|
||||
?.FirstOrDefault(x => x.Id.ToUpperInvariant() == name || x.Name.ToUpperInvariant() == name
|
||||
|| x.Symbol.ToUpperInvariant() == name);
|
||||
|
||||
(CryptoResponseData Elem, int Distance)? nearest = null;
|
||||
if (crypto is null)
|
||||
{
|
||||
nearest = cryptos
|
||||
.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name)))
|
||||
.OrderBy(x => x.Distance)
|
||||
.Where(x => x.Distance <= 2)
|
||||
.FirstOrDefault();
|
||||
(CryptoResponseData Elem, int Distance)? nearest = null;
|
||||
if (crypto is null)
|
||||
{
|
||||
nearest = cryptos
|
||||
.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name)))
|
||||
.OrderBy(x => x.Distance)
|
||||
.Where(x => x.Distance <= 2)
|
||||
.FirstOrDefault();
|
||||
|
||||
crypto = nearest?.Elem;
|
||||
}
|
||||
|
||||
if (nearest != null)
|
||||
{
|
||||
return (null, crypto);
|
||||
}
|
||||
|
||||
return (crypto, null);
|
||||
crypto = nearest?.Elem;
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim getCryptoLock = new SemaphoreSlim(1, 1);
|
||||
public async Task<List<CryptoResponseData>> CryptoData()
|
||||
if (nearest != null)
|
||||
{
|
||||
await getCryptoLock.WaitAsync();
|
||||
try
|
||||
return (null, crypto);
|
||||
}
|
||||
|
||||
return (crypto, null);
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim getCryptoLock = new SemaphoreSlim(1, 1);
|
||||
public async Task<List<CryptoResponseData>> CryptoData()
|
||||
{
|
||||
await getCryptoLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", async _ =>
|
||||
{
|
||||
var fullStrData = await _cache.GetOrAddCachedDataAsync("nadeko:crypto_data", async _ =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
using var _http = _httpFactory.CreateClient();
|
||||
var strData = await _http.GetStringAsync(
|
||||
$"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" +
|
||||
$"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" +
|
||||
$"&start=1" +
|
||||
$"&limit=5000" +
|
||||
$"&convert=USD");
|
||||
using var _http = _httpFactory.CreateClient();
|
||||
var strData = await _http.GetStringAsync(
|
||||
$"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" +
|
||||
$"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" +
|
||||
$"&start=1" +
|
||||
$"&limit=5000" +
|
||||
$"&convert=USD");
|
||||
|
||||
JsonConvert.DeserializeObject<CryptoResponse>(strData); // just to see if its' valid
|
||||
JsonConvert.DeserializeObject<CryptoResponse>(strData); // just to see if its' valid
|
||||
|
||||
return strData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
return strData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
|
||||
}, "", TimeSpan.FromHours(1));
|
||||
}, "", TimeSpan.FromHours(1));
|
||||
|
||||
return JsonConvert.DeserializeObject<CryptoResponse>(fullStrData).Data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error retreiving crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
finally
|
||||
{
|
||||
getCryptoLock.Release();
|
||||
}
|
||||
return JsonConvert.DeserializeObject<CryptoResponse>(fullStrData).Data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error retreiving crypto data: {Message}", ex.Message);
|
||||
return default;
|
||||
}
|
||||
finally
|
||||
{
|
||||
getCryptoLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,246 +5,241 @@ using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public class FeedsService : INService
|
||||
{
|
||||
public class FeedsService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
public FeedsService(Bot bot, DbService db, DiscordSocketClient client, IEmbedBuilderService eb)
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
_db = db;
|
||||
|
||||
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
public FeedsService(Bot bot, DbService db, DiscordSocketClient client, IEmbedBuilderService eb)
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
_db = db;
|
||||
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var guildConfigIds = bot.AllGuildConfigs.Select(x => x.Id).ToList();
|
||||
_subs = uow.GuildConfigs
|
||||
.AsQueryable()
|
||||
.Where(x => guildConfigIds.Contains(x.Id))
|
||||
.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig)
|
||||
.ToList()
|
||||
.SelectMany(x => x.FeedSubs)
|
||||
.GroupBy(x => x.Url.ToLower())
|
||||
.ToDictionary(x => x.Key, x => x.ToHashSet())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
_client = client;
|
||||
_eb = eb;
|
||||
|
||||
var _ = Task.Run(TrackFeeds);
|
||||
var guildConfigIds = bot.AllGuildConfigs.Select(x => x.Id).ToList();
|
||||
_subs = uow.GuildConfigs
|
||||
.AsQueryable()
|
||||
.Where(x => guildConfigIds.Contains(x.Id))
|
||||
.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig)
|
||||
.ToList()
|
||||
.SelectMany(x => x.FeedSubs)
|
||||
.GroupBy(x => x.Url.ToLower())
|
||||
.ToDictionary(x => x.Key, x => x.ToHashSet())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public async Task<EmbedBuilder> TrackFeeds()
|
||||
_client = client;
|
||||
_eb = eb;
|
||||
|
||||
var _ = Task.Run(TrackFeeds);
|
||||
}
|
||||
|
||||
public async Task<EmbedBuilder> TrackFeeds()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (true)
|
||||
var allSendTasks = new List<Task>(_subs.Count);
|
||||
foreach (var kvp in _subs)
|
||||
{
|
||||
var allSendTasks = new List<Task>(_subs.Count);
|
||||
foreach (var kvp in _subs)
|
||||
if (kvp.Value.Count == 0)
|
||||
continue;
|
||||
|
||||
var rssUrl = kvp.Key;
|
||||
try
|
||||
{
|
||||
if (kvp.Value.Count == 0)
|
||||
continue;
|
||||
var feed = await CodeHollow.FeedReader.FeedReader.ReadAsync(rssUrl).ConfigureAwait(false);
|
||||
|
||||
var rssUrl = kvp.Key;
|
||||
try
|
||||
var items = feed
|
||||
.Items
|
||||
.Select(item => (Item: item, LastUpdate: item.PublishingDate?.ToUniversalTime()
|
||||
?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate
|
||||
?.ToUniversalTime()))
|
||||
.Where(data => !(data.LastUpdate is null))
|
||||
.Select(data => (data.Item, LastUpdate: (DateTime) data.LastUpdate))
|
||||
.OrderByDescending(data => data.LastUpdate)
|
||||
.Reverse() // start from the oldest
|
||||
.ToList();
|
||||
|
||||
if (!_lastPosts.TryGetValue(kvp.Key, out DateTime lastFeedUpdate))
|
||||
{
|
||||
var feed = await CodeHollow.FeedReader.FeedReader.ReadAsync(rssUrl).ConfigureAwait(false);
|
||||
lastFeedUpdate = _lastPosts[kvp.Key] =
|
||||
items.Any() ? items[items.Count - 1].LastUpdate : DateTime.UtcNow;
|
||||
}
|
||||
|
||||
var items = feed
|
||||
.Items
|
||||
.Select(item => (Item: item, LastUpdate: item.PublishingDate?.ToUniversalTime()
|
||||
?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate
|
||||
?.ToUniversalTime()))
|
||||
.Where(data => !(data.LastUpdate is null))
|
||||
.Select(data => (data.Item, LastUpdate: (DateTime) data.LastUpdate))
|
||||
.OrderByDescending(data => data.LastUpdate)
|
||||
.Reverse() // start from the oldest
|
||||
.ToList();
|
||||
|
||||
if (!_lastPosts.TryGetValue(kvp.Key, out DateTime lastFeedUpdate))
|
||||
foreach (var (feedItem, itemUpdateDate) in items)
|
||||
{
|
||||
if (itemUpdateDate <= lastFeedUpdate)
|
||||
{
|
||||
lastFeedUpdate = _lastPosts[kvp.Key] =
|
||||
items.Any() ? items[items.Count - 1].LastUpdate : DateTime.UtcNow;
|
||||
continue;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithFooter(rssUrl);
|
||||
|
||||
_lastPosts[kvp.Key] = itemUpdateDate;
|
||||
|
||||
var link = feedItem.SpecificItem.Link;
|
||||
if (!string.IsNullOrWhiteSpace(link) && Uri.IsWellFormedUriString(link, UriKind.Absolute))
|
||||
embed.WithUrl(link);
|
||||
|
||||
var title = string.IsNullOrWhiteSpace(feedItem.Title)
|
||||
? "-"
|
||||
: feedItem.Title;
|
||||
|
||||
var gotImage = false;
|
||||
if (feedItem.SpecificItem is MediaRssFeedItem mrfi &&
|
||||
(mrfi.Enclosure?.MediaType?.StartsWith("image/") ?? false))
|
||||
{
|
||||
var imgUrl = mrfi.Enclosure.Url;
|
||||
if (!string.IsNullOrWhiteSpace(imgUrl) &&
|
||||
Uri.IsWellFormedUriString(imgUrl, UriKind.Absolute))
|
||||
{
|
||||
embed.WithImageUrl(imgUrl);
|
||||
gotImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (feedItem, itemUpdateDate) in items)
|
||||
|
||||
if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi)
|
||||
{
|
||||
if (itemUpdateDate <= lastFeedUpdate)
|
||||
var previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "preview");
|
||||
|
||||
if (previewElement is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithFooter(rssUrl);
|
||||
|
||||
_lastPosts[kvp.Key] = itemUpdateDate;
|
||||
|
||||
var link = feedItem.SpecificItem.Link;
|
||||
if (!string.IsNullOrWhiteSpace(link) && Uri.IsWellFormedUriString(link, UriKind.Absolute))
|
||||
embed.WithUrl(link);
|
||||
|
||||
var title = string.IsNullOrWhiteSpace(feedItem.Title)
|
||||
? "-"
|
||||
: feedItem.Title;
|
||||
|
||||
var gotImage = false;
|
||||
if (feedItem.SpecificItem is MediaRssFeedItem mrfi &&
|
||||
(mrfi.Enclosure?.MediaType?.StartsWith("image/") ?? false))
|
||||
previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "thumbnail");
|
||||
}
|
||||
|
||||
if (previewElement != null)
|
||||
{
|
||||
var imgUrl = mrfi.Enclosure.Url;
|
||||
if (!string.IsNullOrWhiteSpace(imgUrl) &&
|
||||
Uri.IsWellFormedUriString(imgUrl, UriKind.Absolute))
|
||||
var urlAttribute = previewElement.Attribute("url");
|
||||
if (urlAttribute != null && !string.IsNullOrWhiteSpace(urlAttribute.Value)
|
||||
&& Uri.IsWellFormedUriString(urlAttribute.Value,
|
||||
UriKind.Absolute))
|
||||
{
|
||||
embed.WithImageUrl(imgUrl);
|
||||
embed.WithImageUrl(urlAttribute.Value);
|
||||
gotImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi)
|
||||
{
|
||||
var previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "preview");
|
||||
|
||||
if (previewElement is null)
|
||||
{
|
||||
previewElement = afi.Element.Elements()
|
||||
.FirstOrDefault(x => x.Name.LocalName == "thumbnail");
|
||||
}
|
||||
|
||||
if (previewElement != null)
|
||||
{
|
||||
var urlAttribute = previewElement.Attribute("url");
|
||||
if (urlAttribute != null && !string.IsNullOrWhiteSpace(urlAttribute.Value)
|
||||
&& Uri.IsWellFormedUriString(urlAttribute.Value,
|
||||
UriKind.Absolute))
|
||||
{
|
||||
embed.WithImageUrl(urlAttribute.Value);
|
||||
gotImage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
embed.WithTitle(title.TrimTo(256));
|
||||
|
||||
var desc = feedItem.Description?.StripHTML();
|
||||
if (!string.IsNullOrWhiteSpace(feedItem.Description))
|
||||
embed.WithDescription(desc.TrimTo(2048));
|
||||
|
||||
//send the created embed to all subscribed channels
|
||||
var feedSendTasks = kvp.Value
|
||||
.Where(x => x.GuildConfig != null)
|
||||
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
|
||||
?.GetTextChannel(x.ChannelId))
|
||||
.Where(x => x != null)
|
||||
.Select(x => x.EmbedAsync(embed));
|
||||
|
||||
allSendTasks.Add(Task.WhenAll(feedSendTasks));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
|
||||
embed.WithTitle(title.TrimTo(256));
|
||||
|
||||
var desc = feedItem.Description?.StripHTML();
|
||||
if (!string.IsNullOrWhiteSpace(feedItem.Description))
|
||||
embed.WithDescription(desc.TrimTo(2048));
|
||||
|
||||
//send the created embed to all subscribed channels
|
||||
var feedSendTasks = kvp.Value
|
||||
.Where(x => x.GuildConfig != null)
|
||||
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
|
||||
?.GetTextChannel(x.ChannelId))
|
||||
.Where(x => x != null)
|
||||
.Select(x => x.EmbedAsync(embed));
|
||||
|
||||
allSendTasks.Add(Task.WhenAll(feedSendTasks));
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(Task.WhenAll(allSendTasks), Task.Delay(10000)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FeedSub> GetFeeds(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig))
|
||||
.FeedSubs
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
|
||||
{
|
||||
rssFeed.ThrowIfNull(nameof(rssFeed));
|
||||
|
||||
var fs = new FeedSub()
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Url = rssFeed.Trim(),
|
||||
};
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig));
|
||||
|
||||
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (gc.FeedSubs.Count >= 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gc.FeedSubs.Add(fs);
|
||||
uow.SaveChanges();
|
||||
//adding all, in case bot wasn't on this guild when it started
|
||||
foreach (var feed in gc.FeedSubs)
|
||||
{
|
||||
_subs.AddOrUpdate(feed.Url.ToLower(), new HashSet<FeedSub>() {feed}, (k, old) =>
|
||||
{
|
||||
old.Add(feed);
|
||||
return old;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveFeed(ulong guildId, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var items = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs))
|
||||
.FeedSubs
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
|
||||
if (items.Count <= index)
|
||||
return false;
|
||||
var toRemove = items[index];
|
||||
_subs.AddOrUpdate(toRemove.Url.ToLower(), new HashSet<FeedSub>(), (key, old) =>
|
||||
{
|
||||
old.Remove(toRemove);
|
||||
return old;
|
||||
});
|
||||
uow.Remove(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
await Task.WhenAll(Task.WhenAll(allSendTasks), Task.Delay(10000)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FeedSub> GetFeeds(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig))
|
||||
.FeedSubs
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
|
||||
{
|
||||
rssFeed.ThrowIfNull(nameof(rssFeed));
|
||||
|
||||
var fs = new FeedSub()
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Url = rssFeed.Trim(),
|
||||
};
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FeedSubs)
|
||||
.ThenInclude(x => x.GuildConfig));
|
||||
|
||||
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (gc.FeedSubs.Count >= 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gc.FeedSubs.Add(fs);
|
||||
uow.SaveChanges();
|
||||
//adding all, in case bot wasn't on this guild when it started
|
||||
foreach (var feed in gc.FeedSubs)
|
||||
{
|
||||
_subs.AddOrUpdate(feed.Url.ToLower(), new HashSet<FeedSub>() {feed}, (k, old) =>
|
||||
{
|
||||
old.Add(feed);
|
||||
return old;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveFeed(ulong guildId, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var items = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs))
|
||||
.FeedSubs
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
|
||||
if (items.Count <= index)
|
||||
return false;
|
||||
var toRemove = items[index];
|
||||
_subs.AddOrUpdate(toRemove.Url.ToLower(), new HashSet<FeedSub>(), (key, old) =>
|
||||
{
|
||||
old.Remove(toRemove);
|
||||
return old;
|
||||
});
|
||||
uow.Remove(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,14 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public interface ITranslateService
|
||||
{
|
||||
public interface ITranslateService
|
||||
{
|
||||
public Task<string> Translate(string source, string target, string text = null);
|
||||
Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete);
|
||||
IEnumerable<string> GetLanguages();
|
||||
Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string @from, string to);
|
||||
Task<bool> UnregisterUser(ulong channelId, ulong userId);
|
||||
}
|
||||
public Task<string> Translate(string source, string target, string text = null);
|
||||
Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete);
|
||||
IEnumerable<string> GetLanguages();
|
||||
Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string @from, string to);
|
||||
Task<bool> UnregisterUser(ulong channelId, ulong userId);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
@@ -13,223 +10,222 @@ using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyExecutor, INService
|
||||
{
|
||||
public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyExecutor, INService
|
||||
private readonly IGoogleApiService _google;
|
||||
private readonly DbService _db;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly Bot _bot;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, bool> _atcs = new();
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, (string From, string To)>> _users = new();
|
||||
|
||||
public TranslateService(IGoogleApiService google,
|
||||
DbService db,
|
||||
IEmbedBuilderService eb,
|
||||
Bot bot)
|
||||
{
|
||||
private readonly IGoogleApiService _google;
|
||||
private readonly DbService _db;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly Bot _bot;
|
||||
_google = google;
|
||||
_db = db;
|
||||
_eb = eb;
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, bool> _atcs = new();
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, (string From, string To)>> _users = new();
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
public TranslateService(IGoogleApiService google,
|
||||
DbService db,
|
||||
IEmbedBuilderService eb,
|
||||
Bot bot)
|
||||
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
|
||||
var cs = await ctx.AutoTranslateChannels
|
||||
.Include(x => x.Users)
|
||||
.Where(x => guilds.Contains(x.GuildId))
|
||||
.ToListAsyncEF();
|
||||
|
||||
foreach (var c in cs)
|
||||
{
|
||||
_google = google;
|
||||
_db = db;
|
||||
_eb = eb;
|
||||
_bot = bot;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
|
||||
var cs = await ctx.AutoTranslateChannels
|
||||
.Include(x => x.Users)
|
||||
.Where(x => guilds.Contains(x.GuildId))
|
||||
.ToListAsyncEF();
|
||||
|
||||
foreach (var c in cs)
|
||||
{
|
||||
_atcs[c.ChannelId] = c.AutoDelete;
|
||||
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||
}
|
||||
_atcs[c.ChannelId] = c.AutoDelete;
|
||||
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task LateExecute(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||
return;
|
||||
public async Task LateExecute(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||
return;
|
||||
|
||||
if (msg is IUserMessage { Channel: ITextChannel tch } um)
|
||||
{
|
||||
if (!_atcs.TryGetValue(tch.Id, out var autoDelete))
|
||||
return;
|
||||
if (msg is IUserMessage { Channel: ITextChannel tch } um)
|
||||
{
|
||||
if (!_atcs.TryGetValue(tch.Id, out var autoDelete))
|
||||
return;
|
||||
|
||||
if (!_users.TryGetValue(tch.Id, out var users)
|
||||
|| !users.TryGetValue(um.Author.Id, out var langs))
|
||||
return;
|
||||
if (!_users.TryGetValue(tch.Id, out var users)
|
||||
|| !users.TryGetValue(um.Author.Id, out var langs))
|
||||
return;
|
||||
|
||||
var output = await _google.Translate(msg.Content, langs.From, langs.To);
|
||||
var output = await _google.Translate(msg.Content, langs.From, langs.To);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(output)
|
||||
|| msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase))
|
||||
return;
|
||||
if (string.IsNullOrWhiteSpace(output)
|
||||
|| msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase))
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
|
||||
if (autoDelete)
|
||||
{
|
||||
embed
|
||||
.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl())
|
||||
.AddField(langs.From, um.Content)
|
||||
.AddField(langs.To, output);
|
||||
if (autoDelete)
|
||||
{
|
||||
embed
|
||||
.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl())
|
||||
.AddField(langs.From, um.Content)
|
||||
.AddField(langs.To, output);
|
||||
|
||||
await tch.EmbedAsync(embed);
|
||||
await tch.EmbedAsync(embed);
|
||||
|
||||
try
|
||||
{
|
||||
await um.DeleteAsync();
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_atcs.TryUpdate(tch.Id, false, true);
|
||||
}
|
||||
|
||||
return;
|
||||
try
|
||||
{
|
||||
await um.DeleteAsync();
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_atcs.TryUpdate(tch.Id, false, true);
|
||||
}
|
||||
|
||||
await um.ReplyAsync(embed: embed
|
||||
.AddField(langs.To, output)
|
||||
.Build(),
|
||||
allowedMentions: AllowedMentions.None);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> Translate(string source, string target, string text = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||
|
||||
var res = await _google.Translate(text, source, target).ConfigureAwait(false);
|
||||
return res.SanitizeMentions(true);
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
var old = await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
|
||||
|
||||
if (old is null)
|
||||
{
|
||||
ctx.AutoTranslateChannels
|
||||
.Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
AutoDelete = autoDelete,
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
_atcs[channelId] = autoDelete;
|
||||
_users[channelId] = new();
|
||||
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// if autodelete value is different, update the autodelete value
|
||||
// instead of disabling
|
||||
if (old.AutoDelete != autoDelete)
|
||||
{
|
||||
old.AutoDelete = autoDelete;
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs[channelId] = autoDelete;
|
||||
return true;
|
||||
}
|
||||
|
||||
await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.ChannelId == channelId);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs.TryRemove(channelId, out _);
|
||||
_users.TryRemove(channelId, out _);
|
||||
|
||||
return false;
|
||||
await um.ReplyAsync(embed: embed
|
||||
.AddField(langs.To, output)
|
||||
.Build(),
|
||||
allowedMentions: AllowedMentions.None);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> Translate(string source, string target, string text = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||
|
||||
private void UpdateUser(ulong channelId, ulong userId, string from, string to)
|
||||
{
|
||||
var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary<ulong, (string, string)>());
|
||||
dict[userId] = (from, to);
|
||||
}
|
||||
|
||||
public async Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string from, string to)
|
||||
{
|
||||
if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to))
|
||||
return null;
|
||||
var res = await _google.Translate(text, source, target).ConfigureAwait(false);
|
||||
return res.SanitizeMentions(true);
|
||||
}
|
||||
|
||||
var ctx = _db.GetDbContext();
|
||||
var ch = await ctx.AutoTranslateChannels
|
||||
.GetByChannelId(channelId);
|
||||
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
|
||||
var old = await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
|
||||
|
||||
if (ch is null)
|
||||
return null;
|
||||
|
||||
var user = ch.Users
|
||||
.FirstOrDefault(x => x.UserId == userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
ch.Users.Add(user = new()
|
||||
if (old is null)
|
||||
{
|
||||
ctx.AutoTranslateChannels
|
||||
.Add(new()
|
||||
{
|
||||
Source = from,
|
||||
Target = to,
|
||||
UserId = userId,
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
AutoDelete = autoDelete,
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it's different from old settings, update
|
||||
if (user.Source != from || user.Target != to)
|
||||
{
|
||||
user.Source = from;
|
||||
user.Target = to;
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return await UnregisterUser(channelId, userId);
|
||||
}
|
||||
|
||||
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.AutoTranslateUsers
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.UserId == userId &&
|
||||
x.Channel.ChannelId == channelId);
|
||||
|
||||
if (_users.TryGetValue(channelId, out var inner))
|
||||
inner.TryRemove(userId, out _);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
return rows > 0;
|
||||
|
||||
_atcs[channelId] = autoDelete;
|
||||
_users[channelId] = new();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if autodelete value is different, update the autodelete value
|
||||
// instead of disabling
|
||||
if (old.AutoDelete != autoDelete)
|
||||
{
|
||||
old.AutoDelete = autoDelete;
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs[channelId] = autoDelete;
|
||||
return true;
|
||||
}
|
||||
|
||||
await ctx.AutoTranslateChannels
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.ChannelId == channelId);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
_atcs.TryRemove(channelId, out _);
|
||||
_users.TryRemove(channelId, out _);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void UpdateUser(ulong channelId, ulong userId, string from, string to)
|
||||
{
|
||||
var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary<ulong, (string, string)>());
|
||||
dict[userId] = (from, to);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetLanguages() => _google.Languages.Select(x => x.Key);
|
||||
}
|
||||
public async Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string from, string to)
|
||||
{
|
||||
if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to))
|
||||
return null;
|
||||
|
||||
var ctx = _db.GetDbContext();
|
||||
var ch = await ctx.AutoTranslateChannels
|
||||
.GetByChannelId(channelId);
|
||||
|
||||
if (ch is null)
|
||||
return null;
|
||||
|
||||
var user = ch.Users
|
||||
.FirstOrDefault(x => x.UserId == userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
ch.Users.Add(user = new()
|
||||
{
|
||||
Source = from,
|
||||
Target = to,
|
||||
UserId = userId,
|
||||
});
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it's different from old settings, update
|
||||
if (user.Source != from || user.Target != to)
|
||||
{
|
||||
user.Source = from;
|
||||
user.Target = to;
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
UpdateUser(channelId, userId, from, to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return await UnregisterUser(channelId, userId);
|
||||
}
|
||||
|
||||
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
|
||||
{
|
||||
var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.AutoTranslateUsers
|
||||
.ToLinqToDBTable()
|
||||
.DeleteAsync(x => x.UserId == userId &&
|
||||
x.Channel.ChannelId == channelId);
|
||||
|
||||
if (_users.TryGetValue(channelId, out var inner))
|
||||
inner.TryRemove(userId, out _);
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetLanguages() => _google.Languages.Select(x => x.Key);
|
||||
}
|
@@ -1,137 +1,134 @@
|
||||
using NadekoBot.Services;
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services
|
||||
{
|
||||
// public class YtTrackService : INService
|
||||
// {
|
||||
// private readonly IGoogleApiService _google;
|
||||
// private readonly IHttpClientFactory httpClientFactory;
|
||||
// private readonly DiscordSocketClient _client;
|
||||
// private readonly DbService _db;
|
||||
// private readonly ConcurrentDictionary<string, ConcurrentDictionary<ulong, List<YtFollowedChannel>>> followedChannels;
|
||||
// private readonly ConcurrentDictionary<string, DateTime> _latestPublishes = new ConcurrentDictionary<string, DateTime>();
|
||||
//
|
||||
// public YtTrackService(IGoogleApiService google, IHttpClientFactory httpClientFactory, DiscordSocketClient client,
|
||||
// DbService db)
|
||||
// {
|
||||
// this._google = google;
|
||||
// this.httpClientFactory = httpClientFactory;
|
||||
// this._client = client;
|
||||
// this._db = db;
|
||||
//
|
||||
// if (_client.ShardId == 0)
|
||||
// {
|
||||
// _ = CheckLoop();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public async Task CheckLoop()
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// await Task.Delay(10000);
|
||||
// using (var http = httpClientFactory.CreateClient())
|
||||
// {
|
||||
// await Task.WhenAll(followedChannels.Select(kvp => CheckChannel(kvp.Key, kvp.Value.SelectMany(x => x.Value).ToList())));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Checks the specified youtube channel, and sends a message to all provided
|
||||
// /// </summary>
|
||||
// /// <param name="youtubeChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="followedChannels">Where to post updates if there is a new update</param>
|
||||
// private async Task CheckChannel(string youtubeChannelId, List<YtFollowedChannel> followedChannels)
|
||||
// {
|
||||
// var latestVid = (await _google.GetLatestChannelVideosAsync(youtubeChannelId, 1))
|
||||
// .FirstOrDefault();
|
||||
// if (latestVid is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (_latestPublishes.TryGetValue(youtubeChannelId, out var latestPub) && latestPub >= latestVid.PublishedAt)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// _latestPublishes[youtubeChannelId] = latestVid.PublishedAt;
|
||||
//
|
||||
// foreach (var chObj in followedChannels)
|
||||
// {
|
||||
// var gCh = _client.GetChannel(chObj.ChannelId);
|
||||
// if (gCh is ITextChannel ch)
|
||||
// {
|
||||
// var msg = latestVid.GetVideoUrl();
|
||||
// if (!string.IsNullOrWhiteSpace(chObj.UploadMessage))
|
||||
// msg = chObj.UploadMessage + Environment.NewLine + msg;
|
||||
//
|
||||
// await ch.SendMessageAsync(msg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Starts posting updates on the specified discord channel when a new video is posted on the specified YouTube channel.
|
||||
// /// </summary>
|
||||
// /// <param name="guildId">Id of the discord guild</param>
|
||||
// /// <param name="channelId">Id of the discord channel</param>
|
||||
// /// <param name="ytChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="uploadMessage">Message to post when a new video is uploaded, along with video URL</param>
|
||||
// /// <returns>Whether adding was successful</returns>
|
||||
// public async Task<bool> ToggleChannelFollowAsync(ulong guildId, ulong channelId, string ytChannelId, string uploadMessage)
|
||||
// {
|
||||
// // to to see if we can get a video from that channel
|
||||
// var vids = await _google.GetLatestChannelVideosAsync(ytChannelId, 1);
|
||||
// if (vids.Count == 0)
|
||||
// return false;
|
||||
//
|
||||
// using(var uow = _db.GetDbContext())
|
||||
// {
|
||||
// var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.YtFollowedChannels));
|
||||
//
|
||||
// // see if this yt channel was already followed on this discord channel
|
||||
// var oldObj = gc.YtFollowedChannels
|
||||
// .FirstOrDefault(x => x.ChannelId == channelId && x.YtChannelId == ytChannelId);
|
||||
//
|
||||
// if(oldObj is not null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // can only add up to 10 tracked channels per server
|
||||
// if (gc.YtFollowedChannels.Count >= 10)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// var obj = new YtFollowedChannel
|
||||
// {
|
||||
// ChannelId = channelId,
|
||||
// YtChannelId = ytChannelId,
|
||||
// UploadMessage = uploadMessage
|
||||
// };
|
||||
//
|
||||
// // add to database
|
||||
// gc.YtFollowedChannels.Add(obj);
|
||||
//
|
||||
// // add to the local cache:
|
||||
//
|
||||
// // get follows on all guilds
|
||||
// var allGuildFollows = followedChannels.GetOrAdd(ytChannelId, new ConcurrentDictionary<ulong, List<YtFollowedChannel>>());
|
||||
// // add to this guild's follows
|
||||
// allGuildFollows.AddOrUpdate(guildId,
|
||||
// new List<YtFollowedChannel>(),
|
||||
// (key, old) =>
|
||||
// {
|
||||
// old.Add(obj);
|
||||
// return old;
|
||||
// });
|
||||
//
|
||||
// await uow.SaveChangesAsync();
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// public class YtTrackService : INService
|
||||
// {
|
||||
// private readonly IGoogleApiService _google;
|
||||
// private readonly IHttpClientFactory httpClientFactory;
|
||||
// private readonly DiscordSocketClient _client;
|
||||
// private readonly DbService _db;
|
||||
// private readonly ConcurrentDictionary<string, ConcurrentDictionary<ulong, List<YtFollowedChannel>>> followedChannels;
|
||||
// private readonly ConcurrentDictionary<string, DateTime> _latestPublishes = new ConcurrentDictionary<string, DateTime>();
|
||||
//
|
||||
// public YtTrackService(IGoogleApiService google, IHttpClientFactory httpClientFactory, DiscordSocketClient client,
|
||||
// DbService db)
|
||||
// {
|
||||
// this._google = google;
|
||||
// this.httpClientFactory = httpClientFactory;
|
||||
// this._client = client;
|
||||
// this._db = db;
|
||||
//
|
||||
// if (_client.ShardId == 0)
|
||||
// {
|
||||
// _ = CheckLoop();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public async Task CheckLoop()
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// await Task.Delay(10000);
|
||||
// using (var http = httpClientFactory.CreateClient())
|
||||
// {
|
||||
// await Task.WhenAll(followedChannels.Select(kvp => CheckChannel(kvp.Key, kvp.Value.SelectMany(x => x.Value).ToList())));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Checks the specified youtube channel, and sends a message to all provided
|
||||
// /// </summary>
|
||||
// /// <param name="youtubeChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="followedChannels">Where to post updates if there is a new update</param>
|
||||
// private async Task CheckChannel(string youtubeChannelId, List<YtFollowedChannel> followedChannels)
|
||||
// {
|
||||
// var latestVid = (await _google.GetLatestChannelVideosAsync(youtubeChannelId, 1))
|
||||
// .FirstOrDefault();
|
||||
// if (latestVid is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (_latestPublishes.TryGetValue(youtubeChannelId, out var latestPub) && latestPub >= latestVid.PublishedAt)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// _latestPublishes[youtubeChannelId] = latestVid.PublishedAt;
|
||||
//
|
||||
// foreach (var chObj in followedChannels)
|
||||
// {
|
||||
// var gCh = _client.GetChannel(chObj.ChannelId);
|
||||
// if (gCh is ITextChannel ch)
|
||||
// {
|
||||
// var msg = latestVid.GetVideoUrl();
|
||||
// if (!string.IsNullOrWhiteSpace(chObj.UploadMessage))
|
||||
// msg = chObj.UploadMessage + Environment.NewLine + msg;
|
||||
//
|
||||
// await ch.SendMessageAsync(msg);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Starts posting updates on the specified discord channel when a new video is posted on the specified YouTube channel.
|
||||
// /// </summary>
|
||||
// /// <param name="guildId">Id of the discord guild</param>
|
||||
// /// <param name="channelId">Id of the discord channel</param>
|
||||
// /// <param name="ytChannelId">Id of the youtube channel</param>
|
||||
// /// <param name="uploadMessage">Message to post when a new video is uploaded, along with video URL</param>
|
||||
// /// <returns>Whether adding was successful</returns>
|
||||
// public async Task<bool> ToggleChannelFollowAsync(ulong guildId, ulong channelId, string ytChannelId, string uploadMessage)
|
||||
// {
|
||||
// // to to see if we can get a video from that channel
|
||||
// var vids = await _google.GetLatestChannelVideosAsync(ytChannelId, 1);
|
||||
// if (vids.Count == 0)
|
||||
// return false;
|
||||
//
|
||||
// using(var uow = _db.GetDbContext())
|
||||
// {
|
||||
// var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.YtFollowedChannels));
|
||||
//
|
||||
// // see if this yt channel was already followed on this discord channel
|
||||
// var oldObj = gc.YtFollowedChannels
|
||||
// .FirstOrDefault(x => x.ChannelId == channelId && x.YtChannelId == ytChannelId);
|
||||
//
|
||||
// if(oldObj is not null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // can only add up to 10 tracked channels per server
|
||||
// if (gc.YtFollowedChannels.Count >= 10)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// var obj = new YtFollowedChannel
|
||||
// {
|
||||
// ChannelId = channelId,
|
||||
// YtChannelId = ytChannelId,
|
||||
// UploadMessage = uploadMessage
|
||||
// };
|
||||
//
|
||||
// // add to database
|
||||
// gc.YtFollowedChannels.Add(obj);
|
||||
//
|
||||
// // add to the local cache:
|
||||
//
|
||||
// // get follows on all guilds
|
||||
// var allGuildFollows = followedChannels.GetOrAdd(ytChannelId, new ConcurrentDictionary<ulong, List<YtFollowedChannel>>());
|
||||
// // add to this guild's follows
|
||||
// allGuildFollows.AddOrUpdate(guildId,
|
||||
// new List<YtFollowedChannel>(),
|
||||
// (key, old) =>
|
||||
// {
|
||||
// old.Add(obj);
|
||||
// return old;
|
||||
// });
|
||||
//
|
||||
// await uow.SaveChangesAsync();
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// }
|
Reference in New Issue
Block a user