2016-08-16 12:11:45 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Google.Apis.YouTube.v3;
|
|
|
|
|
using Google.Apis.Services;
|
|
|
|
|
using System.Text.RegularExpressions;
|
2016-08-21 14:16:11 +00:00
|
|
|
|
using Google.Apis.Urlshortener.v1;
|
|
|
|
|
using Google.Apis.Urlshortener.v1.Data;
|
2016-10-15 06:25:52 +00:00
|
|
|
|
using NLog;
|
2017-01-24 17:30:21 +00:00
|
|
|
|
using Google.Apis.Customsearch.v1;
|
|
|
|
|
using Google.Apis.Customsearch.v1.Data;
|
2016-08-16 12:11:45 +00:00
|
|
|
|
|
|
|
|
|
namespace NadekoBot.Services.Impl
|
|
|
|
|
{
|
2016-08-21 14:16:11 +00:00
|
|
|
|
public class GoogleApiService : IGoogleApiService
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2017-01-24 17:30:21 +00:00
|
|
|
|
const string search_engine_id = "018084019232060951019:hs5piey28-e";
|
|
|
|
|
|
2016-08-16 12:11:45 +00:00
|
|
|
|
private YouTubeService yt;
|
2016-08-21 14:16:11 +00:00
|
|
|
|
private UrlshortenerService sh;
|
2017-01-24 17:30:21 +00:00
|
|
|
|
private CustomsearchService cs;
|
|
|
|
|
|
2016-10-15 06:25:52 +00:00
|
|
|
|
private Logger _log { get; }
|
2016-08-16 12:11:45 +00:00
|
|
|
|
|
2017-05-22 23:59:31 +00:00
|
|
|
|
public GoogleApiService(IBotCredentials creds)
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2017-05-22 23:59:31 +00:00
|
|
|
|
_creds = creds;
|
|
|
|
|
|
2016-08-21 14:16:11 +00:00
|
|
|
|
var bcs = new BaseClientService.Initializer
|
|
|
|
|
{
|
2016-08-16 12:11:45 +00:00
|
|
|
|
ApplicationName = "Nadeko Bot",
|
2017-05-22 23:59:31 +00:00
|
|
|
|
ApiKey = _creds.GoogleApiKey,
|
2016-08-21 14:16:11 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-10-15 06:25:52 +00:00
|
|
|
|
_log = LogManager.GetCurrentClassLogger();
|
|
|
|
|
|
2016-08-21 14:16:11 +00:00
|
|
|
|
yt = new YouTubeService(bcs);
|
|
|
|
|
sh = new UrlshortenerService(bcs);
|
2017-01-24 17:30:21 +00:00
|
|
|
|
cs = new CustomsearchService(bcs);
|
2016-08-16 12:11:45 +00:00
|
|
|
|
}
|
2016-08-21 22:37:39 +00:00
|
|
|
|
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2016-08-20 22:31:00 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(keywords))
|
|
|
|
|
throw new ArgumentNullException(nameof(keywords));
|
|
|
|
|
|
|
|
|
|
if (count <= 0)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
2016-08-16 12:11:45 +00:00
|
|
|
|
|
|
|
|
|
var match = new Regex("(?:youtu\\.be\\/|list=)(?<id>[\\da-zA-Z\\-_]*)").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";
|
2016-10-19 08:17:16 +00:00
|
|
|
|
query.Q = keywords;
|
2016-08-16 12:11:45 +00:00
|
|
|
|
|
|
|
|
|
return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId);
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-13 15:37:01 +00:00
|
|
|
|
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);
|
2017-05-22 23:59:31 +00:00
|
|
|
|
private readonly IBotCredentials _creds;
|
2016-12-13 04:19:33 +00:00
|
|
|
|
|
2016-08-21 22:37:39 +00:00
|
|
|
|
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2016-08-20 22:41:15 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(id))
|
|
|
|
|
throw new ArgumentNullException(nameof(id));
|
|
|
|
|
|
|
|
|
|
if (count <= 0)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
2016-08-16 12:11:45 +00:00
|
|
|
|
|
2016-12-13 04:19:33 +00:00
|
|
|
|
var match = YtVideoIdRegex.Match(id);
|
2016-08-16 12:11:45 +00:00
|
|
|
|
if (match.Length > 1)
|
|
|
|
|
{
|
|
|
|
|
id = match.Groups["id"].Value;
|
|
|
|
|
}
|
|
|
|
|
var query = yt.Search.List("snippet");
|
|
|
|
|
query.MaxResults = count;
|
|
|
|
|
query.RelatedToVideoId = id;
|
|
|
|
|
query.Type = "video";
|
|
|
|
|
return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-21 22:37:39 +00:00
|
|
|
|
public async Task<IEnumerable<string>> GetVideosByKeywordsAsync(string keywords, int count = 1)
|
2016-08-16 12:11:45 +00:00
|
|
|
|
{
|
2016-08-20 22:41:15 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(keywords))
|
|
|
|
|
throw new ArgumentNullException(nameof(keywords));
|
|
|
|
|
|
|
|
|
|
if (count <= 0)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
2016-08-16 12:11:45 +00:00
|
|
|
|
|
2016-12-13 04:19:33 +00:00
|
|
|
|
string id = "";
|
|
|
|
|
var match = YtVideoIdRegex.Match(keywords);
|
|
|
|
|
if (match.Length > 1)
|
|
|
|
|
{
|
|
|
|
|
id = match.Groups["id"].Value;
|
|
|
|
|
}
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(id))
|
|
|
|
|
{
|
|
|
|
|
return new[] { "http://www.youtube.com/watch?v=" + id };
|
|
|
|
|
}
|
2016-08-16 12:11:45 +00:00
|
|
|
|
var query = yt.Search.List("snippet");
|
|
|
|
|
query.MaxResults = count;
|
|
|
|
|
query.Q = keywords;
|
|
|
|
|
query.Type = "video";
|
|
|
|
|
return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
|
|
|
|
|
}
|
2016-08-21 14:16:11 +00:00
|
|
|
|
|
|
|
|
|
public async Task<string> ShortenUrl(string url)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
|
|
|
throw new ArgumentNullException(nameof(url));
|
2016-11-05 23:59:21 +00:00
|
|
|
|
|
2017-05-22 23:59:31 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
|
2016-11-05 23:59:21 +00:00
|
|
|
|
return url;
|
|
|
|
|
|
2016-10-15 06:25:52 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var response = await sh.Url.Insert(new Url { LongUrl = url }).ExecuteAsync();
|
|
|
|
|
return response.Id;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_log.Warn(ex);
|
|
|
|
|
return url;
|
|
|
|
|
}
|
2016-08-21 14:16:11 +00:00
|
|
|
|
}
|
2016-08-21 22:37:39 +00:00
|
|
|
|
|
|
|
|
|
public async Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50)
|
|
|
|
|
{
|
|
|
|
|
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");
|
2016-10-15 21:48:08 +00:00
|
|
|
|
query.MaxResults = toGet;
|
2016-08-21 22:37:39 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2017-01-05 12:49:50 +00:00
|
|
|
|
//todo AsyncEnumerable
|
2017-01-24 17:30:21 +00:00
|
|
|
|
public async Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds)
|
2016-10-19 10:40:43 +00:00
|
|
|
|
{
|
|
|
|
|
var videoIdsList = videoIds as List<string> ?? videoIds.ToList();
|
|
|
|
|
|
|
|
|
|
Dictionary<string, TimeSpan> toReturn = new Dictionary<string, TimeSpan>();
|
|
|
|
|
|
|
|
|
|
if (!videoIdsList.Any())
|
|
|
|
|
return toReturn;
|
|
|
|
|
var toGet = 0;
|
|
|
|
|
var remaining = videoIdsList.Count;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
toGet = remaining > 50 ? 50 : remaining;
|
|
|
|
|
remaining -= toGet;
|
|
|
|
|
|
|
|
|
|
var q = yt.Videos.List("contentDetails");
|
2016-12-22 05:12:04 +00:00
|
|
|
|
q.Id = string.Join(",", videoIdsList.Take(toGet));
|
|
|
|
|
videoIdsList = videoIdsList.Skip(toGet).ToList();
|
2016-10-19 10:40:43 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2017-01-24 17:30:21 +00:00
|
|
|
|
|
|
|
|
|
public struct ImageResult
|
|
|
|
|
{
|
|
|
|
|
public Result.ImageData Image { get; }
|
|
|
|
|
public string Link { get; }
|
|
|
|
|
|
|
|
|
|
public ImageResult(Result.ImageData image, string link)
|
|
|
|
|
{
|
|
|
|
|
this.Image = image;
|
|
|
|
|
this.Link = link;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-25 02:19:43 +00:00
|
|
|
|
public async Task<ImageResult> GetImageAsync(string query, int start = 1)
|
2017-01-24 17:30:21 +00:00
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(query))
|
|
|
|
|
throw new ArgumentNullException(nameof(query));
|
|
|
|
|
|
|
|
|
|
var req = cs.Cse.List(query);
|
|
|
|
|
req.Cx = search_engine_id;
|
|
|
|
|
req.Num = 1;
|
|
|
|
|
req.Fields = "items(image(contextLink,thumbnailLink),link)";
|
|
|
|
|
req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image;
|
|
|
|
|
req.Start = start;
|
|
|
|
|
|
|
|
|
|
var search = await req.ExecuteAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return new ImageResult(search.Items[0].Image, search.Items[0].Link);
|
|
|
|
|
}
|
2016-08-16 12:11:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|