mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-17 17:34:09 +02:00
240 lines
8.3 KiB
C#
240 lines
8.3 KiB
C#
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
using System.Windows.Forms;
|
|
|
|
namespace Jackett
|
|
{
|
|
public class Server
|
|
{
|
|
public const int Port = 9117;
|
|
|
|
HttpListener listener;
|
|
IndexerManager indexerManager;
|
|
WebApi webApi;
|
|
SonarrApi sonarrApi;
|
|
|
|
|
|
public Server()
|
|
{
|
|
// Allow all SSL.. sucks I know but mono on linux is having problems without it..
|
|
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
|
|
|
|
LoadApiKey();
|
|
|
|
indexerManager = new IndexerManager();
|
|
sonarrApi = new SonarrApi();
|
|
webApi = new WebApi(indexerManager, sonarrApi);
|
|
|
|
}
|
|
|
|
void LoadApiKey()
|
|
{
|
|
var apiKeyFile = Path.Combine(Program.AppConfigDirectory, "api_key.txt");
|
|
if (File.Exists(apiKeyFile))
|
|
ApiKey.CurrentKey = File.ReadAllText(apiKeyFile).Trim();
|
|
else
|
|
{
|
|
ApiKey.CurrentKey = ApiKey.Generate();
|
|
File.WriteAllText(apiKeyFile, ApiKey.CurrentKey);
|
|
}
|
|
}
|
|
|
|
public async Task Start()
|
|
{
|
|
Program.LoggerInstance.Info("Starting HTTP server...");
|
|
|
|
try
|
|
{
|
|
listener = new HttpListener();
|
|
listener.Prefixes.Add("http://*:9117/");
|
|
listener.Start();
|
|
}
|
|
catch (HttpListenerException ex)
|
|
{
|
|
if (ex.ErrorCode == 5)
|
|
{
|
|
var errorStr = "App must be ran as admin for permission to use port "
|
|
+ Port + Environment.NewLine + "Restart app with admin privileges?";
|
|
if (Program.IsWindows)
|
|
{
|
|
var dialogResult = MessageBox.Show(errorStr, "Error", MessageBoxButtons.YesNo);
|
|
if (dialogResult == DialogResult.No)
|
|
{
|
|
Application.Exit();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Program.RestartAsAdmin();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
Program.LoggerInstance.FatalException("Failed to start HTTP server. " + ex.Message, ex);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Program.LoggerInstance.ErrorException("Error starting HTTP server: " + ex.Message, ex);
|
|
return;
|
|
}
|
|
|
|
Program.LoggerInstance.Info("Server started on port " + Port);
|
|
|
|
while (true)
|
|
{
|
|
Exception error = null;
|
|
try
|
|
{
|
|
error = null;
|
|
var context = await listener.GetContextAsync();
|
|
ProcessHttpRequest(context);
|
|
}
|
|
catch (ObjectDisposedException ex)
|
|
{
|
|
Program.LoggerInstance.ErrorException("Critical error, HTTP listener was destroyed", ex);
|
|
Process.GetCurrentProcess().Kill();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = ex;
|
|
Program.LoggerInstance.ErrorException("Error processing HTTP request", ex);
|
|
}
|
|
|
|
if (error != null)
|
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
|
}
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
listener.Stop();
|
|
listener.Abort();
|
|
}
|
|
|
|
async void ProcessHttpRequest(HttpListenerContext context)
|
|
{
|
|
Program.LoggerInstance.Trace("Received request: " + context.Request.Url.ToString());
|
|
Exception exception = null;
|
|
try
|
|
{
|
|
if (await webApi.HandleRequest(context))
|
|
{
|
|
|
|
}
|
|
else if (context.Request.Url.AbsolutePath.StartsWith("/api/"))
|
|
{
|
|
await ProcessTorznab(context);
|
|
}
|
|
else
|
|
{
|
|
var responseBytes = Encoding.UTF8.GetBytes("Invalid request");
|
|
await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
exception = ex;
|
|
Program.LoggerInstance.ErrorException(ex.Message + ex.ToString(), ex);
|
|
}
|
|
|
|
if (exception != null)
|
|
{
|
|
try
|
|
{
|
|
var errorBytes = Encoding.UTF8.GetBytes(exception.Message);
|
|
await context.Response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
context.Response.Close();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
}
|
|
|
|
async Task ProcessTorznab(HttpListenerContext context)
|
|
{
|
|
|
|
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
|
|
var inputStream = context.Request.InputStream;
|
|
|
|
var indexerId = context.Request.Url.Segments[2].TrimEnd('/').ToLower();
|
|
var indexer = indexerManager.GetIndexer(indexerId);
|
|
|
|
if (context.Request.Url.Segments.Length > 4 && context.Request.Url.Segments[3] == "download/")
|
|
{
|
|
var downloadSegment = HttpServerUtility.UrlTokenDecode(context.Request.Url.Segments[4].TrimEnd('/'));
|
|
var downloadLink = Encoding.UTF8.GetString(downloadSegment);
|
|
var downloadBytes = await indexer.Download(new Uri(downloadLink));
|
|
await context.Response.OutputStream.WriteAsync(downloadBytes, 0, downloadBytes.Length);
|
|
return;
|
|
}
|
|
|
|
var torznabQuery = TorznabQuery.FromHttpQuery(query);
|
|
|
|
if (torznabQuery.RageID != 0)
|
|
torznabQuery.ShowTitles = await sonarrApi.GetShowTitle(torznabQuery.RageID);
|
|
else
|
|
torznabQuery.ShowTitles = new string[] { torznabQuery.SearchTerm };
|
|
|
|
var releases = await indexer.PerformQuery(torznabQuery);
|
|
|
|
Program.LoggerInstance.Debug(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName));
|
|
|
|
var severUrl = string.Format("{0}://{1}:{2}/", context.Request.Url.Scheme, context.Request.Url.Host, context.Request.Url.Port);
|
|
|
|
var resultPage = new ResultPage(new ChannelInfo
|
|
{
|
|
Title = indexer.DisplayName,
|
|
Description = indexer.DisplayDescription,
|
|
Link = indexer.SiteLink,
|
|
ImageUrl = new Uri(severUrl + "logos/" + indexerId + ".png"),
|
|
ImageTitle = indexer.DisplayName,
|
|
ImageLink = indexer.SiteLink,
|
|
ImageDescription = indexer.DisplayName
|
|
});
|
|
|
|
// add Jackett proxy to download links...
|
|
foreach (var release in releases)
|
|
{
|
|
if (release.Link == null || release.Link.Scheme == "magnet")
|
|
continue;
|
|
var originalLink = release.Link;
|
|
var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/download.torrent";
|
|
var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexerId, encodedLink);
|
|
release.Link = new Uri(proxyLink);
|
|
}
|
|
|
|
resultPage.Releases.AddRange(releases);
|
|
|
|
var xml = resultPage.ToXml(new Uri(severUrl));
|
|
|
|
var responseBytes = Encoding.UTF8.GetBytes(xml);
|
|
context.Response.ContentEncoding = Encoding.UTF8;
|
|
context.Response.ContentLength64 = responseBytes.LongLength;
|
|
context.Response.ContentType = "application/rss+xml";
|
|
await context.Response.OutputStream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|