mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Applied codestyle to all .cs files
This commit is contained in:
@@ -12,4 +12,4 @@ public enum Booru
|
||||
Yandere,
|
||||
Danbooru,
|
||||
Sankaku
|
||||
}
|
||||
}
|
@@ -7,12 +7,15 @@ public class DapiImageObject : IImageData
|
||||
{
|
||||
[JsonPropertyName("File_Url")]
|
||||
public string FileUrl { get; set; }
|
||||
|
||||
public string Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("Tag_String")]
|
||||
public string TagString { get; set; }
|
||||
|
||||
public int Score { get; set; }
|
||||
public string Rating { get; set; }
|
||||
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(this.FileUrl, type, this.Tags?.Split(' ') ?? this.TagString?.Split(' '), Score.ToString() ?? Rating);
|
||||
}
|
||||
=> new(FileUrl, type, Tags?.Split(' ') ?? TagString?.Split(' '), Score.ToString() ?? Rating);
|
||||
}
|
@@ -10,4 +10,4 @@ public readonly struct DapiTag
|
||||
[JsonConstructor]
|
||||
public DapiTag(string name)
|
||||
=> Name = name;
|
||||
}
|
||||
}
|
@@ -12,8 +12,10 @@ public class DerpiImageObject : IImageData
|
||||
{
|
||||
[JsonPropertyName("view_url")]
|
||||
public string ViewUrl { get; set; }
|
||||
|
||||
public string[] Tags { get; set; }
|
||||
public int Score { get; set; }
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(ViewUrl, type, Tags, Score.ToString("F1"));
|
||||
}
|
||||
}
|
@@ -9,6 +9,11 @@ public sealed class DanbooruImageDownloader : DapiImageDownloader
|
||||
private static readonly ConcurrentDictionary<string, bool> _existentTags = new();
|
||||
private static readonly ConcurrentDictionary<string, bool> _nonexistentTags = new();
|
||||
|
||||
public DanbooruImageDownloader(HttpClient http)
|
||||
: base(Booru.Danbooru, http, "http://danbooru.donmai.us")
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<bool> IsTagValid(string tag, CancellationToken cancel = default)
|
||||
{
|
||||
if (_existentTags.ContainsKey(tag))
|
||||
@@ -17,21 +22,12 @@ public sealed class DanbooruImageDownloader : DapiImageDownloader
|
||||
if (_nonexistentTags.ContainsKey(tag))
|
||||
return false;
|
||||
|
||||
var tags = await _http.GetFromJsonAsync<DapiTag[]>(_baseUrl +
|
||||
"/tags.json" +
|
||||
$"?search[name_or_alias_matches]={tag}",
|
||||
options: this._serializerOptions,
|
||||
cancellationToken: cancel);
|
||||
if (tags is {Length: > 0})
|
||||
{
|
||||
return _existentTags[tag] = true;
|
||||
}
|
||||
var tags = await _http.GetFromJsonAsync<DapiTag[]>(
|
||||
_baseUrl + "/tags.json" + $"?search[name_or_alias_matches]={tag}",
|
||||
_serializerOptions,
|
||||
cancel);
|
||||
if (tags is { Length: > 0 }) return _existentTags[tag] = true;
|
||||
|
||||
return _nonexistentTags[tag] = false;
|
||||
}
|
||||
|
||||
public DanbooruImageDownloader(HttpClient http)
|
||||
: base(Booru.Danbooru, http, "http://danbooru.donmai.us")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,26 +7,29 @@ public abstract class DapiImageDownloader : ImageDownloader<DapiImageObject>
|
||||
{
|
||||
protected readonly string _baseUrl;
|
||||
|
||||
public DapiImageDownloader(Booru booru, HttpClient http, string baseUrl) : base(booru, http)
|
||||
public DapiImageDownloader(Booru booru, HttpClient http, string baseUrl)
|
||||
: base(booru, http)
|
||||
=> _baseUrl = baseUrl;
|
||||
|
||||
public abstract Task<bool> IsTagValid(string tag, CancellationToken cancel = default);
|
||||
|
||||
protected async Task<bool> AllTagsValid(string[] tags, CancellationToken cancel = default)
|
||||
{
|
||||
var results = await tags.Select(tag => IsTagValid(tag, cancel)).WhenAll();
|
||||
|
||||
// if any of the tags is not valid, the query is not valid
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (!result)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(string[] tags, int page,
|
||||
bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
// up to 2 tags allowed on danbooru
|
||||
if (tags.Length > 2)
|
||||
@@ -41,8 +44,6 @@ public abstract class DapiImageDownloader : ImageDownloader<DapiImageObject>
|
||||
var imageObjects = await _http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
||||
if (imageObjects is null)
|
||||
return new();
|
||||
return imageObjects
|
||||
.Where(x => x.FileUrl is not null)
|
||||
.ToList();
|
||||
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,25 +5,29 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class DerpibooruImageDownloader : ImageDownloader<DerpiImageObject>
|
||||
{
|
||||
public DerpibooruImageDownloader(HttpClient http) : base(Booru.Derpibooru, http)
|
||||
public DerpibooruImageDownloader(HttpClient http)
|
||||
: base(Booru.Derpibooru, http)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<List<DerpiImageObject>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<DerpiImageObject>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||
var uri = $"https://www.derpibooru.org/api/v1/json/search/images?q={tagString.Replace('+', ',')}&per_page=49&page={page}";
|
||||
var uri =
|
||||
$"https://www.derpibooru.org/api/v1/json/search/images?q={tagString.Replace('+', ',')}&per_page=49&page={page}";
|
||||
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
req.Headers.AddFakeHeaders();
|
||||
using var res = await _http.SendAsync(req, cancel);
|
||||
res.EnsureSuccessStatusCode();
|
||||
|
||||
|
||||
var container = await res.Content.ReadFromJsonAsync<DerpiContainer>(_serializerOptions, cancel);
|
||||
if (container?.Images is null)
|
||||
return new();
|
||||
|
||||
return container.Images
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.ViewUrl))
|
||||
.ToList();
|
||||
|
||||
return container.Images.Where(x => !string.IsNullOrWhiteSpace(x.ViewUrl)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,13 +5,18 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class E621ImageDownloader : ImageDownloader<E621Object>
|
||||
{
|
||||
public E621ImageDownloader(HttpClient http) : base(Booru.E621, http)
|
||||
public E621ImageDownloader(HttpClient http)
|
||||
: base(Booru.E621, http)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<List<E621Object>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<E621Object>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit: isExplicit);
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||
var uri = $"https://e621.net/posts.json?limit=32&tags={tagString}&page={page}";
|
||||
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
req.Headers.AddFakeHeaders();
|
||||
@@ -22,8 +27,6 @@ public class E621ImageDownloader : ImageDownloader<E621Object>
|
||||
if (data?.Posts is null)
|
||||
return new();
|
||||
|
||||
return data.Posts
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.File?.Url))
|
||||
.ToList();
|
||||
return data.Posts.Where(x => !string.IsNullOrWhiteSpace(x.File?.Url)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
public class E621Response
|
||||
{
|
||||
public List<E621Object> Posts { get; set; }
|
||||
}
|
||||
}
|
@@ -5,26 +5,31 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class GelbooruImageDownloader : ImageDownloader<DapiImageObject>
|
||||
{
|
||||
public GelbooruImageDownloader(HttpClient http) : base(Booru.Gelbooru, http)
|
||||
public GelbooruImageDownloader(HttpClient http)
|
||||
: base(Booru.Gelbooru, http)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||
var uri = $"http://gelbooru.com/index.php?page=dapi&s=post&json=1&q=index&limit=100" +
|
||||
$"&tags={tagString}&pid={page}";
|
||||
var uri = "http://gelbooru.com/index.php?page=dapi&s=post&json=1&q=index&limit=100"
|
||||
+ $"&tags={tagString}&pid={page}";
|
||||
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
using var res = await _http.SendAsync(req, cancel);
|
||||
res.EnsureSuccessStatusCode();
|
||||
var resString = await res.Content.ReadAsStringAsync(cancel);
|
||||
if (string.IsNullOrWhiteSpace(resString))
|
||||
return new();
|
||||
|
||||
|
||||
var images = JsonSerializer.Deserialize<List<DapiImageObject>>(resString, _serializerOptions);
|
||||
if (images is null)
|
||||
return new();
|
||||
|
||||
return images.Where(x => x.FileUrl is not null).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,9 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public interface IImageDownloader
|
||||
{
|
||||
Task<List<ImageData>> DownloadImageDataAsync(string[] tags, int page = 0,
|
||||
bool isExplicit = false, CancellationToken cancel = default);
|
||||
}
|
||||
Task<List<ImageData>> DownloadImageDataAsync(
|
||||
string[] tags,
|
||||
int page = 0,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default);
|
||||
}
|
@@ -7,29 +7,34 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
public abstract class ImageDownloader<T> : IImageDownloader
|
||||
where T : IImageData
|
||||
{
|
||||
public Booru Booru { get; }
|
||||
protected readonly HttpClient _http;
|
||||
|
||||
protected JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString,
|
||||
|
||||
NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString
|
||||
};
|
||||
|
||||
public Booru Booru { get; }
|
||||
|
||||
public ImageDownloader(Booru booru, HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
this.Booru = booru;
|
||||
Booru = booru;
|
||||
}
|
||||
|
||||
public abstract Task<List<T>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default);
|
||||
public abstract Task<List<T>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default);
|
||||
|
||||
public async Task<List<ImageData>> DownloadImageDataAsync(string[] tags, int page, bool isExplicit = false,
|
||||
public async Task<List<ImageData>> DownloadImageDataAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var images = await DownloadImagesAsync(tags, page, isExplicit, cancel);
|
||||
return images.Select(x => x.ToCachedImageData(Booru)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@ public static class ImageDownloaderHelper
|
||||
{
|
||||
if (isExplicit)
|
||||
tags = tags.Append("rating:explicit");
|
||||
|
||||
|
||||
return string.Join('+', tags.Select(x => x.ToLowerInvariant()));
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,15 +11,17 @@ public sealed class KonachanImageDownloader : ImageDownloader<DapiImageObject>
|
||||
: base(Booru.Konachan, http)
|
||||
=> _baseUrl = "https://konachan.com";
|
||||
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||
var uri = $"{_baseUrl}/post.json?s=post&q=index&limit=200&tags={tagString}&page={page}";
|
||||
var imageObjects = await _http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
||||
if (imageObjects is null)
|
||||
return new();
|
||||
return imageObjects
|
||||
.Where(x => x.FileUrl is not null)
|
||||
.ToList();
|
||||
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,22 +5,25 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class Rule34ImageDownloader : ImageDownloader<Rule34Object>
|
||||
{
|
||||
public Rule34ImageDownloader(HttpClient http) : base(Booru.Rule34, http)
|
||||
public Rule34ImageDownloader(HttpClient http)
|
||||
: base(Booru.Rule34, http)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<List<Rule34Object>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<Rule34Object>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
||||
var uri = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit=100" +
|
||||
$"&tags={tagString}&pid={page}";
|
||||
var uri = "https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit=100"
|
||||
+ $"&tags={tagString}&pid={page}";
|
||||
var images = await _http.GetFromJsonAsync<List<Rule34Object>>(uri, _serializerOptions, cancel);
|
||||
|
||||
if (images is null)
|
||||
return new();
|
||||
|
||||
return images
|
||||
.Where(img => !string.IsNullOrWhiteSpace(img.Image))
|
||||
.ToList();
|
||||
|
||||
return images.Where(img => !string.IsNullOrWhiteSpace(img.Image)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,18 +5,24 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class SafebooruImageDownloader : ImageDownloader<SafebooruElement>
|
||||
{
|
||||
public SafebooruImageDownloader(HttpClient http) : base(Booru.Safebooru, http)
|
||||
public SafebooruImageDownloader(HttpClient http)
|
||||
: base(Booru.Safebooru, http)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<List<SafebooruElement>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<SafebooruElement>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit: false);
|
||||
var uri = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=200&tags={tagString}&json=1&pid={page}";
|
||||
var images = await _http.GetFromJsonAsync<List<SafebooruElement>>(uri, _serializerOptions, cancellationToken: cancel);
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
||||
var uri =
|
||||
$"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=200&tags={tagString}&json=1&pid={page}";
|
||||
var images = await _http.GetFromJsonAsync<List<SafebooruElement>>(uri, _serializerOptions, cancel);
|
||||
if (images is null)
|
||||
return new();
|
||||
|
||||
return images;
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,16 +13,20 @@ public sealed class SankakuImageDownloader : ImageDownloader<SankakuImageObject>
|
||||
_baseUrl = "https://capi-v2.sankakucomplex.com";
|
||||
_http.AddFakeHeaders();
|
||||
}
|
||||
|
||||
public override async Task<List<SankakuImageObject>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
|
||||
public override async Task<List<SankakuImageObject>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
// explicit probably not supported
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, false);
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
||||
|
||||
var uri = $"{_baseUrl}/posts?tags={tagString}&limit=50";
|
||||
var data = await _http.GetStringAsync(uri);
|
||||
return JsonSerializer.Deserialize<SankakuImageObject[]>(data, _serializerOptions)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.FileUrl) && x.FileType.StartsWith("image"))
|
||||
.ToList();
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.FileUrl) && x.FileType.StartsWith("image"))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,16 +11,18 @@ public sealed class YandereImageDownloader : ImageDownloader<DapiImageObject>
|
||||
: base(Booru.Yandere, http)
|
||||
=> _baseUrl = "https://yande.re";
|
||||
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(string[] tags, int page, bool isExplicit = false, CancellationToken cancel = default)
|
||||
public override async Task<List<DapiImageObject>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
int page,
|
||||
bool isExplicit = false,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||
|
||||
|
||||
var uri = $"{_baseUrl}/post.json?limit=200&tags={tagString}&page={page}";
|
||||
var imageObjects = await _http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
||||
if (imageObjects is null)
|
||||
return new();
|
||||
return imageObjects
|
||||
.Where(x => x.FileUrl is not null)
|
||||
.ToList();
|
||||
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,13 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class E621Object : IImageData
|
||||
{
|
||||
public FileData File { get; set; }
|
||||
public TagData Tags { get; set; }
|
||||
public ScoreData Score { get; set; }
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(File.Url, Booru.E621, Tags.General, Score.Total.ToString());
|
||||
|
||||
public class FileData
|
||||
{
|
||||
public string Url { get; set; }
|
||||
@@ -17,11 +24,4 @@ public class E621Object : IImageData
|
||||
{
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
||||
public FileData File { get; set; }
|
||||
public TagData Tags { get; set; }
|
||||
public ScoreData Score { get; set; }
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(File.Url, Booru.E621, Tags.General, Score.Total.ToString());
|
||||
}
|
||||
}
|
@@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
public interface IImageData
|
||||
{
|
||||
ImageData ToCachedImageData(Booru type);
|
||||
}
|
||||
}
|
@@ -8,30 +8,32 @@ public class ImageData : IComparable<ImageData>
|
||||
public HashSet<string> Tags { get; }
|
||||
public string Rating { get; }
|
||||
|
||||
public ImageData(string url, Booru type, string[] tags, string rating)
|
||||
public ImageData(
|
||||
string url,
|
||||
Booru type,
|
||||
string[] tags,
|
||||
string rating)
|
||||
{
|
||||
if (type == Booru.Danbooru && !Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
{
|
||||
this.FileUrl = "https://danbooru.donmai.us" + url;
|
||||
}
|
||||
FileUrl = "https://danbooru.donmai.us" + url;
|
||||
else
|
||||
{
|
||||
this.FileUrl = url.StartsWith("http", StringComparison.InvariantCulture) ? url : "https:" + url;
|
||||
}
|
||||
|
||||
this.SearchType = type;
|
||||
this.FileUrl = url;
|
||||
this.Tags = tags.ToHashSet();
|
||||
this.Rating = rating;
|
||||
FileUrl = url.StartsWith("http", StringComparison.InvariantCulture) ? url : "https:" + url;
|
||||
|
||||
SearchType = type;
|
||||
FileUrl = url;
|
||||
Tags = tags.ToHashSet();
|
||||
Rating = rating;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> FileUrl;
|
||||
|
||||
public override int GetHashCode() => FileUrl.GetHashCode();
|
||||
public override int GetHashCode()
|
||||
=> FileUrl.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ImageData ico && ico.FileUrl == this.FileUrl;
|
||||
=> obj is ImageData ico && ico.FileUrl == FileUrl;
|
||||
|
||||
public int CompareTo(ImageData other)
|
||||
=> string.Compare(FileUrl, other.FileUrl, StringComparison.InvariantCulture);
|
||||
}
|
||||
}
|
@@ -9,9 +9,5 @@ public class Rule34Object : IImageData
|
||||
public int Score { get; init; }
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(
|
||||
$"https://img.rule34.xxx//images/{Directory}/{Image}",
|
||||
Booru.Rule34,
|
||||
Tags.Split(' '),
|
||||
Score.ToString());
|
||||
}
|
||||
=> new($"https://img.rule34.xxx//images/{Directory}/{Image}", Booru.Rule34, Tags.Split(' '), Score.ToString());
|
||||
}
|
@@ -7,8 +7,12 @@ public class SafebooruElement : IImageData
|
||||
public string Image { get; set; }
|
||||
|
||||
|
||||
public string FileUrl => $"https://safebooru.org/images/{Directory}/{Image}";
|
||||
public string FileUrl
|
||||
=> $"https://safebooru.org/images/{Directory}/{Image}";
|
||||
|
||||
public string Rating { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public ImageData ToCachedImageData(Booru type) => new(FileUrl, Booru.Safebooru, this.Tags.Split(' '), Rating);
|
||||
}
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(FileUrl, Booru.Safebooru, Tags.Split(' '), Rating);
|
||||
}
|
@@ -5,22 +5,22 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class SankakuImageObject : IImageData
|
||||
{
|
||||
[JsonPropertyName("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("file_type")]
|
||||
public string FileType { get; set; }
|
||||
|
||||
public Tag[] Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("total_score")]
|
||||
public int Score { get; set; }
|
||||
|
||||
public ImageData ToCachedImageData(Booru type)
|
||||
=> new(FileUrl, Booru.Sankaku, Tags.Select(x => x.Name).ToArray(), Score.ToString());
|
||||
|
||||
public class Tag
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
[JsonPropertyName("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("file_type")]
|
||||
public string FileType { get; set; }
|
||||
|
||||
public Tag[] Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("total_score")]
|
||||
public int Score { get; set; }
|
||||
|
||||
public ImageData ToCachedImageData(Nsfw.Common.Booru type)
|
||||
=> new(FileUrl, Nsfw.Common.Booru.Sankaku, Tags.Select(x => x.Name).ToArray(), Score.ToString());
|
||||
}
|
||||
}
|
@@ -5,6 +5,9 @@ namespace NadekoBot.Modules.Nsfw;
|
||||
|
||||
public interface ISearchImagesService
|
||||
{
|
||||
ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; }
|
||||
ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; }
|
||||
ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; }
|
||||
Task<UrlReply> Gelbooru(ulong? guildId, bool forceExplicit, string[] tags);
|
||||
Task<UrlReply> Danbooru(ulong? guildId, bool forceExplicit, string[] tags);
|
||||
Task<UrlReply> Konachan(ulong? guildId, bool forceExplicit, string[] tags);
|
||||
@@ -21,7 +24,4 @@ public interface ISearchImagesService
|
||||
Task<UrlReply> Butts();
|
||||
Task<Gallery> GetNhentaiByIdAsync(uint id);
|
||||
Task<Gallery> GetNhentaiBySearchAsync(string search);
|
||||
ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; }
|
||||
ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; }
|
||||
ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; }
|
||||
}
|
||||
}
|
@@ -24,8 +24,8 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
JToken obj;
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
obj = JArray.Parse(await http
|
||||
.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}"))[0];
|
||||
obj = JArray.Parse(
|
||||
await http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}"))[0];
|
||||
}
|
||||
|
||||
await ctx.Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}");
|
||||
@@ -43,8 +43,8 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
JToken obj;
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
obj = JArray.Parse(await http
|
||||
.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}"))[0];
|
||||
obj = JArray.Parse(
|
||||
await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}"))[0];
|
||||
}
|
||||
|
||||
await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}");
|
||||
@@ -55,7 +55,8 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
@@ -76,36 +77,42 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
return;
|
||||
|
||||
t = new(async state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (tags is null || tags.Length == 0)
|
||||
await InternalDapiCommand(null, true, _service.Hentai);
|
||||
else
|
||||
try
|
||||
{
|
||||
var groups = tags.Split('|');
|
||||
var group = groups[_rng.Next(0, groups.Length)];
|
||||
await InternalDapiCommand(group.Split(' '), true, _service.Hentai);
|
||||
if (tags is null || tags.Length == 0)
|
||||
{
|
||||
await InternalDapiCommand(null, true, _service.Hentai);
|
||||
}
|
||||
else
|
||||
{
|
||||
var groups = tags.Split('|');
|
||||
var group = groups[_rng.Next(0, groups.Length)];
|
||||
await InternalDapiCommand(group.Split(' '), true, _service.Hentai);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
},
|
||||
null,
|
||||
interval * 1000,
|
||||
interval * 1000);
|
||||
|
||||
_service.AutoHentaiTimers.AddOrUpdate(ctx.Channel.Id,
|
||||
t,
|
||||
(key, old) =>
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}, null, interval * 1000, interval * 1000);
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return t;
|
||||
});
|
||||
|
||||
_service.AutoHentaiTimers.AddOrUpdate(ctx.Channel.Id, t, (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return t;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.autohentai_started(
|
||||
interval,
|
||||
string.Join(", ", tags)));
|
||||
await ReplyConfirmLocalizedAsync(strs.autohentai_started(interval, string.Join(", ", tags)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
@@ -126,28 +133,35 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
return;
|
||||
|
||||
t = new(async state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await InternalBoobs();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}, null, interval * 1000, interval * 1000);
|
||||
try
|
||||
{
|
||||
await InternalBoobs();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
},
|
||||
null,
|
||||
interval * 1000,
|
||||
interval * 1000);
|
||||
|
||||
_service.AutoBoobTimers.AddOrUpdate(ctx.Channel.Id, t, (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return t;
|
||||
});
|
||||
_service.AutoBoobTimers.AddOrUpdate(ctx.Channel.Id,
|
||||
t,
|
||||
(key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return t;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.started(interval));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
public async Task AutoButts(int interval = 0)
|
||||
{
|
||||
@@ -166,33 +180,42 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
return;
|
||||
|
||||
t = new(async state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await InternalButts(ctx.Channel);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}, null, interval * 1000, interval * 1000);
|
||||
try
|
||||
{
|
||||
await InternalButts(ctx.Channel);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
},
|
||||
null,
|
||||
interval * 1000,
|
||||
interval * 1000);
|
||||
|
||||
_service.AutoButtTimers.AddOrUpdate(ctx.Channel.Id, t, (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return t;
|
||||
});
|
||||
_service.AutoButtTimers.AddOrUpdate(ctx.Channel.Id,
|
||||
t,
|
||||
(key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return t;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.started(interval));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Hentai(params string[] tags)
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Hentai(params string[] tags)
|
||||
=> InternalDapiCommand(tags, true, _service.Hentai);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public async Task HentaiBomb(params string[] tags)
|
||||
{
|
||||
if (!_hentaiBombBlacklist.Add(ctx.Guild?.Id ?? ctx.User.Id))
|
||||
@@ -219,53 +242,73 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Yandere(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.Yandere);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Konachan(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.Konachan);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Sankaku(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.Sankaku);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task E621(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.E621);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Rule34(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.Rule34);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Danbooru(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.Danbooru);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Gelbooru(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.Gelbooru);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Derpibooru(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.DerpiBooru);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public Task Safebooru(params string[] tags)
|
||||
=> InternalDapiCommand(tags, false, _service.SafeBooru);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public async Task Boobs()
|
||||
{
|
||||
try
|
||||
@@ -273,8 +316,8 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
JToken obj;
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
obj = JArray.Parse(await http
|
||||
.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 12000)}"))[0];
|
||||
obj = JArray.Parse(
|
||||
await http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 12000)}"))[0];
|
||||
}
|
||||
|
||||
await ctx.Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}");
|
||||
@@ -285,8 +328,10 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
public async Task Butts()
|
||||
{
|
||||
try
|
||||
@@ -294,8 +339,8 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
JToken obj;
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
{
|
||||
obj = JArray.Parse(await http
|
||||
.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 6100)}"))[0];
|
||||
obj = JArray.Parse(
|
||||
await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 6100)}"))[0];
|
||||
}
|
||||
|
||||
await ctx.Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}");
|
||||
@@ -306,7 +351,8 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task NsfwTagBlacklist([Leftover] string tag = null)
|
||||
@@ -314,10 +360,7 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
{
|
||||
var blTags = await _service.GetBlacklistedTags(ctx.Guild.Id);
|
||||
await SendConfirmAsync(GetText(strs.blacklisted_tag_list),
|
||||
blTags.Any()
|
||||
? string.Join(", ", blTags)
|
||||
: "-");
|
||||
await SendConfirmAsync(GetText(strs.blacklisted_tag_list), blTags.Any() ? string.Join(", ", blTags) : "-");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -331,9 +374,11 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[Priority(1)]
|
||||
public async Task Nhentai(uint id)
|
||||
{
|
||||
@@ -348,9 +393,11 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
await SendNhentaiGalleryInternalAsync(g);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireNsfw(Group = "nsfw_or_dm"), RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[RequireNsfw(Group = "nsfw_or_dm")]
|
||||
[RequireContext(ContextType.DM, Group = "nsfw_or_dm")]
|
||||
[Priority(0)]
|
||||
public async Task Nhentai([Leftover] string query)
|
||||
{
|
||||
@@ -368,42 +415,43 @@ public class NSFW : NadekoModule<ISearchImagesService>
|
||||
private async Task SendNhentaiGalleryInternalAsync(Gallery g)
|
||||
{
|
||||
var count = 0;
|
||||
var tagString = g.Tags
|
||||
.Shuffle()
|
||||
.Select(tag => $"[{tag.Name}]({tag.Url})")
|
||||
.TakeWhile(tag => (count += tag.Length) < 1000)
|
||||
.Join(" ");
|
||||
var tagString = g.Tags.Shuffle()
|
||||
.Select(tag => $"[{tag.Name}]({tag.Url})")
|
||||
.TakeWhile(tag => (count += tag.Length) < 1000)
|
||||
.Join(" ");
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(g.Title)
|
||||
.WithDescription(g.FullTitle)
|
||||
.WithImageUrl(g.Thumbnail)
|
||||
.WithUrl(g.Url)
|
||||
.AddField(GetText(strs.favorites), g.Likes, true)
|
||||
.AddField(GetText(strs.pages), g.PageCount, true)
|
||||
.AddField(GetText(strs.tags), tagString, true)
|
||||
.WithFooter(g.UploadedAt.ToString("f"))
|
||||
.WithOkColor();
|
||||
.WithTitle(g.Title)
|
||||
.WithDescription(g.FullTitle)
|
||||
.WithImageUrl(g.Thumbnail)
|
||||
.WithUrl(g.Url)
|
||||
.AddField(GetText(strs.favorites), g.Likes, true)
|
||||
.AddField(GetText(strs.pages), g.PageCount, true)
|
||||
.AddField(GetText(strs.tags), tagString, true)
|
||||
.WithFooter(g.UploadedAt.ToString("f"))
|
||||
.WithOkColor();
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
private async Task InternalDapiCommand(string[] tags,
|
||||
private async Task InternalDapiCommand(
|
||||
string[] tags,
|
||||
bool forceExplicit,
|
||||
Func<ulong?, bool, string[], Task<UrlReply>> func)
|
||||
{
|
||||
var data = await func(ctx.Guild?.Id, forceExplicit, tags);
|
||||
|
||||
|
||||
if (data is null || !string.IsNullOrWhiteSpace(data.Error))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_results);
|
||||
return;
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(_eb
|
||||
.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithImageUrl(data.Url)
|
||||
.WithDescription($"[link]({data.Url})")
|
||||
.WithFooter($"{data.Rating} ({data.Provider}) | {string.Join(" | ", data.Tags.Where(x => !string.IsNullOrWhiteSpace(x)).Take(5))}"));
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithImageUrl(data.Url)
|
||||
.WithDescription($"[link]({data.Url})")
|
||||
.WithFooter(
|
||||
$"{data.Rating} ({data.Provider}) | {string.Join(" | ", data.Tags.Where(x => !string.IsNullOrWhiteSpace(x)).Take(5))}"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,10 +3,8 @@ namespace NadekoBot.Modules.Nsfw;
|
||||
|
||||
public interface INsfwService
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class NsfwService
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -5,10 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
||||
|
||||
public class SearchImageCacher : INService
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly Random _rng;
|
||||
|
||||
private static readonly ISet<string> defaultTagBlacklist = new HashSet<string>()
|
||||
private static readonly ISet<string> defaultTagBlacklist = new HashSet<string>
|
||||
{
|
||||
"loli",
|
||||
"lolicon",
|
||||
@@ -17,10 +14,15 @@ public class SearchImageCacher : INService
|
||||
"cub"
|
||||
};
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly Random _rng;
|
||||
|
||||
private readonly Dictionary<Booru, object> _typeLocks = new();
|
||||
private readonly Dictionary<Booru, HashSet<string>> _usedTags = new();
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
private readonly ConcurrentDictionary<(Booru, string), int> maxPages = new();
|
||||
|
||||
public SearchImageCacher(IHttpClientFactory httpFactory, IMemoryCache cache)
|
||||
{
|
||||
_httpFactory = httpFactory;
|
||||
@@ -39,21 +41,23 @@ public class SearchImageCacher : INService
|
||||
=> $"booru:{boory}__tag:{tag}";
|
||||
|
||||
/// <summary>
|
||||
/// Download images of the specified type, and cache them.
|
||||
/// Download images of the specified type, and cache them.
|
||||
/// </summary>
|
||||
/// <param name="tags">Required tags</param>
|
||||
/// <param name="forceExplicit">Whether images will be forced to be explicit</param>
|
||||
/// <param name="type">Provider type</param>
|
||||
/// <param name="cancel">Cancellation token</param>
|
||||
/// <returns>Whether any image is found.</returns>
|
||||
private async Task<bool> UpdateImagesInternalAsync(string[] tags, bool forceExplicit, Booru type, CancellationToken cancel)
|
||||
private async Task<bool> UpdateImagesInternalAsync(
|
||||
string[] tags,
|
||||
bool forceExplicit,
|
||||
Booru type,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var images = await DownloadImagesAsync(tags, forceExplicit, type, cancel);
|
||||
if (images is null || images.Count == 0)
|
||||
{
|
||||
// Log.Warning("Got no images for {0}, tags: {1}", type, string.Join(", ", tags));
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Information("Updating {0}...", type);
|
||||
lock (_typeLocks[type])
|
||||
@@ -65,12 +69,7 @@ public class SearchImageCacher : INService
|
||||
// if user uses no tags for the hentai command and there are no used
|
||||
// tags atm, just select 50 random tags from downloaded images to seed
|
||||
if (typeUsedTags.Count == 0)
|
||||
images.SelectMany(x => x.Tags)
|
||||
.Distinct()
|
||||
.Shuffle()
|
||||
.Take(50)
|
||||
.ToList()
|
||||
.ForEach(x => typeUsedTags.Add(x));
|
||||
images.SelectMany(x => x.Tags).Distinct().Shuffle().Take(50).ToList().ForEach(x => typeUsedTags.Add(x));
|
||||
|
||||
foreach (var img in images)
|
||||
{
|
||||
@@ -78,7 +77,7 @@ public class SearchImageCacher : INService
|
||||
// do not put that image in the cache
|
||||
if (defaultTagBlacklist.Overlaps(img.Tags))
|
||||
continue;
|
||||
|
||||
|
||||
// if image doesn't have a proper absolute uri, skip it
|
||||
if (!Uri.IsWellFormedUriString(img.FileUrl, UriKind.Absolute))
|
||||
continue;
|
||||
@@ -88,26 +87,29 @@ public class SearchImageCacher : INService
|
||||
// both 'kiss' (real tag returned by the image) and 'kissing' will be populated with
|
||||
// retreived images
|
||||
foreach (var tag in img.Tags.Concat(tags).Distinct())
|
||||
{
|
||||
if (typeUsedTags.Contains(tag))
|
||||
{
|
||||
var set = _cache.GetOrCreate<HashSet<ImageData>>(Key(type, tag), e =>
|
||||
{
|
||||
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
|
||||
return new();
|
||||
});
|
||||
|
||||
if(set.Count < 100)
|
||||
var set = _cache.GetOrCreate<HashSet<ImageData>>(Key(type, tag),
|
||||
e =>
|
||||
{
|
||||
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
|
||||
return new();
|
||||
});
|
||||
|
||||
if (set.Count < 100)
|
||||
set.Add(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ImageData QueryLocal(string[] tags, bool forceExplicit, Booru type, HashSet<string> blacklistedTags)
|
||||
private ImageData QueryLocal(
|
||||
string[] tags,
|
||||
bool forceExplicit,
|
||||
Booru type,
|
||||
HashSet<string> blacklistedTags)
|
||||
{
|
||||
var setList = new List<HashSet<ImageData>>();
|
||||
|
||||
@@ -118,25 +120,18 @@ public class SearchImageCacher : INService
|
||||
if (tags.Length == 0)
|
||||
{
|
||||
// get all tags in the cache
|
||||
if (_usedTags.TryGetValue(type, out var allTags)
|
||||
&& allTags.Count > 0)
|
||||
{
|
||||
tags = new[] {allTags.ToList()[_rng.Next(0, allTags.Count)]};
|
||||
}
|
||||
if (_usedTags.TryGetValue(type, out var allTags) && allTags.Count > 0)
|
||||
tags = new[] { allTags.ToList()[_rng.Next(0, allTags.Count)] };
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
// if any tag is missing from cache, that means there is no result
|
||||
if (_cache.TryGetValue<HashSet<ImageData>>(Key(type, tag), out var set))
|
||||
setList.Add(set);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
if (setList.Count == 0)
|
||||
return null;
|
||||
@@ -152,14 +147,11 @@ public class SearchImageCacher : INService
|
||||
|
||||
// go through all other sets, and
|
||||
for (var i = 1; i < setList.Count; ++i)
|
||||
{
|
||||
// if any of the elements in result set are not present in the current set
|
||||
// remove it from the result set
|
||||
resultSet.IntersectWith(setList[i]);
|
||||
}
|
||||
|
||||
resultList = resultSet.ToList();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -173,30 +165,26 @@ public class SearchImageCacher : INService
|
||||
// if no items in the set -> not found
|
||||
if (resultList.Count == 0)
|
||||
return null;
|
||||
|
||||
|
||||
var toReturn = resultList[_rng.Next(0, resultList.Count)];
|
||||
|
||||
// remove from cache
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
if (_cache.TryGetValue<HashSet<ImageData>>(Key(type, tag), out var items))
|
||||
{
|
||||
items.Remove(toReturn);
|
||||
}
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ImageData> GetImageNew(string[] tags, bool forceExplicit, Booru type,
|
||||
HashSet<string> blacklistedTags, CancellationToken cancel)
|
||||
public async Task<ImageData> GetImageNew(
|
||||
string[] tags,
|
||||
bool forceExplicit,
|
||||
Booru type,
|
||||
HashSet<string> blacklistedTags,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
// make sure tags are proper
|
||||
tags = tags
|
||||
.Where(x => x is not null)
|
||||
.Select(tag => tag.ToLowerInvariant().Trim())
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
tags = tags.Where(x => x is not null).Select(tag => tag.ToLowerInvariant().Trim()).Distinct().ToArray();
|
||||
|
||||
if (tags.Length > 2 && type == Booru.Danbooru)
|
||||
tags = tags[..2];
|
||||
@@ -222,15 +210,17 @@ public class SearchImageCacher : INService
|
||||
|
||||
if (!success)
|
||||
return default;
|
||||
|
||||
|
||||
image = QueryLocal(tags, forceExplicit, type, blacklistedTags);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<(Booru, string), int> maxPages = new();
|
||||
|
||||
public async Task<List<ImageData>> DownloadImagesAsync(string[] tags, bool isExplicit, Booru type, CancellationToken cancel)
|
||||
public async Task<List<ImageData>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
bool isExplicit,
|
||||
Booru type,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var tagStr = string.Join(' ', tags.OrderByDescending(x => x));
|
||||
var page = 0;
|
||||
@@ -257,7 +247,10 @@ public class SearchImageCacher : INService
|
||||
|
||||
if (result is null or { Count: 0 })
|
||||
{
|
||||
Log.Information("Tag {0}, page {1} has no result on {2}.", string.Join(", ", tags), page, type.ToString());
|
||||
Log.Information("Tag {0}, page {1} has no result on {2}.",
|
||||
string.Join(", ", tags),
|
||||
page,
|
||||
type.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -282,7 +275,12 @@ public class SearchImageCacher : INService
|
||||
_ => throw new NotImplementedException($"{booru} downloader not implemented.")
|
||||
};
|
||||
|
||||
private async Task<List<ImageData>> DownloadImagesAsync(string[] tags, bool isExplicit, Booru type, int page, CancellationToken cancel)
|
||||
private async Task<List<ImageData>> DownloadImagesAsync(
|
||||
string[] tags,
|
||||
bool isExplicit,
|
||||
Booru type,
|
||||
int page,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -306,7 +304,8 @@ public class SearchImageCacher : INService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error downloading an image:\nTags: {0}\nType: {1}\nPage: {2}\nMessage: {3}",
|
||||
Log.Error(ex,
|
||||
"Error downloading an image:\nTags: {0}\nType: {1}\nPage: {2}\nMessage: {3}",
|
||||
string.Join(", ", tags),
|
||||
type,
|
||||
page,
|
||||
@@ -314,4 +313,4 @@ public class SearchImageCacher : INService
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,14 +1,18 @@
|
||||
#nullable disable
|
||||
using Newtonsoft.Json.Linq;
|
||||
using LinqToDB;
|
||||
using NadekoBot.Modules.Nsfw.Common;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Booru = NadekoBot.Modules.Nsfw.Common.Booru;
|
||||
using SearchImageCacher = NadekoBot.Modules.Nsfw.Common.SearchImageCacher;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NadekoBot.Modules.Nsfw;
|
||||
|
||||
public record TagRequest(ulong GuildId, bool ForceExplicit, Booru SearchType, params string[] Tags);
|
||||
public record TagRequest(
|
||||
ulong GuildId,
|
||||
bool ForceExplicit,
|
||||
Booru SearchType,
|
||||
params string[] Tags);
|
||||
|
||||
public record UrlReply
|
||||
{
|
||||
public string Error { get; init; }
|
||||
@@ -20,18 +24,21 @@ public record UrlReply
|
||||
|
||||
public class SearchImagesService : ISearchImagesService, INService
|
||||
{
|
||||
private readonly Random _rng;
|
||||
private readonly HttpClient _http;
|
||||
private readonly SearchImageCacher _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly DbService _db;
|
||||
private ConcurrentDictionary<ulong, HashSet<string>> BlacklistedTags { get; }
|
||||
|
||||
public ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; } = new();
|
||||
public ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; } = new();
|
||||
public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new();
|
||||
private readonly Random _rng;
|
||||
private readonly HttpClient _http;
|
||||
private readonly SearchImageCacher _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly DbService _db;
|
||||
|
||||
public SearchImagesService(DbService db,
|
||||
private readonly object taglock = new();
|
||||
|
||||
public SearchImagesService(
|
||||
DbService db,
|
||||
IHttpClientFactory http,
|
||||
SearchImageCacher cacher,
|
||||
IHttpClientFactory httpFactory)
|
||||
@@ -44,19 +51,21 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
_httpFactory = httpFactory;
|
||||
|
||||
using var uow = db.GetDbContext();
|
||||
BlacklistedTags = new(
|
||||
uow.NsfwBlacklistedTags
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(
|
||||
x => x.Key,
|
||||
x => new HashSet<string>(x.Select(x => x.Tag))));
|
||||
BlacklistedTags = new(uow.NsfwBlacklistedTags.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key, x => new HashSet<string>(x.Select(x => x.Tag))));
|
||||
}
|
||||
|
||||
private Task<UrlReply> GetNsfwImageAsync(ulong? guildId, bool forceExplicit, string[] tags, Booru dapi, CancellationToken cancel = default)
|
||||
private Task<UrlReply> GetNsfwImageAsync(
|
||||
ulong? guildId,
|
||||
bool forceExplicit,
|
||||
string[] tags,
|
||||
Booru dapi,
|
||||
CancellationToken cancel = default)
|
||||
=> GetNsfwImageAsync(guildId ?? 0, tags ?? Array.Empty<string>(), forceExplicit, dapi, cancel);
|
||||
|
||||
private bool IsValidTag(string tag) => tag.All(x => x != '+' && x != '?' && x != '/'); // tags mustn't contain + or ? or /
|
||||
private bool IsValidTag(string tag)
|
||||
=> tag.All(x => x != '+' && x != '?' && x != '/'); // tags mustn't contain + or ? or /
|
||||
|
||||
private async Task<UrlReply> GetNsfwImageAsync(
|
||||
ulong guildId,
|
||||
@@ -66,64 +75,41 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
CancellationToken cancel)
|
||||
{
|
||||
if (!tags.All(x => IsValidTag(x)))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Error = "One or more tags are invalid.",
|
||||
Url = ""
|
||||
};
|
||||
}
|
||||
return new() { Error = "One or more tags are invalid.", Url = "" };
|
||||
|
||||
Log.Information("Getting {V} image for Guild: {GuildId}...", dapi.ToString(), guildId);
|
||||
try
|
||||
{
|
||||
BlacklistedTags.TryGetValue(guildId, out var blTags);
|
||||
|
||||
if (dapi == Booru.E621) {
|
||||
if (dapi == Booru.E621)
|
||||
for (var i = 0; i < tags.Length; ++i)
|
||||
if (tags[i] == "yuri")
|
||||
tags[i] = "female/female";
|
||||
}
|
||||
|
||||
if (dapi == Booru.Derpibooru)
|
||||
{
|
||||
for (var i = 0; i < tags.Length; ++i)
|
||||
if (tags[i] == "yuri")
|
||||
tags[i] = "lesbian";
|
||||
}
|
||||
|
||||
var result = await _cache.GetImageNew(tags, forceExplicit, dapi, blTags ?? new HashSet<string>(), cancel);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Error = "Image not found.",
|
||||
Url = ""
|
||||
};
|
||||
}
|
||||
return new() { Error = "Image not found.", Url = "" };
|
||||
|
||||
var reply = new UrlReply
|
||||
{
|
||||
Error = "",
|
||||
Url = result.FileUrl,
|
||||
Rating = result.Rating,
|
||||
Provider = result.SearchType.ToString()
|
||||
Error = "", Url = result.FileUrl, Rating = result.Rating, Provider = result.SearchType.ToString()
|
||||
};
|
||||
|
||||
reply.Tags.AddRange(result.Tags);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed getting {Dapi} image: {Message}", dapi, ex.Message);
|
||||
return new()
|
||||
{
|
||||
Error = ex.Message,
|
||||
Url = ""
|
||||
};
|
||||
return new() { Error = ex.Message, Url = "" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,18 +136,13 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
|
||||
public Task<UrlReply> SafeBooru(ulong? guildId, bool forceExplicit, string[] tags)
|
||||
=> GetNsfwImageAsync(guildId, forceExplicit, tags, Booru.Safebooru);
|
||||
|
||||
|
||||
public Task<UrlReply> Sankaku(ulong? guildId, bool forceExplicit, string[] tags)
|
||||
=> GetNsfwImageAsync(guildId, forceExplicit, tags, Booru.Sankaku);
|
||||
|
||||
public async Task<UrlReply> Hentai(ulong? guildId, bool forceExplicit, string[] tags)
|
||||
{
|
||||
var providers = new[] {
|
||||
Booru.Danbooru,
|
||||
Booru.Konachan,
|
||||
Booru.Gelbooru,
|
||||
Booru.Yandere
|
||||
};
|
||||
var providers = new[] { Booru.Danbooru, Booru.Konachan, Booru.Gelbooru, Booru.Yandere };
|
||||
|
||||
using var cancelSource = new CancellationTokenSource();
|
||||
|
||||
@@ -174,7 +155,7 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
|
||||
// get its result
|
||||
var result = task.GetAwaiter().GetResult();
|
||||
if(result.Error == "")
|
||||
if (result.Error == "")
|
||||
{
|
||||
// if we have a non-error result, cancel other searches and return the result
|
||||
cancelSource.Cancel();
|
||||
@@ -184,14 +165,10 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
// if the result is an error, remove that task from the waiting list,
|
||||
// and wait for another task to complete
|
||||
tasks.Remove(task);
|
||||
}
|
||||
while (tasks.Count > 0); // keep looping as long as there is any task remaining to be attempted
|
||||
} while (tasks.Count > 0); // keep looping as long as there is any task remaining to be attempted
|
||||
|
||||
// if we ran out of tasks, that means all tasks failed - return an error
|
||||
return new()
|
||||
{
|
||||
Error = "No hentai image found."
|
||||
};
|
||||
return new() { Error = "No hentai image found." };
|
||||
}
|
||||
|
||||
public async Task<UrlReply> Boobs()
|
||||
@@ -200,24 +177,15 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
{
|
||||
JToken obj;
|
||||
obj = JArray.Parse(await _http.GetStringAsync($"http://api.oboobs.ru/boobs/{_rng.Next(0, 12000)}"))[0];
|
||||
return new()
|
||||
{
|
||||
Error = "",
|
||||
Url = $"http://media.oboobs.ru/{obj["preview"]}",
|
||||
};
|
||||
return new() { Error = "", Url = $"http://media.oboobs.ru/{obj["preview"]}" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error retreiving boob image: {Message}", ex.Message);
|
||||
return new()
|
||||
{
|
||||
Error = ex.Message,
|
||||
Url = "",
|
||||
};
|
||||
return new() { Error = ex.Message, Url = "" };
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object taglock = new();
|
||||
public ValueTask<bool> ToggleBlacklistTag(ulong guildId, string tag)
|
||||
{
|
||||
lock (taglock)
|
||||
@@ -235,28 +203,20 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.NsfwBlacklistedTags.Add(new()
|
||||
{
|
||||
Tag = tag,
|
||||
GuildId = guildId
|
||||
});
|
||||
uow.NsfwBlacklistedTags.Add(new() { Tag = tag, GuildId = guildId });
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return new(isAdded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ValueTask<string[]> GetBlacklistedTags(ulong guildId)
|
||||
{
|
||||
lock (taglock)
|
||||
{
|
||||
if (BlacklistedTags.TryGetValue(guildId, out var tags))
|
||||
{
|
||||
return new(tags.ToArray());
|
||||
}
|
||||
if (BlacklistedTags.TryGetValue(guildId, out var tags)) return new(tags.ToArray());
|
||||
|
||||
return new(Array.Empty<string>());
|
||||
}
|
||||
@@ -268,24 +228,17 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
{
|
||||
JToken obj;
|
||||
obj = JArray.Parse(await _http.GetStringAsync($"http://api.obutts.ru/butts/{_rng.Next(0, 6100)}"))[0];
|
||||
return new()
|
||||
{
|
||||
Error = "",
|
||||
Url = $"http://media.obutts.ru/{obj["preview"]}",
|
||||
};
|
||||
return new() { Error = "", Url = $"http://media.obutts.ru/{obj["preview"]}" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error retreiving butt image: {Message}", ex.Message);
|
||||
return new()
|
||||
{
|
||||
Error = ex.Message,
|
||||
Url = "",
|
||||
};
|
||||
return new() { Error = ex.Message, Url = "" };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Nhentai
|
||||
|
||||
private string GetNhentaiExtensionInternal(string s)
|
||||
=> s switch
|
||||
{
|
||||
@@ -294,15 +247,14 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
"g" => "gif",
|
||||
_ => "jpg"
|
||||
};
|
||||
|
||||
|
||||
private Gallery ModelToGallery(NhentaiApiModel.Gallery model)
|
||||
{
|
||||
var thumbnail = $"https://t.nhentai.net/galleries/{model.MediaId}/thumb."
|
||||
+ GetNhentaiExtensionInternal(model.Images.Thumbnail.T);
|
||||
|
||||
var url = $"https://nhentai.net/g/{model.Id}";
|
||||
return new(
|
||||
model.Id.ToString(),
|
||||
return new(model.Id.ToString(),
|
||||
url,
|
||||
model.Title.English,
|
||||
model.Title.Pretty,
|
||||
@@ -310,11 +262,7 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
model.NumPages,
|
||||
model.NumFavorites,
|
||||
model.UploadDate.ToUnixTimestamp().UtcDateTime,
|
||||
model.Tags.Map(x => new Tag()
|
||||
{
|
||||
Name = x.Name,
|
||||
Url = "https://nhentai.com/" + x.Url
|
||||
}));
|
||||
model.Tags.Map(x => new Tag { Name = x.Name, Url = "https://nhentai.com/" + x.Url }));
|
||||
}
|
||||
|
||||
private async Task<NhentaiApiModel.Gallery> GetNhentaiByIdInternalAsync(uint id)
|
||||
@@ -331,7 +279,7 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<NhentaiApiModel.Gallery[]> SearchNhentaiInternalAsync(string search)
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
@@ -346,7 +294,7 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<Gallery> GetNhentaiByIdAsync(uint id)
|
||||
{
|
||||
var model = await GetNhentaiByIdInternalAsync(id);
|
||||
@@ -354,25 +302,19 @@ public class SearchImagesService : ISearchImagesService, INService
|
||||
return ModelToGallery(model);
|
||||
}
|
||||
|
||||
private static readonly string[] _bannedTags =
|
||||
{
|
||||
"loli",
|
||||
"lolicon",
|
||||
"shota",
|
||||
"shotacon",
|
||||
"cub"
|
||||
};
|
||||
|
||||
private static readonly string[] _bannedTags = { "loli", "lolicon", "shota", "shotacon", "cub" };
|
||||
|
||||
public async Task<Gallery> GetNhentaiBySearchAsync(string search)
|
||||
{
|
||||
var models = await SearchNhentaiInternalAsync(search);
|
||||
|
||||
models = models.Where(x => !x.Tags.Any(t => _bannedTags.Contains(t.Name))).ToArray();
|
||||
|
||||
|
||||
if (models.Length == 0)
|
||||
return null;
|
||||
|
||||
|
||||
return ModelToGallery(models[_rng.Next(0, models.Length)]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user