forked from EllieBotDevs/elliebot
230 lines
7.9 KiB
C#
230 lines
7.9 KiB
C#
|
#nullable disable
|
||
|
using Google;
|
||
|
using Google.Apis.Services;
|
||
|
using Google.Apis.Urlshortener.v1;
|
||
|
using Google.Apis.YouTube.v3;
|
||
|
using Newtonsoft.Json.Linq;
|
||
|
using System.Net;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using System.Xml;
|
||
|
|
||
|
namespace EllieBot.Services;
|
||
|
|
||
|
public sealed partial class GoogleApiService : IGoogleApiService, IEService
|
||
|
{
|
||
|
private static readonly Regex
|
||
|
_plRegex = new(@"(?:youtu\.be\/|list=)(?<id>[\da-zA-Z\-_]*)", RegexOptions.Compiled);
|
||
|
|
||
|
|
||
|
private readonly YouTubeService _yt;
|
||
|
private readonly UrlshortenerService _sh;
|
||
|
|
||
|
//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 IBotCredsProvider _creds;
|
||
|
private readonly IHttpClientFactory _httpFactory;
|
||
|
|
||
|
public GoogleApiService(IBotCredsProvider creds, IHttpClientFactory factory) : this()
|
||
|
{
|
||
|
_creds = creds;
|
||
|
_httpFactory = factory;
|
||
|
|
||
|
var bcs = new BaseClientService.Initializer
|
||
|
{
|
||
|
ApplicationName = "Ellie Bot",
|
||
|
ApiKey = _creds.GetCreds().GoogleApiKey
|
||
|
};
|
||
|
|
||
|
_yt = new(bcs);
|
||
|
_sh = new(bcs);
|
||
|
}
|
||
|
|
||
|
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
|
||
|
{
|
||
|
if (string.IsNullOrWhiteSpace(keywords))
|
||
|
throw new ArgumentNullException(nameof(keywords));
|
||
|
|
||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||
|
|
||
|
var match = _plRegex.Match(keywords);
|
||
|
if (match.Length > 1)
|
||
|
return new[] { match.Groups["id"].Value };
|
||
|
var query = _yt.Search.List("snippet");
|
||
|
query.MaxResults = count;
|
||
|
query.Type = "playlist";
|
||
|
query.Q = keywords;
|
||
|
|
||
|
return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId);
|
||
|
}
|
||
|
|
||
|
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 2, string user = null)
|
||
|
{
|
||
|
if (string.IsNullOrWhiteSpace(id))
|
||
|
throw new ArgumentNullException(nameof(id));
|
||
|
|
||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||
|
|
||
|
var query = _yt.Search.List("snippet");
|
||
|
query.MaxResults = count;
|
||
|
query.Q = id;
|
||
|
// query.RelatedToVideoId = id;
|
||
|
query.Type = "video";
|
||
|
query.QuotaUser = user;
|
||
|
// bad workaround as there's no replacement for related video querying right now.
|
||
|
// Query youtube with the id of the video, take a second video in the results
|
||
|
// skip the first one as that's probably the same video.
|
||
|
return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId).Skip(1);
|
||
|
}
|
||
|
|
||
|
public async Task<IEnumerable<string>> GetVideoLinksByKeywordAsync(string keywords, int count = 1)
|
||
|
{
|
||
|
if (string.IsNullOrWhiteSpace(keywords))
|
||
|
throw new ArgumentNullException(nameof(keywords));
|
||
|
|
||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(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()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId);
|
||
|
}
|
||
|
|
||
|
public async Task<IEnumerable<(string Name, string Id, string Url, string Thumbnail)>> GetVideoInfosByKeywordAsync(
|
||
|
string keywords,
|
||
|
int count = 1)
|
||
|
{
|
||
|
if (string.IsNullOrWhiteSpace(keywords))
|
||
|
throw new ArgumentNullException(nameof(keywords));
|
||
|
|
||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||
|
|
||
|
var query = _yt.Search.List("snippet");
|
||
|
query.MaxResults = count;
|
||
|
query.Q = keywords;
|
||
|
query.Type = "video";
|
||
|
return (await query.ExecuteAsync()).Items.Select(i
|
||
|
=> (i.Snippet.Title.TrimTo(50),
|
||
|
i.Id.VideoId,
|
||
|
"https://www.youtube.com/watch?v=" + i.Id.VideoId,
|
||
|
i.Snippet.Thumbnails.High.Url));
|
||
|
}
|
||
|
|
||
|
public Task<string> ShortenUrl(Uri url)
|
||
|
=> ShortenUrl(url.ToString());
|
||
|
|
||
|
public async Task<string> ShortenUrl(string url)
|
||
|
{
|
||
|
if (string.IsNullOrWhiteSpace(url))
|
||
|
throw new ArgumentNullException(nameof(url));
|
||
|
|
||
|
if (string.IsNullOrWhiteSpace(_creds.GetCreds().GoogleApiKey))
|
||
|
return url;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
var response = await _sh.Url.Insert(new()
|
||
|
{
|
||
|
LongUrl = url
|
||
|
})
|
||
|
.ExecuteAsync();
|
||
|
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)
|
||
|
{
|
||
|
if (string.IsNullOrWhiteSpace(playlistId))
|
||
|
throw new ArgumentNullException(nameof(playlistId));
|
||
|
|
||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||
|
|
||
|
string nextPageToken = null;
|
||
|
|
||
|
var toReturn = new List<string>(count);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
var toGet = count > 50 ? 50 : count;
|
||
|
count -= toGet;
|
||
|
|
||
|
var query = _yt.PlaylistItems.List("contentDetails");
|
||
|
query.MaxResults = toGet;
|
||
|
query.PlaylistId = playlistId;
|
||
|
query.PageToken = nextPageToken;
|
||
|
|
||
|
var data = await query.ExecuteAsync();
|
||
|
|
||
|
toReturn.AddRange(data.Items.Select(i => i.ContentDetails.VideoId));
|
||
|
nextPageToken = data.NextPageToken;
|
||
|
} while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken));
|
||
|
|
||
|
return toReturn;
|
||
|
}
|
||
|
|
||
|
public async Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds)
|
||
|
{
|
||
|
var videoIdsList = videoIds as List<string> ?? videoIds.ToList();
|
||
|
|
||
|
var 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()).Items;
|
||
|
foreach (var i in items)
|
||
|
toReturn.Add(i.Id, XmlConvert.ToTimeSpan(i.ContentDetails.Duration));
|
||
|
} while (remaining > 0);
|
||
|
|
||
|
return toReturn;
|
||
|
}
|
||
|
|
||
|
public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
return string.Concat(JArray.Parse(text)[0].Select(x => x[0]));
|
||
|
}
|
||
|
|
||
|
private string ConvertToLanguageCode(string language)
|
||
|
{
|
||
|
Languages.TryGetValue(language, out var mode);
|
||
|
return mode;
|
||
|
}
|
||
|
}
|
||
|
|