Merge pull request #163 from C9Glax/cuttingedge
Connector Bugs, AprilFools Mode
This commit is contained in:
commit
0260868968
45
.github/workflows/docker-image-dev.yml
vendored
Normal file
45
.github/workflows/docker-image-dev.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: Docker Image CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "dev" ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# https://github.com/docker/setup-qemu-action#usage
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.2.0
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/docker-setup-buildx
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.1.0
|
||||||
|
|
||||||
|
# https://github.com/docker/login-action#docker-hub
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
# https://github.com/docker/build-push-action#multi-platform-image
|
||||||
|
- name: Build and push API
|
||||||
|
uses: docker/build-push-action@v4.1.1
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
file: ./Dockerfile
|
||||||
|
#platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
|
||||||
|
platforms: linux/amd64
|
||||||
|
pull: true
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
glax/tranga-api:dev
|
@ -44,8 +44,8 @@ internal sealed class TrangaCli : Command<TrangaCli.Settings>
|
|||||||
if(settings.fileLogger is true)
|
if(settings.fileLogger is true)
|
||||||
enabledLoggers.Add(Logger.LoggerType.FileLogger);
|
enabledLoggers.Add(Logger.LoggerType.FileLogger);
|
||||||
|
|
||||||
string? logFilePath = settings.fileLoggerPath ?? "";
|
string? logFolderPath = settings.fileLoggerPath ?? "";
|
||||||
Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, logFilePath);
|
Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, logFolderPath);
|
||||||
|
|
||||||
TrangaSettings? trangaSettings = null;
|
TrangaSettings? trangaSettings = null;
|
||||||
|
|
||||||
|
@ -20,17 +20,17 @@ public class Logger : TextWriter
|
|||||||
private readonly FormattedConsoleLogger? _formattedConsoleLogger;
|
private readonly FormattedConsoleLogger? _formattedConsoleLogger;
|
||||||
private readonly MemoryLogger _memoryLogger;
|
private readonly MemoryLogger _memoryLogger;
|
||||||
|
|
||||||
public Logger(LoggerType[] enabledLoggers, TextWriter? stdOut, Encoding? encoding, string? logFilePath)
|
public Logger(LoggerType[] enabledLoggers, TextWriter? stdOut, Encoding? encoding, string? logFolderPath)
|
||||||
{
|
{
|
||||||
this.Encoding = encoding ?? Encoding.UTF8;
|
this.Encoding = encoding ?? Encoding.UTF8;
|
||||||
if(enabledLoggers.Contains(LoggerType.FileLogger) && (logFilePath is null || logFilePath == ""))
|
DateTime now = DateTime.Now;
|
||||||
|
if(enabledLoggers.Contains(LoggerType.FileLogger) && (logFolderPath is null || logFolderPath == ""))
|
||||||
{
|
{
|
||||||
DateTime now = DateTime.Now;
|
string filePath = Path.Join(LogDirectoryPath,
|
||||||
logFilePath = Path.Join(LogDirectoryPath,
|
|
||||||
$"{now.ToShortDateString()}_{now.Hour}-{now.Minute}-{now.Second}.log");
|
$"{now.ToShortDateString()}_{now.Hour}-{now.Minute}-{now.Second}.log");
|
||||||
_fileLogger = new FileLogger(logFilePath, encoding);
|
_fileLogger = new FileLogger(filePath, encoding);
|
||||||
}else if (enabledLoggers.Contains(LoggerType.FileLogger) && logFilePath is not null)
|
}else if (enabledLoggers.Contains(LoggerType.FileLogger) && logFolderPath is not null)
|
||||||
_fileLogger = new FileLogger(logFilePath, encoding);
|
_fileLogger = new FileLogger(Path.Join(logFolderPath, $"{now.ToShortDateString()}_{now.Hour}-{now.Minute}-{now.Second}.log") , encoding);
|
||||||
|
|
||||||
|
|
||||||
if (enabledLoggers.Contains(LoggerType.ConsoleLogger) && stdOut is not null)
|
if (enabledLoggers.Contains(LoggerType.ConsoleLogger) && stdOut is not null)
|
||||||
@ -43,6 +43,7 @@ public class Logger : TextWriter
|
|||||||
throw new ArgumentException($"stdOut can not be null for LoggerType {LoggerType.ConsoleLogger}");
|
throw new ArgumentException($"stdOut can not be null for LoggerType {LoggerType.ConsoleLogger}");
|
||||||
}
|
}
|
||||||
_memoryLogger = new MemoryLogger(encoding);
|
_memoryLogger = new MemoryLogger(encoding);
|
||||||
|
WriteLine(GetType().ToString(), $"Logfile: {logFilePath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteLine(string caller, string? value)
|
public void WriteLine(string caller, string? value)
|
||||||
|
@ -164,7 +164,7 @@ public class JobBoss : GlobalBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
HashSet<string> coverFileNames = cachedPublications.Select(manga => manga.coverFileNameInCache!).ToHashSet();
|
HashSet<string> coverFileNames = cachedPublications.Select(manga => manga.coverFileNameInCache!).ToHashSet();
|
||||||
foreach (string fileName in Directory.GetFiles(settings.coverImageCache))
|
foreach (string fileName in Directory.GetFiles(settings.coverImageCache)) //Cleanup Unused Covers
|
||||||
{
|
{
|
||||||
if(!coverFileNames.Any(existingManga => fileName.Contains(existingManga)))
|
if(!coverFileNames.Any(existingManga => fileName.Contains(existingManga)))
|
||||||
File.Delete(fileName);
|
File.Delete(fileName);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
using PuppeteerSharp;
|
using PuppeteerSharp;
|
||||||
|
using PuppeteerSharp.Input;
|
||||||
|
|
||||||
namespace Tranga.MangaConnectors;
|
namespace Tranga.MangaConnectors;
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ internal class ChromiumDownloadClient : DownloadClient
|
|||||||
{
|
{
|
||||||
if (content.Contains("text/html"))
|
if (content.Contains("text/html"))
|
||||||
{
|
{
|
||||||
if(clickButton is not null)
|
if (clickButton is not null && page.QuerySelectorAsync(clickButton).Result is not null)
|
||||||
page.ClickAsync(clickButton).Wait();
|
page.ClickAsync(clickButton).Wait();
|
||||||
string htmlString = page.GetContentAsync().Result;
|
string htmlString = page.GetContentAsync().Result;
|
||||||
stream = new MemoryStream(Encoding.Default.GetBytes(htmlString));
|
stream = new MemoryStream(Encoding.Default.GetBytes(htmlString));
|
||||||
|
@ -237,7 +237,7 @@ public abstract class MangaConnector : GlobalBase
|
|||||||
return HttpStatusCode.Created;
|
return HttpStatusCode.Created;
|
||||||
|
|
||||||
//Create a temporary folder to store images
|
//Create a temporary folder to store images
|
||||||
string tempFolder = Directory.CreateTempSubdirectory().FullName;
|
string tempFolder = Directory.CreateTempSubdirectory("trangatemp").FullName;
|
||||||
|
|
||||||
int chapter = 0;
|
int chapter = 0;
|
||||||
//Download all Images to temporary Folder
|
//Download all Images to temporary Folder
|
||||||
@ -260,8 +260,10 @@ public abstract class MangaConnector : GlobalBase
|
|||||||
progressToken?.Increment();
|
progressToken?.Increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(comicInfoPath is not null)
|
if(comicInfoPath is not null){
|
||||||
File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml"));
|
File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml"));
|
||||||
|
File.Delete(comicInfoPath); //Delete tmp-file
|
||||||
|
}
|
||||||
|
|
||||||
Log($"Creating archive {saveArchiveFilePath}");
|
Log($"Creating archive {saveArchiveFilePath}");
|
||||||
//ZIP-it and ship-it
|
//ZIP-it and ship-it
|
||||||
|
@ -20,12 +20,18 @@ public class MangaDex : MangaConnector
|
|||||||
int total = int.MaxValue; //How many total results are there, is updated on first request
|
int total = int.MaxValue; //How many total results are there, is updated on first request
|
||||||
HashSet<Manga> retManga = new();
|
HashSet<Manga> retManga = new();
|
||||||
int loadedPublicationData = 0;
|
int loadedPublicationData = 0;
|
||||||
|
List<JsonNode> results = new();
|
||||||
|
|
||||||
|
//Request all search-results
|
||||||
while (offset < total) //As long as we haven't requested all "Pages"
|
while (offset < total) //As long as we haven't requested all "Pages"
|
||||||
{
|
{
|
||||||
//Request next Page
|
//Request next Page
|
||||||
RequestResult requestResult =
|
RequestResult requestResult = downloadClient.MakeRequest(
|
||||||
downloadClient.MakeRequest(
|
$"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}" +
|
||||||
$"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}", RequestType.MangaInfo);
|
$"&contentRating%5B%5D=safe&contentRating%5B%5D=suggestive&contentRating%5B%5D=erotica" +
|
||||||
|
$"&contentRating%5B%5D=pornographic" +
|
||||||
|
$"&includes%5B%5D=manga&includes%5B%5D=cover_art&includes%5B%5D=author" +
|
||||||
|
$"&includes%5B%5D=artist&includes%5B%5D=tag", RequestType.MangaInfo);
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
||||||
break;
|
break;
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
||||||
@ -39,18 +45,14 @@ public class MangaDex : MangaConnector
|
|||||||
else continue;
|
else continue;
|
||||||
|
|
||||||
if (result.ContainsKey("data"))
|
if (result.ContainsKey("data"))
|
||||||
{
|
results.AddRange(result["data"]!.AsArray()!);//Manga-data-Array
|
||||||
JsonArray mangaInResult = result["data"]!.AsArray(); //Manga-data-Array
|
}
|
||||||
//Loop each Manga and extract information from JSON
|
|
||||||
foreach (JsonNode? mangaNode in mangaInResult)
|
foreach (JsonNode mangaNode in results)
|
||||||
{
|
{
|
||||||
if(mangaNode is null)
|
Log($"Getting publication data. {++loadedPublicationData}/{total}");
|
||||||
continue;
|
if(MangaFromJsonObject(mangaNode.AsObject()) is { } manga)
|
||||||
Log($"Getting publication data. {++loadedPublicationData}/{total}");
|
retManga.Add(manga); //Add Publication (Manga) to result
|
||||||
if(MangaFromJsonObject((JsonObject) mangaNode) is { } manga)
|
|
||||||
retManga.Add(manga); //Add Publication (Manga) to result
|
|
||||||
}
|
|
||||||
}//else continue;
|
|
||||||
}
|
}
|
||||||
Log($"Retrieved {retManga.Count} publications. Term=\"{publicationTitle}\"");
|
Log($"Retrieved {retManga.Count} publications. Term=\"{publicationTitle}\"");
|
||||||
return retManga.ToArray();
|
return retManga.ToArray();
|
||||||
@ -78,94 +80,96 @@ public class MangaDex : MangaConnector
|
|||||||
|
|
||||||
private Manga? MangaFromJsonObject(JsonObject manga)
|
private Manga? MangaFromJsonObject(JsonObject manga)
|
||||||
{
|
{
|
||||||
if (!manga.ContainsKey("attributes"))
|
if (!manga.TryGetPropertyValue("id", out JsonNode? idNode))
|
||||||
return null;
|
return null;
|
||||||
JsonObject attributes = manga["attributes"]!.AsObject();
|
string publicationId = idNode!.GetValue<string>();
|
||||||
|
|
||||||
|
if (!manga.TryGetPropertyValue("attributes", out JsonNode? attributesNode))
|
||||||
|
return null;
|
||||||
|
JsonObject attributes = attributesNode!.AsObject();
|
||||||
|
|
||||||
|
if (!attributes.TryGetPropertyValue("title", out JsonNode? titleNode))
|
||||||
|
return null;
|
||||||
|
string title = titleNode!.AsObject().ContainsKey("en") switch
|
||||||
|
{
|
||||||
|
true => titleNode.AsObject()["en"]!.GetValue<string>(),
|
||||||
|
false => titleNode.AsObject().First().Value!.GetValue<string>()
|
||||||
|
};
|
||||||
|
|
||||||
if(!manga.ContainsKey("id"))
|
|
||||||
return null;
|
|
||||||
string publicationId = manga["id"]!.GetValue<string>();
|
|
||||||
|
|
||||||
if(!attributes.ContainsKey("title"))
|
|
||||||
return null;
|
|
||||||
string title = attributes["title"]!.AsObject().ContainsKey("en") && attributes["title"]!["en"] is not null
|
|
||||||
? attributes["title"]!["en"]!.GetValue<string>()
|
|
||||||
: attributes["title"]![((IDictionary<string, JsonNode?>)attributes["title"]!.AsObject()).Keys.First()]!.GetValue<string>();
|
|
||||||
|
|
||||||
if(!attributes.ContainsKey("description"))
|
|
||||||
return null;
|
|
||||||
string? description = attributes["description"]!.AsObject().ContainsKey("en") && attributes["description"]!["en"] is not null
|
|
||||||
? attributes["description"]!["en"]!.GetValue<string?>()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if(!attributes.ContainsKey("altTitles"))
|
|
||||||
return null;
|
|
||||||
JsonArray altTitlesObject = attributes["altTitles"]!.AsArray();
|
|
||||||
Dictionary<string, string> altTitlesDict = new();
|
Dictionary<string, string> altTitlesDict = new();
|
||||||
foreach (JsonNode? altTitleNode in altTitlesObject)
|
if (attributes.TryGetPropertyValue("altTitles", out JsonNode? altTitlesNode))
|
||||||
{
|
{
|
||||||
JsonObject altTitleObject = (JsonObject)altTitleNode!;
|
foreach (JsonNode? altTitleNode in altTitlesNode!.AsArray())
|
||||||
string key = ((IDictionary<string, JsonNode?>)altTitleObject).Keys.ToArray()[0];
|
|
||||||
altTitlesDict.TryAdd(key, altTitleObject[key]!.GetValue<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!attributes.ContainsKey("tags"))
|
|
||||||
return null;
|
|
||||||
JsonArray tagsObject = attributes["tags"]!.AsArray();
|
|
||||||
HashSet<string> tags = new();
|
|
||||||
foreach (JsonNode? tagNode in tagsObject)
|
|
||||||
{
|
|
||||||
JsonObject tagObject = (JsonObject)tagNode!;
|
|
||||||
if(tagObject["attributes"]!["name"]!.AsObject().ContainsKey("en"))
|
|
||||||
tags.Add(tagObject["attributes"]!["name"]!["en"]!.GetValue<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
string? posterId = null;
|
|
||||||
HashSet<string> authorIds = new();
|
|
||||||
if (manga.ContainsKey("relationships") && manga["relationships"] is not null)
|
|
||||||
{
|
|
||||||
JsonArray relationships = manga["relationships"]!.AsArray();
|
|
||||||
posterId = relationships.FirstOrDefault(relationship => relationship!["type"]!.GetValue<string>() == "cover_art")!["id"]!.GetValue<string>();
|
|
||||||
foreach (JsonNode? node in relationships.Where(relationship =>
|
|
||||||
relationship!["type"]!.GetValue<string>() == "author"))
|
|
||||||
authorIds.Add(node!["id"]!.GetValue<string>());
|
|
||||||
}
|
|
||||||
string? coverUrl = GetCoverUrl(publicationId, posterId);
|
|
||||||
string? coverCacheName = null;
|
|
||||||
if (coverUrl is not null)
|
|
||||||
coverCacheName = SaveCoverImageToCache(coverUrl, RequestType.MangaCover);
|
|
||||||
|
|
||||||
List<string> authors = GetAuthors(authorIds);
|
|
||||||
|
|
||||||
Dictionary<string, string> linksDict = new();
|
|
||||||
if (attributes.ContainsKey("links") && attributes["links"] is not null)
|
|
||||||
{
|
|
||||||
JsonObject linksObject = attributes["links"]!.AsObject();
|
|
||||||
foreach (string key in ((IDictionary<string, JsonNode?>)linksObject).Keys)
|
|
||||||
{
|
{
|
||||||
linksDict.Add(key, linksObject[key]!.GetValue<string>());
|
JsonObject altTitleNodeObject = altTitleNode!.AsObject();
|
||||||
|
altTitlesDict.TryAdd(altTitleNodeObject.First().Key, altTitleNodeObject.First().Value!.GetValue<string>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int? year = attributes.ContainsKey("year") && attributes["year"] is not null
|
if (!attributes.TryGetPropertyValue("description", out JsonNode? descriptionNode))
|
||||||
? attributes["year"]!.GetValue<int?>()
|
return null;
|
||||||
: null;
|
string description = descriptionNode!.AsObject().ContainsKey("en") switch
|
||||||
|
{
|
||||||
|
true => descriptionNode.AsObject()["en"]!.GetValue<string>(),
|
||||||
|
false => descriptionNode.AsObject().First().Value!.GetValue<string>()
|
||||||
|
};
|
||||||
|
|
||||||
|
Dictionary<string, string> linksDict = new();
|
||||||
|
if (attributes.TryGetPropertyValue("links", out JsonNode? linksNode))
|
||||||
|
foreach (KeyValuePair<string, JsonNode> linkKv in linksNode!.AsObject())
|
||||||
|
linksDict.TryAdd(linkKv.Key, linkKv.Value.GetValue<string>());
|
||||||
|
|
||||||
string? originalLanguage =
|
string? originalLanguage =
|
||||||
attributes.ContainsKey("originalLanguage") && attributes["originalLanguage"] is not null
|
attributes.TryGetPropertyValue("originalLanguage", out JsonNode? originalLanguageNode) switch
|
||||||
? attributes["originalLanguage"]!.GetValue<string?>()
|
{
|
||||||
: null;
|
true => originalLanguageNode!.GetValue<string>(),
|
||||||
|
false => null
|
||||||
if(!attributes.ContainsKey("status"))
|
};
|
||||||
return null;
|
|
||||||
string status = attributes["status"]!.GetValue<string>();
|
Manga.ReleaseStatusByte status = Manga.ReleaseStatusByte.Unreleased;
|
||||||
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
|
if (attributes.TryGetPropertyValue("status", out JsonNode? statusNode))
|
||||||
switch (status.ToLower())
|
|
||||||
{
|
{
|
||||||
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
|
status = statusNode!.GetValue<string>().ToLower() switch
|
||||||
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
|
{
|
||||||
case "hiatus": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break;
|
"ongoing" => Manga.ReleaseStatusByte.Continuing,
|
||||||
case "cancelled": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
|
"completed" => Manga.ReleaseStatusByte.Completed,
|
||||||
|
"hiatus" => Manga.ReleaseStatusByte.OnHiatus,
|
||||||
|
"cancelled" => Manga.ReleaseStatusByte.Cancelled,
|
||||||
|
_ => Manga.ReleaseStatusByte.Unreleased
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int? year = attributes.TryGetPropertyValue("year", out JsonNode? yearNode) switch
|
||||||
|
{
|
||||||
|
true => yearNode!.GetValue<int>(),
|
||||||
|
false => null
|
||||||
|
};
|
||||||
|
|
||||||
|
HashSet<string> tags = new(128);
|
||||||
|
if (attributes.TryGetPropertyValue("tags", out JsonNode? tagsNode))
|
||||||
|
foreach (JsonNode? tagNode in tagsNode!.AsArray())
|
||||||
|
tags.Add(tagNode!["attributes"]!["name"]!["en"]!.GetValue<string>());
|
||||||
|
|
||||||
|
|
||||||
|
if (!manga.TryGetPropertyValue("relationships", out JsonNode? relationshipsNode))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
JsonNode? coverNode = relationshipsNode!.AsArray()
|
||||||
|
.FirstOrDefault(rel => rel!["type"]!.GetValue<string>().Equals("cover_art"));
|
||||||
|
if (coverNode is null)
|
||||||
|
return null;
|
||||||
|
string fileName = coverNode["attributes"]!["fileName"]!.GetValue<string>();
|
||||||
|
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
|
||||||
|
string coverCacheName = SaveCoverImageToCache(coverUrl, RequestType.MangaCover);
|
||||||
|
|
||||||
|
List<string> authors = new();
|
||||||
|
JsonNode?[] authorNodes = relationshipsNode.AsArray()
|
||||||
|
.Where(rel => rel!["type"]!.GetValue<string>().Equals("author") || rel!["type"]!.GetValue<string>().Equals("artist")).ToArray();
|
||||||
|
foreach (JsonNode? authorNode in authorNodes)
|
||||||
|
{
|
||||||
|
string authorName = authorNode!["attributes"]!["name"]!.GetValue<string>();
|
||||||
|
if(!authors.Contains(authorName))
|
||||||
|
authors.Add(authorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Manga pub = new(
|
Manga pub = new(
|
||||||
@ -179,9 +183,9 @@ public class MangaDex : MangaConnector
|
|||||||
linksDict,
|
linksDict,
|
||||||
year,
|
year,
|
||||||
originalLanguage,
|
originalLanguage,
|
||||||
status,
|
Enum.GetName(status) ?? "",
|
||||||
publicationId,
|
publicationId,
|
||||||
releaseStatus
|
status
|
||||||
);
|
);
|
||||||
cachedPublications.Add(pub);
|
cachedPublications.Add(pub);
|
||||||
return pub;
|
return pub;
|
||||||
@ -200,7 +204,7 @@ public class MangaDex : MangaConnector
|
|||||||
//Request next "Page"
|
//Request next "Page"
|
||||||
RequestResult requestResult =
|
RequestResult requestResult =
|
||||||
downloadClient.MakeRequest(
|
downloadClient.MakeRequest(
|
||||||
$"https://api.mangadex.org/manga/{manga.publicationId}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}", RequestType.MangaDexFeed);
|
$"https://api.mangadex.org/manga/{manga.publicationId}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}&contentRating%5B%5D=safe&contentRating%5B%5D=suggestive&contentRating%5B%5D=erotica&contentRating%5B%5D=pornographic", RequestType.MangaDexFeed);
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
||||||
break;
|
break;
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
||||||
@ -216,6 +220,7 @@ public class MangaDex : MangaConnector
|
|||||||
{
|
{
|
||||||
JsonObject chapter = (JsonObject)jsonNode!;
|
JsonObject chapter = (JsonObject)jsonNode!;
|
||||||
JsonObject attributes = chapter["attributes"]!.AsObject();
|
JsonObject attributes = chapter["attributes"]!.AsObject();
|
||||||
|
|
||||||
string chapterId = chapter["id"]!.GetValue<string>();
|
string chapterId = chapter["id"]!.GetValue<string>();
|
||||||
|
|
||||||
string? title = attributes.ContainsKey("title") && attributes["title"] is not null
|
string? title = attributes.ContainsKey("title") && attributes["title"] is not null
|
||||||
@ -230,6 +235,14 @@ public class MangaDex : MangaConnector
|
|||||||
? attributes["chapter"]!.GetValue<string>()
|
? attributes["chapter"]!.GetValue<string>()
|
||||||
: "null";
|
: "null";
|
||||||
|
|
||||||
|
|
||||||
|
if (attributes.ContainsKey("pages") && attributes["pages"] is not null &&
|
||||||
|
attributes["pages"]!.GetValue<int>() < 1)
|
||||||
|
{
|
||||||
|
Log($"Skipping {chapterId} Vol.{volume} Ch.{chapterNum} {title} because it has no pages or is externally linked.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(chapterNum is not "null")
|
if(chapterNum is not "null")
|
||||||
chapters.Add(new Chapter(manga, title, volume, chapterNum, chapterId));
|
chapters.Add(new Chapter(manga, title, volume, chapterNum, chapterId));
|
||||||
}
|
}
|
||||||
@ -252,7 +265,7 @@ public class MangaDex : MangaConnector
|
|||||||
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
|
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
|
||||||
//Request URLs for Chapter-Images
|
//Request URLs for Chapter-Images
|
||||||
RequestResult requestResult =
|
RequestResult requestResult =
|
||||||
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", RequestType.MangaDexImage);
|
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false", RequestType.MangaDexImage);
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
||||||
{
|
{
|
||||||
progressToken?.Cancel();
|
progressToken?.Cancel();
|
||||||
@ -279,50 +292,4 @@ public class MangaDex : MangaConnector
|
|||||||
//Download Chapter-Images
|
//Download Chapter-Images
|
||||||
return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken);
|
return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetCoverUrl(string publicationId, string? posterId)
|
|
||||||
{
|
|
||||||
Log($"Getting CoverUrl for Publication {publicationId}");
|
|
||||||
if (posterId is null)
|
|
||||||
{
|
|
||||||
Log("No cover.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Request information where to download Cover
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest($"https://api.mangadex.org/cover/{posterId}", RequestType.MangaCover);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return null;
|
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
|
||||||
if (result is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
string fileName = result["data"]!["attributes"]!["fileName"]!.GetValue<string>();
|
|
||||||
|
|
||||||
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
|
|
||||||
Log($"Cover-Url {publicationId} -> {coverUrl}");
|
|
||||||
return coverUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> GetAuthors(IEnumerable<string> authorIds)
|
|
||||||
{
|
|
||||||
Log("Retrieving authors.");
|
|
||||||
List<string> ret = new();
|
|
||||||
foreach (string authorId in authorIds)
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest($"https://api.mangadex.org/author/{authorId}", RequestType.MangaDexAuthor);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return ret;
|
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
|
||||||
if (result is null)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
string authorName = result["data"]!["attributes"]!["name"]!.GetValue<string>();
|
|
||||||
ret.Add(authorName);
|
|
||||||
Log($"Got author {authorId} -> {authorName}");
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -206,6 +206,9 @@ public class Server : GlobalBase
|
|||||||
case "Settings/customRequestLimit":
|
case "Settings/customRequestLimit":
|
||||||
SendResponse(HttpStatusCode.OK, response, settings.requestLimits);
|
SendResponse(HttpStatusCode.OK, response, settings.requestLimits);
|
||||||
break;
|
break;
|
||||||
|
case "Settings/AprilFoolsMode":
|
||||||
|
SendResponse(HttpStatusCode.OK, response, settings.aprilFoolsMode);
|
||||||
|
break;
|
||||||
case "NotificationConnectors":
|
case "NotificationConnectors":
|
||||||
SendResponse(HttpStatusCode.OK, response, notificationConnectors);
|
SendResponse(HttpStatusCode.OK, response, notificationConnectors);
|
||||||
break;
|
break;
|
||||||
@ -397,7 +400,7 @@ public class Server : GlobalBase
|
|||||||
case "Settings/UpdateDownloadLocation":
|
case "Settings/UpdateDownloadLocation":
|
||||||
if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) ||
|
if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) ||
|
||||||
!requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) ||
|
!requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) ||
|
||||||
!Boolean.TryParse(moveFilesStr, out bool moveFiles))
|
!bool.TryParse(moveFilesStr, out bool moveFiles))
|
||||||
{
|
{
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
SendResponse(HttpStatusCode.BadRequest, response);
|
||||||
break;
|
break;
|
||||||
@ -405,6 +408,16 @@ public class Server : GlobalBase
|
|||||||
settings.UpdateDownloadLocation(downloadLocation, moveFiles);
|
settings.UpdateDownloadLocation(downloadLocation, moveFiles);
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
SendResponse(HttpStatusCode.Accepted, response);
|
||||||
break;
|
break;
|
||||||
|
case "Settings/AprilFoolsMode":
|
||||||
|
if (!requestVariables.TryGetValue("enabled", out string? aprilFoolsModeEnabledStr) ||
|
||||||
|
bool.TryParse(aprilFoolsModeEnabledStr, out bool aprilFoolsModeEnabled))
|
||||||
|
{
|
||||||
|
SendResponse(HttpStatusCode.BadRequest, response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
settings.UpdateAprilFoolsMode(aprilFoolsModeEnabled);
|
||||||
|
SendResponse(HttpStatusCode.Accepted, response);
|
||||||
|
break;
|
||||||
/*case "Settings/UpdateWorkingDirectory":
|
/*case "Settings/UpdateWorkingDirectory":
|
||||||
if (!requestVariables.TryGetValue("workingDirectory", out string? workingDirectory))
|
if (!requestVariables.TryGetValue("workingDirectory", out string? workingDirectory))
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,8 @@ public partial class Tranga : GlobalBase
|
|||||||
new Bato(this),
|
new Bato(this),
|
||||||
new MangaLife(this)
|
new MangaLife(this)
|
||||||
};
|
};
|
||||||
|
foreach(DirectoryInfo dir in new DirectoryInfo(Path.GetTempPath()).GetDirectories("trangatemp"))//Cleanup old temp folders
|
||||||
|
dir.Delete();
|
||||||
jobBoss = new(this, this._connectors);
|
jobBoss = new(this, this._connectors);
|
||||||
StartJobBoss();
|
StartJobBoss();
|
||||||
this._server = new Server(this);
|
this._server = new Server(this);
|
||||||
@ -71,10 +73,23 @@ public partial class Tranga : GlobalBase
|
|||||||
{
|
{
|
||||||
while (keepRunning)
|
while (keepRunning)
|
||||||
{
|
{
|
||||||
jobBoss.CheckJobs();
|
if(!settings.aprilFoolsMode || !IsAprilFirst())
|
||||||
|
jobBoss.CheckJobs();
|
||||||
|
else
|
||||||
|
Log("April Fools Mode in Effect");
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
t.Start();
|
t.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsAprilFirst()
|
||||||
|
{
|
||||||
|
//UTC 01 Apr +-12hrs
|
||||||
|
DateTime start = new DateTime(DateTime.Now.Year, 03, 31, 12, 0, 0, DateTimeKind.Utc);
|
||||||
|
DateTime end = new DateTime(DateTime.Now.Year, 04, 02, 12, 0, 0, DateTimeKind.Utc);
|
||||||
|
if (DateTime.UtcNow > start && DateTime.UtcNow < end)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,16 +17,16 @@ public partial class Tranga : GlobalBase
|
|||||||
|
|
||||||
string[]? consoleLogger = GetArg(args, ArgEnum.ConsoleLogger);
|
string[]? consoleLogger = GetArg(args, ArgEnum.ConsoleLogger);
|
||||||
string[]? fileLogger = GetArg(args, ArgEnum.FileLogger);
|
string[]? fileLogger = GetArg(args, ArgEnum.FileLogger);
|
||||||
string? filePath = GetArg(args, ArgEnum.FileLoggerPath)?[0];
|
string? directoryPath = GetArg(args, ArgEnum.FileLoggerPath)?[0];
|
||||||
if (filePath is not null && !Directory.Exists(new FileInfo(filePath).DirectoryName))
|
if (directoryPath is not null && !Directory.Exists(directoryPath))
|
||||||
Directory.CreateDirectory(new FileInfo(filePath).DirectoryName!);
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
|
||||||
List<Logger.LoggerType> enabledLoggers = new();
|
List<Logger.LoggerType> enabledLoggers = new();
|
||||||
if(consoleLogger is not null)
|
if(consoleLogger is not null)
|
||||||
enabledLoggers.Add(Logger.LoggerType.ConsoleLogger);
|
enabledLoggers.Add(Logger.LoggerType.ConsoleLogger);
|
||||||
if (fileLogger is not null)
|
if (fileLogger is not null)
|
||||||
enabledLoggers.Add(Logger.LoggerType.FileLogger);
|
enabledLoggers.Add(Logger.LoggerType.FileLogger);
|
||||||
Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, filePath);
|
Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, directoryPath);
|
||||||
|
|
||||||
TrangaSettings? settings = null;
|
TrangaSettings? settings = null;
|
||||||
string[]? downloadLocationPath = GetArg(args, ArgEnum.DownloadLocation);
|
string[]? downloadLocationPath = GetArg(args, ArgEnum.DownloadLocation);
|
||||||
@ -109,7 +109,7 @@ public partial class Tranga : GlobalBase
|
|||||||
{ ArgEnum.WorkingDirectory, new(new []{"-w", "--workingDirectory"}, 1, "Directory in which application-data is saved") },
|
{ ArgEnum.WorkingDirectory, new(new []{"-w", "--workingDirectory"}, 1, "Directory in which application-data is saved") },
|
||||||
{ ArgEnum.ConsoleLogger, new(new []{"-c", "--consoleLogger"}, 0, "Enables the consoleLogger") },
|
{ ArgEnum.ConsoleLogger, new(new []{"-c", "--consoleLogger"}, 0, "Enables the consoleLogger") },
|
||||||
{ ArgEnum.FileLogger, new(new []{"-f", "--fileLogger"}, 0, "Enables the fileLogger") },
|
{ ArgEnum.FileLogger, new(new []{"-f", "--fileLogger"}, 0, "Enables the fileLogger") },
|
||||||
{ ArgEnum.FileLoggerPath, new (new []{"-l", "--fPath"}, 1, "LogFilePath" ) },
|
{ ArgEnum.FileLoggerPath, new (new []{"-l", "--fPath"}, 1, "Log Folder Path" ) },
|
||||||
{ ArgEnum.Help, new(new []{"-h", "--help"}, 0, "Print this") }
|
{ ArgEnum.Help, new(new []{"-h", "--help"}, 0, "Print this") }
|
||||||
//{ ArgEnum., new(new []{""}, 1, "") }
|
//{ ArgEnum., new(new []{""}, 1, "") }
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Tranga.LibraryConnectors;
|
using Tranga.LibraryConnectors;
|
||||||
using Tranga.MangaConnectors;
|
using Tranga.MangaConnectors;
|
||||||
@ -13,14 +12,15 @@ public class TrangaSettings
|
|||||||
public string downloadLocation { get; private set; }
|
public string downloadLocation { get; private set; }
|
||||||
public string workingDirectory { get; private set; }
|
public string workingDirectory { get; private set; }
|
||||||
public int apiPortNumber { get; init; }
|
public int apiPortNumber { get; init; }
|
||||||
public string userAgent { get; set; } = DefaultUserAgent;
|
public string userAgent { get; private set; } = DefaultUserAgent;
|
||||||
[JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json");
|
[JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json");
|
||||||
[JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json");
|
[JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json");
|
||||||
[JsonIgnore] public string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json");
|
[JsonIgnore] public string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json");
|
||||||
[JsonIgnore] public string jobsFolderPath => Path.Join(workingDirectory, "jobs");
|
[JsonIgnore] public string jobsFolderPath => Path.Join(workingDirectory, "jobs");
|
||||||
[JsonIgnore] public string coverImageCache => Path.Join(workingDirectory, "imageCache");
|
[JsonIgnore] public string coverImageCache => Path.Join(workingDirectory, "imageCache");
|
||||||
[JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")}) / 1.0";
|
[JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")}) / 1.0";
|
||||||
public ushort? version { get; set; } = 1;
|
public ushort? version { get; } = 1;
|
||||||
|
public bool aprilFoolsMode { get; private set; } = true;
|
||||||
[JsonIgnore]internal static readonly Dictionary<RequestType, int> DefaultRequestLimits = new ()
|
[JsonIgnore]internal static readonly Dictionary<RequestType, int> DefaultRequestLimits = new ()
|
||||||
{
|
{
|
||||||
{RequestType.MangaInfo, 250},
|
{RequestType.MangaInfo, 250},
|
||||||
@ -102,6 +102,12 @@ public class TrangaSettings
|
|||||||
})!;
|
})!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateAprilFoolsMode(bool enabled)
|
||||||
|
{
|
||||||
|
this.aprilFoolsMode = enabled;
|
||||||
|
ExportSettings();
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateDownloadLocation(string newPath, bool moveFiles = true)
|
public void UpdateDownloadLocation(string newPath, bool moveFiles = true)
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
Loading…
Reference in New Issue
Block a user