Merge remote-tracking branch 'refs/remotes/db-2001/json-api' into Server-V2

This commit is contained in:
Glax 2024-04-19 22:06:55 +02:00
commit bd14722791
3 changed files with 487 additions and 68 deletions

4
.gitignore vendored
View File

@ -19,4 +19,6 @@ riderModule.iml
/.idea
cover.jpg
cover.png
/.vscode
/.vscode
/.vs/
Tranga/Properties/launchSettings.json

View File

@ -12,17 +12,17 @@ namespace Tranga;
public partial class Server : GlobalBase
{
private readonly HttpListener _listener = new ();
private readonly HttpListener _listener = new();
private readonly Tranga _parent;
public Server(Tranga parent) : base(parent)
{
this._parent = parent;
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
this._listener.Prefixes.Add($"http://*:{settings.apiPortNumber}/");
else
this._listener.Prefixes.Add($"http://localhost:{settings.apiPortNumber}/");
Thread listenThread = new (Listen);
Thread listenThread = new(Listen);
listenThread.Start();
Thread watchThread = new(WatchRunning);
watchThread.Start();
@ -30,7 +30,7 @@ public partial class Server : GlobalBase
private void WatchRunning()
{
while(_parent.keepRunning)
while (_parent.keepRunning)
Thread.Sleep(1000);
this._listener.Close();
}
@ -38,7 +38,7 @@ public partial class Server : GlobalBase
private void Listen()
{
this._listener.Start();
foreach(string prefix in this._listener.Prefixes)
foreach (string prefix in this._listener.Prefixes)
Log($"Listening on {prefix}");
while (this._listener.IsListening && _parent.keepRunning)
{
@ -54,7 +54,7 @@ public partial class Server : GlobalBase
}
catch (HttpListenerException)
{
}
}
}
@ -63,9 +63,9 @@ public partial class Server : GlobalBase
{
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
if(request.HttpMethod == "OPTIONS")
if (request.HttpMethod == "OPTIONS")
SendResponse(HttpStatusCode.OK, context.Response);
if(request.Url!.LocalPath.Contains("favicon"))
if (request.Url!.LocalPath.Contains("favicon"))
SendResponse(HttpStatusCode.NoContent, response);
if (Regex.IsMatch(request.Url.LocalPath, ""))
@ -85,16 +85,16 @@ public partial class Server : GlobalBase
case "DELETE":
HandleDelete(request, response);
break;
default:
default:
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
}
private Dictionary<string, string> GetRequestVariables(string query)
{
Dictionary<string, string> ret = new();
Regex queryRex = new (@"\?{1}&?([A-z0-9-=]+=[A-z0-9-=]+)+(&[A-z0-9-=]+=[A-z0-9-=]+)*");
Regex queryRex = new(@"\?{1}&?([A-z0-9-=]+=[A-z0-9-=]+)+(&[A-z0-9-=]+=[A-z0-9-=]+)*");
if (!queryRex.IsMatch(query))
return ret;
query = query.Substring(1);
@ -108,6 +108,22 @@ public partial class Server : GlobalBase
return ret;
}
private Dictionary<string, string> GetRequestBody(HttpListenerRequest request)
{
if (!request.HasEntityBody)
{
Log("No request body");
Dictionary<string, string> emptyBody = new();
return emptyBody;
}
Stream body = request.InputStream;
Encoding encoding = request.ContentEncoding;
StreamReader reader = new StreamReader(body, encoding);
string s = reader.ReadToEnd();
Dictionary<string, string> requestBody = JsonConvert.DeserializeObject<Dictionary<string, string>>(s);
return requestBody;
}
private void HandleGet(HttpListenerRequest request, HttpListenerResponse response)
{
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
@ -274,7 +290,13 @@ public partial class Server : GlobalBase
private void HandlePost(HttpListenerRequest request, HttpListenerResponse response)
{
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query); //Variables in the URI
Dictionary<string, string> requestBody = GetRequestBody(request); //Variables in the JSON body
Dictionary<string, string> requestParams = new(); //The actual variable used for the API
//Concatenate the two dictionaries for compatibility with older versions of front-ends
requestParams = requestVariables.Concat(requestBody).ToDictionary(x => x.Key, x => x.Value);
string? connectorName, internalId, jobId, chapterNumStr, customFolderName, translatedLanguage, notificationConnectorStr, libraryConnectorStr;
MangaConnector? connector;
Manga? tmpManga;
@ -286,7 +308,7 @@ public partial class Server : GlobalBase
switch (path)
{
case "Manga":
if(!requestVariables.TryGetValue("internalId", out internalId) ||
if(!requestParams.TryGetValue("internalId", out internalId) ||
!_parent.TryGetPublicationById(internalId, out tmpManga))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -296,9 +318,9 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.OK, response, manga);
break;
case "Jobs/MonitorManga":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("interval", out string? intervalStr) ||
if(!requestParams.TryGetValue("connector", out connectorName) ||
!requestParams.TryGetValue("internalId", out internalId) ||
!requestParams.TryGetValue("interval", out string? intervalStr) ||
!_parent.TryGetConnector(connectorName, out connector)||
!_parent.TryGetPublicationById(internalId, out tmpManga) ||
!TimeSpan.TryParse(intervalStr, out TimeSpan interval))
@ -309,7 +331,7 @@ public partial class Server : GlobalBase
manga = (Manga)tmpManga!;
if (requestVariables.TryGetValue("ignoreBelowChapterNum", out chapterNumStr))
if (requestParams.TryGetValue("ignoreBelowChapterNum", out chapterNumStr))
{
if (!float.TryParse(chapterNumStr, numberFormatDecimalPoint, out float chapterNum))
{
@ -319,16 +341,16 @@ public partial class Server : GlobalBase
manga.ignoreChaptersBelow = chapterNum;
}
if (requestVariables.TryGetValue("customFolderName", out customFolderName))
if (requestParams.TryGetValue("customFolderName", out customFolderName))
manga.MovePublicationFolder(settings.downloadLocation, customFolderName);
requestVariables.TryGetValue("translatedLanguage", out translatedLanguage);
requestParams.TryGetValue("translatedLanguage", out translatedLanguage);
_parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, true, interval, translatedLanguage: translatedLanguage??"en"));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
if(!requestParams.TryGetValue("connector", out connectorName) ||
!requestParams.TryGetValue("internalId", out internalId) ||
!_parent.TryGetConnector(connectorName, out connector)||
!_parent.TryGetPublicationById(internalId, out tmpManga))
{
@ -338,7 +360,7 @@ public partial class Server : GlobalBase
manga = (Manga)tmpManga!;
if (requestVariables.TryGetValue("ignoreBelowChapterNum", out chapterNumStr))
if (requestParams.TryGetValue("ignoreBelowChapterNum", out chapterNumStr))
{
if (!float.TryParse(chapterNumStr, numberFormatDecimalPoint, out float chapterNum))
{
@ -348,15 +370,15 @@ public partial class Server : GlobalBase
manga.ignoreChaptersBelow = chapterNum;
}
if (requestVariables.TryGetValue("customFolderName", out customFolderName))
if (requestParams.TryGetValue("customFolderName", out customFolderName))
manga.MovePublicationFolder(settings.downloadLocation, customFolderName);
requestVariables.TryGetValue("translatedLanguage", out translatedLanguage);
requestParams.TryGetValue("translatedLanguage", out translatedLanguage);
_parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en"));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/UpdateMetadata":
if (!requestVariables.TryGetValue("internalId", out internalId))
if (!requestParams.TryGetValue("internalId", out internalId))
{
foreach (Job pJob in _parent.jobBoss.jobs.Where(possibleDncJob =>
possibleDncJob.jobType is Job.JobType.DownloadNewChaptersJob).ToArray())//ToArray to avoid modyifying while adding new jobs
@ -384,7 +406,7 @@ public partial class Server : GlobalBase
}
break;
case "Jobs/StartNow":
if (!requestVariables.TryGetValue("jobId", out jobId) ||
if (!requestParams.TryGetValue("jobId", out jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out job))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -394,7 +416,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/Cancel":
if (!requestVariables.TryGetValue("jobId", out jobId) ||
if (!requestParams.TryGetValue("jobId", out jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out job))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -404,8 +426,8 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Settings/UpdateDownloadLocation":
if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) ||
!requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) ||
if (!requestParams.TryGetValue("downloadLocation", out string? downloadLocation) ||
!requestParams.TryGetValue("moveFiles", out string? moveFilesStr) ||
!bool.TryParse(moveFilesStr, out bool moveFiles))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -415,7 +437,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Settings/AprilFoolsMode":
if (!requestVariables.TryGetValue("enabled", out string? aprilFoolsModeEnabledStr) ||
if (!requestParams.TryGetValue("enabled", out string? aprilFoolsModeEnabledStr) ||
bool.TryParse(aprilFoolsModeEnabledStr, out bool aprilFoolsModeEnabled))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -425,7 +447,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
/*case "Settings/UpdateWorkingDirectory":
if (!requestVariables.TryGetValue("workingDirectory", out string? workingDirectory))
if (!requestParams.TryGetValue("workingDirectory", out string? workingDirectory))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -434,7 +456,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;*/
case "Settings/userAgent":
if(!requestVariables.TryGetValue("userAgent", out string? customUserAgent))
if(!requestParams.TryGetValue("userAgent", out string? customUserAgent))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -447,8 +469,8 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Settings/customRequestLimit":
if (!requestVariables.TryGetValue("requestType", out string? requestTypeStr) ||
!requestVariables.TryGetValue("requestsPerMinute", out string? requestsPerMinuteStr) ||
if (!requestParams.TryGetValue("requestType", out string? requestTypeStr) ||
!requestParams.TryGetValue("requestsPerMinute", out string? requestsPerMinuteStr) ||
!Enum.TryParse(requestTypeStr, out RequestType requestType) ||
!int.TryParse(requestsPerMinuteStr, out int requestsPerMinute))
{
@ -469,7 +491,7 @@ public partial class Server : GlobalBase
settings.ExportSettings();
break;
case "NotificationConnectors/Update":
if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) ||
if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) ||
!Enum.TryParse(notificationConnectorStr, out notificationConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -478,8 +500,8 @@ public partial class Server : GlobalBase
if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Gotify)
{
if (!requestVariables.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
!requestVariables.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
if (!requestParams.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
!requestParams.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -488,7 +510,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.LunaSea)
{
if (!requestVariables.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
if (!requestParams.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -497,8 +519,8 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy)
{
if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
!requestVariables.TryGetValue("ntfyAuth", out string? ntfyAuth))
if (!requestParams.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
!requestParams.TryGetValue("ntfyAuth", out string? ntfyAuth))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -513,7 +535,7 @@ public partial class Server : GlobalBase
break;
case "NotificationConnectors/Test":
NotificationConnector notificationConnector;
if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) ||
if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) ||
!Enum.TryParse(notificationConnectorStr, out notificationConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -522,8 +544,8 @@ public partial class Server : GlobalBase
if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Gotify)
{
if (!requestVariables.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
!requestVariables.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
if (!requestParams.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
!requestParams.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -531,7 +553,7 @@ public partial class Server : GlobalBase
notificationConnector = new Gotify(this, gotifyUrl, gotifyAppToken);
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.LunaSea)
{
if (!requestVariables.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
if (!requestParams.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -539,8 +561,8 @@ public partial class Server : GlobalBase
notificationConnector = new LunaSea(this, lunaseaWebhook);
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy)
{
if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
!requestVariables.TryGetValue("ntfyAuth", out string? ntfyAuth))
if (!requestParams.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
!requestParams.TryGetValue("ntfyAuth", out string? ntfyAuth))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -557,7 +579,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "NotificationConnectors/Reset":
if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) ||
if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) ||
!Enum.TryParse(notificationConnectorStr, out notificationConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -567,7 +589,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "LibraryConnectors/Update":
if (!requestVariables.TryGetValue("libraryConnector", out libraryConnectorStr) ||
if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) ||
!Enum.TryParse(libraryConnectorStr, out libraryConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -576,9 +598,9 @@ public partial class Server : GlobalBase
if (libraryConnectorType is LibraryConnector.LibraryType.Kavita)
{
if (!requestVariables.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
!requestVariables.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
!requestVariables.TryGetValue("kavitaPassword", out string? kavitaPassword))
if (!requestParams.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
!requestParams.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
!requestParams.TryGetValue("kavitaPassword", out string? kavitaPassword))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -587,8 +609,8 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
}else if (libraryConnectorType is LibraryConnector.LibraryType.Komga)
{
if (!requestVariables.TryGetValue("komgaUrl", out string? komgaUrl) ||
!requestVariables.TryGetValue("komgaAuth", out string? komgaAuth))
if (!requestParams.TryGetValue("komgaUrl", out string? komgaUrl) ||
!requestParams.TryGetValue("komgaAuth", out string? komgaAuth))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -603,7 +625,7 @@ public partial class Server : GlobalBase
break;
case "LibraryConnectors/Test":
LibraryConnector libraryConnector;
if (!requestVariables.TryGetValue("libraryConnector", out libraryConnectorStr) ||
if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) ||
!Enum.TryParse(libraryConnectorStr, out libraryConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -612,9 +634,9 @@ public partial class Server : GlobalBase
if (libraryConnectorType is LibraryConnector.LibraryType.Kavita)
{
if (!requestVariables.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
!requestVariables.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
!requestVariables.TryGetValue("kavitaPassword", out string? kavitaPassword))
if (!requestParams.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
!requestParams.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
!requestParams.TryGetValue("kavitaPassword", out string? kavitaPassword))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -622,8 +644,8 @@ public partial class Server : GlobalBase
libraryConnector = new Kavita(this, kavitaUrl, kavitaUsername, kavitaPassword);
}else if (libraryConnectorType is LibraryConnector.LibraryType.Komga)
{
if (!requestVariables.TryGetValue("komgaUrl", out string? komgaUrl) ||
!requestVariables.TryGetValue("komgaAuth", out string? komgaAuth))
if (!requestParams.TryGetValue("komgaUrl", out string? komgaUrl) ||
!requestParams.TryGetValue("komgaAuth", out string? komgaAuth))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
@ -639,7 +661,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "LibraryConnectors/Reset":
if (!requestVariables.TryGetValue("libraryConnector", out libraryConnectorStr) ||
if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) ||
!Enum.TryParse(libraryConnectorStr, out libraryConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -656,7 +678,13 @@ public partial class Server : GlobalBase
private void HandleDelete(HttpListenerRequest request, HttpListenerResponse response)
{
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query); //Variables in the URI
Dictionary<string, string> requestBody = GetRequestBody(request); //Variables in the JSON body
Dictionary<string, string> requestParams = new(); //The actual variable used for the API
//Concatenate the two dictionaries for compatibility with older versions of front-ends
requestParams = requestVariables.Concat(requestBody).ToDictionary(x => x.Key, x => x.Value);
string? connectorName, internalId;
MangaConnector connector;
Manga manga;
@ -664,7 +692,7 @@ public partial class Server : GlobalBase
switch (path)
{
case "Jobs":
if (!requestVariables.TryGetValue("jobId", out string? jobId) ||
if (!requestParams.TryGetValue("jobId", out string? jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out Job? job))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -674,8 +702,8 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
if(!requestParams.TryGetValue("connector", out connectorName) ||
!requestParams.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null)
{
@ -688,7 +716,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "NotificationConnectors":
if (!requestVariables.TryGetValue("notificationConnector", out string? notificationConnectorStr) ||
if (!requestParams.TryGetValue("notificationConnector", out string? notificationConnectorStr) ||
!Enum.TryParse(notificationConnectorStr, out NotificationConnector.NotificationConnectorType notificationConnectorType))
{
SendResponse(HttpStatusCode.BadRequest, response);
@ -698,7 +726,7 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
case "LibraryConnectors":
if (!requestVariables.TryGetValue("libraryConnectors", out string? libraryConnectorStr) ||
if (!requestParams.TryGetValue("libraryConnector", out string? libraryConnectorStr) ||
!Enum.TryParse(libraryConnectorStr,
out LibraryConnector.LibraryType libraryConnectoryType))
{
@ -709,6 +737,12 @@ public partial class Server : GlobalBase
SendResponse(HttpStatusCode.Accepted, response);
break;
default:
Log("Invalid Request:");
Log(request.Url!.Query);
foreach (KeyValuePair<string, string> kvp in requestParams)
{
Log("Request variable = {0}, Variable Value = {1}", kvp.Key, kvp.Value);
}
SendResponse(HttpStatusCode.BadRequest, response);
break;
}

383
docs/API_Calls.md Normal file
View File

@ -0,0 +1,383 @@
## Tranga API Calls
This document serves to outline all of the different HTTP API calls that Tranga accepts. Tranga expects specific HTTP methods for its calls and therefore careful attention must be paid when making them.
In the examples below, `{apiUri}` refers to your `http(s)://TRANGA.FRONTEND.URI/api`. Parameters are included in the HTTP request URI and the request body is in JSON format. Tranga responses are always
in the JSON format within the Response Body.
#### [GET] /Connectors
Retrieves the available manga sites (connectors) that Tranga is currently able to download manga from.
- Parameters:
None
- Request Body:
None
#### [GET] /Jobs
Retrieves all jobs that Tranga is keeping track of, includes Running Jobs, Waiting Jobs, Manga Tracking (Monitoring) Jobs.
- Parameters:
None
- Request Body:
None
#### [DELETE] /Jobs
Removes the specified job given by the job ID
- Request Variables:
- None
- Request Body:
```
{
jobId: ${Tranga Job ID}
}
```
#### [POST] /Jobs/Cancel
Cancels a running job or prevents a queued job from running.
- Parameters:
None
- Request Body:
```
{
jobId: ${Tranga Job ID}
}
```
#### [POST] /Jobs/DownloadNewChapters
Manually adds a Job to Tranga's queue to check for and download new chapters for a specified manga
- Parameters:
None
- Request Body:
```
{
connector: ${Manga Connector to Download From}
internalId: ${Tranga Manga ID}
translatedLanguage: ${Manga Language}
}
```
#### [GET] /Jobs/Running
Retrieves all currently running jobs.
- Parameters:
None
- Request Body:
None
#### [POST] /Jobs/StartNow
Manually starts a configured job
- Parameters:
None
- Request Body:
```
{
jobId: ${Tranga Job ID}
}
```
#### [GET]/Jobs/Waiting
Retrieves all currently queued jobs.
- Parameters:
None
- Request Body:
None
#### [GET] /Jobs/MonitorJobs
Retrieves all jobs for Mangas that Tranga is currently tracking.
- Parameters:
None
- Request Body:
None
#### [POST] /Jobs/MonitorManga
Adds a new manga for Tranga to monitor
- Parameters:
None
- Request Body:
```
{
connector: ${Manga Connector to download from}
internalId: ${Tranga Manga ID}
interval: ${Interval at which to run job, in the HH:MM:SS format}
translatedLanguage: ${Supported language code}
ignoreBelowChapterNum: ${Chapter number to start downloading from}
customFolderName: ${Folder Name to save Manga to}
}
```
#### [GET] /Jobs/Progress
Retrieves the current completion progress of a running or waiting job. Tranga's ID for the Job is returned with each of the `GET /Job/` API calls.
- Parameters:
- `{jobId}`: Tranga Job ID
- Request Body:
None
#### [POST] /Jobs/UpdateMetadata
Updates the metadata for all monitored mangas
- Parameters:
None
- Request Body:
None
#### [GET] /LibraryConnectors
Retrieves the currently configured library servers
- Parameters:
None
- Request Body:
None
#### [DELETE] /LibraryConnectors/Reset
Resets or clears a configured library connector
- Parameters:
None
- Request Body:
```
{
libraryConnector: Komga/Kavita
}
```
#### [POST] /LibraryConnectors/Test
Verifies the behavior of a library connector before saving it. The connector must be checked to verify that the connection is active.
- Parameters:
None
- Request Body:
```
{
libraryConnector: Komga/Kavita
libraryURL: ${Library URL}
komgaAuth: Only for when libraryConnector = Komga
kavitaUsername: Only for when libraryConnector = Kavita
kavitaPassword: Only for when libraryConnector = Kavita
}
```
#### [GET] /LibraryConnectors/Types
Retrives Key-Value pairs for all of Tranga's currently supported library servers.
- Parameters:
None
- Request Body:
None
#### [POST] /LibraryConnectors/Update
Updates or Adds a Library Connector to Tranga
- Parameters: None
- Request Body:
```
{
libraryConnector: Komga/Kavita
libraryURL: ${Library URL}
komgaAuth: Only for when libraryConnector = Komga
kavitaUsername: Only for when libraryConnector = Kavita
kavitaPassword: Only for when libraryConnector = Kavita
}
```
#### [GET] /LogFile
Retrieves the log file from the running Tranga instance
- Parameters:
None
- Request Body:
None
#### [GET] /Manga/FromConnector
Retrieves the details about a specified manga from a specific connector. If the manga title returned by Tranga is a URL (determined by the presence of `http` in the title, the API call should use the second
call with the `url` rather than the `title`.
- Parameters:
- `{connector}`: Manga Connector
- `{url/title}`: Manga URL/Title
- Request Body:
None
#### [GET] /Manga/Chapters
Retrieves the currently available chapters for a specified manga from a connector. The `{internalId}` is how Tranga uniquely recognizes and distinguishes different Manga.
- Parameters:
- `{connector}`: Manga Connector
- `{internalId}`: Tranga Manga ID
- `{translatedLanguage}`: Translated Language
- Request Body:
None
#### [GET] /Manga/Cover
Retrives the URL of the cover image for a specific manga that Tranga is tracking.
- Parameters:
- `{internalId}`: Tranga Manga ID
- Request Body:
None
#### [GET] /NotificationConnectors
Retrieves the currently configured notification providers
- Parameters:
None
- Request Body:
None
#### [DELETE] /NotificationConnectors/Reset
Resets or clears a configured notification connector
- Parameters:
None
- Request Body:
```
{
notificationConnector: Gotify/Ntfy/LunaSea
}
```
#### [POST] /NotificationConnectors/Test
Tests a notification connector with the currently input settings. The connector behavior must be checked to verify that the input settings are correct.
- Parameters:
None
- Request Body:
```
{
notificationConnector: Gotify/Ntfy/LunaSea
gotifyUrl:
gotifyAppToken:
lunaseaWebhook:
ntfyUrl:
ntfyAuth:
}
```
#### [POST] /NotificationConnectors/Update
Updates or Adds a notification connector to Tranga
- Parameters:
None
- Request Body:
```
{
notificationConnector: Gotify/Ntfy/LunaSea
gotifyUrl:
gotifyAppToken:
lunaseaWebhook:
ntfyUrl:
ntfyAuth:
}
```
#### [GET] /NotificationConnectors/Types
Retrives Key-Value pairs for all of Tranga's currently supported notification providers.
- Parameters:
None
- Request Body:
None
#### [GET] /Ping
This call is used periodically by the web frontend to establish that connection to the server is active.
- Parameters:
None
- Request Body:
None
#### [GET] /Settings
Retrieves the content of Tranga's `settings.json`
- Parameters:
None
- Request Body:
None
#### [GET] /Settings/customRequestLimit
Retrieves the configured rate limits for different types of manga connector requests.
- Parameters:
None
- Request Body:
None
#### [POST] /Settings/customRequestLimit
Sets the rate limits for different types of manga connector requests.
- Parameters:
None
- Request Body:
```
{
requestType: {Request Byte}
requestsPerMinute: {Rate Limit in Requests Per Minute}
}
```
#### [POST] /Settings/UpdateDownloadLocation
Updates the root directory of where Tranga downloads manga
- Parameters:
None
- Request Body:
```
{
downloadLocation: {New Root Directory}
moveFiles: "true"/"false"
}
```
#### [POST] /Settings/userAgent
Updates the user agent that Tranga uses when scraping the web
- Parameters
- Request Body:
```
{
userAgent: {User Agent String}
}
```