mirror of
https://github.com/C9Glax/tranga.git
synced 2025-06-14 15:27:53 +02:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
553a77320d | |||
68e877298a | |||
58fef5c307 | |||
c8654dbb85 | |||
133b3146b5 | |||
312672a05c | |||
d659a26987 | |||
8c6c95d07d | |||
c4949936cd | |||
3ca96cea78 |
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Has a interactive CLI-Version as well as API-Version (no documentation yet).
|
||||||
|
Only one Connector so far: MangaDex.org (Timeout between requests 750ms)
|
||||||
|
Can automatically download new Chapters every given time-period.
|
@ -37,7 +37,7 @@ public static class Tranga_Cli
|
|||||||
switch (menu)
|
switch (menu)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
PrintTasks(taskManager);
|
PrintTasks(taskManager.GetAllTasks());
|
||||||
Console.WriteLine("Press any key.");
|
Console.WriteLine("Press any key.");
|
||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
menu = 0;
|
menu = 0;
|
||||||
@ -61,8 +61,14 @@ public static class Tranga_Cli
|
|||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
menu = 0;
|
menu = 0;
|
||||||
break;
|
break;
|
||||||
|
case 4:
|
||||||
|
ExecuteTask(taskManager);
|
||||||
|
Console.WriteLine("Press any key.");
|
||||||
|
Console.ReadKey();
|
||||||
|
menu = 0;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
selection = Menu(folderPath);
|
selection = Menu(taskManager, folderPath);
|
||||||
switch (selection)
|
switch (selection)
|
||||||
{
|
{
|
||||||
case ConsoleKey.L:
|
case ConsoleKey.L:
|
||||||
@ -74,6 +80,9 @@ public static class Tranga_Cli
|
|||||||
case ConsoleKey.D:
|
case ConsoleKey.D:
|
||||||
menu = 3;
|
menu = 3;
|
||||||
break;
|
break;
|
||||||
|
case ConsoleKey.E:
|
||||||
|
menu = 4;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
menu = 0;
|
menu = 0;
|
||||||
break;
|
break;
|
||||||
@ -81,40 +90,65 @@ public static class Tranga_Cli
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
taskManager.Shutdown();
|
|
||||||
|
if (taskManager.GetAllTasks().Any(task => task.isBeingExecuted))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Force quit (Even with running tasks?) y/N");
|
||||||
|
selection = Console.ReadKey().Key;
|
||||||
|
taskManager.Shutdown(selection == ConsoleKey.Y);
|
||||||
|
}else
|
||||||
|
taskManager.Shutdown(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConsoleKey Menu(string folderPath)
|
private static ConsoleKey Menu(TaskManager taskManager, string folderPath)
|
||||||
{
|
{
|
||||||
|
int taskCount = taskManager.GetAllTasks().Length;
|
||||||
|
int taskRunningCount = taskManager.GetAllTasks().Count(task => task.isBeingExecuted);
|
||||||
Console.Clear();
|
Console.Clear();
|
||||||
Console.WriteLine($"Download Folder: {folderPath}");
|
Console.WriteLine($"Download Folder: {folderPath} Tasks (Running/Total): {taskRunningCount}/{taskCount}");
|
||||||
Console.WriteLine("Select Option:");
|
Console.WriteLine("Select Option:");
|
||||||
Console.WriteLine("L: List tasks");
|
Console.WriteLine("L: List tasks");
|
||||||
Console.WriteLine("C: Create Task");
|
Console.WriteLine("C: Create Task");
|
||||||
Console.WriteLine("D: Delete Task");
|
Console.WriteLine("D: Delete Task");
|
||||||
|
Console.WriteLine("E: Execute Task now");
|
||||||
Console.WriteLine("Q: Exit with saving");
|
Console.WriteLine("Q: Exit with saving");
|
||||||
ConsoleKey selection = Console.ReadKey().Key;
|
ConsoleKey selection = Console.ReadKey().Key;
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int PrintTasks(TaskManager taskManager)
|
private static void PrintTasks(TrangaTask[] tasks)
|
||||||
{
|
{
|
||||||
|
int taskCount = tasks.Length;
|
||||||
|
int taskRunningCount = tasks.Count(task => task.isBeingExecuted);
|
||||||
Console.Clear();
|
Console.Clear();
|
||||||
TrangaTask[] tasks = taskManager.GetAllTasks();
|
|
||||||
int tIndex = 0;
|
int tIndex = 0;
|
||||||
Console.WriteLine("Tasks:");
|
Console.WriteLine($"Tasks (Running/Total): {taskRunningCount}/{taskCount}");
|
||||||
foreach(TrangaTask trangaTask in tasks)
|
foreach(TrangaTask trangaTask in tasks)
|
||||||
Console.WriteLine($"{tIndex++}: {trangaTask.task} - {trangaTask.reoccurrence} - {trangaTask.publication?.sortName} - {trangaTask.connectorName}");
|
Console.WriteLine($"{tIndex++}: {trangaTask.task} - {trangaTask.reoccurrence} - {trangaTask.publication?.sortName} - {trangaTask.connectorName} - {trangaTask.lastExecuted} - {(trangaTask.isBeingExecuted ? "Running" : "Waiting")}");
|
||||||
return tasks.Length;
|
}
|
||||||
|
|
||||||
|
private static void ExecuteTask(TaskManager taskManager)
|
||||||
|
{
|
||||||
|
TrangaTask[] tasks = taskManager.GetAllTasks();
|
||||||
|
PrintTasks(tasks);
|
||||||
|
|
||||||
|
Console.WriteLine($"Select Task (0-{tasks.Length - 1}):");
|
||||||
|
|
||||||
|
string? selectedTask = Console.ReadLine();
|
||||||
|
while(selectedTask is null || selectedTask.Length < 1)
|
||||||
|
selectedTask = Console.ReadLine();
|
||||||
|
int selectedTaskIndex = Convert.ToInt32(selectedTask);
|
||||||
|
|
||||||
|
taskManager.ExecuteTaskNow(tasks[selectedTaskIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveTask(TaskManager taskManager)
|
private static void RemoveTask(TaskManager taskManager)
|
||||||
{
|
{
|
||||||
int length = PrintTasks(taskManager);
|
|
||||||
|
|
||||||
TrangaTask[] tasks = taskManager.GetAllTasks();
|
TrangaTask[] tasks = taskManager.GetAllTasks();
|
||||||
Console.WriteLine($"Select Task (0-{length - 1}):");
|
PrintTasks(tasks);
|
||||||
|
|
||||||
|
Console.WriteLine($"Select Task (0-{tasks.Length - 1}):");
|
||||||
|
|
||||||
string? selectedTask = Console.ReadLine();
|
string? selectedTask = Console.ReadLine();
|
||||||
while(selectedTask is null || selectedTask.Length < 1)
|
while(selectedTask is null || selectedTask.Length < 1)
|
||||||
|
@ -20,6 +20,15 @@ public abstract class Connector
|
|||||||
|
|
||||||
protected void DownloadChapter(string[] imageUrls, string saveArchiveFilePath)
|
protected void DownloadChapter(string[] imageUrls, string saveArchiveFilePath)
|
||||||
{
|
{
|
||||||
|
string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar);
|
||||||
|
string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray());
|
||||||
|
if (!Directory.Exists(directoryPath))
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
|
||||||
|
string fullPath = $"{saveArchiveFilePath}.cbz";
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
return;
|
||||||
|
|
||||||
string tempFolder = Path.GetTempFileName();
|
string tempFolder = Path.GetTempFileName();
|
||||||
File.Delete(tempFolder);
|
File.Delete(tempFolder);
|
||||||
Directory.CreateDirectory(tempFolder);
|
Directory.CreateDirectory(tempFolder);
|
||||||
@ -31,14 +40,7 @@ public abstract class Connector
|
|||||||
string extension = split[split.Length - 1];
|
string extension = split[split.Length - 1];
|
||||||
DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"));
|
DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar);
|
|
||||||
string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray());
|
|
||||||
if (!Directory.Exists(directoryPath))
|
|
||||||
Directory.CreateDirectory(directoryPath);
|
|
||||||
|
|
||||||
string fullPath = $"{saveArchiveFilePath}.cbz";
|
|
||||||
File.Delete(fullPath);
|
|
||||||
ZipFile.CreateFromDirectory(tempFolder, fullPath);
|
ZipFile.CreateFromDirectory(tempFolder, fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
@ -21,7 +22,11 @@ public class MangaDex : Connector
|
|||||||
HashSet<Publication> publications = new();
|
HashSet<Publication> publications = new();
|
||||||
while (offset < total)
|
while (offset < total)
|
||||||
{
|
{
|
||||||
DownloadClient.RequestResult requestResult = _downloadClient.MakeRequest($"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}");
|
DownloadClient.RequestResult requestResult =
|
||||||
|
_downloadClient.MakeRequest(
|
||||||
|
$"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}");
|
||||||
|
if (requestResult.statusCode != HttpStatusCode.OK)
|
||||||
|
break;
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
||||||
offset += limit;
|
offset += limit;
|
||||||
if (result is null)
|
if (result is null)
|
||||||
@ -121,7 +126,10 @@ public class MangaDex : Connector
|
|||||||
while (offset < total)
|
while (offset < total)
|
||||||
{
|
{
|
||||||
DownloadClient.RequestResult requestResult =
|
DownloadClient.RequestResult requestResult =
|
||||||
_downloadClient.MakeRequest($"https://api.mangadex.org/manga/{id}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}");
|
_downloadClient.MakeRequest(
|
||||||
|
$"https://api.mangadex.org/manga/{id}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}");
|
||||||
|
if (requestResult.statusCode != HttpStatusCode.OK)
|
||||||
|
break;
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
||||||
|
|
||||||
offset += limit;
|
offset += limit;
|
||||||
@ -163,6 +171,8 @@ public class MangaDex : Connector
|
|||||||
{
|
{
|
||||||
DownloadClient.RequestResult requestResult =
|
DownloadClient.RequestResult requestResult =
|
||||||
_downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'");
|
_downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'");
|
||||||
|
if (requestResult.statusCode != HttpStatusCode.OK)
|
||||||
|
return;
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
||||||
if (result is null)
|
if (result is null)
|
||||||
return;
|
return;
|
||||||
@ -188,12 +198,16 @@ public class MangaDex : Connector
|
|||||||
public override void DownloadCover(Publication publication)
|
public override void DownloadCover(Publication publication)
|
||||||
{
|
{
|
||||||
string publicationPath = Path.Join(downloadLocation, publication.folderName);
|
string publicationPath = Path.Join(downloadLocation, publication.folderName);
|
||||||
DirectoryInfo dirInfo = new DirectoryInfo(publicationPath);
|
Directory.CreateDirectory(publicationPath);
|
||||||
|
DirectoryInfo dirInfo = new (publicationPath);
|
||||||
foreach(FileInfo fileInfo in dirInfo.EnumerateFiles())
|
foreach(FileInfo fileInfo in dirInfo.EnumerateFiles())
|
||||||
if (fileInfo.Name.Contains("cover."))
|
if (fileInfo.Name.Contains("cover."))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DownloadClient.RequestResult requestResult = _downloadClient.MakeRequest($"https://api.mangadex.org/cover/{publication.posterUrl}");
|
DownloadClient.RequestResult requestResult =
|
||||||
|
_downloadClient.MakeRequest($"https://api.mangadex.org/cover/{publication.posterUrl}");
|
||||||
|
if (requestResult.statusCode != HttpStatusCode.OK)
|
||||||
|
return;
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
||||||
if (result is null)
|
if (result is null)
|
||||||
return;
|
return;
|
||||||
|
@ -7,6 +7,10 @@ public static class TaskExecutor
|
|||||||
Connector? connector = connectors.FirstOrDefault(c => c.name == trangaTask.connectorName);
|
Connector? connector = connectors.FirstOrDefault(c => c.name == trangaTask.connectorName);
|
||||||
if (connector is null)
|
if (connector is null)
|
||||||
throw new ArgumentException($"Connector {trangaTask.connectorName} is not a known connector.");
|
throw new ArgumentException($"Connector {trangaTask.connectorName} is not a known connector.");
|
||||||
|
|
||||||
|
if (trangaTask.isBeingExecuted)
|
||||||
|
return;
|
||||||
|
trangaTask.isBeingExecuted = true;
|
||||||
trangaTask.lastExecuted = DateTime.Now;
|
trangaTask.lastExecuted = DateTime.Now;
|
||||||
|
|
||||||
switch (trangaTask.task)
|
switch (trangaTask.task)
|
||||||
@ -21,6 +25,8 @@ public static class TaskExecutor
|
|||||||
UpdatePublications(connector, chapterCollection);
|
UpdatePublications(connector, chapterCollection);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trangaTask.isBeingExecuted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdatePublications(Connector connector, Dictionary<Publication, List<Chapter>> chapterCollection)
|
private static void UpdatePublications(Connector connector, Dictionary<Publication, List<Chapter>> chapterCollection)
|
||||||
|
@ -34,6 +34,15 @@ public class TaskManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ExecuteTaskNow(TrangaTask task)
|
||||||
|
{
|
||||||
|
Task t = new Task(() =>
|
||||||
|
{
|
||||||
|
TaskExecutor.Execute(this.connectors, task, this._chapterCollection);
|
||||||
|
});
|
||||||
|
t.Start();
|
||||||
|
}
|
||||||
|
|
||||||
public void AddTask(TrangaTask.Task task, string connectorName, Publication? publication, TimeSpan reoccurrence,
|
public void AddTask(TrangaTask.Task task, string connectorName, Publication? publication, TimeSpan reoccurrence,
|
||||||
string language = "")
|
string language = "")
|
||||||
{
|
{
|
||||||
@ -44,6 +53,8 @@ public class TaskManager
|
|||||||
if (!_allTasks.Any(trangaTask => trangaTask.task != task && trangaTask.connectorName != connector.name &&
|
if (!_allTasks.Any(trangaTask => trangaTask.task != task && trangaTask.connectorName != connector.name &&
|
||||||
trangaTask.publication?.downloadUrl != publication?.downloadUrl))
|
trangaTask.publication?.downloadUrl != publication?.downloadUrl))
|
||||||
{
|
{
|
||||||
|
if(task != TrangaTask.Task.UpdatePublications)
|
||||||
|
_chapterCollection.Add((Publication)publication!, new List<Chapter>());
|
||||||
_allTasks.Add(new TrangaTask(connector.name, task, publication, reoccurrence, language));
|
_allTasks.Add(new TrangaTask(connector.name, task, publication, reoccurrence, language));
|
||||||
ExportTasks(Directory.GetCurrentDirectory());
|
ExportTasks(Directory.GetCurrentDirectory());
|
||||||
}
|
}
|
||||||
@ -74,13 +85,21 @@ public class TaskManager
|
|||||||
return this._chapterCollection.Keys.ToArray();
|
return this._chapterCollection.Keys.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Shutdown()
|
public void Shutdown(bool force = false)
|
||||||
{
|
{
|
||||||
_continueRunning = false;
|
_continueRunning = false;
|
||||||
ExportTasks(Directory.GetCurrentDirectory());
|
ExportTasks(Directory.GetCurrentDirectory());
|
||||||
|
|
||||||
|
if(force)
|
||||||
|
Environment.Exit(_allTasks.Count(task => task.isBeingExecuted));
|
||||||
|
|
||||||
|
//Wait for tasks to finish
|
||||||
|
while(_allTasks.Any(task => task.isBeingExecuted))
|
||||||
|
Thread.Sleep(10);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashSet<TrangaTask> ImportTasks(string importFolderPath)
|
private HashSet<TrangaTask> ImportTasks(string importFolderPath)
|
||||||
{
|
{
|
||||||
string filePath = Path.Join(importFolderPath, "tasks.json");
|
string filePath = Path.Join(importFolderPath, "tasks.json");
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
@ -96,7 +115,7 @@ public class TaskManager
|
|||||||
return importTasks.ToHashSet();
|
return importTasks.ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExportTasks(string exportFolderPath)
|
private void ExportTasks(string exportFolderPath)
|
||||||
{
|
{
|
||||||
string filePath = Path.Join(exportFolderPath, "tasks.json");
|
string filePath = Path.Join(exportFolderPath, "tasks.json");
|
||||||
string toWrite = JsonConvert.SerializeObject(_allTasks.ToArray());
|
string toWrite = JsonConvert.SerializeObject(_allTasks.ToArray());
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Tranga;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Tranga;
|
||||||
|
|
||||||
public class TrangaTask
|
public class TrangaTask
|
||||||
{
|
{
|
||||||
@ -8,6 +10,7 @@ public class TrangaTask
|
|||||||
public Task task { get; }
|
public Task task { get; }
|
||||||
public Publication? publication { get; }
|
public Publication? publication { get; }
|
||||||
public string language { get; }
|
public string language { get; }
|
||||||
|
[JsonIgnore]public bool isBeingExecuted { get; set; }
|
||||||
|
|
||||||
public TrangaTask(string connectorName, Task task, Publication? publication, TimeSpan reoccurrence, string language = "")
|
public TrangaTask(string connectorName, Task task, Publication? publication, TimeSpan reoccurrence, string language = "")
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user