Compare commits

...

25 Commits

Author SHA1 Message Date
Bogdan
1529527af9 Fixed: (Cardigann) Bump to v8 2023-01-15 18:20:31 -06:00
Bogdan
a11bd1c3c7 Fixed: (GreatPosterWall) Revert category to default to Movies 2023-01-14 22:20:06 -06:00
Bogdan
915b320a4a Fixed: (Shizaproject) Obsolete: Site unavailable 2023-01-14 18:43:32 -06:00
Bogdan
155f72cc45 Fixed: (AvistaZ/CinemaZ) Remove Music category mapping 2023-01-14 18:43:15 -06:00
Bogdan
3f73fec5c3 Fixed: (Rarbg) Add slash to IndexerUrl, increase RateLimit to 5s 2023-01-14 18:06:44 -06:00
Bogdan
8515623ceb Fixed: (SpeedApp) Fix cleanse token from response when it's the only field 2023-01-14 11:53:47 -06:00
Bogdan
963cddb582 Fixed: (SpeedCD) Add wildcard to season in tvsearch, add freeleech toggle, improve query selectors 2023-01-14 10:52:55 -06:00
Bogdan
ede323b8ed Fixed: (IPTorrents/SceneTime) Remove advanced from freeleech only setting 2023-01-13 22:32:37 -06:00
Bogdan
07d7fc98b0 Fixed: (Orpheus) Add remaster title and year to release title 2023-01-12 21:41:45 -06:00
Bogdan
1b78fd38db Fixed: (FileList) RequestGenerator refactoring, append slash to IndexerUrl 2023-01-12 21:41:13 -06:00
Bogdan
5a9d4d6280 Fixed: (UI) Transpile ES6 libs to fix issues on some browsers 2023-01-11 21:23:10 -06:00
Qstick
70685de5d2 Fixed: Correctly handle relative redirects with dot segments 2023-01-11 21:20:30 -06:00
Bogdan
9860183433 Fixed: (AvistaZ/Anthelion) Cleanse pid, api_key and token 2023-01-11 21:04:50 -06:00
Qstick
50331c61ae Fixed: Use selected BaseUrl for external link
Fixes #1310
2023-01-10 22:19:15 -06:00
Bogdan
bd3408f170 Fixed: (HD-Torrents) Add more alt domains, add Internal flag and fix Blu-Ray categories 2023-01-10 22:01:44 -06:00
Bogdan
c043bf8da9 Fixed: (HD-Space) Use torrent name as release title 2023-01-10 22:00:40 -06:00
Bakerboy448
ea3fa6f28d Fixed: (BeyondHD) Cleanse RSSKey on Grabs 2023-01-10 21:59:35 -06:00
Bogdan
8917347c0b Fixed: (IPTorrents) Fix pagination when limit is zero 2023-01-10 13:30:21 -06:00
Bogdan
2cebdf4a06 Fixed: (AvistaZ) Use different timezone offset than the rest 2023-01-09 20:21:56 -06:00
Bogdan
985110cfb9 Fixed: (ImmortalSeed) Updated categories, improved searchUrl build and fixed auth 2023-01-08 15:44:51 -06:00
Bogdan
de876247a3 Fixed: (MyAnonamouse) Added search type options, search in description/series/filenames as settings 2023-01-08 15:39:25 -06:00
Qstick
bad6c301f8 More CF cases from FlareSolverrSharp 2023-01-08 13:59:17 -06:00
Weblate
fc3b23394a Translated using Weblate (Ukrainian)
Currently translated at 67.5% (318 of 471 strings)

Translated using Weblate (Russian)

Currently translated at 76.4% (360 of 471 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Dutch)

Currently translated at 88.5% (417 of 471 strings)

Translated using Weblate (Finnish)

Currently translated at 99.5% (469 of 471 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (471 of 471 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (471 of 471 strings)

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Davide Palma <github@davidepalma.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Iagocds <cdsiago@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: andrey4korop <andrey999@i.ua>
Co-authored-by: verhese <sean.verheyen1@telenet.be>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2023-01-07 13:34:30 -06:00
Qstick
92c3656bad New: (HDSpace) Parse Genre and Description 2023-01-07 13:26:49 -06:00
Qstick
1acbee2a57 New: (Notification) Mailgun
Fixes #1297
2023-01-07 13:14:00 -06:00
55 changed files with 1103 additions and 615 deletions

View File

@@ -142,8 +142,8 @@ module.exports = (env) => {
module: {
rules: [
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
test: /\.jsx?$/,
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
use: [
{
loader: 'babel-loader',

View File

@@ -79,6 +79,7 @@ class IndexerIndexRow extends Component {
privacy,
priority,
status,
fields,
appProfile,
added,
capabilities,
@@ -96,6 +97,8 @@ class IndexerIndexRow extends Component {
isIndexerInfoModalOpen
} = this.state;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
return (
<>
{
@@ -250,7 +253,7 @@ class IndexerIndexRow extends Component {
className={styles.externalLink}
name={icons.EXTERNAL_LINK}
title={translate('Website')}
to={indexerUrls[0].replace('api.', '')}
to={baseUrl.replace('api.', '')}
/> : null
}
@@ -299,6 +302,7 @@ IndexerIndexRow.propTypes = {
name: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
redirect: PropTypes.bool.isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
appProfile: PropTypes.object.isRequired,
status: PropTypes.object,
capabilities: PropTypes.object,

View File

@@ -20,11 +20,14 @@ function IndexerInfoModalContent(props) {
encoding,
language,
indexerUrls,
fields,
protocol,
capabilities,
onModalClose
} = props;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -57,7 +60,7 @@ function IndexerInfoModalContent(props) {
/>
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
<Link to={baseUrl}>{baseUrl}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
@@ -114,6 +117,7 @@ IndexerInfoModalContent.propTypes = {
encoding: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired,
capabilities: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -11,7 +11,8 @@
"lint": "esprint check",
"lint-fix": "esprint check --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
"check-modules": "are-you-es5 check . -r"
},
"repository": "https://github.com/Prowlarr/Prowlarr",
"author": "Team Prowlarr",
@@ -93,6 +94,7 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.13",
"babel-loader": "9.1.0",
"babel-plugin-inline-classnames": "2.0.1",

View File

@@ -53,6 +53,26 @@ namespace NzbDrone.Common.Test.Http
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "./relative", "relative")]
[TestCase("/", "./relative", "/relative")]
[TestCase("/base", "./relative", "/relative")]
[TestCase("/base/sub", "./relative", "/base/relative")]
[TestCase("/base/sub/", "./relative", "/base/sub/relative")]
[TestCase("base/sub", "./relative", "base/relative")]
[TestCase("base/sub/", "./relative", "base/sub/relative")]
[TestCase("", "../relative", "relative")]
[TestCase("/", "../relative", "/relative")]
[TestCase("/base", "../relative", "/relative")]
[TestCase("/base/sub", "../relative", "/base/relative")]
[TestCase("/base/sub/", "../relative", "/base/sub/relative")]
[TestCase("base/sub", "../relative", "base/relative")]
[TestCase("base/sub/", "../relative", "base/sub/relative")]
public void should_combine_uri_with_dot_segment(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath) + new HttpUri(relativePath);
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "", "")]
[TestCase("/", "", "/")]
[TestCase("base", "", "base")]

View File

@@ -24,12 +24,16 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
// Indexer and Download Client Responses
// avistaz response
[TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")]
[TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")]
[TestCase(@"""token"":""2b51db35e1910123321025a12b9933d2""")]
// animebytes response
[TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")]

View File

@@ -76,6 +76,7 @@ namespace NzbDrone.Common.Http
get
{
var newUrl = Headers["Location"];
if (newUrl == null)
{
newUrl = Headers["Refresh"];

View File

@@ -166,6 +166,37 @@ namespace NzbDrone.Common.Http
return relativePath;
}
if (relativePath.StartsWith("./"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
}
if (relativePath.StartsWith("../"))
{
relativePath = relativePath.TrimStart('.').TrimStart('/');
var lastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, lastIndex) + "/";
}
var secondLastIndex = basePath.LastIndexOf("/");
if (lastIndex > 0)
{
basePath = basePath.Substring(0, secondLastIndex) + "/";
}
}
var baseSlashIndex = basePath.LastIndexOf('/');
if (baseSlashIndex >= 0)

View File

@@ -1,4 +1,3 @@
using System;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
@@ -8,10 +7,10 @@ namespace NzbDrone.Common.Instrumentation
{
public class CleanseLogMessage
{
private static readonly Regex[] CleansingRules = new[]
{
private static readonly Regex[] CleansingRules =
{
// Url
new Regex(@"(?<=[?&: ;])(apikey|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?&: ;])(apikey|api_key|(?:(?:access|api)[-_]?)?token|pass(?:key|wd)?|auth|authkey|user|u?id|api|[a-z_]*apikey|account|pid|pwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"rss\.torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -21,6 +20,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// UNIT3D
new Regex(@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
@@ -58,9 +58,10 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"(?:avistaz|exoticaz|cinemaz|privatehd)\.[a-z]{2,3}/rss/download/(?<secret>[^&=]+?)/(?<secret>[^&=]+?)\.torrent", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:animebytes)\.[a-z]{2,3}/torrent/[0-9]+/download/(?<secret>[^&=]+?)[""]", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""info_hash"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""token"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""pass[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@",""rss[- _]?key"":""(?<secret>[^&=]+?)"",", RegexOptions.Compiled | RegexOptions.IgnoreCase),
};
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-15 04:26:21"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21"));
torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -21,10 +21,15 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "FileList",
Settings = new FileListSettings() { Username = "someuser", Passkey = "somepass" }
Settings = new FileListSettings
{
BaseUrl = "https://filelist.io/",
Username = "someuser",
Passkey = "somepass"
}
};
}
@@ -35,9 +40,9 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), recentFeed)));
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(4);
releases.First().Should().BeOfType<TorrentInfo>();
@@ -50,12 +55,14 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19").ToUniversalTime());
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(2 + 12);
torrentInfo.Seeders.Should().Be(12);
releases.Any(t => t.IndexerFlags.Contains(IndexerFlag.Internal)).Should().Be(true);
}
}
}

View File

@@ -16,34 +16,35 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[SetUp]
public void Setup()
{
Subject.Settings = new FileListSettings()
Subject.Settings = new FileListSettings
{
BaseUrl = "https://filelist.io/",
Passkey = "abcd",
Username = "somename",
BaseUrl = "https://filelist.io"
Username = "somename"
};
Subject.Capabilities = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.FreeLeech
IndexerFlag.FreeLeech,
IndexerFlag.Internal,
}
};
@@ -53,7 +54,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
_movieSearchCriteria = new MovieSearchCriteria
{
SearchTerm = "Star Wars",
Categories = new int[] { 2000 }
Categories = new[] { 2000 }
};
}
@@ -65,13 +66,13 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
[Test]
public void should_use_categories_for_feed()
{
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new int[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
var results = Subject.GetSearchRequests(new MovieSearchCriteria { Categories = new[] { NewznabStandardCategory.MoviesSD.Id, NewznabStandardCategory.MoviesDVD.Id } });
results.GetAllTiers().Should().HaveCount(1);
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("&category=1,2&");
page.Url.Query.Should().Contain("&category=1%2C2");
}
[Test]
@@ -100,7 +101,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
var page = results.GetAllTiers().First().First();
page.Url.Query.Should().Contain("type=name");
page.Url.Query.Should().Contain("query=Star Wars");
page.Url.Query.Should().Contain("query=Star+Wars");
}
}
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
Subject.Definition = new IndexerDefinition()
{
Name = "Orpheus",
Settings = new OrpheusSettings() { Apikey = "somekey" }
Settings = new OrpheusSettings { Apikey = "somekey" }
};
}
@@ -37,14 +37,14 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(65);
releases.First().Should().BeOfType<GazelleInfo>();
var torrentInfo = releases.First() as GazelleInfo;
torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [MP3 V2 (VBR)] [BD]");
torrentInfo.Title.Should().Be("The Beatles - Abbey Road (1969) [2.0 Mix 2019] [MP3 V2 (VBR)] [BD]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://orpheus.network/ajax.php?action=download&id=1902448");
torrentInfo.InfoUrl.Should().Be("https://orpheus.network/torrents.php?id=466&torrentid=1902448");

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(025)]
public class speedcd_userpasssettings_to_speedcdsettings : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "SpeedCDSettings" }).Where(new { Implementation = "SpeedCD" });
}
}
}

View File

@@ -29,7 +29,11 @@ namespace NzbDrone.Core.Http.CloudFlare
response.StatusCode.Equals(HttpStatusCode.Forbidden))
{
var responseHtml = response.Content;
if (responseHtml.Contains("<title>Just a moment...") || responseHtml.Contains("<title>DDOS-GUARD"))
if (responseHtml.Contains("<title>Just a moment...</title>") ||
responseHtml.Contains("<title>Access denied</title>") ||
responseHtml.Contains("<title>Attention Required! | Cloudflare</title>") ||
responseHtml.Trim().Equals("error code: 1020") ||
responseHtml.Contains("<title>DDOS-GUARD</title>"))
{
return true;
}

View File

@@ -29,7 +29,7 @@ namespace NzbDrone.Core.IndexerVersions
/* Update Service will fall back if version # does not exist for an indexer per Ta */
private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 7;
private const int DEFINITION_VERSION = 8;
//Used when moving yml to C#
private readonly List<string> _defintionBlocklist = new List<string>()

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Avistaz;
using NzbDrone.Core.Messaging.Events;
@@ -10,18 +9,23 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AvistaZ : AvistazBase
{
public override string Name => "AvistaZ";
public override string[] IndexerUrls => new string[] { "https://avistaz.to/" };
public override string[] IndexerUrls => new[] { "https://avistaz.to/" };
public override string Description => "Aka AsiaTorrents";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public AvistaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public AvistaZ(IIndexerRepository indexerRepository,
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
return new AvistazRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
@@ -30,18 +34,23 @@ namespace NzbDrone.Core.Indexers.Definitions
};
}
public override IParseIndexerResponse GetParser()
{
return new AvistaZParser();
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
}
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
@@ -52,9 +61,13 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
return caps;
}
}
public class AvistaZParser : AvistazParserBase
{
protected override string TimezoneOffset => "+01:00";
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

View File

@@ -13,12 +13,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public abstract class AvistazBase : TorrentIndexerBase<AvistazSettings>
{
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string[] IndexerUrls => new string[] { "" };
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities();
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
private IIndexerRepository _indexerRepository;
public AvistazBase(IIndexerRepository indexerRepository,
@@ -34,7 +33,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
return new AvistazRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
@@ -45,14 +44,12 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public override IParseIndexerResponse GetParser()
{
return new AvistazParser();
return new AvistazParserBase();
}
protected virtual IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities();
return caps;
return new IndexerCapabilities();
}
protected override async Task DoLogin()

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions;
@@ -10,13 +11,10 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public class AvistazParser : IParseIndexerResponse
public class AvistazParserBase : IParseIndexerResponse
{
private readonly HashSet<string> _hdResolutions = new HashSet<string> { "1080p", "1080i", "720p" };
public AvistazParser()
{
}
protected virtual string TimezoneOffset => "-05:00"; // Avistaz does not specify a timezone & returns server time
private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" };
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -61,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
InfoUrl = details,
Guid = details,
Categories = cats,
PublishDate = DateTime.Parse(row.CreatedAt + "-05:00").ToUniversalTime(), // Avistaz does not specify a timezone & returns server time
PublishDate = DateTime.Parse($"{row.CreatedAt} {TimezoneOffset}", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
Size = row.FileSize,
Files = row.FileCount,
Grabs = row.Completed,

View File

@@ -12,17 +12,12 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public class AvistazRequestGenerator : IIndexerRequestGenerator
{
public AvistazSettings Settings { get; set; }
public IDictionary<string, string> AuthCookieCache { get; set; }
public IIndexerHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; }
protected virtual string SearchUrl => Settings.BaseUrl + "api/v1/jackett/torrents";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
protected virtual string SearchUrl => Settings.BaseUrl + "api/v1/jackett/torrents";
// hook to adjust the search category
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories, string genre)
@@ -45,7 +40,8 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
}
// resolution filter to improve the search
if (!categories.Contains(NewznabStandardCategory.Movies.Id) && !categories.Contains(NewznabStandardCategory.TV.Id) &&
if (!categories.Contains(NewznabStandardCategory.Movies.Id) &&
!categories.Contains(NewznabStandardCategory.TV.Id) &&
!categories.Contains(NewznabStandardCategory.Audio.Id))
{
if (categories.Contains(NewznabStandardCategory.MoviesUHD.Id) || categories.Contains(NewznabStandardCategory.TVUHD.Id))

View File

@@ -2,7 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using AngleSharp.Dom;
@@ -680,6 +680,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
case "urlencode":
data = data.UrlEncode(_encoding);
break;
case "htmldecode":
data = WebUtility.HtmlDecode(data);
break;
case "htmlencode":
data = WebUtility.HtmlEncode(data);
break;
case "timeago":
case "reltime":
data = DateTimeUtil.FromTimeAgo(data).ToString(DateTimeUtil.Rfc1123ZPattern);

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Avistaz;
using NzbDrone.Core.Messaging.Events;
@@ -10,18 +9,23 @@ namespace NzbDrone.Core.Indexers.Definitions
public class CinemaZ : AvistazBase
{
public override string Name => "CinemaZ";
public override string[] IndexerUrls => new string[] { "https://cinemaz.to/" };
public override string[] IndexerUrls => new[] { "https://cinemaz.to/" };
public override string Description => "CinemaZ (EuTorrents) is a Private Torrent Tracker for FOREIGN NON-ASIAN MOVIES.";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public CinemaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public CinemaZ(IIndexerRepository indexerRepository,
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
return new AvistazRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
@@ -52,7 +56,6 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
return caps;
}

View File

@@ -10,18 +10,23 @@ namespace NzbDrone.Core.Indexers.Definitions
public class ExoticaZ : AvistazBase
{
public override string Name => "ExoticaZ";
public override string[] IndexerUrls => new string[] { "https://exoticaz.to/" };
public override string[] IndexerUrls => new[] { "https://exoticaz.to/" };
public override string Description => "ExoticaZ (YourExotic) is a Private Torrent Tracker for 3X";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public ExoticaZ(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public ExoticaZ(IIndexerRepository indexerRepository,
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
return new AvistazRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
@@ -52,7 +57,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
public class ExoticaZParser : AvistazParser
public class ExoticaZParser : AvistazParserBase
{
private readonly IndexerCapabilitiesCategories _categories;

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
@@ -9,7 +8,8 @@ namespace NzbDrone.Core.Indexers.FileList
public class FileList : TorrentIndexerBase<FileListSettings>
{
public override string Name => "FileList.io";
public override string[] IndexerUrls => new string[] { "https://filelist.io" };
public override string[] IndexerUrls => new[] { "https://filelist.io/" };
public override string[] LegacyUrls => new[] { "https://filelist.io" };
public override string Description => "FileList (FL) is a ROMANIAN Private Torrent Tracker for 0DAY / GENERAL";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@@ -18,14 +18,18 @@ namespace NzbDrone.Core.Indexers.FileList
public override bool SupportsRedirect => true;
public override IndexerCapabilities Capabilities => SetCapabilities();
public FileList(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public FileList(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new FileListRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new FileListRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -38,21 +42,21 @@ namespace NzbDrone.Core.Indexers.FileList
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.ImdbId, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
},
{
BookSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.Internal,

View File

@@ -1,4 +1,3 @@
using System;
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.FileList
@@ -24,5 +23,7 @@ namespace NzbDrone.Core.Indexers.FileList
[JsonProperty(PropertyName = "upload_date")]
public string UploadDate { get; set; }
public string Category { get; set; }
[JsonProperty(PropertyName = "small_description")]
public string SmallDescription { get; set; }
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using NzbDrone.Common.Http;
@@ -25,9 +27,12 @@ namespace NzbDrone.Core.Indexers.FileList
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse,
"Unexpected response status {0} code from API request",
indexerResponse.HttpResponse.StatusCode);
throw new IndexerException(indexerResponse, "Unexpected response status {0} code from API request", indexerResponse.HttpResponse.StatusCode);
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var queryResults = JsonConvert.DeserializeObject<List<FileListTorrent>>(indexerResponse.Content);
@@ -49,10 +54,10 @@ namespace NzbDrone.Core.Indexers.FileList
imdbId = int.Parse(result.ImdbId.Substring(2));
}
var downloadVolumeFactor = result.FreeLeech == true ? 0 : 1;
var uploadVolumeFactor = result.DoubleUp == true ? 2 : 1;
var downloadVolumeFactor = result.FreeLeech ? 0 : 1;
var uploadVolumeFactor = result.DoubleUp ? 2 : 1;
torrentInfos.Add(new TorrentInfo()
torrentInfos.Add(new TorrentInfo
{
Guid = string.Format("FileList-{0}", id),
Title = result.Name,
@@ -62,7 +67,9 @@ namespace NzbDrone.Core.Indexers.FileList
InfoUrl = GetInfoUrl(id),
Seeders = result.Seeders,
Peers = result.Leechers + result.Seeders,
PublishDate = DateTime.Parse(result.UploadDate + " +0200"),
PublishDate = DateTime.Parse(result.UploadDate + " +0200", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
Description = result.SmallDescription,
Genres = result.SmallDescription.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
ImdbId = imdbId,
IndexerFlags = flags,
Files = (int)result.Files,
@@ -70,7 +77,7 @@ namespace NzbDrone.Core.Indexers.FileList
DownloadVolumeFactor = downloadVolumeFactor,
UploadVolumeFactor = uploadVolumeFactor,
MinimumRatio = 1,
MinimumSeedTime = 172800, //48 hours
MinimumSeedTime = 172800, // 48 hours
});
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.FileList
{
@@ -13,60 +15,74 @@ namespace NzbDrone.Core.Indexers.FileList
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetDefaultParameters();
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() || searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
parameters.Add("action", "search-torrents");
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
parameters.Add("type", "imdb");
parameters.Add("query", searchCriteria.FullImdbId);
}
else if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
parameters.Add("type", "name");
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
}
if (searchCriteria.Season.HasValue)
{
parameters.Add("season", searchCriteria.Season.ToString());
parameters.Add("episode", searchCriteria.Episode);
}
}
pageableRequests.Add(GetRequest(searchCriteria, parameters));
return pageableRequests;
}
public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetDefaultParameters();
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=imdb&query={0}", searchCriteria.FullImdbId)));
parameters.Add("action", "search-torrents");
parameters.Add("type", "imdb");
parameters.Add("query", searchCriteria.FullImdbId);
}
else if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
var titleYearSearchQuery = string.Format("{0}", searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=name&query={0}", titleYearSearchQuery.Trim())));
}
else
{
pageableRequests.Add(GetRequest("latest-torrents", searchCriteria.Categories, ""));
parameters.Add("action", "search-torrents");
parameters.Add("type", "name");
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
}
pageableRequests.Add(GetRequest(searchCriteria, parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetDefaultParameters();
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
var titleYearSearchQuery = string.Format("{0}", searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=name&query={0}", titleYearSearchQuery.Trim())));
}
else
{
pageableRequests.Add(GetRequest("latest-torrents", searchCriteria.Categories, ""));
parameters.Add("action", "search-torrents");
parameters.Add("type", "name");
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
}
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=imdb&query={0}&season={1}&episode={2}", searchCriteria.FullImdbId, searchCriteria.Season, searchCriteria.Episode)));
}
else if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
var titleYearSearchQuery = string.Format("{0}", searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=name&query={0}&season={1}&episode={2}", titleYearSearchQuery.Trim(), searchCriteria.Season, searchCriteria.Episode)));
}
else
{
pageableRequests.Add(GetRequest("latest-torrents", searchCriteria.Categories, ""));
}
pageableRequests.Add(GetRequest(searchCriteria, parameters));
return pageableRequests;
}
@@ -74,47 +90,65 @@ namespace NzbDrone.Core.Indexers.FileList
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetDefaultParameters();
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
var titleYearSearchQuery = string.Format("{0}", searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=name&query={0}", titleYearSearchQuery.Trim())));
}
else
{
pageableRequests.Add(GetRequest("latest-torrents", searchCriteria.Categories, ""));
parameters.Add("action", "search-torrents");
parameters.Add("type", "name");
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
}
pageableRequests.Add(GetRequest(searchCriteria, parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetDefaultParameters();
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
var titleYearSearchQuery = string.Format("{0}", searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest("search-torrents", searchCriteria.Categories, string.Format("&type=name&query={0}", titleYearSearchQuery.Trim())));
}
else
{
pageableRequests.Add(GetRequest("latest-torrents", searchCriteria.Categories, ""));
parameters.Add("action", "search-torrents");
parameters.Add("type", "name");
parameters.Add("query", searchCriteria.SanitizedSearchTerm.Trim());
}
pageableRequests.Add(GetRequest(searchCriteria, parameters));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRequest(string searchType, int[] categories, string parameters)
private IEnumerable<IndexerRequest> GetRequest(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
{
var categoriesQuery = string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
if (parameters.Get("action") is null)
{
parameters.Add("action", "latest-torrents");
}
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}&username={3}&passkey={4}{5}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.Username.Trim(), Settings.Passkey.Trim(), parameters);
parameters.Add("category", string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories)));
var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/api.php?{parameters.GetQueryString()}";
yield return new IndexerRequest(searchUrl, HttpAccept.Json);
}
private NameValueCollection GetDefaultParameters()
{
var parameters = new NameValueCollection
{
{ "username", Settings.Username.Trim() },
{ "passkey", Settings.Passkey.Trim() }
};
if (Settings.FreeleechOnly)
{
baseUrl += "&freeleech=1";
parameters.Add("freeleech", "1");
}
yield return new IndexerRequest(baseUrl, HttpAccept.Json);
return parameters;
}
}
}

View File

@@ -16,12 +16,7 @@ namespace NzbDrone.Core.Indexers.FileList
public class FileListSettings : NoAuthTorrentBaseSettings
{
private static readonly FileListSettingsValidator Validator = new FileListSettingsValidator();
public FileListSettings()
{
BaseUrl = "https://filelist.io";
}
private static readonly FileListSettingsValidator Validator = new ();
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Indexers.Definitions;
public class GreatPosterWall : Gazelle.Gazelle
{
public override string Name => "GreatPosterWall";
public override string[] IndexerUrls => new string[] { "https://greatposterwall.com/" };
public override string[] IndexerUrls => new[] { "https://greatposterwall.com/" };
public override string Description => "GreatPosterWall (GPW) is a CHINESE Private site for MOVIES";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@@ -30,7 +30,7 @@ public class GreatPosterWall : Gazelle.Gazelle
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new GreatPosterWallRequestGenerator()
return new GreatPosterWallRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
@@ -81,8 +81,6 @@ public class GreatPosterWallRequestGenerator : GazelleRequestGenerator
public class GreatPosterWallParser : GazelleParser
{
private readonly HashSet<string> _hdResolutions = new HashSet<string> { "1080p", "1080i", "720p" };
public GreatPosterWallParser(GazelleSettings settings, IndexerCapabilities capabilities)
: base(settings, capabilities)
{
@@ -121,7 +119,6 @@ public class GreatPosterWallParser : GazelleParser
foreach (var torrent in result.Torrents)
{
var infoUrl = GetInfoUrl(result.GroupId.ToString(), torrent.TorrentId);
var time = DateTime.SpecifyKind(torrent.Time, DateTimeKind.Unspecified);
var release = new GazelleInfo
@@ -134,7 +131,7 @@ public class GreatPosterWallParser : GazelleParser
PosterUrl = GetPosterUrl(result.Cover),
DownloadUrl = GetDownloadUrl(torrent.TorrentId, torrent.CanUseToken),
PublishDate = new DateTimeOffset(time, TimeSpan.FromHours(8)).UtcDateTime, // Time is Chinese Time, add 8 hours difference from UTC
Categories = ParseCategories(torrent),
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies },
Size = torrent.Size,
Seeders = torrent.Seeders,
Peers = torrent.Seeders + torrent.Leechers,
@@ -181,7 +178,7 @@ public class GreatPosterWallParser : GazelleParser
.ToArray();
}
protected string GetDownloadUrl(int torrentId, bool canUseToken)
private string GetDownloadUrl(int torrentId, bool canUseToken)
{
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/torrents.php")
@@ -191,20 +188,6 @@ public class GreatPosterWallParser : GazelleParser
return url.FullUri;
}
protected virtual List<IndexerCategory> ParseCategories(GreatPosterWallTorrent torrent)
{
var cats = new List<IndexerCategory>();
cats.Add(torrent.Resolution switch
{
var res when _hdResolutions.Contains(res) => NewznabStandardCategory.MoviesHD,
"2160p" => NewznabStandardCategory.MoviesUHD,
_ => NewznabStandardCategory.MoviesSD
});
return cats;
}
}
public class GreatPosterWallResponse

View File

@@ -7,10 +7,9 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
@@ -18,21 +17,20 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class HDSpace : TorrentIndexerBase<UserPassTorrentBaseSettings>
{
public override string Name => "HD-Space";
public override string[] IndexerUrls => new string[] { "https://hd-space.org/" };
private string LoginUrl => Settings.BaseUrl + "index.php?page=login";
public override string[] IndexerUrls => new[] { "https://hd-space.org/" };
public override string Description => "HD-Space (HDS) is a Private Torrent Tracker for HD MOVIES / TV";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
private string LoginUrl => Settings.BaseUrl + "index.php?page=login";
public HDSpace(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
@@ -41,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new HDSpaceRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new HDSpaceRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -56,10 +54,10 @@ namespace NzbDrone.Core.Indexers.Definitions
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true
AllowAutoRedirect = true,
Method = HttpMethod.Post
};
requestBuilder.Method = HttpMethod.Post;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = Cookies;
@@ -70,7 +68,7 @@ namespace NzbDrone.Core.Indexers.Definitions
.AddFormParameter("uid", Settings.Username)
.AddFormParameter("pwd", Settings.Password)
.SetCookies(loginPage.GetCookies())
.SetHeader("Content-Type", "multipart/form-data")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", LoginUrl)
.Build();
@@ -108,17 +106,17 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
}
{
MusicSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Movie / Blu-ray");
@@ -155,10 +153,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public HDSpaceRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdb = null)
{
var searchUrl = string.Format("{0}/index.php?page=torrents&", Settings.BaseUrl.TrimEnd('/'));
@@ -249,7 +243,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var resultParser = new HtmlParser();
var searchResultDocument = resultParser.ParseDocument(indexerResponse.Content);
var rows = searchResultDocument.QuerySelectorAll("table.lista > tbody > tr");
var rows = searchResultDocument.QuerySelectorAll("div#bodyarea table.lista:not(:contains(\"Our Team Recommend\")) > tbody > tr:has(a[href^=\"index.php?page=torrent-details&id=\"])");
foreach (var row in rows)
{
@@ -264,30 +258,46 @@ namespace NzbDrone.Core.Indexers.Definitions
release.MinimumRatio = 1;
release.MinimumSeedTime = 86400; // 24 hours
var qLink = row.Children[1].FirstElementChild;
release.Title = qLink.TextContent.Trim();
release.InfoUrl = _settings.BaseUrl + qLink.GetAttribute("href");
var qLink = row.QuerySelector("td:nth-child(2) a[href^=\"index.php?page=torrent-details&id=\"]");
release.Title = qLink?.TextContent.Trim();
release.InfoUrl = _settings.BaseUrl + qLink?.GetAttribute("href");
release.Guid = release.InfoUrl;
var imdbLink = row.Children[1].QuerySelector("a[href*=imdb]");
var downloadUrl = row.QuerySelector("td:nth-child(4) a[href^=\"download.php?id=\"]")?.GetAttribute("href");
release.DownloadUrl = _settings.BaseUrl + downloadUrl;
// Use the torrent filename as release title
var torrentTitle = ParseUtil.GetArgumentFromQueryString(downloadUrl, "f")?
.Replace("&amp;", "&")
.Replace("&#039;", "'")
.Replace(".torrent", "")
.Trim();
if (torrentTitle.IsNotNullOrWhiteSpace())
{
release.Title = torrentTitle;
}
var qGenres = row.QuerySelector("td:nth-child(2) span[style=\"color: #000000 \"]");
if (qGenres != null)
{
var description = qGenres.TextContent.Split('\xA0').Last().Replace(" ", "");
release.Description = description;
release.Genres = description.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
}
var imdbLink = row.QuerySelector("td:nth-child(2) a[href*=imdb]");
if (imdbLink != null)
{
release.ImdbId = ParseUtil.GetImdbID(imdbLink.GetAttribute("href").Split('/').Last()).GetValueOrDefault();
}
var qDownload = row.Children[3].FirstElementChild;
release.DownloadUrl = _settings.BaseUrl + qDownload.GetAttribute("href");
var dateStr = row.Children[4].TextContent.Trim();
//"July 11, 2015, 13:34:09", "Today|Yesterday at 20:04:23"
release.PublishDate = DateTimeUtil.FromUnknown(dateStr);
var sizeStr = row.Children[5].TextContent;
release.Size = ParseUtil.GetBytes(sizeStr);
release.Seeders = ParseUtil.CoerceInt(row.Children[7].TextContent);
release.Peers = ParseUtil.CoerceInt(row.Children[8].TextContent) + release.Seeders;
var grabs = row.QuerySelector("td:nth-child(10)").TextContent;
grabs = grabs.Replace("---", "0");
release.PublishDate = DateTimeUtil.FromUnknown(row.QuerySelector("td:nth-child(5)")?.TextContent.Trim());
release.Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(6)")?.TextContent.Trim());
release.Seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)")?.TextContent);
release.Peers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)")?.TextContent) + release.Seeders;
var grabs = row.QuerySelector("td:nth-child(10)")?.TextContent.Trim().Replace("---", "0");
release.Grabs = ParseUtil.CoerceInt(grabs);
if (row.QuerySelector("img[title=\"FreeLeech\"]") != null)
@@ -311,6 +321,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var qCat = row.QuerySelector("a[href^=\"index.php?page=torrents&category=\"]");
var cat = qCat.GetAttribute("href").Split('=')[2];
release.Categories = _categories.MapTrackerCatToNewznab(cat);
torrentInfos.Add(release);
}

View File

@@ -7,17 +7,14 @@ using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
@@ -25,12 +22,18 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "HD-Torrents";
public override string[] IndexerUrls => new string[] { "https://hdts.ru/", "https://hd-torrents.org/" };
public override string[] IndexerUrls => new[]
{
"https://hdts.ru/",
"https://hd-torrents.org/",
"https://hd-torrents.net/",
"https://hd-torrents.me/",
};
public override string Description => "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.";
private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
private string LoginUrl => Settings.BaseUrl + "login.php";
public HDTorrents(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
@@ -39,7 +42,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new HDTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new HDTorrentsRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -51,10 +54,10 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true
LogResponseContent = true,
Method = HttpMethod.Post
};
requestBuilder.Method = HttpMethod.Post;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var cookies = Cookies;
@@ -63,7 +66,8 @@ namespace NzbDrone.Core.Indexers.Definitions
var authLoginRequest = requestBuilder
.AddFormParameter("uid", Settings.Username)
.AddFormParameter("pwd", Settings.Password)
.SetHeader("Content-Type", "multipart/form-data")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", LoginUrl)
.Build();
var response = await ExecuteAuth(authLoginRequest);
@@ -76,12 +80,7 @@ namespace NzbDrone.Core.Indexers.Definitions
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (httpResponse.Content.Contains("Error:You're not authorized"))
{
return true;
}
return false;
return httpResponse.Content.Contains("Error:You're not authorized");
}
private IndexerCapabilities SetCapabilities()
@@ -89,21 +88,25 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
}
{
MusicSearchParam.Q
},
Flags = new List<IndexerFlag>
{
IndexerFlag.Internal
}
};
caps.Categories.AddCategoryMapping("70", NewznabStandardCategory.MoviesUHD, "Movie/UHD/Blu-Ray");
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.MoviesHD, "Movie/Blu-Ray");
caps.Categories.AddCategoryMapping("70", NewznabStandardCategory.MoviesBluRay, "Movie/UHD/Blu-Ray");
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.MoviesBluRay, "Movie/Blu-Ray");
caps.Categories.AddCategoryMapping("71", NewznabStandardCategory.MoviesUHD, "Movie/UHD/Remux");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.MoviesHD, "Movie/Remux");
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.MoviesHD, "Movie/1080p/i");
@@ -144,10 +147,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public HDTorrentsRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = Settings.BaseUrl + "torrents.php?" + string.Join(string.Empty, Capabilities.Categories.MapTorznabCapsToTrackers(categories).Select(cat => $"category[]={cat}&"));
@@ -224,7 +223,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private readonly IndexerCapabilitiesCategories _categories;
private readonly Regex _posterRegex = new Regex(@"src=\\'./([^']+)\\'", RegexOptions.IgnoreCase);
private readonly HashSet<string> _freeleechRanks = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
private readonly HashSet<string> _freeleechRanks = new (StringComparer.OrdinalIgnoreCase)
{
"VIP",
"Uploader",
@@ -248,7 +247,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var dom = parser.ParseDocument(indexerResponse.Content);
var userInfo = dom.QuerySelector("table.navus tr");
var userRank = userInfo.Children[1].TextContent.Replace("Rank:", string.Empty).Trim();
var userRank = userInfo?.Children[1].TextContent.Replace("Rank:", string.Empty).Trim();
var hasFreeleech = _freeleechRanks.Contains(userRank);
var rows = dom.QuerySelectorAll("table.mainblockcontenttt tr:has(td.mainblockcontent)");
@@ -259,7 +258,9 @@ namespace NzbDrone.Core.Indexers.Definitions
var details = new Uri(_settings.BaseUrl + mainLink.GetAttribute("href"));
var posterMatch = _posterRegex.Match(mainLink.GetAttribute("onmouseover"));
var poster = posterMatch.Success ? _settings.BaseUrl + posterMatch.Groups[1].Value.Replace("\\", "/") : null;
var poster = posterMatch.Success
? _settings.BaseUrl + posterMatch.Groups[1].Value.Replace("\\", "/")
: null;
var link = new Uri(_settings.BaseUrl + row.Children[4].FirstElementChild.GetAttribute("href"));
var description = row.Children[2].QuerySelector("span").TextContent;
@@ -329,6 +330,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var imdbLink = row.QuerySelector("a[href*=\"www.imdb.com/title/\"]")?.GetAttribute("href");
var imdb = !string.IsNullOrWhiteSpace(imdbLink) ? ParseUtil.GetImdbID(imdbLink) : null;
var flags = new HashSet<IndexerFlag>();
if (row.QuerySelector("img[src$=\"internal.png\"]") != null)
{
flags.Add(IndexerFlag.Internal);
}
var release = new TorrentInfo
{
Title = title,
@@ -343,6 +351,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Grabs = grabs,
Seeders = seeders,
Peers = peers,
IndexerFlags = flags,
DownloadVolumeFactor = dlVolumeFactor,
UploadVolumeFactor = upVolumeFactor,
MinimumRatio = 1,

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "IPTorrents";
public override string[] IndexerUrls => new string[]
public override string[] IndexerUrls => new[]
{
"https://iptorrents.com/",
"https://iptorrents.me/",
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new IPTorrentsRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new IPTorrentsRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -64,21 +64,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(72, NewznabStandardCategory.Movies, "Movies");
@@ -161,10 +161,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public IPTorrentsSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public IPTorrentsRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, int limit, int offset, string imdbId = null)
{
var searchUrl = Settings.BaseUrl + "t";
@@ -194,9 +190,16 @@ namespace NzbDrone.Core.Indexers.Definitions
qc.Add(cat, string.Empty);
}
qc.Add("p", ((offset / limit) + 1).ToString());
if (offset > 0 && limit > 0)
{
var page = (int)(offset / limit) + 1;
qc.Add("p", page.ToString());
}
searchUrl = searchUrl + "?" + qc.GetQueryString();
if (qc.Count > 0)
{
searchUrl += $"?{qc.GetQueryString()}";
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
@@ -356,14 +359,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public class IPTorrentsSettings : CookieTorrentBaseSettings
{
public IPTorrentsSettings()
{
}
[FieldDefinition(2, Label = "Cookie User-Agent", Type = FieldType.Textbox, HelpText = "User-Agent associated with cookie used from Browser")]
public string UserAgent { get; set; }
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Search Freeleech torrents only")]
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, HelpText = "Search Freeleech torrents only")]
public bool FreeLeechOnly { get; set; }
}
}

View File

@@ -1,16 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
@@ -18,21 +16,18 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class ImmortalSeed : TorrentIndexerBase<UserPassTorrentBaseSettings>
{
public override string Name => "ImmortalSeed";
public override string[] IndexerUrls => new string[] { "https://immortalseed.me/" };
public override string[] IndexerUrls => new[] { "https://immortalseed.me/" };
public override string Description => "ImmortalSeed (iS) is a Private Torrent Tracker for MOVIES / TV / GENERAL";
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
public ImmortalSeed(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
@@ -41,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new ImmortalSeedRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new ImmortalSeedRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -53,7 +48,8 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true
LogResponseContent = true,
AllowAutoRedirect = true
};
requestBuilder.Method = HttpMethod.Post;
@@ -65,12 +61,12 @@ namespace NzbDrone.Core.Indexers.Definitions
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.SetHeader("Content-Type", "multipart/form-data")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (!response.Content.Contains("You have successfully logged in"))
if (!response.Content.Contains("logout.php"))
{
throw new IndexerAuthException("ImmortalSeed Auth Failed");
}
@@ -83,12 +79,7 @@ namespace NzbDrone.Core.Indexers.Definitions
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (httpResponse.Content.Contains("You do not have permission to access this page."))
{
return true;
}
return false;
return httpResponse.Content.Contains("You do not have permission to access this page.");
}
private IndexerCapabilities SetCapabilities()
@@ -96,21 +87,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Other, "Nuked");
@@ -119,42 +110,45 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(35, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.TV, "Childrens/Cartoons");
caps.Categories.AddCategoryMapping(54, NewznabStandardCategory.TVDocumentary, "Documentary - HD");
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.BooksComics, "Comics");
caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.TVDocumentary, "Documentary - SD");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.BooksEBook, "Ebooks");
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.BooksComics, "Ebooks -- Comics");
caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.BooksMags, "Ebooks -- Magazines");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCGames, "Games");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.ConsoleXBox, "Games Xbox");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.PCGames, "Games-PC Rips");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.ConsolePS4, "Games-PSx");
caps.Categories.AddCategoryMapping(61, NewznabStandardCategory.ConsoleNDS, "Games -- Nintendo");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.PCGames, "Games -- PC");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.ConsolePS3, "Games -- Playstation");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.ConsoleXBox, "Games -- Xbox");
caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.PCMobileOther, "Mobile");
caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.PCMobileAndroid, "Mobile -- Android");
caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.PCMobileiOS, "Mobile -- IOS");
caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.PCMobileOther, "Mobile -- Windows");
caps.Categories.AddCategoryMapping(59, NewznabStandardCategory.MoviesUHD, "Movies-4k");
caps.Categories.AddCategoryMapping(60, NewznabStandardCategory.MoviesForeign, "Non-English 4k Movies");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesHD, "Movies HD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.MoviesForeign, "Movies HD Non-English");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.MoviesSD, "TS/CAM/PPV");
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.MoviesForeign, "Movies Low Def Non-English");
caps.Categories.AddCategoryMapping(60, NewznabStandardCategory.MoviesForeign, "Movies-4k -- Non-English");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesHD, "Movies-HD");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.MoviesForeign, "Movies-HD -- Non-English");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.MoviesSD, "Movies-Low Def");
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.MoviesForeign, "Movies-Low Def -- Non-English");
caps.Categories.AddCategoryMapping(62, NewznabStandardCategory.Movies, "Movies-Packs");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.MoviesSD, "Movies-SD");
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.MoviesForeign, "Movies SD Non-English");
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.MoviesForeign, "Movies-SD -- Non-English");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.AudioOther, "Music");
caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.AudioLossless, "FLAC");
caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.AudioMP3, "MP3");
caps.Categories.AddCategoryMapping(39, NewznabStandardCategory.AudioOther, "Music Other");
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.AudioVideo, "Music Video");
caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.AudioLossless, "Music -- FLAC");
caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.AudioMP3, "Music -- MP3");
caps.Categories.AddCategoryMapping(39, NewznabStandardCategory.AudioOther, "Music -- Other");
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.AudioVideo, "Music -- Video");
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.Other, "Other");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVSport, "Sports Tv");
caps.Categories.AddCategoryMapping(44, NewznabStandardCategory.TVSport, "Sports Fitness-Instructional");
caps.Categories.AddCategoryMapping(58, NewznabStandardCategory.TVSport, "Olympics");
caps.Categories.AddCategoryMapping(44, NewznabStandardCategory.TVSport, "Sports Tv -- Fitness-Instructional");
caps.Categories.AddCategoryMapping(58, NewznabStandardCategory.TVSport, "Sports Tv -- Olympics");
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.TVSD, "TV - 480p");
caps.Categories.AddCategoryMapping(64, NewznabStandardCategory.TVUHD, "TV - 4K");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TVHD, "TV - High Definition");
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.TVSD, "TV - Standard Definition - x264");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TVSD, "TV - Standard Definition - XviD");
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.TVSD, "TV SD - x264");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.TVSD, "TV SD - XviD");
caps.Categories.AddCategoryMapping(63, NewznabStandardCategory.TVUHD, "TV Season Packs - 4K");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVHD, "TV Season Packs - HD");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TVSD, "TV Season Packs - SD");
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.BooksEBook, "Ebooks");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.PCGames, "Games-PC ISO");
caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.BooksMags, "Magazines");
caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.PCMobileiOS, "IOS");
caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.PCMobileAndroid, "Android");
caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.PC0day, "Windows");
caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.TVDocumentary, "Documentary - SD");
return caps;
}
@@ -165,27 +159,33 @@ namespace NzbDrone.Core.Indexers.Definitions
public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public ImmortalSeedRequestGenerator()
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria)
{
}
var parameters = new NameValueCollection();
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = Settings.BaseUrl + "browse.php?";
var term = searchCriteria.SanitizedSearchTerm;
if (term.IsNotNullOrWhiteSpace())
{
searchUrl += string.Format("do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no", WebUtility.UrlEncode(term));
parameters.Add("do", "search");
parameters.Add("keywords", term.Trim());
parameters.Add("search_type", "t_name");
parameters.Add("category", "0");
parameters.Add("include_dead_torrents", "no");
}
if (categories != null && categories.Length > 0)
{
if (term.IsNotNullOrWhiteSpace())
{
searchUrl += "&";
}
var queryCats = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
searchUrl += "selectedcats2=" + string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(categories));
if (queryCats.Count > 0)
{
parameters.Add("selectedcats2", string.Join(",", queryCats));
}
var searchUrl = Settings.BaseUrl + "browse.php";
if (parameters.Count > 0)
{
searchUrl += $"?{parameters.GetQueryString()}";
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
@@ -196,8 +196,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}
@@ -205,8 +204,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}
@@ -214,8 +212,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}
@@ -223,8 +220,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}
@@ -232,8 +228,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}

View File

@@ -9,6 +9,8 @@ using System.Threading.Tasks;
using FluentValidation;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Annotations;
@@ -25,29 +27,30 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public class MyAnonamouse : TorrentIndexerBase<MyAnonamouseSettings>
{
private static readonly Regex TorrentIdRegex = new Regex(@"tor/download.php\?tid=(?<id>\d+)$");
public override string Name => "MyAnonamouse";
public override string[] IndexerUrls => new string[] { "https://www.myanonamouse.net/" };
public override string[] IndexerUrls => new[] { "https://www.myanonamouse.net/" };
public override string Description => "MyAnonaMouse (MAM) is a large ebook and audiobook tracker.";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override int PageSize => 100;
public override IndexerCapabilities Capabilities => SetCapabilities();
private readonly ICacheManager _cacheManager;
private static readonly Regex TorrentIdRegex = new Regex(@"tor/download.php\?tid=(?<id>\d+)$");
public MyAnonamouse(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public MyAnonamouse(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, ICacheManager cacheManager)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
_cacheManager = cacheManager;
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new MyAnonamouseRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new MyAnonamouseRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new MyAnonamouseParser(Settings, Capabilities.Categories);
return new MyAnonamouseParser(Settings, Capabilities.Categories, _httpClient, _cacheManager, _logger);
}
public override async Task<byte[]> Download(Uri link)
@@ -71,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var indexerReq = new IndexerRequest(freeleechRequest);
var response = await FetchIndexerResponse(indexerReq).ConfigureAwait(false);
var resource = Json.Deserialize<MyAnonamouseFreeleechResponse>(response.Content);
var resource = Json.Deserialize<MyAnonamouseBuyPersonalFreeleechResponse>(response.Content);
if (resource.Success)
{
@@ -101,9 +104,9 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping("13", NewznabStandardCategory.AudioAudiobook, "AudioBooks");
@@ -210,84 +213,97 @@ namespace NzbDrone.Core.Indexers.Definitions
public MyAnonamouseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public MyAnonamouseRequestGenerator()
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria)
{
}
var term = searchCriteria.SanitizedSearchTerm.Trim();
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var qParams = new NameValueCollection
var searchType = Settings.SearchType switch
{
{ "tor[text]", term },
{ "tor[srchIn][title]", "true" },
{ "tor[srchIn][author]", "true" },
{ "tor[searchType]", Settings.ExcludeVip ? "nVIP" : "all" }, // exclude VIP torrents
{ "tor[searchIn]", "torrents" },
{ "tor[hash]", "" },
{ "tor[sortType]", "default" },
{ "tor[startNumber]", "0" },
{ "thumbnails", "1" }, // gives links for thumbnail sized versions of their posters
//{ "posterLink", "1"}, // gives links for a full sized poster
//{ "dlLink", "1"}, // include the url to download the torrent
{ "description", "1" } // include the description
//{"bookmarks", "0"} // include if the item is bookmarked or not
(int)MyAnonamouseSearchType.Active => "active",
(int)MyAnonamouseSearchType.Freeleech => "fl",
(int)MyAnonamouseSearchType.FreeleechOrVip => "fl-VIP",
(int)MyAnonamouseSearchType.Vip => "VIP",
(int)MyAnonamouseSearchType.NotVip => "nVIP",
_ => "all"
};
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
var parameters = new NameValueCollection
{
{ "tor[text]", term },
{ "tor[searchType]", searchType },
{ "tor[srchIn][title]", "true" },
{ "tor[srchIn][author]", "true" },
{ "tor[srchIn][narrator]", "true" },
{ "tor[searchIn]", "torrents" },
{ "tor[sortType]", "default" },
{ "tor[perpage]", searchCriteria.Limit?.ToString() ?? "100" },
{ "tor[startNumber]", searchCriteria.Offset?.ToString() ?? "0" },
{ "thumbnails", "1" }, // gives links for thumbnail sized versions of their posters
{ "description", "1" } // include the description
};
if (Settings.SearchInDescription)
{
parameters.Add("tor[srchIn][description]", "true");
}
if (Settings.SearchInSeries)
{
parameters.Add("tor[srchIn][series]", "true");
}
if (Settings.SearchInFilenames)
{
parameters.Add("tor[srchIn][filenames]", "true");
}
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
if (catList.Any())
{
var index = 0;
foreach (var cat in catList)
{
qParams.Add("tor[cat][" + index + "]", cat);
parameters.Add("tor[cat][" + index + "]", cat);
index++;
}
}
else
{
qParams.Add("tor[cat][]", "0");
parameters.Add("tor[cat][]", "0");
}
var urlSearch = Settings.BaseUrl + "tor/js/loadSearchJSONbasic.php";
var searchUrl = Settings.BaseUrl + "tor/js/loadSearchJSONbasic.php";
if (qParams.Count > 0)
if (parameters.Count > 0)
{
urlSearch += $"?{qParams.GetQueryString()}";
searchUrl += $"?{parameters.GetQueryString()}";
}
var request = new IndexerRequest(urlSearch, HttpAccept.Json);
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}
@@ -296,7 +312,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria));
return pageableRequests;
}
@@ -309,11 +325,26 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly MyAnonamouseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly IIndexerHttpClient _httpClient;
private readonly Logger _logger;
private readonly ICached<string> _userClassCache;
private readonly HashSet<string> _vipFreeleechUserClasses = new (StringComparer.OrdinalIgnoreCase)
{
"VIP",
"Elite VIP",
};
public MyAnonamouseParser(MyAnonamouseSettings settings, IndexerCapabilitiesCategories categories)
public MyAnonamouseParser(MyAnonamouseSettings settings,
IndexerCapabilitiesCategories categories,
IIndexerHttpClient httpClient,
ICacheManager cacheManager,
Logger logger)
{
_settings = settings;
_categories = categories;
_httpClient = httpClient;
_logger = logger;
_userClassCache = cacheManager.GetCache<string>(GetType());
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -321,7 +352,7 @@ namespace NzbDrone.Core.Indexers.Definitions
// Throw auth errors here before we try to parse
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.Forbidden)
{
throw new IndexerAuthException("[403 Forbidden] - mam_session_id expired or invalid");
throw new IndexerAuthException("[403 Forbidden] - mam_id expired or invalid");
}
// Throw common http errors here before we try to parse
@@ -351,45 +382,46 @@ namespace NzbDrone.Core.Indexers.Definitions
return torrentInfos.ToArray();
}
var hasUserVip = HasUserVip();
foreach (var item in jsonResponse.Data)
{
//TODO shift to ReleaseInfo object initializer for consistency
var release = new TorrentInfo();
var id = item.Id;
release.Title = item.Title;
// release.Description = item.Value<string>("description");
var author = string.Empty;
release.Title = item.Title;
release.Description = item.Description;
if (item.AuthorInfo != null)
{
var authorInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(item.AuthorInfo);
author = authorInfo?.First().Value;
}
var author = authorInfo?.Take(5).Select(v => v.Value).Join(", ");
if (author != null)
{
release.Title += " by " + author;
if (author.IsNotNullOrWhiteSpace())
{
release.Title += " by " + author;
}
}
var flags = new List<string>();
var langCode = item.LangCode;
if (!string.IsNullOrEmpty(langCode))
var languageCode = item.LanguageCode;
if (!string.IsNullOrEmpty(languageCode))
{
flags.Add(langCode);
flags.Add(languageCode);
}
var filetype = item.Filetype;
if (!string.IsNullOrEmpty(filetype))
{
flags.Add(filetype);
flags.Add(filetype.ToUpper());
}
if (flags.Count > 0)
{
release.Title += " [" + string.Join(" / ", flags) + "]";
release.Title += " [" + flags.Join(" / ") + "]";
}
if (item.Vip)
@@ -397,26 +429,20 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Title += " [VIP]";
}
var category = item.Category;
release.Categories = _categories.MapTrackerCatToNewznab(category);
release.DownloadUrl = _settings.BaseUrl + "/tor/download.php?tid=" + id;
release.InfoUrl = _settings.BaseUrl + "/t/" + id;
release.DownloadUrl = _settings.BaseUrl + "tor/download.php?tid=" + id;
release.InfoUrl = _settings.BaseUrl + "t/" + id;
release.Guid = release.InfoUrl;
var dateStr = item.Added;
var dateTime = DateTime.ParseExact(dateStr, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
release.PublishDate = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc).ToLocalTime();
release.Categories = _categories.MapTrackerCatToNewznab(item.Category);
release.PublishDate = DateTime.ParseExact(item.Added, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
release.Grabs = item.Grabs;
release.Files = item.NumFiles;
release.Seeders = item.Seeders;
release.Peers = item.Leechers + release.Seeders;
var size = item.Size;
release.Size = ParseUtil.GetBytes(size);
release.DownloadVolumeFactor = item.Free ? 0 : 1;
release.Size = ParseUtil.GetBytes(item.Size);
release.DownloadVolumeFactor = item.Free ? 0 : hasUserVip && item.FreeVip ? 0 : 1;
release.UploadVolumeFactor = 1;
release.MinimumRatio = 1;
release.MinimumSeedTime = 259200; // 72 hours
torrentInfos.Add(release);
}
@@ -424,6 +450,33 @@ namespace NzbDrone.Core.Indexers.Definitions
return torrentInfos.ToArray();
}
private bool HasUserVip()
{
var cacheKey = "myanonamouse_user_class_" + _settings.ToJson().SHA256Hash();
var userClass = _userClassCache.Get(
cacheKey,
() =>
{
var request = new HttpRequestBuilder(_settings.BaseUrl.Trim('/'))
.Resource("/jsonLoad.php")
.Accept(HttpAccept.Json)
.Build();
_logger.Debug("Fetching user data: " + request.Url.FullUri);
request.Cookies.Add("mam_id", _settings.MamId);
var response = _httpClient.Get(request);
var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseUserDataResponse>(response.Content);
return jsonResponse.UserClass?.Trim();
},
TimeSpan.FromHours(1));
return _vipFreeleechUserClasses.Contains(userClass);
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
@@ -442,39 +495,68 @@ namespace NzbDrone.Core.Indexers.Definitions
public MyAnonamouseSettings()
{
MamId = "";
SearchType = (int)MyAnonamouseSearchType.All;
SearchInDescription = false;
SearchInSeries = false;
SearchInFilenames = false;
}
[FieldDefinition(2, Label = "Mam Id", HelpText = "Mam Session Id (Created Under Preferences -> Security)")]
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Mam Id", HelpText = "Mam Session Id (Created Under Preferences -> Security)")]
public string MamId { get; set; }
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "Exclude VIP", HelpText = "Exclude VIP Torrents from search results")]
public bool ExcludeVip { get; set; }
[FieldDefinition(3, Type = FieldType.Select, Label = "Search Type", SelectOptions = typeof(MyAnonamouseSearchType), HelpText = "Specify the desired search type")]
public int SearchType { get; set; }
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Freeleech", HelpText = "Use freeleech token for download")]
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Buy Freeleech Token", HelpText = "Buy personal freeleech token for download")]
public bool Freeleech { get; set; }
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "Search in description", HelpText = "Search text in the description")]
public bool SearchInDescription { get; set; }
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "Search in series", HelpText = "Search text in the series")]
public bool SearchInSeries { get; set; }
[FieldDefinition(7, Type = FieldType.Checkbox, Label = "Search in filenames", HelpText = "Search text in the filenames")]
public bool SearchInFilenames { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public enum MyAnonamouseSearchType
{
[FieldOption(Label="All torrents", Hint = "Search everything")]
All = 0,
[FieldOption(Label="Only active", Hint = "Last update had 1+ seeders")]
Active = 1,
[FieldOption(Label="Freeleech", Hint = "Freeleech torrents")]
Freeleech = 2,
[FieldOption(Label="Freeleech or VIP", Hint = "Freeleech or VIP torrents")]
FreeleechOrVip = 3,
[FieldOption(Label="VIP", Hint = "VIP torrents")]
Vip = 4,
[FieldOption(Label="Not VIP", Hint = "Torrents not VIP")]
NotVip = 5,
}
public class MyAnonamouseTorrent
{
public int Id { get; set; }
public string Title { get; set; }
[JsonProperty(PropertyName = "author_info")]
public string AuthorInfo { get; set; }
public string Description { get; set; }
[JsonProperty(PropertyName = "lang_code")]
public string LangCode { get; set; }
public string LanguageCode { get; set; }
public string Filetype { get; set; }
public bool Vip { get; set; }
public bool Free { get; set; }
[JsonProperty(PropertyName = "fl_vip")]
public bool FreeVip { get; set; }
public string Category { get; set; }
public string Added { get; set; }
[JsonProperty(PropertyName = "times_completed")]
public int Grabs { get; set; }
public int Seeders { get; set; }
@@ -489,9 +571,15 @@ namespace NzbDrone.Core.Indexers.Definitions
public List<MyAnonamouseTorrent> Data { get; set; }
}
public class MyAnonamouseFreeleechResponse
public class MyAnonamouseBuyPersonalFreeleechResponse
{
public bool Success { get; set; }
public string Error { get; set; }
}
public class MyAnonamouseUserDataResponse
{
[JsonProperty(PropertyName = "class")]
public string UserClass { get; set; }
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using FluentValidation;
using NLog;
@@ -25,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class Orpheus : TorrentIndexerBase<OrpheusSettings>
{
public override string Name => "Orpheus";
public override string[] IndexerUrls => new string[] { "https://orpheus.network/" };
public override string[] IndexerUrls => new[] { "https://orpheus.network/" };
public override string Description => "Orpheus (APOLLO) is a Private Torrent Tracker for MUSIC";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
@@ -39,7 +38,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new OrpheusRequestGenerator() { Settings = Settings, Capabilities = Capabilities, HttpClient = _httpClient };
return new OrpheusRequestGenerator { Settings = Settings, Capabilities = Capabilities, HttpClient = _httpClient };
}
public override IParseIndexerResponse GetParser()
@@ -52,13 +51,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year
},
{
MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio, "Music");
@@ -121,10 +120,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IIndexerHttpClient HttpClient { get; set; }
public OrpheusRequestGenerator()
{
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
@@ -260,24 +255,14 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var torrent in result.Torrents)
{
var id = torrent.TorrentId;
var artist = WebUtility.HtmlDecode(result.Artist);
var album = WebUtility.HtmlDecode(result.GroupName);
var title = $"{result.Artist} - {result.GroupName} ({result.GroupYear}) [{torrent.Format} {torrent.Encoding}] [{torrent.Media}]";
if (torrent.HasCue)
{
title += " [Cue]";
}
var title = GetTitle(result, torrent);
var infoUrl = GetInfoUrl(result.GroupId, id);
GazelleInfo release = new GazelleInfo()
var release = new GazelleInfo
{
Guid = infoUrl,
// Splice Title from info to avoid calling API again for every torrent.
Title = WebUtility.HtmlDecode(title),
Container = torrent.Encoding,
Codec = torrent.Format,
Size = long.Parse(torrent.Size),
@@ -314,7 +299,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var id = result.TorrentId;
var infoUrl = GetInfoUrl(result.GroupId, id);
GazelleInfo release = new GazelleInfo()
var release = new GazelleInfo
{
Guid = infoUrl,
Title = WebUtility.HtmlDecode(result.GroupName),
@@ -352,6 +337,25 @@ namespace NzbDrone.Core.Indexers.Definitions
.ToArray();
}
private string GetTitle(GazelleRelease result, GazelleTorrent torrent)
{
var title = $"{result.Artist} - {result.GroupName} ({result.GroupYear})";
if (torrent.RemasterTitle.IsNotNullOrWhiteSpace())
{
title += $" [{string.Format("{0} {1}", torrent.RemasterTitle, torrent.RemasterYear).Trim()}]";
}
title += $" [{torrent.Format} {torrent.Encoding}] [{torrent.Media}]";
if (torrent.HasCue)
{
title += " [Cue]";
}
return title;
}
private string GetDownloadUrl(int torrentId, bool canUseToken)
{
// AuthKey is required but not checked, just pass in a dummy variable
@@ -391,7 +395,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class OrpheusSettings : NoAuthTorrentBaseSettings
{
private static readonly OrpheusSettingsValidator Validator = new OrpheusSettingsValidator();
private static readonly OrpheusSettingsValidator Validator = new ();
public OrpheusSettings()
{

View File

@@ -6,21 +6,26 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
public class PrivateHD : Avistaz.AvistazBase
public class PrivateHD : AvistazBase
{
public override string Name => "PrivateHD";
public override string[] IndexerUrls => new string[] { "https://privatehd.to/" };
public override string[] IndexerUrls => new[] { "https://privatehd.to/" };
public override string Description => "PrivateHD is a Private Torrent Tracker for HD MOVIES / TV and the sister-site of AvistaZ, CinemaZ, ExoticaZ, and AnimeTorrents";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public PrivateHD(IIndexerRepository indexerRepository, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public PrivateHD(IIndexerRepository indexerRepository,
IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
return new AvistazRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
@@ -34,13 +39,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
}
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);

View File

@@ -17,19 +17,15 @@ namespace NzbDrone.Core.Indexers.Rarbg
{
public class Rarbg : TorrentIndexerBase<RarbgSettings>
{
private readonly IRarbgTokenProvider _tokenProvider;
public override string Name => "Rarbg";
public override string[] IndexerUrls => new string[] { "https://torrentapi.org" };
public override string[] IndexerUrls => new[] { "https://torrentapi.org/" };
public override string[] LegacyUrls => new[] { "https://torrentapi.org" };
public override string Description => "RARBG is a Public torrent site for MOVIES / TV / GENERAL";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override TimeSpan RateLimit => TimeSpan.FromSeconds(4);
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
private readonly IRarbgTokenProvider _tokenProvider;
public Rarbg(IRarbgTokenProvider tokenProvider, IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
@@ -44,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
public override IParseIndexerResponse GetParser()
{
return new RarbgParser(Capabilities);
return new RarbgParser(Capabilities, _logger);
}
private IndexerCapabilities SetCapabilities()
@@ -52,17 +48,17 @@ namespace NzbDrone.Core.Indexers.Rarbg
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
}
{
MusicSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.XXX, "XXX (18+)");
@@ -106,7 +102,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
if (jsonResponse.Resource.error_code.HasValue)
{
if (jsonResponse.Resource.error_code == 4 || jsonResponse.Resource.error_code == 2)
if (jsonResponse.Resource.error_code is 4 or 2)
{
_logger.Debug("Invalid or expired token, refreshing token from Rarbg");
_tokenProvider.ExpireToken(Settings);
@@ -118,7 +114,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
request.HttpRequest.Url = request.Url.SetQuery(qs.GetQueryString());
response = await FetchIndexerResponse(request);
}
else if (jsonResponse.Resource.error_code == 5)
else if (jsonResponse.Resource.error_code is 5)
{
_logger.Debug("Rarbg temp rate limit hit, retrying request");
response = await FetchIndexerResponse(request);
@@ -155,9 +151,9 @@ namespace NzbDrone.Core.Indexers.Rarbg
Settings.Validate().Filter("BaseUrl").ThrowOnError();
var request = new HttpRequestBuilder(Settings.BaseUrl.Trim('/'))
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
.Accept(HttpAccept.Json)
.Build();
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
.Accept(HttpAccept.Json)
.Build();
_httpClient.Get(request);
@@ -166,7 +162,8 @@ namespace NzbDrone.Core.Indexers.Rarbg
captchaToken = ""
};
}
else if (action == "getCaptchaCookie")
if (action == "getCaptchaCookie")
{
if (query["responseUrl"].IsNullOrWhiteSpace())
{
@@ -200,7 +197,8 @@ namespace NzbDrone.Core.Indexers.Rarbg
captchaToken = cfClearanceCookie
};
}
else if (action == "getUrls")
if (action == "getUrls")
{
var links = IndexerUrls;

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
@@ -14,10 +15,12 @@ namespace NzbDrone.Core.Indexers.Rarbg
private static readonly Regex RegexGuid = new Regex(@"^magnet:\?xt=urn:btih:([a-f0-9]+)", RegexOptions.Compiled);
private readonly IndexerCapabilities _capabilities;
private readonly Logger _logger;
public RarbgParser(IndexerCapabilities capabilities)
public RarbgParser(IndexerCapabilities capabilities, Logger logger)
{
_capabilities = capabilities;
_logger = logger;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -41,12 +44,19 @@ namespace NzbDrone.Core.Indexers.Rarbg
if (jsonResponse.Resource.error_code.HasValue)
{
if (jsonResponse.Resource.error_code == 20 || jsonResponse.Resource.error_code == 8
|| jsonResponse.Resource.error_code == 9 || jsonResponse.Resource.error_code == 10
|| jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.error_code == 13
|| jsonResponse.Resource.error_code == 14)
if (jsonResponse.Resource.error_code is 20 or 8 or 9 or 10 or 5 or 13 or 14)
{
// No results, rate limit, or imdbid/tvdb not found
var reason = $"{jsonResponse.Resource.error} ({jsonResponse.Resource.error_code})";
if (jsonResponse.Resource.rate_limit is 1)
{
_logger.Debug("No results due to rate limiting. Reason: {0}", reason);
}
else
{
_logger.Debug("No results or imdbid/tvdb not found. Reason: {0}", reason);
}
return results;
}

View File

@@ -22,12 +22,11 @@ namespace NzbDrone.Core.Indexers.Rarbg
private IEnumerable<IndexerRequest> GetRequest(string term, int[] categories, string imdbId = null, int? tmdbId = null, int? tvdbId = null)
{
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl.Trim('/'))
.Resource("/pubapi_v2.php")
.AddQueryParam("mode", "search")
.Accept(HttpAccept.Json);
requestBuilder.AddQueryParam("mode", "search");
if (imdbId.IsNotNullOrWhiteSpace())
{
requestBuilder.AddQueryParam("search_imdb", imdbId);

View File

@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
() =>
{
var requestBuilder = new HttpRequestBuilder(settings.BaseUrl.Trim('/'))
.WithRateLimit(3.0)
.WithRateLimit(5.0)
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
.Accept(HttpAccept.Json);

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -16,7 +15,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
@@ -38,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new SceneTimeRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new SceneTimeRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -56,21 +54,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.XXX, "Movies Adult");
@@ -112,10 +110,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public SceneTimeSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public SceneTimeRequestGenerator()
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var qc = new NameValueCollection
@@ -280,11 +274,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SceneTimeSettings : CookieTorrentBaseSettings
{
public SceneTimeSettings()
{
}
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Search Freeleech torrents only")]
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, HelpText = "Search Freeleech torrents only")]
public bool FreeLeechOnly { get; set; }
}
}

View File

@@ -19,6 +19,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Site unavailable")]
public class Shizaproject : TorrentIndexerBase<NoAuthTorrentBaseSettings>
{
public override string Name => "ShizaProject";

View File

@@ -7,7 +7,6 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using FluentValidation;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -19,14 +18,13 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
public class SpeedCD : TorrentIndexerBase<UserPassTorrentBaseSettings>
public class SpeedCD : TorrentIndexerBase<SpeedCDSettings>
{
public override string Name => "SpeedCD";
public override string[] IndexerUrls => new string[]
public override string[] IndexerUrls => new[]
{
"https://speed.cd/",
"https://speed.click/",
@@ -40,14 +38,18 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public SpeedCD(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
public SpeedCD(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new SpeedCDRequestGenerator() { Settings = Settings, Capabilities = Capabilities, Encoding = Encoding };
return new SpeedCDRequestGenerator { Settings = Settings, Capabilities = Capabilities, Encoding = Encoding };
}
public override IParseIndexerResponse GetParser()
@@ -113,12 +115,7 @@ namespace NzbDrone.Core.Indexers.Definitions
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
if (!httpResponse.Content.Contains("/browse.php"))
{
return true;
}
return false;
return !httpResponse.Content.Contains("/browse.php");
}
private IndexerCapabilities SetCapabilities()
@@ -126,21 +123,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesOther, "Movies/XviD");
@@ -182,17 +179,13 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SpeedCDRequestGenerator : IIndexerRequestGenerator
{
public UserPassTorrentBaseSettings Settings { get; set; }
public SpeedCDSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Encoding Encoding { get; set; }
public SpeedCDRequestGenerator()
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, bool deep = false)
{
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = string.Format("{0}/browse/", Settings.BaseUrl.TrimEnd('/'));
var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/browse/";
var qc = new List<string>();
@@ -202,18 +195,19 @@ namespace NzbDrone.Core.Indexers.Definitions
qc.Add(cat);
}
if (imdbId.IsNotNullOrWhiteSpace())
if (Settings.FreeleechOnly)
{
qc.Add("freeleech");
}
if (deep)
{
qc.Add("deep");
qc.Add("q");
qc.Add(imdbId);
}
else
{
qc.Add("q");
qc.Add(term.UrlEncode(Encoding));
}
qc.Add("q");
qc.Add(term.UrlEncode(Encoding));
searchUrl += string.Join("/", qc);
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
@@ -225,7 +219,14 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
var term = $"{searchCriteria.SanitizedSearchTerm}";
if (searchCriteria.FullImdbId.IsNotNullOrWhiteSpace())
{
term = $"{searchCriteria.FullImdbId}";
}
pageableRequests.Add(GetPagedRequests(term.Trim(), searchCriteria.Categories, searchCriteria.FullImdbId.IsNotNullOrWhiteSpace()));
return pageableRequests;
}
@@ -234,7 +235,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
@@ -243,7 +244,24 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
var term = $"{searchCriteria.SanitizedTvSearchString}";
if (searchCriteria.FullImdbId.IsNotNullOrWhiteSpace())
{
term = $"{searchCriteria.FullImdbId}";
if (searchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
{
term += $" {searchCriteria.EpisodeSearchString}";
}
if (searchCriteria.Season.HasValue)
{
term += "*";
}
}
pageableRequests.Add(GetPagedRequests(term.Trim(), searchCriteria.Categories, searchCriteria.FullImdbId.IsNotNullOrWhiteSpace()));
return pageableRequests;
}
@@ -252,7 +270,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
@@ -261,7 +279,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
@@ -272,10 +290,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SpeedCDParser : IParseIndexerResponse
{
private readonly UserPassTorrentBaseSettings _settings;
private readonly SpeedCDSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public SpeedCDParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
public SpeedCDParser(SpeedCDSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
@@ -291,28 +309,26 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var row in rows)
{
var cells = row.QuerySelectorAll("td");
var title = Regex.Replace(row.QuerySelector("td:nth-child(2) > div > a[href^=\"/t/\"]").TextContent, @"(?i:\[REQ\])", "").Trim(' ', '.');
var downloadUrl = new Uri(_settings.BaseUrl + row.QuerySelector("td:nth-child(4) a[href^=\"/download/\"]").GetAttribute("href").TrimStart('/'));
var infoUrl = new Uri(_settings.BaseUrl + row.QuerySelector("td:nth-child(2) > div > a[href^=\"/t/\"]").GetAttribute("href").TrimStart('/'));
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(6)").TextContent);
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)").TextContent);
var title = row.QuerySelector("td[class='lft'] > div > a").TextContent.Trim();
var link = new Uri(_settings.BaseUrl + row.QuerySelector("img[title='Download']").ParentElement.GetAttribute("href").TrimStart('/'));
var details = new Uri(_settings.BaseUrl + row.QuerySelector("td[class='lft'] > div > a").GetAttribute("href").TrimStart('/'));
var size = ParseUtil.GetBytes(cells[5].TextContent);
var grabs = ParseUtil.CoerceInt(cells[6].TextContent);
var seeders = ParseUtil.CoerceInt(cells[7].TextContent);
var leechers = ParseUtil.CoerceInt(cells[8].TextContent);
var pubDateStr = row.QuerySelector("span[class^='elapsedDate']").GetAttribute("title").Replace(" at", "");
var pubDateStr = row.QuerySelector("td:nth-child(2) span[class^=\"elapsedDate\"]").GetAttribute("title").Replace(" at", "");
var publishDate = DateTime.ParseExact(pubDateStr, "dddd, MMMM d, yyyy h:mmtt", CultureInfo.InvariantCulture);
var cat = row.QuerySelector("a").GetAttribute("href").Split('/').Last();
var downloadVolumeFactor = row.QuerySelector("span:contains(\"[Freeleech]\")") != null ? 0 : 1;
var cat = row.QuerySelector("td:nth-child(1) a").GetAttribute("href").Split('/').Last();
var downloadVolumeFactor = row.QuerySelector("td:nth-child(2) span:contains(\"[Freeleech]\")") != null ? 0 : 1;
var release = new TorrentInfo
{
Title = title,
DownloadUrl = link.AbsoluteUri,
Guid = link.AbsoluteUri,
InfoUrl = details.AbsoluteUri,
DownloadUrl = downloadUrl.AbsoluteUri,
Guid = infoUrl.AbsoluteUri,
InfoUrl = infoUrl.AbsoluteUri,
PublishDate = publishDate,
Categories = _categories.MapTrackerCatToNewznab(cat),
Size = size,
@@ -333,4 +349,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class SpeedCDSettings : UserPassTorrentBaseSettings
{
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
public bool FreeleechOnly { get; set; }
}
}

View File

@@ -466,5 +466,6 @@
"AreYouSureYouWantToDeleteCategory": "Haluatko varmasti poistaa kartoitetun kategorian?",
"DeleteClientCategory": "Poista lataustyökalukategoria",
"DownloadClientCategory": "Lataustyökalukategoria",
"MappedCategories": "Kartoitetut kategoriat"
"MappedCategories": "Kartoitetut kategoriat",
"AuthenticationRequired": "Todennus vaaditaan"
}

View File

@@ -469,5 +469,5 @@
"DownloadClientCategory": "Letöltési kliens kategória",
"AuthenticationRequired": "Azonosítás szükséges",
"AuthenticationRequiredHelpText": "Módosítsd, hogy mely kérésekhez van szükség hitelesítésre. Ne változtasson, hacsak nem érti a kockázatokat.",
"AuthenticationRequiredWarning": "A hitelesítés nélküli távoli hozzáférés megakadályozása érdekében a Prowlarrnak mostantól engedélyezni kell a hitelesítést. Opcionálisan letilthatja a helyi címekről történő hitelesítést."
"AuthenticationRequiredWarning": "A hitelesítés nélküli távoli hozzáférés megakadályozása érdekében a Prowlarrnak mostantól engedélyezni kell a hitelesítést. Konfigurálja a hitelesítési módszert és a hitelesítési adatokat. Opcionálisan letilthatja a helyi címekről történő hitelesítést. További információkért tekintsd meg a GYIK-et."
}

View File

@@ -154,7 +154,7 @@
"CertificateValidation": "Certificaat Validatie",
"BypassProxyForLocalAddresses": "Omzeil Proxy voor Lokale Adressen",
"Branch": "Branch",
"BindAddressHelpText": "Geldig IPv4 adres of '*' voor alle interfaces",
"BindAddressHelpText": "Geldig IP-adres, localhost of '*' voor alle interfaces",
"DeleteBackup": "Verwijder Veiligheidskopie",
"BackupIntervalHelpText": "Tussentijd voor automatische back-up",
"Backups": "Veiligheidskopieën",

View File

@@ -469,5 +469,5 @@
"MappedCategories": "Categorias Mapeadas",
"AuthenticationRequired": "Autenticação Requerida",
"AuthenticationRequiredHelpText": "Altera para quais solicitações a autenticação é necessária. Não mude a menos que você entenda os riscos.",
"AuthenticationRequiredWarning": "Para impedir o acesso remoto sem autenticação, o Prowlarr agora exige que a autenticação seja habilitada. Você pode, opcionalmente, desabilitar a autenticação de endereços locais."
"AuthenticationRequiredWarning": "Para impedir o acesso remoto sem autenticação, o Prowlarr agora exige que a autenticação seja habilitada. Configure seu método de autenticação e credenciais. Você pode, opcionalmente, desabilitar a autenticação de endereços locais. Consulte o FAQ para obter informações adicionais."
}

View File

@@ -99,7 +99,7 @@
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData Radarr",
"BeforeUpdate": "До обновления",
"BindAddress": "Привязать адрес",
"BindAddressHelpText": "Действительный IP4-адрес или '*' для всех интерфейсов",
"BindAddressHelpText": "Действительный IP-адрес, локальный адрес или '*' для всех интерфейсов",
"Branch": "Ветка",
"BranchUpdate": "Ветвь для обновления Radarr",
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",

View File

@@ -60,7 +60,7 @@
"ApplyTagsHelpTexts2": "Додати: додати теги до наявного списку тегів",
"AuthenticationMethodHelpText": "Для доступу до Radarr потрібні ім’я користувача та пароль",
"BackupFolderHelpText": "Відносні шляхи будуть у каталозі AppData Radarr",
"BindAddressHelpText": "Дійсна адреса IPv4 або '*' для всіх інтерфейсів",
"BindAddressHelpText": "Дійсна адреса IP або '*' для всіх інтерфейсів",
"BranchUpdate": "Гілка для оновлення Radarr",
"AllIndexersHiddenDueToFilter": "Всі фільми заховані відповідно до фільтра.",
"AnalyticsEnabledHelpText": "Надсилайте анонімну інформацію про використання та помилки на сервери Radarr. Це включає інформацію про ваш веб-переглядач, які сторінки Radarr WebUI ви використовуєте, звіти про помилки, а також версію ОС і часу виконання. Ми будемо використовувати цю інформацію, щоб визначити пріоритети функцій і виправлення помилок.",
@@ -212,7 +212,7 @@
"Exception": "Виняток",
"ExistingTag": "Існуючий тег",
"Failed": "Не вдалося",
"FeatureRequests": "Запити щодо функцій",
"FeatureRequests": "Майбутні запити",
"Events": "Події",
"Fixed": "Виправлено",
"Filters": "Фільтри",
@@ -293,7 +293,7 @@
"Enable": "Увімкнути",
"Filename": "Ім'я файлу",
"Host": "Хост",
"PriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Round-Robin використовується для клієнтів з однаковим пріоритетом.",
"PriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Круговий алгоритм використовується для клієнтів з таким же пріоритетом.",
"SSLCertPathHelpText": "Шлях до файлу pfx",
"SSLPort": "Порт SSL",
"Started": "Розпочато",
@@ -303,5 +303,19 @@
"Style": "Стиль",
"SuggestTranslationChange": "Запропонуйте зміну перекладу",
"TableOptionsColumnsMessage": "Виберіть, які стовпці відображаються та в якому порядку вони відображаються",
"SystemTimeCheckMessage": "Системний час вимкнено більш ніж на 1 день. Заплановані завдання можуть не працювати належним чином, доки час не буде виправлено"
"SystemTimeCheckMessage": "Системний час вимкнено більш ніж на 1 день. Заплановані завдання можуть не працювати належним чином, доки час не буде виправлено",
"OnGrab": "При захопленні",
"SSLCertPath": "Шлях сертифіката SSL",
"UI": "Інтерфейс користувача",
"Reddit": "Reddit",
"RSS": "RSS",
"Seeders": "Сиди",
"Wiki": "Wiki",
"Grabbed": "Захоплений",
"Logging": "Журналування",
"NetCore": ".NET",
"Peers": "Піри",
"Usenet": "Usenet",
"SSLCertPassword": "Пароль SSL сертифіката",
"MaintenanceRelease": "Випуск для обслуговування: виправлення помилок та інші покращення. Щоб отримати докладнішу інформацію, перегляньте історію фіксації Github"
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using FluentValidation.Results;
using NLog;
namespace NzbDrone.Core.Notifications.Mailgun
{
public class MailGun : NotificationBase<MailgunSettings>
{
private readonly IMailgunProxy _proxy;
private readonly Logger _logger;
public MailGun(IMailgunProxy proxy, Logger logger)
{
_proxy = proxy;
_logger = logger;
}
public override string Name => "Mailgun";
public override string Link => "https://mailgun.com";
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheckMessage)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheckMessage.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
try
{
const string title = "Test Notification";
const string body = "This is a test message from Prowlarr, though Mailgun.";
_proxy.SendNotification(title, body, Settings);
_logger.Info("Successsfully sent email though Mailgun.");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message though Mailgun.");
failures.Add(new ValidationFailure("", "Unable to send test message though Mailgun."));
}
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Mailgun
{
public class MailgunException : NzbDroneException
{
public MailgunException(string message)
: base(message)
{
}
public MailgunException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
}
}
}

View File

@@ -0,0 +1,68 @@
using System.Net;
using System.Net.Http;
using NLog;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Notifications.Mailgun
{
public interface IMailgunProxy
{
void SendNotification(string title, string message, MailgunSettings settings);
}
public class MailgunProxy : IMailgunProxy
{
private const string BaseUrlEu = "https://api.eu.mailgun.net/v3";
private const string BaseUrlUs = "https://api.mailgun.net/v3";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public MailgunProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, MailgunSettings settings)
{
try
{
var request = BuildRequest(settings, $"{settings.SenderDomain}/messages", HttpMethod.Post, title, message).Build();
_httpClient.Execute(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error("Unathorized - ApiKey is invalid");
throw new MailgunException("Unauthorized - ApiKey is invalid");
}
_logger.Error(ex, "Unable to connect to Mailgun. Status code: " + ex.Message);
throw new MailgunException("Unable to connect to Mailgun. Status code: {0}", ex);
}
}
private HttpRequestBuilder BuildRequest(MailgunSettings settings, string resource, HttpMethod method, string messageSubject, string messageBody)
{
var loginCredentials = new NetworkCredential("api", settings.ApiKey);
var url = settings.UseEuEndpoint ? BaseUrlEu : BaseUrlUs;
var requestBuilder = new HttpRequestBuilder(url).Resource(resource);
requestBuilder.Method = method;
requestBuilder.NetworkCredential = loginCredentials;
requestBuilder.AddFormParameter("from", $"{settings.From}");
foreach (var recipient in settings.Recipients)
{
requestBuilder.AddFormParameter("to", $"{recipient}");
}
requestBuilder.AddFormParameter("subject", $"{messageSubject}");
requestBuilder.AddFormParameter("text", $"{messageBody}");
return requestBuilder;
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Mailgun
{
public class MailGunSettingsValidator : AbstractValidator<MailgunSettings>
{
public MailGunSettingsValidator()
{
RuleFor(c => c.ApiKey).NotEmpty();
RuleFor(c => c.From).NotEmpty();
RuleFor(c => c.Recipients).NotEmpty();
}
}
public class MailgunSettings : IProviderConfig
{
private static readonly MailGunSettingsValidator Validator = new MailGunSettingsValidator();
public MailgunSettings()
{
Recipients = Array.Empty<string>();
}
[FieldDefinition(0, Label = "API Key", HelpText = "The API key generated from MailGun")]
public string ApiKey { get; set; }
[FieldDefinition(1, Label = "Use EU Endpoint?", HelpText = "Use the EU MailGun endpoint", Type = FieldType.Checkbox)]
public bool UseEuEndpoint { get; set; }
[FieldDefinition(2, Label = "From Address")]
public string From { get; set; }
[FieldDefinition(3, Label = "Sender Domain")]
public string SenderDomain { get; set; }
[FieldDefinition(4, Label = "Recipient Address(es)", Type = FieldType.Tag, Placeholder = "example@email.com,example1@email.com")]
public IEnumerable<string> Recipients { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -1577,6 +1577,11 @@ acorn-jsx@^5.3.2:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^6.0.6:
version "6.4.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
version "8.8.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
@@ -1726,6 +1731,16 @@ archiver@^5.3.1:
tar-stream "^2.2.0"
zip-stream "^4.1.0"
are-you-es5@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/are-you-es5/-/are-you-es5-2.1.2.tgz#d75511a174a3f842d70cc784aec0bcaff5a57547"
integrity sha512-gkT2bLCfzyJJr3lYoxZKScdD/Yp5zzghi+3KawONTvH/7rrgU3RMXYvGQnOTlqEFVgZFpEGj/6bZ6sF/9YtddA==
dependencies:
acorn "^6.0.6"
array-flatten "^2.1.0"
commander "^2.19.0"
find-up "^4.1.0"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -1749,6 +1764,11 @@ arr-union@^2.0.1:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d"
integrity sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==
array-flatten@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
array-includes@^3.1.4, array-includes@^3.1.5, array-includes@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
@@ -2197,7 +2217,7 @@ colorette@^2.0.14:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
commander@^2.20.0, commander@^2.20.3:
commander@^2.19.0, commander@^2.20.0, commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==