This commit is contained in:
parent
f79743ee93
commit
fa2598084f
807
Tranga/Server.cs
807
Tranga/Server.cs
@ -1,807 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Tranga.Jobs;
|
|
||||||
using Tranga.LibraryConnectors;
|
|
||||||
using Tranga.MangaConnectors;
|
|
||||||
using Tranga.NotificationConnectors;
|
|
||||||
|
|
||||||
namespace Tranga;
|
|
||||||
|
|
||||||
public partial class Server : GlobalBase
|
|
||||||
{
|
|
||||||
private readonly HttpListener _listener = new();
|
|
||||||
private readonly Tranga _parent;
|
|
||||||
|
|
||||||
public Server(Tranga parent) : base(parent)
|
|
||||||
{
|
|
||||||
this._parent = parent;
|
|
||||||
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);
|
|
||||||
listenThread.Start();
|
|
||||||
Thread watchThread = new(WatchRunning);
|
|
||||||
watchThread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WatchRunning()
|
|
||||||
{
|
|
||||||
while (_parent.keepRunning)
|
|
||||||
Thread.Sleep(1000);
|
|
||||||
this._listener.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Listen()
|
|
||||||
{
|
|
||||||
this._listener.Start();
|
|
||||||
foreach (string prefix in this._listener.Prefixes)
|
|
||||||
Log($"Listening on {prefix}");
|
|
||||||
while (this._listener.IsListening && _parent.keepRunning)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpListenerContext context = this._listener.GetContext();
|
|
||||||
//Log($"{context.Request.HttpMethod} {context.Request.Url} {context.Request.UserAgent}");
|
|
||||||
Task t = new(() =>
|
|
||||||
{
|
|
||||||
HandleRequest(context);
|
|
||||||
});
|
|
||||||
t.Start();
|
|
||||||
}
|
|
||||||
catch (HttpListenerException)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleRequest(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
HttpListenerRequest request = context.Request;
|
|
||||||
HttpListenerResponse response = context.Response;
|
|
||||||
if (request.HttpMethod == "OPTIONS")
|
|
||||||
SendResponse(HttpStatusCode.OK, context.Response);
|
|
||||||
if (request.Url!.LocalPath.Contains("favicon"))
|
|
||||||
SendResponse(HttpStatusCode.NoContent, response);
|
|
||||||
|
|
||||||
if (Regex.IsMatch(request.Url.LocalPath, "/v2(/.*)?"))
|
|
||||||
{
|
|
||||||
HandleRequestV2(context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (request.HttpMethod)
|
|
||||||
{
|
|
||||||
case "GET":
|
|
||||||
HandleGet(request, response);
|
|
||||||
break;
|
|
||||||
case "POST":
|
|
||||||
HandlePost(request, response);
|
|
||||||
break;
|
|
||||||
case "DELETE":
|
|
||||||
HandleDelete(request, response);
|
|
||||||
break;
|
|
||||||
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-=]+)*");
|
|
||||||
if (!queryRex.IsMatch(query))
|
|
||||||
return ret;
|
|
||||||
query = query.Substring(1);
|
|
||||||
foreach (string keyValuePair in query.Split('&').Where(str => str.Length >= 3))
|
|
||||||
{
|
|
||||||
string var = keyValuePair.Split('=')[0];
|
|
||||||
string val = Regex.Replace(keyValuePair.Substring(var.Length + 1), "%20", " ");
|
|
||||||
val = Regex.Replace(val, "%[0-9]{2}", "");
|
|
||||||
ret.Add(var, val);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, string> GetRequestBody(HttpListenerRequest request)
|
|
||||||
{
|
|
||||||
if (!request.HasEntityBody)
|
|
||||||
{
|
|
||||||
Log("No request body");
|
|
||||||
return new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
Stream body = request.InputStream;
|
|
||||||
Encoding encoding = request.ContentEncoding;
|
|
||||||
using StreamReader streamReader = new (body, encoding);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Dictionary<string, string> requestBody =
|
|
||||||
JsonConvert.DeserializeObject<Dictionary<string, string>>(streamReader.ReadToEnd())
|
|
||||||
?? new();
|
|
||||||
return requestBody;
|
|
||||||
}
|
|
||||||
catch (JsonException e)
|
|
||||||
{
|
|
||||||
Log(e.Message);
|
|
||||||
}
|
|
||||||
return new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleGet(HttpListenerRequest request, HttpListenerResponse response)
|
|
||||||
{
|
|
||||||
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
|
|
||||||
string? connectorName, jobId, internalId;
|
|
||||||
MangaConnector? connector;
|
|
||||||
Manga? manga;
|
|
||||||
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
|
|
||||||
switch (path)
|
|
||||||
{
|
|
||||||
case "Connectors":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.GetConnectors().Select(con => con.name).ToArray());
|
|
||||||
break;
|
|
||||||
case "Manga/Cover":
|
|
||||||
if (!requestVariables.TryGetValue("internalId", out internalId) ||
|
|
||||||
!_parent.TryGetPublicationById(internalId, out manga))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
string filePath = settings.GetFullCoverPath((Manga)manga!);
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
FileStream coverStream = new(filePath, FileMode.Open);
|
|
||||||
SendResponse(HttpStatusCode.OK, response, coverStream);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.NotFound, response);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Manga/FromConnector":
|
|
||||||
requestVariables.TryGetValue("title", out string? title);
|
|
||||||
requestVariables.TryGetValue("url", out string? url);
|
|
||||||
if (!requestVariables.TryGetValue("connector", out connectorName) ||
|
|
||||||
!_parent.TryGetConnector(connectorName, out connector) ||
|
|
||||||
(title is null && url is null))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url is not null)
|
|
||||||
{
|
|
||||||
HashSet<Manga> ret = new();
|
|
||||||
manga = connector!.GetMangaFromUrl(url);
|
|
||||||
if (manga is not null)
|
|
||||||
ret.Add((Manga)manga);
|
|
||||||
SendResponse(HttpStatusCode.OK, response, ret);
|
|
||||||
}else
|
|
||||||
SendResponse(HttpStatusCode.OK, response, connector!.GetManga(title!));
|
|
||||||
break;
|
|
||||||
case "Manga/Chapters":
|
|
||||||
if(!requestVariables.TryGetValue("connector", out connectorName) ||
|
|
||||||
!requestVariables.TryGetValue("internalId", out internalId) ||
|
|
||||||
!_parent.TryGetConnector(connectorName, out connector) ||
|
|
||||||
!_parent.TryGetPublicationById(internalId, out manga))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
requestVariables.TryGetValue("translatedLanguage", out string? translatedLanguage);
|
|
||||||
SendResponse(HttpStatusCode.OK, response, connector!.GetChapters((Manga)manga!, translatedLanguage??"en"));
|
|
||||||
break;
|
|
||||||
case "Jobs":
|
|
||||||
if (!requestVariables.TryGetValue("jobId", out jobId))
|
|
||||||
{
|
|
||||||
if(!_parent.jobBoss.jobs.Any(jjob => jjob.id == jobId))
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
else
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.First(jjob => jjob.id == jobId));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs);
|
|
||||||
break;
|
|
||||||
case "Jobs/Progress":
|
|
||||||
if (requestVariables.TryGetValue("jobId", out jobId))
|
|
||||||
{
|
|
||||||
if(!_parent.jobBoss.jobs.Any(jjob => jjob.id == jobId))
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
else
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.First(jjob => jjob.id == jobId).progressToken);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Select(jjob => jjob.progressToken));
|
|
||||||
break;
|
|
||||||
case "Jobs/Running":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Running));
|
|
||||||
break;
|
|
||||||
case "Jobs/Waiting":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Standby).OrderBy(jjob => jjob.nextExecution));
|
|
||||||
break;
|
|
||||||
case "Jobs/MonitorJobs":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob is DownloadNewChapters).OrderBy(jjob => ((DownloadNewChapters)jjob).manga.sortName));
|
|
||||||
break;
|
|
||||||
case "Settings":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, settings);
|
|
||||||
break;
|
|
||||||
case "Settings/userAgent":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, settings.userAgent);
|
|
||||||
break;
|
|
||||||
case "Settings/customRequestLimit":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, settings.requestLimits);
|
|
||||||
break;
|
|
||||||
case "Settings/AprilFoolsMode":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, settings.aprilFoolsMode);
|
|
||||||
break;
|
|
||||||
case "NotificationConnectors":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, notificationConnectors);
|
|
||||||
break;
|
|
||||||
case "NotificationConnectors/Types":
|
|
||||||
SendResponse(HttpStatusCode.OK, response,
|
|
||||||
Enum.GetValues<NotificationConnector.NotificationConnectorType>().Select(nc => new KeyValuePair<byte, string?>((byte)nc, Enum.GetName(nc))));
|
|
||||||
break;
|
|
||||||
case "LibraryConnectors":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, libraryConnectors);
|
|
||||||
break;
|
|
||||||
case "LibraryConnectors/Types":
|
|
||||||
SendResponse(HttpStatusCode.OK, response,
|
|
||||||
Enum.GetValues<LibraryConnector.LibraryType>().Select(lc => new KeyValuePair<byte, string?>((byte)lc, Enum.GetName(lc))));
|
|
||||||
break;
|
|
||||||
case "Ping":
|
|
||||||
SendResponse(HttpStatusCode.OK, response, "Pong");
|
|
||||||
break;
|
|
||||||
case "LogMessages":
|
|
||||||
if (logger is null || !File.Exists(logger?.logFilePath))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.NotFound, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestVariables.TryGetValue("count", out string? count))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
uint messageCount = uint.Parse(count);
|
|
||||||
SendResponse(HttpStatusCode.OK, response, logger.Tail(messageCount));
|
|
||||||
}
|
|
||||||
catch (FormatException f)
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.InternalServerError, response, f);
|
|
||||||
}
|
|
||||||
}else
|
|
||||||
SendResponse(HttpStatusCode.OK, response, logger.GetLog());
|
|
||||||
break;
|
|
||||||
case "LogFile":
|
|
||||||
if (logger is null || !File.Exists(logger?.logFilePath))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.NotFound, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
string logDir = new FileInfo(logger.logFilePath).DirectoryName!;
|
|
||||||
string tmpFilePath = Path.Join(logDir, "Tranga.log");
|
|
||||||
File.Copy(logger.logFilePath, tmpFilePath);
|
|
||||||
SendResponse(HttpStatusCode.OK, response, new FileStream(tmpFilePath, FileMode.Open));
|
|
||||||
File.Delete(tmpFilePath);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandlePost(HttpListenerRequest request, HttpListenerResponse response)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
Manga manga;
|
|
||||||
Job? job;
|
|
||||||
NotificationConnector.NotificationConnectorType notificationConnectorType;
|
|
||||||
LibraryConnector.LibraryType libraryConnectorType;
|
|
||||||
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
|
|
||||||
switch (path)
|
|
||||||
{
|
|
||||||
case "Manga":
|
|
||||||
if(!requestParams.TryGetValue("internalId", out internalId) ||
|
|
||||||
!_parent.TryGetPublicationById(internalId, out tmpManga))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
manga = (Manga)tmpManga!;
|
|
||||||
SendResponse(HttpStatusCode.OK, response, manga);
|
|
||||||
break;
|
|
||||||
case "Jobs/MonitorManga":
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
manga = (Manga)tmpManga!;
|
|
||||||
|
|
||||||
if (requestParams.TryGetValue("ignoreBelowChapterNum", out chapterNumStr))
|
|
||||||
{
|
|
||||||
if (!float.TryParse(chapterNumStr, numberFormatDecimalPoint, out float chapterNum))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
manga.ignoreChaptersBelow = chapterNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestParams.TryGetValue("customFolderName", out customFolderName))
|
|
||||||
manga.MovePublicationFolder(settings.downloadLocation, customFolderName);
|
|
||||||
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(!requestParams.TryGetValue("connector", out connectorName) ||
|
|
||||||
!requestParams.TryGetValue("internalId", out internalId) ||
|
|
||||||
!_parent.TryGetConnector(connectorName, out connector)||
|
|
||||||
!_parent.TryGetPublicationById(internalId, out tmpManga))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
manga = (Manga)tmpManga!;
|
|
||||||
|
|
||||||
if (requestParams.TryGetValue("ignoreBelowChapterNum", out chapterNumStr))
|
|
||||||
{
|
|
||||||
if (!float.TryParse(chapterNumStr, numberFormatDecimalPoint, out float chapterNum))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
manga.ignoreChaptersBelow = chapterNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestParams.TryGetValue("customFolderName", out customFolderName))
|
|
||||||
manga.MovePublicationFolder(settings.downloadLocation, customFolderName);
|
|
||||||
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 (!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
|
|
||||||
{
|
|
||||||
DownloadNewChapters dncJob = pJob as DownloadNewChapters ??
|
|
||||||
throw new Exception("Has to be DownloadNewChapters Job");
|
|
||||||
_parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga));
|
|
||||||
}
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Job[] possibleDncJobs = _parent.jobBoss.GetJobsLike(internalId: internalId).ToArray();
|
|
||||||
switch (possibleDncJobs.Length)
|
|
||||||
{
|
|
||||||
case <1: SendResponse(HttpStatusCode.BadRequest, response, "Could not find matching release"); break;
|
|
||||||
case >1: SendResponse(HttpStatusCode.BadRequest, response, "Multiple releases??"); break;
|
|
||||||
default:
|
|
||||||
DownloadNewChapters dncJob = possibleDncJobs[0] as DownloadNewChapters ??
|
|
||||||
throw new Exception("Has to be DownloadNewChapters Job");
|
|
||||||
_parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Jobs/StartNow":
|
|
||||||
if (!requestParams.TryGetValue("jobId", out jobId) ||
|
|
||||||
!_parent.jobBoss.TryGetJobById(jobId, out job))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_parent.jobBoss.AddJobToQueue(job!);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "Jobs/Cancel":
|
|
||||||
if (!requestParams.TryGetValue("jobId", out jobId) ||
|
|
||||||
!_parent.jobBoss.TryGetJobById(jobId, out job))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
job!.Cancel();
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "Settings/UpdateDownloadLocation":
|
|
||||||
if (!requestParams.TryGetValue("downloadLocation", out string? downloadLocation) ||
|
|
||||||
!requestParams.TryGetValue("moveFiles", out string? moveFilesStr) ||
|
|
||||||
!bool.TryParse(moveFilesStr, out bool moveFiles))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
settings.UpdateDownloadLocation(downloadLocation, moveFiles);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "Settings/AprilFoolsMode":
|
|
||||||
if (!requestParams.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":
|
|
||||||
if (!requestParams.TryGetValue("workingDirectory", out string? workingDirectory))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
settings.UpdateWorkingDirectory(workingDirectory);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;*/
|
|
||||||
case "Settings/userAgent":
|
|
||||||
if(!requestParams.TryGetValue("userAgent", out string? customUserAgent))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
settings.UpdateUserAgent(customUserAgent);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "Settings/userAgent/Reset":
|
|
||||||
settings.UpdateUserAgent(null);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "Settings/customRequestLimit":
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.requestLimits.ContainsKey(requestType))
|
|
||||||
{
|
|
||||||
settings.requestLimits[requestType] = requestsPerMinute;
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}else
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
settings.ExportSettings();
|
|
||||||
break;
|
|
||||||
case "Settings/customRequestLimit/Reset":
|
|
||||||
settings.requestLimits = TrangaSettings.DefaultRequestLimits;
|
|
||||||
settings.ExportSettings();
|
|
||||||
break;
|
|
||||||
case "NotificationConnectors/Update":
|
|
||||||
if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) ||
|
|
||||||
!Enum.TryParse(notificationConnectorStr, out notificationConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Gotify)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
|
|
||||||
!requestParams.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
AddNotificationConnector(new Gotify(this, gotifyUrl, gotifyAppToken));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.LunaSea)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
AddNotificationConnector(new LunaSea(this, lunaseaWebhook));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
|
|
||||||
!requestParams.TryGetValue("ntfyAuth", out string? ntfyAuth))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
AddNotificationConnector(new Ntfy(this, ntfyUrl, ntfyAuth));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "NotificationConnectors/Test":
|
|
||||||
NotificationConnector notificationConnector;
|
|
||||||
if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) ||
|
|
||||||
!Enum.TryParse(notificationConnectorStr, out notificationConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Gotify)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
|
|
||||||
!requestParams.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
notificationConnector = new Gotify(this, gotifyUrl, gotifyAppToken);
|
|
||||||
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.LunaSea)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
notificationConnector = new LunaSea(this, lunaseaWebhook);
|
|
||||||
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
|
|
||||||
!requestParams.TryGetValue("ntfyAuth", out string? ntfyAuth))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
notificationConnector = new Ntfy(this, ntfyUrl, ntfyAuth);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationConnector.SendNotification("Tranga Test", "This is Test-Notification.");
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "NotificationConnectors/Reset":
|
|
||||||
if (!requestParams.TryGetValue("notificationConnector", out notificationConnectorStr) ||
|
|
||||||
!Enum.TryParse(notificationConnectorStr, out notificationConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
DeleteNotificationConnector(notificationConnectorType);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "LibraryConnectors/Update":
|
|
||||||
if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) ||
|
|
||||||
!Enum.TryParse(libraryConnectorStr, out libraryConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (libraryConnectorType is LibraryConnector.LibraryType.Kavita)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
|
|
||||||
!requestParams.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
|
|
||||||
!requestParams.TryGetValue("kavitaPassword", out string? kavitaPassword))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
AddLibraryConnector(new Kavita(this, kavitaUrl, kavitaUsername, kavitaPassword));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}else if (libraryConnectorType is LibraryConnector.LibraryType.Komga)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("komgaUrl", out string? komgaUrl) ||
|
|
||||||
!requestParams.TryGetValue("komgaAuth", out string? komgaAuth))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
AddLibraryConnector(new Komga(this, komgaUrl, komgaAuth));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "LibraryConnectors/Test":
|
|
||||||
LibraryConnector libraryConnector;
|
|
||||||
if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) ||
|
|
||||||
!Enum.TryParse(libraryConnectorStr, out libraryConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (libraryConnectorType is LibraryConnector.LibraryType.Kavita)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
|
|
||||||
!requestParams.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
|
|
||||||
!requestParams.TryGetValue("kavitaPassword", out string? kavitaPassword))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
libraryConnector = new Kavita(this, kavitaUrl, kavitaUsername, kavitaPassword);
|
|
||||||
}else if (libraryConnectorType is LibraryConnector.LibraryType.Komga)
|
|
||||||
{
|
|
||||||
if (!requestParams.TryGetValue("komgaUrl", out string? komgaUrl) ||
|
|
||||||
!requestParams.TryGetValue("komgaAuth", out string? komgaAuth))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
libraryConnector = new Komga(this, komgaUrl, komgaAuth);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
libraryConnector.UpdateLibrary();
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "LibraryConnectors/Reset":
|
|
||||||
if (!requestParams.TryGetValue("libraryConnector", out libraryConnectorStr) ||
|
|
||||||
!Enum.TryParse(libraryConnectorStr, out libraryConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
DeleteLibraryConnector(libraryConnectorType);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDelete(HttpListenerRequest request, HttpListenerResponse response)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
|
|
||||||
switch (path)
|
|
||||||
{
|
|
||||||
case "Jobs":
|
|
||||||
if (!requestParams.TryGetValue("jobId", out string? jobId) ||
|
|
||||||
!_parent.jobBoss.TryGetJobById(jobId, out Job? job))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_parent.jobBoss.RemoveJob(job!);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "Jobs/DownloadNewChapters":
|
|
||||||
if(!requestParams.TryGetValue("connector", out connectorName) ||
|
|
||||||
!requestParams.TryGetValue("internalId", out internalId) ||
|
|
||||||
_parent.GetConnector(connectorName) is null ||
|
|
||||||
_parent.GetPublicationById(internalId) is null)
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
connector = _parent.GetConnector(connectorName)!;
|
|
||||||
manga = (Manga)_parent.GetPublicationById(internalId)!;
|
|
||||||
_parent.jobBoss.RemoveJobs(_parent.jobBoss.GetJobsLike(connector, manga));
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "NotificationConnectors":
|
|
||||||
if (!requestParams.TryGetValue("notificationConnector", out string? notificationConnectorStr) ||
|
|
||||||
!Enum.TryParse(notificationConnectorStr, out NotificationConnector.NotificationConnectorType notificationConnectorType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
DeleteNotificationConnector(notificationConnectorType);
|
|
||||||
SendResponse(HttpStatusCode.Accepted, response);
|
|
||||||
break;
|
|
||||||
case "LibraryConnectors":
|
|
||||||
if (!requestParams.TryGetValue("libraryConnector", out string? libraryConnectorStr) ||
|
|
||||||
!Enum.TryParse(libraryConnectorStr,
|
|
||||||
out LibraryConnector.LibraryType libraryConnectoryType))
|
|
||||||
{
|
|
||||||
SendResponse(HttpStatusCode.BadRequest, response);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
DeleteLibraryConnector(libraryConnectoryType);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null)
|
|
||||||
{
|
|
||||||
//Log($"Response: {statusCode} {content}");
|
|
||||||
response.StatusCode = (int)statusCode;
|
|
||||||
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
|
|
||||||
response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE");
|
|
||||||
response.AddHeader("Access-Control-Max-Age", "1728000");
|
|
||||||
response.AppendHeader("Access-Control-Allow-Origin", "*");
|
|
||||||
|
|
||||||
if (content is not Stream)
|
|
||||||
{
|
|
||||||
response.ContentType = "application/json";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response.OutputStream.Write(content is not null
|
|
||||||
? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content))
|
|
||||||
: Array.Empty<byte>());
|
|
||||||
response.OutputStream.Close();
|
|
||||||
}
|
|
||||||
catch (HttpListenerException e)
|
|
||||||
{
|
|
||||||
Log(e.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(content is FileStream stream)
|
|
||||||
{
|
|
||||||
string contentType = stream.Name.Split('.')[^1];
|
|
||||||
switch (contentType.ToLower())
|
|
||||||
{
|
|
||||||
case "gif":
|
|
||||||
response.ContentType = "image/gif";
|
|
||||||
break;
|
|
||||||
case "png":
|
|
||||||
response.ContentType = "image/png";
|
|
||||||
break;
|
|
||||||
case "jpg":
|
|
||||||
case "jpeg":
|
|
||||||
response.ContentType = "image/jpeg";
|
|
||||||
break;
|
|
||||||
case "log":
|
|
||||||
response.ContentType = "text/plain";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
stream.CopyTo(response.OutputStream);
|
|
||||||
response.OutputStream.Close();
|
|
||||||
stream.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
196
Tranga/Server/Server.cs
Normal file
196
Tranga/Server/Server.cs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Tranga.Server;
|
||||||
|
|
||||||
|
public class Server : GlobalBase, IDisposable
|
||||||
|
{
|
||||||
|
private readonly HttpListener _listener = new();
|
||||||
|
private readonly Tranga _parent;
|
||||||
|
private bool _running = true;
|
||||||
|
|
||||||
|
public Server(Tranga parent) : base(parent)
|
||||||
|
{
|
||||||
|
this._parent = parent;
|
||||||
|
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);
|
||||||
|
listenThread.Start();
|
||||||
|
while(_parent.keepRunning && _running)
|
||||||
|
Thread.Sleep(100);
|
||||||
|
this.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Listen()
|
||||||
|
{
|
||||||
|
this._listener.Start();
|
||||||
|
foreach (string prefix in this._listener.Prefixes)
|
||||||
|
Log($"Listening on {prefix}");
|
||||||
|
while (this._listener.IsListening && _parent.keepRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpListenerContext context = this._listener.GetContext();
|
||||||
|
//Log($"{context.Request.HttpMethod} {context.Request.Url} {context.Request.UserAgent}");
|
||||||
|
Task t = new(() =>
|
||||||
|
{
|
||||||
|
HandleRequest(context);
|
||||||
|
});
|
||||||
|
t.Start();
|
||||||
|
}
|
||||||
|
catch (HttpListenerException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRequest(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
HttpListenerRequest request = context.Request;
|
||||||
|
HttpListenerResponse response = context.Response;
|
||||||
|
if (request.HttpMethod == "OPTIONS")
|
||||||
|
SendResponse(HttpStatusCode.OK, context.Response);
|
||||||
|
if (request.Url!.LocalPath.Contains("favicon"))
|
||||||
|
SendResponse(HttpStatusCode.NoContent, response);
|
||||||
|
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
|
||||||
|
|
||||||
|
if (!Regex.IsMatch(request.Url.LocalPath, "/v2(/.*)?"))
|
||||||
|
{
|
||||||
|
SendResponse(HttpStatusCode.NotFound, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = requestVariables.UnionBy(requestBody, v => v.Key)
|
||||||
|
.ToDictionary(kv => kv.Key, kv => kv.Value); //The actual variable used for the API
|
||||||
|
|
||||||
|
ValueTuple<HttpStatusCode, object?> responseMessage = request.HttpMethod switch
|
||||||
|
{
|
||||||
|
"GET" => HandleGet(path, requestParams),
|
||||||
|
"POST" => HandlePost(path, requestParams),
|
||||||
|
"DELETE" => HandleDelete(path, requestParams),
|
||||||
|
_ => new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.MethodNotAllowed, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
SendResponse(responseMessage.Item1, response, responseMessage.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
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-=]+)*");
|
||||||
|
if (!queryRex.IsMatch(query))
|
||||||
|
return ret;
|
||||||
|
query = query.Substring(1);
|
||||||
|
foreach (string keyValuePair in query.Split('&').Where(str => str.Length >= 3))
|
||||||
|
{
|
||||||
|
string var = keyValuePair.Split('=')[0];
|
||||||
|
string val = Regex.Replace(keyValuePair.Substring(var.Length + 1), "%20", " ");
|
||||||
|
val = Regex.Replace(val, "%[0-9]{2}", "");
|
||||||
|
ret.Add(var, val);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> GetRequestBody(HttpListenerRequest request)
|
||||||
|
{
|
||||||
|
if (!request.HasEntityBody)
|
||||||
|
{
|
||||||
|
Log("No request body");
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
Stream body = request.InputStream;
|
||||||
|
Encoding encoding = request.ContentEncoding;
|
||||||
|
using StreamReader streamReader = new (body, encoding);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dictionary<string, string> requestBody =
|
||||||
|
JsonConvert.DeserializeObject<Dictionary<string, string>>(streamReader.ReadToEnd())
|
||||||
|
?? new();
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
catch (JsonException e)
|
||||||
|
{
|
||||||
|
Log(e.Message);
|
||||||
|
}
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null)
|
||||||
|
{
|
||||||
|
//Log($"Response: {statusCode} {content}");
|
||||||
|
response.StatusCode = (int)statusCode;
|
||||||
|
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
|
||||||
|
response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE");
|
||||||
|
response.AddHeader("Access-Control-Max-Age", "1728000");
|
||||||
|
response.AppendHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
if (content is not Stream)
|
||||||
|
{
|
||||||
|
response.ContentType = "application/json";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.OutputStream.Write(content is not null
|
||||||
|
? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content))
|
||||||
|
: Array.Empty<byte>());
|
||||||
|
response.OutputStream.Close();
|
||||||
|
}
|
||||||
|
catch (HttpListenerException e)
|
||||||
|
{
|
||||||
|
Log(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(content is FileStream stream)
|
||||||
|
{
|
||||||
|
string contentType = stream.Name.Split('.')[^1];
|
||||||
|
switch (contentType.ToLower())
|
||||||
|
{
|
||||||
|
case "gif":
|
||||||
|
response.ContentType = "image/gif";
|
||||||
|
break;
|
||||||
|
case "png":
|
||||||
|
response.ContentType = "image/png";
|
||||||
|
break;
|
||||||
|
case "jpg":
|
||||||
|
case "jpeg":
|
||||||
|
response.ContentType = "image/jpeg";
|
||||||
|
break;
|
||||||
|
case "log":
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
stream.CopyTo(response.OutputStream);
|
||||||
|
response.OutputStream.Close();
|
||||||
|
stream.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueTuple<HttpStatusCode, object?> HandleGet(string path, Dictionary<string, string> requestParameters)
|
||||||
|
{
|
||||||
|
return new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.NotImplemented, "Not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueTuple<HttpStatusCode, object?> HandlePost(string path, Dictionary<string, string> requestParameters)
|
||||||
|
{
|
||||||
|
return new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.NotImplemented, "Not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueTuple<HttpStatusCode, object?> HandleDelete(string path, Dictionary<string, string> requestParameters)
|
||||||
|
{
|
||||||
|
return new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.NotImplemented, "Not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_running = false;
|
||||||
|
((IDisposable)_listener).Dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Tranga;
|
|
||||||
|
|
||||||
public partial class Server
|
|
||||||
{
|
|
||||||
private void HandleRequestV2(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
HttpListenerRequest request = context.Request;
|
|
||||||
HttpListenerResponse response = context.Response;
|
|
||||||
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
|
|
||||||
|
|
||||||
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 = requestVariables.UnionBy(requestBody, v => v.Key)
|
|
||||||
.ToDictionary(kv => kv.Key, kv => kv.Value); //The actual variable used for the API
|
|
||||||
|
|
||||||
ValueTuple<HttpStatusCode, object?> responseMessage = request.HttpMethod switch
|
|
||||||
{
|
|
||||||
"GET" => HandleGetV2(path, requestParams),
|
|
||||||
"POST" => HandlePostV2(path, requestParams),
|
|
||||||
"DELETE" => HandleDeleteV2(path, requestParams),
|
|
||||||
_ => new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.MethodNotAllowed, null)
|
|
||||||
};
|
|
||||||
|
|
||||||
SendResponse(responseMessage.Item1, response, responseMessage.Item2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueTuple<HttpStatusCode, object?> HandleGetV2(string path, Dictionary<string, string> requestParameters)
|
|
||||||
{
|
|
||||||
return new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.NotImplemented, "Not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueTuple<HttpStatusCode, object?> HandlePostV2(string path, Dictionary<string, string> requestParameters)
|
|
||||||
{
|
|
||||||
return new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.NotImplemented, "Not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueTuple<HttpStatusCode, object?> HandleDeleteV2(string path, Dictionary<string, string> requestParameters)
|
|
||||||
{
|
|
||||||
return new ValueTuple<HttpStatusCode, object?>(HttpStatusCode.NotImplemented, "Not implemented.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ public partial class Tranga : GlobalBase
|
|||||||
{
|
{
|
||||||
public bool keepRunning;
|
public bool keepRunning;
|
||||||
public JobBoss jobBoss;
|
public JobBoss jobBoss;
|
||||||
private Server _server;
|
private Server.Server _server;
|
||||||
private HashSet<MangaConnector> _connectors;
|
private HashSet<MangaConnector> _connectors;
|
||||||
|
|
||||||
public Tranga(Logger? logger, TrangaSettings settings) : base(logger, settings)
|
public Tranga(Logger? logger, TrangaSettings settings) : base(logger, settings)
|
||||||
@ -30,7 +30,7 @@ public partial class Tranga : GlobalBase
|
|||||||
dir.Delete();
|
dir.Delete();
|
||||||
jobBoss = new(this, this._connectors);
|
jobBoss = new(this, this._connectors);
|
||||||
StartJobBoss();
|
StartJobBoss();
|
||||||
this._server = new Server(this);
|
this._server = new Server.Server(this);
|
||||||
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
|
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
|
||||||
SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]);
|
SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user