mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 08:34:27 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			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 NadekoBot.Services;
 | 
						|
 | 
						|
public sealed partial class GoogleApiService : IGoogleApiService, INService
 | 
						|
{
 | 
						|
    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 = "Nadeko 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;
 | 
						|
    }
 | 
						|
}
 | 
						|
 |