NadekoBot/NadekoBot.Modules.Searches/Common/SearchImageCacher.cs
2017-10-09 02:52:46 +02:00

224 lines
7.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using NadekoBot.Common;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using NLog;
namespace NadekoBot.Modules.Searches.Common
{
public class SearchImageCacher
{
private readonly NadekoRandom _rng;
private readonly ConcurrentDictionary<DapiSearchType, SemaphoreSlim> _locks = new ConcurrentDictionary<DapiSearchType, SemaphoreSlim>();
private readonly SortedSet<ImageCacherObject> _cache;
private readonly Logger _log;
private readonly HttpClient _http;
public SearchImageCacher()
{
_http = new HttpClient();
_http.AddFakeHeaders();
_log = LogManager.GetCurrentClassLogger();
_rng = new NadekoRandom();
_cache = new SortedSet<ImageCacherObject>();
}
public async Task<ImageCacherObject> GetImage(string tag, bool forceExplicit, DapiSearchType type,
HashSet<string> blacklistedTags = null)
{
tag = tag?.ToLowerInvariant();
blacklistedTags = blacklistedTags ?? new HashSet<string>();
if (type == DapiSearchType.E621)
tag = tag?.Replace("yuri", "female/female");
var _lock = GetLock(type);
await _lock.WaitAsync();
try
{
ImageCacherObject[] imgs;
if (!string.IsNullOrWhiteSpace(tag))
{
imgs = _cache.Where(x => x.Tags.IsSupersetOf(tag.Split('+')) && x.SearchType == type && (!forceExplicit || x.Rating == "e")).ToArray();
}
else
{
tag = null;
imgs = _cache.Where(x => x.SearchType == type).ToArray();
}
ImageCacherObject img;
if (imgs.Length == 0)
img = null;
else
img = imgs[_rng.Next(imgs.Length)];
if (img != null)
{
_cache.Remove(img);
return img;
}
else
{
var images = await DownloadImages(tag, forceExplicit, type).ConfigureAwait(false);
images = images
.Where(x => x.Tags.All(t => !blacklistedTags.Contains(t)))
.ToArray();
if (images.Length == 0)
return null;
var toReturn = images[_rng.Next(images.Length)];
#if !GLOBAL_NADEKO
foreach (var dledImg in images)
{
if(dledImg != toReturn)
_cache.Add(dledImg);
}
#endif
return toReturn;
}
}
finally
{
_lock.Release();
}
}
private SemaphoreSlim GetLock(DapiSearchType type)
{
return _locks.GetOrAdd(type, _ => new SemaphoreSlim(1, 1));
}
public async Task<ImageCacherObject[]> DownloadImages(string tag, bool isExplicit, DapiSearchType type)
{
tag = tag?.Replace(" ", "_").ToLowerInvariant();
if (isExplicit)
tag = "rating%3Aexplicit+" + tag;
var website = "";
switch (type)
{
case DapiSearchType.Safebooru:
website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=1000&tags={tag}";
break;
case DapiSearchType.E621:
website = $"https://e621.net/post/index.json?limit=1000&tags={tag}";
break;
case DapiSearchType.Danbooru:
website = $"http://danbooru.donmai.us/posts.json?limit=100&tags={tag}";
break;
case DapiSearchType.Gelbooru:
website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Rule34:
website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Konachan:
website = $"https://konachan.com/post.json?s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Yandere:
website = $"https://yande.re/post.json?limit=100&tags={tag}";
break;
}
if (type == DapiSearchType.Konachan || type == DapiSearchType.Yandere ||
type == DapiSearchType.E621 || type == DapiSearchType.Danbooru)
{
var data = await _http.GetStringAsync(website).ConfigureAwait(false);
return JsonConvert.DeserializeObject<DapiImageObject[]>(data)
.Where(x => x.File_Url != null)
.Select(x => new ImageCacherObject(x, type))
.ToArray();
}
return (await LoadXmlAsync(website, type)).ToArray();
}
private async Task<ImageCacherObject[]> LoadXmlAsync(string website, DapiSearchType type)
{
var list = new List<ImageCacherObject>();
using (var reader = XmlReader.Create(await _http.GetStreamAsync(website), new XmlReaderSettings()
{
Async = true,
}))
{
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "post")
{
list.Add(new ImageCacherObject(new DapiImageObject()
{
File_Url = reader["file_url"],
Tags = reader["tags"],
Rating = reader["rating"] ?? "e"
}, type));
}
}
}
return list.ToArray();
}
public void Clear()
{
_cache.Clear();
}
}
public class ImageCacherObject : IComparable<ImageCacherObject>
{
public DapiSearchType SearchType { get; }
public string FileUrl { get; }
public HashSet<string> Tags { get; }
public string Rating { get; }
public ImageCacherObject(DapiImageObject obj, DapiSearchType type)
{
if (type == DapiSearchType.Danbooru)
this.FileUrl = "https://danbooru.donmai.us" + obj.File_Url;
else
this.FileUrl = obj.File_Url.StartsWith("http") ? obj.File_Url : "https:" + obj.File_Url;
this.SearchType = type;
this.Rating = obj.Rating;
this.Tags = new HashSet<string>((obj.Tags ?? obj.Tag_String).Split(' '));
}
public override string ToString()
{
return FileUrl;
}
public int CompareTo(ImageCacherObject other)
{
return FileUrl.CompareTo(other.FileUrl);
}
}
public class DapiImageObject
{
public string File_Url { get; set; }
public string Tags { get; set; }
public string Tag_String { get; set; }
public string Rating { get; set; }
}
public enum DapiSearchType
{
Safebooru,
E621,
Gelbooru,
Konachan,
Rule34,
Yandere,
Danbooru
}
}