mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-14 16:04:26 -04:00
Compare commits
50 Commits
changelog-
...
v0.4.7.201
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e700b63c2 | ||
|
|
df0b8fc660 | ||
|
|
f96dbbfc21 | ||
|
|
4a75f92cb5 | ||
|
|
dd05a9dbd4 | ||
|
|
e78b8d5346 | ||
|
|
74a1d95ab7 | ||
|
|
f929a7e62f | ||
|
|
e9e4248af4 | ||
|
|
9e3b43ef12 | ||
|
|
738a690aac | ||
|
|
3b7c59e9bb | ||
|
|
b8ca28d955 | ||
|
|
8797bb7d1c | ||
|
|
be430732f5 | ||
|
|
e7b1380b85 | ||
|
|
c29735741c | ||
|
|
f56a13a375 | ||
|
|
148d8ee249 | ||
|
|
3547028b96 | ||
|
|
e4ffa1873e | ||
|
|
2e85a21576 | ||
|
|
0a111e7572 | ||
|
|
25217c0ee8 | ||
|
|
791592927c | ||
|
|
4137193a60 | ||
|
|
99816bfd36 | ||
|
|
59e5b5bd52 | ||
|
|
7fa0a2b33c | ||
|
|
0593ca6b9e | ||
|
|
06a26b5c87 | ||
|
|
dcae6dc151 | ||
|
|
04e3ed0ffe | ||
|
|
1ed5ed9179 | ||
|
|
d292d086ee | ||
|
|
f68915c5dd | ||
|
|
01e970e1a7 | ||
|
|
68df439498 | ||
|
|
33de7ca7ab | ||
|
|
ae2d9b795b | ||
|
|
eadea745f8 | ||
|
|
f958c4aefa | ||
|
|
4cf9fb0e79 | ||
|
|
bfa68347e6 | ||
|
|
f97b35403d | ||
|
|
bf2e057247 | ||
|
|
5a278f4e9d | ||
|
|
232a6efd0d | ||
|
|
7e01c93b2c | ||
|
|
d58f6551e6 |
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.4.5'
|
||||
majorVersion: '0.4.7'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.2.0.1448 has been released on `develop`
|
||||
|
||||
A reminder about the `develop` branch
|
||||
|
||||
- **develop - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first. This version will receive updates either weeklyish or bi-weeklyish depending on development.**
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v5](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v5 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- Lidarr v1 coming to `develop` as beta soon^(tm)
|
||||
- Readarr official beta on `develop` coming soon^(tm) currently dealing with metadata issues
|
||||
- [Radarr](https://www.reddit.com/r/radarr/comments/sgrsb3/new_stable_release_master_v4045909/) v4.0.4 released to `master` (stable)
|
||||
- [Radarr Postgres Database Support coming soon (PR#6873)](https://github.com/radarr/radarr/pull/6873)
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.2.0.1448 (changes since v0.2.0.1426)
|
||||
|
||||
- Sync Indexers on app start, go to http if not sync'd yet
|
||||
|
||||
- Misc definition handling improvements
|
||||
|
||||
- Fixed: Updated ruTorrent stopped state helptext
|
||||
|
||||
- Fixed: Added missing translate for Database
|
||||
|
||||
- Fixed: Download limit check was using the query limit instead of the grab limit.
|
||||
|
||||
- Other bug fixes and improvements, see github history
|
||||
@@ -1,212 +0,0 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.2.0.1678 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v6](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- Lidarr v1 coming to `develop` as beta soon^(tm)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
|
||||
- Radarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.2.0.1678 (changes since v0.2.0.1448)
|
||||
|
||||
- Bump moment from 2.29.1 to 2.29.2
|
||||
|
||||
- #834 #256 fix for unable to load the Indexes page
|
||||
|
||||
- Fix .editorconfig to disallow `this`
|
||||
|
||||
- New: MyAnonamouse freeleech support
|
||||
|
||||
- Fixed: (BHD) TMDb Parsing Exception
|
||||
|
||||
- Fixed: MoreThanTV indexer from browse page layout changes (#922)
|
||||
|
||||
- We don't have two Radarrs
|
||||
|
||||
- Fix indent from 37c393a659
|
||||
|
||||
- Fixed: (HDBits) Treat 403 as Query Limit
|
||||
|
||||
- Fixed: (PTP) Treat 403 as Query Limit
|
||||
|
||||
- New: (BTN) Rate Limit to 1 Query per 5 Seconds
|
||||
|
||||
- Fixed: (BTN) Handle Query Limit Error
|
||||
|
||||
- New: (Lidarr/Radarr/Readarr/Sonarr) Improved Errors
|
||||
|
||||
- Fixed: Loading old commands from database
|
||||
|
||||
- Fixed: Cleanup Temp files after backup creation
|
||||
|
||||
- Add Support
|
||||
|
||||
- Translated using Weblate (Finnish)
|
||||
|
||||
- Fixed: Indexer Infobox Error (#920)
|
||||
|
||||
- New: Indexer Description in Add Indexer Modal
|
||||
|
||||
- Fixed: Missing Translates
|
||||
|
||||
- New: Add Search Capabilities to Indexer API & InfoBox
|
||||
|
||||
- Fixed: Update from version in logs
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Translated using Weblate (Portuguese (Brazil))
|
||||
|
||||
- Fixed: Validation when testing indexers, connections and download clients
|
||||
|
||||
- Fixed: Prevent delete of last profile
|
||||
|
||||
- New: Load more (page) results on Search UI
|
||||
|
||||
- Update webpack packages
|
||||
|
||||
- Frontend Package Updates
|
||||
|
||||
- Backend Package Updates
|
||||
|
||||
- Bump dotnet to 6.0.3
|
||||
|
||||
- Translated using Weblate (Spanish)
|
||||
|
||||
- Fixed: (Gazelle) Replace Periods for Space in Search Term
|
||||
|
||||
- Fixed: (HDSpace) Replace Periods for Space in Search Term
|
||||
|
||||
- Fixed: (Anthelion) Replace Periods for Space in Search Term
|
||||
|
||||
- Fixed: (Redacted) Map Categories Comedy & E-Learning Videos to 'Other'
|
||||
|
||||
- Fixed: No longer require first run as admin on windows (#885)
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- indexer(xthor): moved to YAML definition v5
|
||||
|
||||
- Fixed: '/indexers' URL Base breaking UI navigation
|
||||
|
||||
- Translated using Weblate (French)
|
||||
|
||||
- Fix app settings delete modal not closing and reloading app profiles
|
||||
|
||||
- Translated using Weblate (French)
|
||||
|
||||
- Bump Swashbuckle to 6.3.0
|
||||
|
||||
- Translated using Weblate (Portuguese (Brazil))
|
||||
|
||||
- fixup! New: (DanishBytes) Move to YML
|
||||
|
||||
- New: (DanishBytes) Move to YML
|
||||
|
||||
- Update translation files
|
||||
|
||||
- New: (RuTracker.org) add .bet mirror (#876)
|
||||
|
||||
- Fixed:(pornolab) language formatting
|
||||
|
||||
- New: Housekeeper for ApplicationStatus
|
||||
|
||||
- Fixed: Cleanse Tracker api_token from logs
|
||||
|
||||
- New: (HDTorrents) Add hd-torrents.org as Url option
|
||||
|
||||
- New: (Cardigann) Allow JSON filters
|
||||
|
||||
- Fixed: Convert List<HistoryEventTypes> to Int before passing to DB
|
||||
|
||||
- Fixed: WhereBuilder for Postgres
|
||||
|
||||
- Translated using Weblate (Finnish)
|
||||
|
||||
- Fixed: Make authentication cookie name unique to Prowlarr
|
||||
|
||||
- Update Categories
|
||||
|
||||
- Fixed: Enable response compression over https
|
||||
|
||||
- Fixed: (RuTracker) Update Cats
|
||||
|
||||
- Fixed: Clarify App Sync Settings (#847)
|
||||
|
||||
- Set version header to X-Application-Version (missing hyphen)
|
||||
|
||||
- Go to http if def exists on def server
|
||||
|
||||
- Fixed: (BHD) Handle API Auth Errors
|
||||
|
||||
- Fixed: (Immortalseed) Keywordless Search
|
||||
|
||||
- Fixed: (Cardigann) TraktId was mapping to TvRageId
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Support for Genre, Year, and TraktID
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Support for categorydesc
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Add Support for MapTrackerCatDescToNewznab
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Improved Search Logging
|
||||
|
||||
- Fixed: Corrected Query Limit and Grab Limit HelpText
|
||||
|
||||
- New: (Avistaz) Better error reporting for unauthorized tests
|
||||
|
||||
- Fixed: (Cardigann) Requests Failing for Definitions without LegacyLinks
|
||||
|
||||
- Bump SharpZipLib from 1.3.1 to 1.3.3 in /src/NzbDrone.Common
|
||||
|
||||
- Fixed: (Cardigann) Smarter redirect domain compare
|
||||
|
||||
- Fixed: (Cardigann) Treat "Refresh" header as redirect
|
||||
|
||||
- Fixed: (Cardigann) Replace legacy links with default link when making requests
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
@@ -1,117 +0,0 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.3.0.1730 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [*Coming Soon* - Better \*Arr App Sync](https://github.com/Prowlarr/Prowlarr/pull/983)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v6](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- Lidarr v1 coming to `develop` as beta soon^(tm)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
|
||||
- Radarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.3.0.1730 (changes since v0.3.0.1724)
|
||||
|
||||
- Fixed: Prevent endless loop when calling IndexerUrls for Torznab
|
||||
|
||||
- Deleted translation using Weblate (Chinese (Min Nan))
|
||||
|
||||
- Fix some translations
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
|
||||
## v0.3.0.1724 (changes since v0.2.0.1678)
|
||||
|
||||
- Fixed: Prevent endless loop when calling IndexerUrls for Newznab ( #982 )
|
||||
|
||||
- Fixed: Default List for Cardigann LegacyLinks
|
||||
|
||||
- New: Auto map known legacy BaseUrls for non-Cardigann
|
||||
|
||||
- Fixed: (BTN) Move to HTTPS ( #979 )
|
||||
|
||||
- Typo for myanonamouse.
|
||||
|
||||
- Fixed: (MoreThanTV) Better Response Cleansing ( #928 )
|
||||
|
||||
- New: SceneHD Indexer
|
||||
|
||||
- Fixed: (MaM) Handle Auth Errors & Session Expiry
|
||||
|
||||
- Fixed: Remove Indexer if categories were changed to not include in sync ( #912 )
|
||||
|
||||
- Fixed: Sync Indexers on App Edit
|
||||
|
||||
- Cleanup Config Values ( #894 )
|
||||
|
||||
- Fixed: (Cardigann) Handle json field selector that returns arrays ( #950 )
|
||||
|
||||
- New: Schedule refresh and process monitored download tasks at high priority
|
||||
|
||||
- Centralise image choice, update to latest images
|
||||
|
||||
- Don't return early after re-running checks after startup grace period ( #7147 )
|
||||
|
||||
- Fixed: Delay health check notifications on startup
|
||||
|
||||
- New: Add date picker for custom filter dates
|
||||
|
||||
- Bump Monotorrent to 2.0.5
|
||||
|
||||
- Remove old DotNetVersion method and dep
|
||||
|
||||
- New: Add backup size information ( #957 )
|
||||
|
||||
- Fixed: (BeyondHD) Use TryCoerceInt for tmdbId ( #960 )
|
||||
|
||||
- Fixed: (TorrentDay) TV Search returning Series not S/E Results ( #816 )
|
||||
|
||||
- Fixed: (CinemaZ and ExoticaZ) Better Log Cleansing
|
||||
|
||||
- Fixed: (exoticaz) Category Parsing
|
||||
|
||||
- Fixed: (Indexer) HDTorrents search imdbid + season/episode
|
||||
|
||||
- Bump version to 0.3.0
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
@@ -1,203 +0,0 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.4.2.1879 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v7](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Lidarr v1 coming to `master` as recently released](https://www.reddit.com/r/Lidarr/comments/v5fdhi/new_stable_release_master_v1022592/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.4.2.1879 (changes since v0.3.0.1730)
|
||||
|
||||
- Don't require user agent for IPTorrents
|
||||
|
||||
- Fixed: (Applications) ApiPath can be null from -arr in some cases
|
||||
|
||||
- ProtectionService Test Fixture
|
||||
|
||||
- Fixed: Lidarr null ref when building indexer for sync
|
||||
|
||||
- Fixed: Lidarr null ref when building indexer for sync
|
||||
|
||||
- Double MultipartBodyLengthLimit for Backup Restore to 256MB
|
||||
|
||||
- Fixed: (IPTorrents) Allow UA override for CF
|
||||
|
||||
- Fixed: Log Cleanse Indexer Response Logic and Test Cases
|
||||
|
||||
- Fixed: Set update executable permissions correctly
|
||||
|
||||
- Fixed: Don't call for server notifications on event driven check
|
||||
|
||||
- Update file and folder handling methods from Radarr (#1051)
|
||||
|
||||
- Running Integration Tests against Postgres Database (#838)
|
||||
|
||||
- Updated NLog Version (#7365)
|
||||
|
||||
- Add additional link logging to DownloadService
|
||||
|
||||
- Fixed: Correctly remove TorrentParadiseMl
|
||||
|
||||
- V6 Cardigann Changes (#1045)
|
||||
|
||||
- Sliding expiration for auth cookie and a little clean up
|
||||
|
||||
- Bump version to 0.4.2
|
||||
|
||||
- Update Sentry to 3.18.0
|
||||
|
||||
- Update Swashbuckle to 6.3.1
|
||||
|
||||
- Bump dotnet to 6.0.6
|
||||
|
||||
- Update AngleSharp to 0.17.0
|
||||
|
||||
- Remove ShowRSS C# Implementation
|
||||
|
||||
- Swallow HTTP issues on analytics call
|
||||
|
||||
- Fix NullRef in analytics service
|
||||
|
||||
- Bump version to 0.4.1
|
||||
|
||||
- Fix Donation Links
|
||||
|
||||
- Fix Tooltips in Dark Theme
|
||||
|
||||
- Fixed: (AnimeBytes) Cleanse Passkey from response
|
||||
|
||||
- Fixed: (Cardigann) Use variables in keywordsfilters block
|
||||
|
||||
- New: (BeyondHD) Better status messages for failures
|
||||
|
||||
- Fixed: VIP Healthcheck not triggered for expired indexers
|
||||
|
||||
- Use DryIoc for Automoqer, drop Unity dependency
|
||||
|
||||
- New: Send description element in nab response
|
||||
|
||||
- (Filelist) Update help text for pass key (#1039)
|
||||
|
||||
- Fixed: (Exoticaz) Category parsing kills search/feed
|
||||
|
||||
- New: (PassThePopcorn) Freeleech only option
|
||||
|
||||
- Fixed: (Cardigann) Searching with nab Parent should also use Child categories
|
||||
|
||||
- Fixed: Better Cleansing of Tracker Announce Keys
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Update FE dev dependencies
|
||||
|
||||
- Ensure .Mono and .Windows projects have all dependencies in build output
|
||||
|
||||
- Fixed: (Gazelle) Parse grouptime as long or date
|
||||
|
||||
- Fixed: (ExoticaZ) Category Parsing
|
||||
|
||||
- Fixed: Input options background color on mobile
|
||||
|
||||
- Fixed: Update AltHub API URL (#1010)
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- New: Dark Theme
|
||||
|
||||
- New: Move to CSS Variables for Colorings
|
||||
|
||||
- New: Native Theme Engine
|
||||
|
||||
- diversify chartcolors for doughnut & stackedbar
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Catch Postgres log connection errors
|
||||
|
||||
- Clean lingering Postgres Connections on Close
|
||||
|
||||
- New: Instance name in System/Status API endpoint
|
||||
|
||||
- New: Instance name for Page Title
|
||||
|
||||
- New: Instance Name used for Syslog
|
||||
|
||||
- New: Set Instance Name
|
||||
|
||||
- Fixed: Use separate guid for download protection
|
||||
|
||||
- Fixed: (RuTracker) Support Raw search from apps
|
||||
|
||||
- Fixed: Localization for two part language dialects
|
||||
|
||||
- Fixed: (AnimeBytes) Handle series synonyms with commas (#984)
|
||||
|
||||
- New: Add Lidarr and Readarr DiscographySeedTime Sync
|
||||
|
||||
- New: Add Sonarr SeasonSeedTime Sync
|
||||
|
||||
- Fixed: Indexer Tags Helptext
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- New: Seed Settings Sync
|
||||
|
||||
- New: Only sync indexers with matching app tags
|
||||
|
||||
- Indexer Cleanup
|
||||
|
||||
- Bump version to 0.4.0
|
||||
|
||||
- Bump version to 0.3.1
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Fixed: Correct User-Agent api logging
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
@@ -1,110 +0,0 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.4.3.1921 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.4.3.1921 (changes since v0.4.2.1879)
|
||||
|
||||
- Fixed: (GazelleGames) Use API instead of scraping
|
||||
|
||||
- Translated using Weblate (Hungarian)
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- New: Search by DoubanId
|
||||
|
||||
- Fixed: UI Typos (#1072)
|
||||
|
||||
- Translated using Weblate (Chinese (Traditional) (zh_TW))
|
||||
|
||||
- Update README.md
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Debounce analytics service
|
||||
|
||||
- Fixed: Set Download and Upload Factors from Generic Torznab
|
||||
|
||||
- Translated using Weblate (Portuguese (Brazil))
|
||||
|
||||
- Translation Improvements
|
||||
|
||||
- Cleanup Language and Localization code
|
||||
|
||||
- Added translation using Weblate (Lithuanian)
|
||||
|
||||
- Fixed: BeyondHD using improperly cased Content-Type header
|
||||
|
||||
- Fix NullRef in Cloudflare detection service
|
||||
|
||||
- New: (AvistaZ) Parse Languages and Subs, pass in response
|
||||
|
||||
- Rework Cloudflare Protection Detection
|
||||
|
||||
- New: (FlareSolverr) DDOS Guard Support
|
||||
|
||||
- Bump Mailkit to 3.3.0 (#1054)
|
||||
|
||||
- New: Add linux-x86 builds
|
||||
|
||||
- Remove unused XmlRPC dependency
|
||||
|
||||
- Fixed: (Cardigann) Use Indexer Encoding for Form Parameters
|
||||
|
||||
- Fixed: (Cardigann) Use Session Cookie when making SimpleCaptchaCall
|
||||
|
||||
- Fixed: Delete CustomFilters not handled properly
|
||||
|
||||
- Modern HTTP Client (#685)
|
||||
|
||||
- Bump version to 0.4.3
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
@@ -1,83 +0,0 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.4.4.1947 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- Readarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.4.4.1947 (changes since [v0.4.3.1921](https://www.reddit.com/r/prowlarr/comments/wbanhd/new_develop_release_v0431921/))
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Fixed: Correctly persist FlareSolverr Cookies to ensure it doesn't run on every request
|
||||
|
||||
- Fixed: Correctly use FlareSolverr User Agent
|
||||
|
||||
- Remove duplicate package NLog.Extensions in Prowlarr.Common
|
||||
|
||||
- Fixed: (Cardigann) fix imatch for rows
|
||||
|
||||
- Support for digest auth with HttpRequests
|
||||
|
||||
- Fixed: (Cardigann) Genre is optional
|
||||
|
||||
- Fixed: (Cardigann) Genre Parsing
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Fixed: (Cardigann) Genre Parsing for Releases
|
||||
|
||||
- Fixed: (Cardigann) messy row strdump
|
||||
|
||||
- New: (Cardigann) Additional query support
|
||||
|
||||
- Bump version to 0.4.4
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
@@ -1,6 +0,0 @@
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
@@ -1,6 +0,0 @@
|
||||
- **Users who do not wish to be on the alpha `nightly` or beta `develop` testing branches should take advantage of this parity and switch to `master`
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**
|
||||
@@ -1,7 +0,0 @@
|
||||
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- Readarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
|
||||
|
||||
onKeyUp = (event) => {
|
||||
const jumpBarItems = this.state.jumpBarItems.order;
|
||||
if (event.path.length === 4) {
|
||||
if (event.composedPath && event.composedPath().length === 4) {
|
||||
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
||||
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Generate a Markdown change log of pull requests from commits between two tags
|
||||
scriptDir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
|
||||
ghRepo="Prowlarr"
|
||||
#branch="develop"
|
||||
#read -r -p "What Repo?: " ghRepo
|
||||
#read -r -p "What Org?: [Default:$ghRepo]" ghOrg
|
||||
read -r -p "What Branch? [master|develop|nightly]:" branch
|
||||
ghOrg=${ghOrg:-$ghRepo}
|
||||
ghRepoUrl=https://github.com/$ghOrg/$ghRepo
|
||||
|
||||
case "${branch}" in
|
||||
master)
|
||||
hotioBranch='release'
|
||||
lsioBranch='latest'
|
||||
branchType='Stable'
|
||||
;;
|
||||
develop)
|
||||
hotioBranch='testing'
|
||||
lsioBranch='develop'
|
||||
branchType='Beta'
|
||||
;;
|
||||
nightly)
|
||||
hotioBranch='nightly'
|
||||
lsioBranch='nightly'
|
||||
branchType='Alpha'
|
||||
;;
|
||||
esac
|
||||
baseDir=$(dirname "$scriptDir")
|
||||
changelogDir="$baseDir/changelogs/"
|
||||
templateDir="$changelogDir/templates/"
|
||||
# Get a list of all tags in reverse order
|
||||
# Assumes the tags are in version format like v1.2.3
|
||||
gitTags=$(git ls-remote -t --exit-code --refs --sort='-v:refname' "$ghRepoUrl" | sed -E 's/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/g')
|
||||
|
||||
# Make the tags an array
|
||||
|
||||
# shellcheck disable=SC2206
|
||||
tags=($gitTags)
|
||||
|
||||
latestTag=${tags[0]}
|
||||
previousTag=${tags[1]}
|
||||
|
||||
# Get a log of commits that occurred between two tags
|
||||
# See Pretty format placeholders at https://git-scm.com/docs/pretty-formats
|
||||
# -i -E --grep="(Fixed:|New:)"'
|
||||
commits=$(git log --pretty=format:' - %s%n' "$previousTag".."$latestTag")
|
||||
# Store our changelog in a variable to be saved to a file at the end
|
||||
markdown="# New ${branchType^} Release"
|
||||
markdown+='\n\n'
|
||||
markdown+="$ghRepo $latestTag has been released on \`$branch\`"
|
||||
markdown+='\n\n'
|
||||
branchmsg=$(cat "$templateDir"/branch-$branch.md)
|
||||
if [ -n "$branchmsg" ]; then
|
||||
{
|
||||
markdown+=$branchmsg
|
||||
markdown+='\n\n'
|
||||
}
|
||||
fi
|
||||
markdown+="# Announcements"
|
||||
markdown+='\n\n'
|
||||
markdown+=$(cat "$templateDir"/announcements.md)
|
||||
markdown+='\n\n'
|
||||
markdown+="# Additional Commentary"
|
||||
markdown+='\n\n'
|
||||
markdown+=$(cat "$templateDir"/commentary.md)
|
||||
markdown+='\n\n'
|
||||
markdown+="# Releases"
|
||||
markdown+='\n\n'
|
||||
markdown+="## Native"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [GitHub Releases]($ghRepoUrl/releases)"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [Wiki Installation Instructions](https://wiki.servarr.com/${ghRepo,,}/installation)"
|
||||
markdown+="\n\n"
|
||||
markdown+="## Docker"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [hotio/$ghRepo:$hotioBranch](https://hotio.dev/containers/${ghRepo,,})"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [lscr.io/linuxserver/$ghRepo:$lsioBranch](https://docs.linuxserver.io/images/docker-${ghRepo,,})"
|
||||
markdown+="\n\n"
|
||||
markdown+="## NAS Packages"
|
||||
markdown+="\n\n"
|
||||
markdown+="- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally"
|
||||
markdown+="\n\n"
|
||||
markdown+="- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally"
|
||||
markdown+="\n\n"
|
||||
markdown+="------------"
|
||||
markdown+="\n\n"
|
||||
markdown+="# Release Notes"
|
||||
markdown+="\n\n"
|
||||
markdown+="## $latestTag (changes since $previousTag)"
|
||||
markdown+="\n\n"
|
||||
markdown+="$commits"
|
||||
markdown+="\n\n"
|
||||
markdown+=" - Other bug fixes and improvements, see GitHub history"
|
||||
# Loop over each commit and look for merged pull requests
|
||||
#for COMMIT in $COMMITS; do
|
||||
|
||||
#done
|
||||
|
||||
# Save our markdown to a file
|
||||
mkdir -p "$changelogDir"
|
||||
echo -e "$markdown" >"$changelogDir/CHANGELOG-$latestTag.md"
|
||||
exit 0
|
||||
@@ -98,15 +98,30 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Internal
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_clean_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
}
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_keep_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
|
||||
cleansedMessage.Should().Contain("shouldkeep1");
|
||||
cleansedMessage.Should().Contain("shouldkeep2");
|
||||
cleansedMessage.Should().Contain("shouldkeep3");
|
||||
}
|
||||
|
||||
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
|
||||
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
|
||||
[TestCase(@"Auth-Success ip 32.2.3.5")]
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
AddRequestHeaders(requestMessage, request.Headers);
|
||||
}
|
||||
|
||||
var httpClient = GetClient(request.Url);
|
||||
var httpClient = GetClient(request.Url, request.ProxySettings);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
@@ -154,9 +154,9 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri)
|
||||
protected virtual System.Net.Http.HttpClient GetClient(HttpUri uri, HttpProxySettings requestProxy)
|
||||
{
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings(uri);
|
||||
var proxySettings = requestProxy ?? _proxySettingsProvider.GetProxySettings(uri);
|
||||
|
||||
var key = proxySettings?.Key ?? NO_PROXY_KEY;
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
PreAuthenticate = true,
|
||||
MaxConnectionsPerServer = 12,
|
||||
ConnectCallback = onConnect,
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace NzbDrone.Common.Http
|
||||
public HttpMethod Method { get; set; }
|
||||
public HttpHeader Headers { get; set; }
|
||||
public Encoding Encoding { get; set; }
|
||||
public IWebProxy Proxy { get; set; }
|
||||
public HttpProxySettings ProxySettings { get; set; }
|
||||
public byte[] ContentData { get; set; }
|
||||
public string ContentSummary { get; set; }
|
||||
public ICredentials Credentials { get; set; }
|
||||
|
||||
@@ -89,13 +89,13 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return (Request.Url += new HttpUri(match.Groups[2].Value)).FullUri;
|
||||
return (Request.Url + new HttpUri(match.Groups[2].Value)).FullUri;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return (Request.Url += new HttpUri(newUrl)).FullUri;
|
||||
return (Request.Url + new HttpUri(newUrl)).FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
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),
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||
<PackageReference Include="Sentry" Version="3.21.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "success",
|
||||
"response": []
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using NzbDrone.Core.Indexers.Definitions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
{
|
||||
@@ -64,5 +65,19 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
torrentInfo.DownloadVolumeFactor.Should().Be(1);
|
||||
torrentInfo.UploadVolumeFactor.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task should_not_error_if_empty_response()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/GazelleGames/recentfeed-empty.json");
|
||||
|
||||
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 { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
|
||||
};
|
||||
|
||||
Mocker.GetMock<IRarbgTokenProvider>()
|
||||
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>(), It.IsAny<string>()))
|
||||
.Setup(v => v.GetToken(It.IsAny<RarbgSettings>()))
|
||||
.Returns("validtoken");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="NBuilder" Version="6.1.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using System.Net.Sockets;
|
||||
using NLog;
|
||||
using Npgsql;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -125,6 +126,37 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://wiki.servarr.com/prowlarr/faq#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
}
|
||||
catch (NpgsqlException e)
|
||||
{
|
||||
if (e.InnerException is SocketException)
|
||||
{
|
||||
var retryCount = 3;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Logger.Error(e, "Failure to connect to Postgres DB, {0} retries remaining", retryCount);
|
||||
|
||||
try
|
||||
{
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (--retryCount > 0)
|
||||
{
|
||||
System.Threading.Thread.Sleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ProwlarrStartupException(ex, "Error creating main database");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ProwlarrStartupException(e, "Error creating main database");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ProwlarrStartupException(e, "Error creating main database");
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Applications;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IApplication>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IApplication>))]
|
||||
[CheckOn(typeof(ProviderStatusChangedEvent<IApplication>))]
|
||||
public class ApplicationLongTermStatusCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IApplicationFactory _providerFactory;
|
||||
private readonly IApplicationStatusService _providerStatusService;
|
||||
|
||||
public ApplicationLongTermStatusCheck(IApplicationFactory providerFactory,
|
||||
IApplicationStatusService providerStatusService,
|
||||
ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_providerFactory = providerFactory;
|
||||
_providerStatusService = providerStatusService;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var enabledProviders = _providerFactory.GetAvailableProviders();
|
||||
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
||||
i => i.Definition.Id,
|
||||
s => s.ProviderId,
|
||||
(i, s) => new { Provider = i, Status = s })
|
||||
.Where(p => p.Status.InitialFailure.HasValue &&
|
||||
p.Status.InitialFailure.Value.Before(
|
||||
DateTime.UtcNow.AddHours(-6)))
|
||||
.ToList();
|
||||
|
||||
if (backOffProviders.Empty())
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
if (backOffProviders.Count == enabledProviders.Count)
|
||||
{
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Error,
|
||||
_localizationService.GetLocalizedString("ApplicationLongTermStatusCheckAllClientMessage"),
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(),
|
||||
HealthCheckResult.Warning,
|
||||
string.Format(_localizationService.GetLocalizedString("ApplicationLongTermStatusCheckSingleClientMessage"),
|
||||
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
|
||||
"#applications-are-unavailable-due-to-failures");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.PassThePopcorn;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class PTPOldSettingsCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
public PTPOldSettingsCheck(IIndexerFactory indexerFactory, ILocalizationService localizationService)
|
||||
: base(localizationService)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var ptpIndexers = _indexerFactory.All().Where(i => i.Settings.GetType() == typeof(PassThePopcornSettings));
|
||||
|
||||
var ptpIndexerOldSettings = ptpIndexers
|
||||
.Where(i => (i.Settings as PassThePopcornSettings).APIUser.IsNullOrWhiteSpace()).Select(i => i.Name);
|
||||
|
||||
if (ptpIndexerOldSettings.Any())
|
||||
{
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("PtpOldSettingsCheckMessage"), string.Join(", ", ptpIndexerOldSettings)));
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Http.CloudFlare;
|
||||
using NzbDrone.Core.Localization;
|
||||
@@ -20,10 +21,12 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public class FlareSolverr : HttpIndexerProxyBase<FlareSolverrSettings>
|
||||
{
|
||||
private readonly ICached<string> _cache;
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
|
||||
public FlareSolverr(IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
public FlareSolverr(IHttpProxySettingsProvider proxySettingsProvider, IProwlarrCloudRequestBuilder cloudRequestBuilder, IHttpClient httpClient, Logger logger, ILocalizationService localizationService, ICacheManager cacheManager)
|
||||
: base(cloudRequestBuilder, httpClient, logger, localizationService)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_cache = cacheManager.GetCache<string>(typeof(string), "UserAgent");
|
||||
}
|
||||
|
||||
@@ -100,6 +103,10 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
|
||||
var maxTimeout = Settings.RequestTimeout * 1000;
|
||||
|
||||
// Use Proxy if no credentials are set (creds not supported as of FS 2.2.9)
|
||||
var proxySettings = _proxySettingsProvider.GetProxySettings();
|
||||
var proxyUrl = proxySettings != null && proxySettings.Username.IsNullOrWhiteSpace() && proxySettings.Password.IsNullOrWhiteSpace() ? GetProxyUri(proxySettings) : null;
|
||||
|
||||
if (request.Method == HttpMethod.Get)
|
||||
{
|
||||
req = new FlareSolverrRequestGet
|
||||
@@ -107,7 +114,11 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
Cmd = "request.get",
|
||||
Url = url,
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
UserAgent = userAgent,
|
||||
Proxy = new FlareSolverrProxy
|
||||
{
|
||||
Url = proxyUrl?.AbsoluteUri
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (request.Method == HttpMethod.Post)
|
||||
@@ -130,7 +141,11 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
ContentLength = null
|
||||
},
|
||||
MaxTimeout = maxTimeout,
|
||||
UserAgent = userAgent
|
||||
UserAgent = userAgent,
|
||||
Proxy = new FlareSolverrProxy
|
||||
{
|
||||
Url = proxyUrl?.AbsoluteUri
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (contentTypeType.Contains("multipart/form-data")
|
||||
@@ -191,38 +206,59 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
public class FlareSolverrRequest
|
||||
private Uri GetProxyUri(HttpProxySettings proxySettings)
|
||||
{
|
||||
switch (proxySettings.Type)
|
||||
{
|
||||
case ProxyType.Http:
|
||||
return new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
case ProxyType.Socks4:
|
||||
return new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
case ProxyType.Socks5:
|
||||
return new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlareSolverrRequest
|
||||
{
|
||||
public string Cmd { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
public Cookie[] Cookies { get; set; }
|
||||
public FlareSolverrProxy Proxy { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
private class FlareSolverrRequestGet : FlareSolverrRequest
|
||||
{
|
||||
public string Headers { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
private class FlareSolverrRequestPost : FlareSolverrRequest
|
||||
{
|
||||
public string PostData { get; set; }
|
||||
public int MaxTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
private class FlareSolverrRequestPostUrlEncoded : FlareSolverrRequestPost
|
||||
{
|
||||
public HeadersPost Headers { get; set; }
|
||||
}
|
||||
|
||||
public class HeadersPost
|
||||
private class FlareSolverrProxy
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
private class HeadersPost
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
public string ContentLength { get; set; }
|
||||
}
|
||||
|
||||
public class FlareSolverrResponse
|
||||
private class FlareSolverrResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Message { get; set; }
|
||||
@@ -232,7 +268,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public Solution Solution { get; set; }
|
||||
}
|
||||
|
||||
public class Solution
|
||||
private class Solution
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Status { get; set; }
|
||||
@@ -242,7 +278,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public string UserAgent { get; set; }
|
||||
}
|
||||
|
||||
public class Cookie
|
||||
private class Cookie
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
@@ -259,7 +295,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
|
||||
public System.Net.Cookie ToCookieObj() => new System.Net.Cookie(Name, Value);
|
||||
}
|
||||
|
||||
public class Headers
|
||||
private class Headers
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Date { get; set; }
|
||||
|
||||
@@ -3,7 +3,9 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
using NzbDrone.Core.Notifications.Prowl;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Http
|
||||
{
|
||||
@@ -18,14 +20,13 @@ namespace NzbDrone.Core.IndexerProxies.Http
|
||||
|
||||
public override HttpRequest PreRequest(HttpRequest request)
|
||||
{
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(Settings.Host + ":" + Settings.Port, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(Settings.Host + ":" + Settings.Port, false, null);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Http,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying HTTP(S) Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
@@ -25,14 +26,13 @@ namespace NzbDrone.Core.IndexerProxies.Socks4
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(uri, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(uri);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Socks4,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying Socks4 Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using NLog;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Localization;
|
||||
|
||||
namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
@@ -26,14 +27,13 @@ namespace NzbDrone.Core.IndexerProxies.Socks5
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Settings.Username.IsNotNullOrWhiteSpace() && Settings.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.Proxy = new WebProxy(uri, false, null, new NetworkCredential(Settings.Username, Settings.Password));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = new WebProxy(uri);
|
||||
}
|
||||
request.ProxySettings = new HttpProxySettings(ProxyType.Socks5,
|
||||
Settings.Host,
|
||||
Settings.Port,
|
||||
null,
|
||||
false,
|
||||
Settings.Username,
|
||||
Settings.Password);
|
||||
|
||||
_logger.Debug("Applying Socks5 Proxy {0} to request {1}", Name, request.Url);
|
||||
|
||||
|
||||
@@ -160,6 +160,18 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (criteriaBase.Categories != null && criteriaBase.Categories.Length > 0)
|
||||
{
|
||||
//Only query supported indexers
|
||||
indexers = indexers.Where(i => ((IndexerDefinition)i.Definition).Capabilities.Categories.SupportedCategories(criteriaBase.Categories).Any()).ToList();
|
||||
|
||||
if (indexers.Count == 0)
|
||||
{
|
||||
_logger.Debug("All provided categories are unsupported by selected indexers: {0}", string.Join(", ", criteriaBase.Categories));
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Searching indexer(s): [{0}] for {1}", string.Join(", ", indexers.Select(i => i.Definition.Name).ToList()), criteriaBase.ToString());
|
||||
|
||||
var tasks = indexers.Select(x => DispatchIndexer(searchAction, x, criteriaBase));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -282,44 +283,29 @@ namespace NzbDrone.Core.IndexerVersions
|
||||
{
|
||||
var startupFolder = _appFolderInfo.AppDataFolder;
|
||||
|
||||
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}");
|
||||
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
|
||||
|
||||
var currentDefs = _versionService.All().ToDictionary(x => x.DefinitionId, x => x.Sha);
|
||||
|
||||
try
|
||||
{
|
||||
EnsureDefinitionsFolder();
|
||||
|
||||
foreach (var def in response.Resource)
|
||||
var definitionsFolder = Path.Combine(startupFolder, "Definitions");
|
||||
var saveFile = Path.Combine(definitionsFolder, $"indexers.zip");
|
||||
|
||||
_httpClient.DownloadFile($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/package.zip", saveFile);
|
||||
|
||||
using (ZipArchive archive = ZipFile.OpenRead(saveFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var saveFile = Path.Combine(startupFolder, "Definitions", $"{def.File}.yml");
|
||||
|
||||
if (currentDefs.TryGetValue(def.Id, out var defSha) && defSha == def.Sha)
|
||||
{
|
||||
_logger.Trace("Indexer already up to date: {0}", def.File);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
_httpClient.DownloadFile($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/{def.File}", saveFile);
|
||||
|
||||
_versionService.Upsert(new IndexerDefinitionVersion { Sha = def.Sha, DefinitionId = def.Id, File = def.File, LastUpdated = DateTime.UtcNow });
|
||||
|
||||
_cache.Remove(def.File);
|
||||
_logger.Debug("Updated definition: {0}", def.File);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("Definition download failed: {0}, {1}", def.File, ex.Message);
|
||||
}
|
||||
archive.ExtractToDirectory(definitionsFolder, true);
|
||||
}
|
||||
|
||||
_diskProvider.DeleteFile(saveFile);
|
||||
|
||||
_cache.Clear();
|
||||
|
||||
_logger.Debug("Updated indexer definitions");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Definition download failed, error creating definitions folder in {0}", startupFolder);
|
||||
_logger.Error(ex, "Definition update failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
[Obsolete("Moved to YML for Cardigann")]
|
||||
public class Anthelion : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "Anthelion";
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
// hook to adjust the search category
|
||||
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories)
|
||||
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories, string genre)
|
||||
{
|
||||
var categoryMapping = Capabilities.Categories.MapTorznabCapsToTrackers(categories).Distinct().ToList();
|
||||
var qc = new List<KeyValuePair<string, string>> // NameValueCollection don't support cat[]=19&cat[]=6
|
||||
@@ -34,6 +34,16 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{ "type", categoryMapping.Any() ? categoryMapping.First() : "0" }
|
||||
};
|
||||
|
||||
if (Settings.FreeleechOnly)
|
||||
{
|
||||
qc.Add("discount[]", "1");
|
||||
}
|
||||
|
||||
if (genre.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("tags", genre);
|
||||
}
|
||||
|
||||
// resolution filter to improve the search
|
||||
if (!categories.Contains(NewznabStandardCategory.Movies.Id) && !categories.Contains(NewznabStandardCategory.TV.Id) &&
|
||||
!categories.Contains(NewznabStandardCategory.Audio.Id))
|
||||
@@ -71,7 +81,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, searchCriteria.Genre);
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -93,7 +103,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, null);
|
||||
|
||||
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
|
||||
|
||||
@@ -104,7 +114,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, searchCriteria.Genre);
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -136,7 +146,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.Categories, null);
|
||||
|
||||
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public AvistazSettings()
|
||||
{
|
||||
Token = "";
|
||||
FreeleechOnly = false;
|
||||
}
|
||||
|
||||
public string Token { get; set; }
|
||||
@@ -35,6 +36,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
[FieldDefinition(4, Label = "PID", HelpText = "PID from My Account or My Profile page")]
|
||||
public string Pid { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -152,6 +152,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.Offset"] = searchCriteria.Offset?.ToString() ?? null;
|
||||
variables[".Query.Extended"] = null;
|
||||
variables[".Query.APIKey"] = null;
|
||||
variables[".Query.Genre"] = null;
|
||||
|
||||
//Movie
|
||||
variables[".Query.Movie"] = null;
|
||||
@@ -168,6 +169,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
variables[".Query.TVRageID"] = null;
|
||||
variables[".Query.TVMazeID"] = null;
|
||||
variables[".Query.TraktID"] = null;
|
||||
variables[".Query.DoubanID"] = null;
|
||||
variables[".Query.Episode"] = null;
|
||||
|
||||
//Music
|
||||
@@ -179,6 +181,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
//Book
|
||||
variables[".Query.Author"] = null;
|
||||
variables[".Query.Title"] = null;
|
||||
variables[".Query.Publisher"] = null;
|
||||
|
||||
return variables;
|
||||
}
|
||||
@@ -942,6 +945,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
public bool CheckIfLoginIsNeeded(HttpResponse response)
|
||||
{
|
||||
if (_definition.Login == null || _definition.Login.Test == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.HasHttpRedirect)
|
||||
{
|
||||
var domainHint = GetRedirectDomainHint(response);
|
||||
@@ -949,17 +957,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
var errormessage = "Got redirected to another domain. Try changing the indexer URL to " + domainHint + ".";
|
||||
|
||||
throw new CardigannException(errormessage);
|
||||
_logger.Warn(errormessage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_definition.Login == null || _definition.Login.Test == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
return true;
|
||||
@@ -1121,6 +1124,8 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
|
||||
var request = new CardigannRequest(requestbuilder.SetEncoding(_encoding).Build(), variables, searchPath);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = searchPath.Followredirect;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.Genre
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.Genre
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -80,6 +80,30 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
_logger.Debug("Gazelle authentication succeeded.");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var response = await base.Download(link);
|
||||
|
||||
if (response.Length >= 1
|
||||
&& response[0] != 'd' // simple test for torrent vs HTML content
|
||||
&& link.Query.Contains("usetoken=1"))
|
||||
{
|
||||
var html = Encoding.GetString(response);
|
||||
if (html.Contains("You do not have any freeleech tokens left.")
|
||||
|| html.Contains("You do not have enough freeleech tokens")
|
||||
|| html.Contains("This torrent is too large.")
|
||||
|| html.Contains("You cannot use tokens here"))
|
||||
{
|
||||
// download again with usetoken=0
|
||||
var requestLinkNew = link.ToString().Replace("usetoken=1", "usetoken=0");
|
||||
|
||||
response = await base.Download(new Uri(requestLinkNew));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse response)
|
||||
{
|
||||
if (response.HasHttpRedirect || (response.Content != null && response.Content.Contains("\"bad credentials\"")))
|
||||
|
||||
@@ -30,22 +30,19 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
||||
protected IEnumerable<IndexerRequest> GetRequest(string searchParameters)
|
||||
{
|
||||
var filter = "";
|
||||
if (searchParameters == null)
|
||||
{
|
||||
}
|
||||
|
||||
var request =
|
||||
new IndexerRequest(
|
||||
$"{APIUrl}?{searchParameters}{filter}",
|
||||
$"{APIUrl}?{searchParameters}",
|
||||
HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
private string GetBasicSearchParameters(string searchTerm, int[] categories)
|
||||
protected string GetBasicSearchParameters(string searchTerm, int[] categories)
|
||||
{
|
||||
var searchString = GetSearchTerm(searchTerm);
|
||||
|
||||
@@ -67,7 +64,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
|
||||
|
||||
@@ -341,7 +341,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in jsonResponse.Resource.Response)
|
||||
Dictionary<string, GazelleGamesGroup> response;
|
||||
|
||||
try
|
||||
{
|
||||
response = ((JObject)jsonResponse.Resource.Response).ToObject<Dictionary<string, GazelleGamesGroup>>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in response)
|
||||
{
|
||||
Dictionary<string, GazelleGamesTorrent> torrents;
|
||||
|
||||
@@ -455,7 +466,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class GazelleGamesResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public Dictionary<string, GazelleGamesGroup> Response { get; set; }
|
||||
public object Response { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesGroup
|
||||
|
||||
369
src/NzbDrone.Core/Indexers/Definitions/GreatPosterWall.cs
Normal file
369
src/NzbDrone.Core/Indexers/Definitions/GreatPosterWall.cs
Normal file
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Gazelle;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
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 Description => "GreatPosterWall (GPW) is a CHINESE Private site for MOVIES";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public GreatPosterWall(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new GreatPosterWallRequestGenerator()
|
||||
{
|
||||
Settings = Settings,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger,
|
||||
Capabilities = Capabilities
|
||||
};
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new GreatPosterWallParser(Settings, Capabilities);
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies 电影");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreatPosterWallRequestGenerator : GazelleRequestGenerator
|
||||
{
|
||||
protected override bool ImdbInTags => false;
|
||||
|
||||
public override IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
|
||||
|
||||
if (searchCriteria.ImdbId != null)
|
||||
{
|
||||
parameters += string.Format("&searchstr={0}", searchCriteria.FullImdbId);
|
||||
}
|
||||
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(parameters));
|
||||
return pageableRequests;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreatPosterWallParser : GazelleParser
|
||||
{
|
||||
public GreatPosterWallParser(GazelleSettings settings, IndexerCapabilities capabilities)
|
||||
: base(settings, capabilities)
|
||||
{
|
||||
}
|
||||
|
||||
public override IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
// Remove cookie cache
|
||||
CookiesUpdater(null, null);
|
||||
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<GreatPosterWallResponse>(indexerResponse.HttpResponse);
|
||||
if (jsonResponse.Resource.Status != "success" ||
|
||||
jsonResponse.Resource.Status.IsNullOrWhiteSpace() ||
|
||||
jsonResponse.Resource.Response == null)
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in jsonResponse.Resource.Response.Results)
|
||||
{
|
||||
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
|
||||
{
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
Title = torrent.FileName,
|
||||
InfoUrl = infoUrl,
|
||||
Guid = infoUrl,
|
||||
PosterUrl = GetPosterUrl(result.Cover),
|
||||
DownloadUrl = GetDownloadUrl(torrent.TorrentId, torrent.CanUseToken),
|
||||
PublishDate = new DateTimeOffset(time, TimeSpan.FromHours(8)).LocalDateTime, // Time is Chinese Time, add 8 hours difference from UTC and then convert back to local time
|
||||
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies },
|
||||
Size = torrent.Size,
|
||||
Seeders = torrent.Seeders,
|
||||
Peers = torrent.Seeders + torrent.Leechers,
|
||||
Grabs = torrent.Snatches,
|
||||
Files = torrent.FileCount,
|
||||
Scene = torrent.Scene,
|
||||
DownloadVolumeFactor = torrent.IsFreeleech || torrent.IsNeutralLeech || torrent.IsPersonalFreeleech ? 0 : 1,
|
||||
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1
|
||||
};
|
||||
|
||||
var imdbId = ParseUtil.GetImdbID(result.ImdbId);
|
||||
|
||||
if (imdbId != null)
|
||||
{
|
||||
release.ImdbId = (int)imdbId;
|
||||
}
|
||||
|
||||
switch (torrent.FreeType)
|
||||
{
|
||||
case "11":
|
||||
release.DownloadVolumeFactor = 0.75;
|
||||
break;
|
||||
case "12":
|
||||
release.DownloadVolumeFactor = 0.5;
|
||||
break;
|
||||
case "13":
|
||||
release.DownloadVolumeFactor = 0.25;
|
||||
break;
|
||||
case "1":
|
||||
release.DownloadVolumeFactor = 0;
|
||||
break;
|
||||
case "2":
|
||||
release.DownloadVolumeFactor = 0;
|
||||
release.UploadVolumeFactor = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
protected string GetDownloadUrl(int torrentId, bool canUseToken)
|
||||
{
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("action", "download")
|
||||
.AddQueryParam("usetoken", _settings.UseFreeleechToken && canUseToken ? "1" : "0")
|
||||
.AddQueryParam("id", torrentId);
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
}
|
||||
|
||||
public class GreatPosterWallResponse
|
||||
{
|
||||
[JsonProperty("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonProperty("response")]
|
||||
public Response Response { get; set; }
|
||||
}
|
||||
|
||||
public class Response
|
||||
{
|
||||
[JsonProperty("currentPage")]
|
||||
public int CurrentPage { get; set; }
|
||||
|
||||
[JsonProperty("pages")]
|
||||
public int Pages { get; set; }
|
||||
|
||||
[JsonProperty("results")]
|
||||
public List<Result> Results { get; set; }
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
[JsonProperty("groupId")]
|
||||
public int GroupId { get; set; }
|
||||
|
||||
[JsonProperty("groupName")]
|
||||
public string GroupName { get; set; }
|
||||
|
||||
[JsonProperty("groupSubName")]
|
||||
public string GroupSubName { get; set; }
|
||||
|
||||
[JsonProperty("cover")]
|
||||
public string Cover { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[JsonProperty("bookmarked")]
|
||||
public bool Bookmarked { get; set; }
|
||||
|
||||
[JsonProperty("groupYear")]
|
||||
public int GroupYear { get; set; }
|
||||
|
||||
[JsonProperty("releaseType")]
|
||||
public string ReleaseType { get; set; }
|
||||
|
||||
[JsonProperty("groupTime")]
|
||||
public string GroupTime { get; set; }
|
||||
|
||||
[JsonProperty("maxSize")]
|
||||
public object MaxSize { get; set; }
|
||||
|
||||
[JsonProperty("totalSnatched")]
|
||||
public int TotalSnatched { get; set; }
|
||||
|
||||
[JsonProperty("totalSeeders")]
|
||||
public int TotalSeeders { get; set; }
|
||||
|
||||
[JsonProperty("totalLeechers")]
|
||||
public int TotalLeechers { get; set; }
|
||||
|
||||
[JsonProperty("imdbId")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[JsonProperty("imdbRating")]
|
||||
public string ImdbRating { get; set; }
|
||||
|
||||
[JsonProperty("imdbVote")]
|
||||
public string ImdbVote { get; set; }
|
||||
|
||||
[JsonProperty("doubanId")]
|
||||
public string DoubanId { get; set; }
|
||||
|
||||
[JsonProperty("doubanRating")]
|
||||
public string DoubanRating { get; set; }
|
||||
|
||||
[JsonProperty("doubanVote")]
|
||||
public string DoubanVote { get; set; }
|
||||
|
||||
[JsonProperty("rtRating")]
|
||||
public string RtRating { get; set; }
|
||||
|
||||
[JsonProperty("region")]
|
||||
public string Region { get; set; }
|
||||
|
||||
[JsonProperty("torrents")]
|
||||
public List<GreatPosterWallTorrent> Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class GreatPosterWallTorrent
|
||||
{
|
||||
[JsonProperty("torrentId")]
|
||||
public int TorrentId { get; set; }
|
||||
|
||||
[JsonProperty("editionId")]
|
||||
public int EditionId { get; set; }
|
||||
|
||||
[JsonProperty("remasterYear")]
|
||||
public int RemasterYear { get; set; }
|
||||
|
||||
[JsonProperty("remasterTitle")]
|
||||
public string RemasterTitle { get; set; }
|
||||
|
||||
[JsonProperty("remasterCustomTitle")]
|
||||
public string RemasterCustomTitle { get; set; }
|
||||
|
||||
[JsonProperty("scene")]
|
||||
public bool Scene { get; set; }
|
||||
|
||||
[JsonProperty("jinzhuan")]
|
||||
public bool Jinzhuan { get; set; }
|
||||
|
||||
[JsonProperty("fileCount")]
|
||||
public int FileCount { get; set; }
|
||||
|
||||
[JsonProperty("time")]
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("snatches")]
|
||||
public int Snatches { get; set; }
|
||||
|
||||
[JsonProperty("seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("isFreeleech")]
|
||||
public bool IsFreeleech { get; set; }
|
||||
|
||||
[JsonProperty("isNeutralLeech")]
|
||||
public bool IsNeutralLeech { get; set; }
|
||||
|
||||
[JsonProperty("freeType")]
|
||||
public string FreeType { get; set; }
|
||||
|
||||
[JsonProperty("isPersonalFreeleech")]
|
||||
public bool IsPersonalFreeleech { get; set; }
|
||||
|
||||
[JsonProperty("canUseToken")]
|
||||
public bool CanUseToken { get; set; }
|
||||
|
||||
[JsonProperty("hasSnatched")]
|
||||
public bool HasSnatched { get; set; }
|
||||
|
||||
[JsonProperty("resolution")]
|
||||
public string Resolution { get; set; }
|
||||
|
||||
[JsonProperty("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[JsonProperty("codec")]
|
||||
public string Codec { get; set; }
|
||||
|
||||
[JsonProperty("container")]
|
||||
public string Container { get; set; }
|
||||
|
||||
[JsonProperty("processing")]
|
||||
public string Processing { get; set; }
|
||||
|
||||
[JsonProperty("chineseDubbed")]
|
||||
public string ChineseDubbed { get; set; }
|
||||
|
||||
[JsonProperty("specialSub")]
|
||||
public string SpecialSub { get; set; }
|
||||
|
||||
[JsonProperty("subtitles")]
|
||||
public string Subtitles { get; set; }
|
||||
|
||||
[JsonProperty("fileName")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonProperty("releaseGroup")]
|
||||
public string ReleaseGroup { get; set; }
|
||||
}
|
||||
@@ -124,19 +124,24 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.MoviesBluRay, "Movie / Blu-ray");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.MoviesHD, "Movie / 1080p");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.MoviesHD, "Movie / 720p");
|
||||
caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.MoviesUHD, "Movie / 2160p");
|
||||
caps.Categories.AddCategoryMapping(40, NewznabStandardCategory.MoviesHD, "Movie / Remux");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.MoviesHD, "Movie / HD-DVD");
|
||||
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.MoviesUHD, "Movie / 4K UHD");
|
||||
caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.TVHD, "TV Show / 720p HDTV");
|
||||
caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.TVHD, "TV Show / 1080p HDTV");
|
||||
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.TVUHD, "TV Show / 2160p HDTV");
|
||||
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVDocumentary, "Documentary / 720p");
|
||||
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.TVDocumentary, "Documentary / 1080p");
|
||||
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.TVDocumentary, "Documentary / 2160p");
|
||||
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVAnime, "Animation / 720p");
|
||||
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.TVAnime, "Animation / 1080p");
|
||||
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.TVAnime, "Animation / 2160p");
|
||||
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.AudioLossless, "Music / HQ Audio");
|
||||
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.AudioVideo, "Music / Videos");
|
||||
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.XXX, "XXX / 720p");
|
||||
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.XXX, "XXX / 1080p");
|
||||
caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.XXX, "XXX / 2160p");
|
||||
caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.MoviesOther, "Trailers");
|
||||
caps.Categories.AddCategoryMapping(37, NewznabStandardCategory.PC, "Software");
|
||||
caps.Categories.AddCategoryMapping(38, NewznabStandardCategory.Other, "Others");
|
||||
|
||||
@@ -190,6 +190,12 @@ public class MoreThanTVParser : IParseIndexerResponse
|
||||
{
|
||||
// Parse required data
|
||||
var downloadAnchor = torrent.QuerySelector("span a[href^=\"/torrents.php?action=download\"]");
|
||||
|
||||
if (downloadAnchor == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.QuerySelector("a[class=\"overlay_torrent\"]").TextContent.Trim();
|
||||
title = CleanUpTitle(title);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using DryIoc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -268,7 +269,10 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
parameters.Add("offset", searchCriteria.Offset.ToString());
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(string.Format("{0}&{1}", baseUrl, parameters.GetQueryString()), HttpAccept.Rss);
|
||||
var request = new IndexerRequest(string.Format("{0}&{1}", baseUrl, parameters.GetQueryString()), HttpAccept.Rss);
|
||||
request.HttpRequest.AllowAutoRedirect = true;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
private static string NewsnabifyTitle(string title)
|
||||
|
||||
1190
src/NzbDrone.Core/Indexers/Definitions/NzbIndex.cs
Normal file
1190
src/NzbDrone.Core/Indexers/Definitions/NzbIndex.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -255,6 +255,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.Genre
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.Genre
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -8,7 +13,9 @@ using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Http.CloudFlare;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Rarbg
|
||||
@@ -95,6 +102,57 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
return caps;
|
||||
}
|
||||
|
||||
protected override async Task<IndexerQueryResult> FetchPage(IndexerRequest request, IParseIndexerResponse parser)
|
||||
{
|
||||
var response = await FetchIndexerResponse(request);
|
||||
|
||||
// try and recover from token or rate limit errors
|
||||
var jsonResponse = new HttpResponse<RarbgResponse>(response.HttpResponse);
|
||||
|
||||
if (jsonResponse.Resource.error_code.HasValue)
|
||||
{
|
||||
if (jsonResponse.Resource.error_code == 4 || jsonResponse.Resource.error_code == 2)
|
||||
{
|
||||
_logger.Debug("Invalid or expired token, refreshing token from Rarbg");
|
||||
_tokenProvider.ExpireToken(Settings);
|
||||
var newToken = _tokenProvider.GetToken(Settings);
|
||||
|
||||
var qs = HttpUtility.ParseQueryString(request.HttpRequest.Url.Query);
|
||||
qs.Set("token", newToken);
|
||||
|
||||
request.HttpRequest.Url = request.Url.SetQuery(qs.GetQueryString());
|
||||
response = await FetchIndexerResponse(request);
|
||||
}
|
||||
else if (jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.rate_limit.HasValue)
|
||||
{
|
||||
_logger.Debug("Rarbg rate limit hit, retying request");
|
||||
response = await FetchIndexerResponse(request);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var releases = parser.ParseResponse(response).ToList();
|
||||
|
||||
if (releases.Count == 0)
|
||||
{
|
||||
_logger.Trace(response.Content);
|
||||
}
|
||||
|
||||
return new IndexerQueryResult
|
||||
{
|
||||
Releases = releases,
|
||||
Response = response.HttpResponse
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.WithData(response.HttpResponse, 128 * 1024);
|
||||
_logger.Trace("Unexpected Response content ({0} bytes): {1}", response.HttpResponse.ResponseData.Length, response.HttpResponse.Content);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||
{
|
||||
if (action == "checkCaptcha")
|
||||
|
||||
@@ -40,9 +40,11 @@ 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 == 9 || jsonResponse.Resource.error_code == 10
|
||||
|| jsonResponse.Resource.error_code == 5 || jsonResponse.Resource.error_code == 13
|
||||
|| jsonResponse.Resource.error_code == 14)
|
||||
{
|
||||
// No results or imdbid not found
|
||||
// No results, rate limit, or imdbid/tvdb not found
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
requestBuilder.AddQueryParam("search_themoviedb", tmdbId);
|
||||
}
|
||||
else if (tvdbId.HasValue && tmdbId > 0)
|
||||
else if (tvdbId.HasValue && tvdbId > 0)
|
||||
{
|
||||
requestBuilder.AddQueryParam("search_tvdb", tvdbId);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings, Settings.BaseUrl));
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
requestBuilder.AddQueryParam("app_id", BuildInfo.AppName);
|
||||
|
||||
@@ -69,42 +69,36 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories, searchCriteria.FullImdbId, tvdbId: searchCriteria.TvdbId);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories, searchCriteria.FullImdbId, tvdbId: searchCriteria.TvdbId));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
return GetRequestChain(request, 2);
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var request = GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
|
||||
return GetRequestChain(request, 2);
|
||||
}
|
||||
|
||||
private IndexerPageableRequestChain GetRequestChain(IEnumerable<IndexerRequest> requests, int retry)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
for (int i = 0; i < retry; i++)
|
||||
{
|
||||
pageableRequests.AddTier(requests);
|
||||
}
|
||||
pageableRequests.Add(GetRequest(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
public string error { get; set; }
|
||||
public int? error_code { get; set; }
|
||||
public int? rate_limit { get; set; }
|
||||
public List<RarbgTorrent> torrent_results { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Rarbg
|
||||
{
|
||||
public interface IRarbgTokenProvider
|
||||
{
|
||||
string GetToken(RarbgSettings settings, string baseUrl);
|
||||
string GetToken(RarbgSettings settings);
|
||||
void ExpireToken(RarbgSettings settings);
|
||||
}
|
||||
|
||||
public class RarbgTokenProvider : IRarbgTokenProvider
|
||||
@@ -26,12 +26,17 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GetToken(RarbgSettings settings, string baseUrl)
|
||||
public void ExpireToken(RarbgSettings settings)
|
||||
{
|
||||
return _tokenCache.Get(baseUrl,
|
||||
_tokenCache.Remove(settings.BaseUrl);
|
||||
}
|
||||
|
||||
public string GetToken(RarbgSettings settings)
|
||||
{
|
||||
return _tokenCache.Get(settings.BaseUrl,
|
||||
() =>
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(baseUrl.Trim('/'))
|
||||
var requestBuilder = new HttpRequestBuilder(settings.BaseUrl.Trim('/'))
|
||||
.WithRateLimit(3.0)
|
||||
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
|
||||
.Accept(HttpAccept.Json);
|
||||
|
||||
61
src/NzbDrone.Core/Indexers/Definitions/RetroFlix.cs
Normal file
61
src/NzbDrone.Core/Indexers/Definitions/RetroFlix.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class RetroFlix : SpeedAppBase
|
||||
{
|
||||
public override string Name => "RetroFlix";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://retroflix.net/" };
|
||||
|
||||
public override string Description => "Private Torrent Tracker for Classic Movies / TV / General Releases";
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(2.1);
|
||||
|
||||
public RetroFlix(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, IIndexerRepository indexerRepository)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger, indexerRepository)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q,
|
||||
TvSearchParam.Season,
|
||||
TvSearchParam.Ep,
|
||||
TvSearchParam.ImdbId
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q,
|
||||
MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q,
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q,
|
||||
},
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(401, NewznabStandardCategory.Movies, "Movies");
|
||||
caps.Categories.AddCategoryMapping(402, NewznabStandardCategory.TV, "TV Series");
|
||||
caps.Categories.AddCategoryMapping(406, NewznabStandardCategory.AudioVideo, "Music Videos");
|
||||
caps.Categories.AddCategoryMapping(407, NewznabStandardCategory.TVSport, "Sports");
|
||||
caps.Categories.AddCategoryMapping(409, NewznabStandardCategory.Books, "Books");
|
||||
caps.Categories.AddCategoryMapping(408, NewznabStandardCategory.Audio, "HQ Audio");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -1480,6 +1480,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = false;
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,187 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
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.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class SpeedApp : TorrentIndexerBase<SpeedAppSettings>
|
||||
public class SpeedApp : SpeedAppBase
|
||||
{
|
||||
public override string Name => "SpeedApp.io";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://speedapp.io" };
|
||||
|
||||
private string ApiUrl => $"{Settings.BaseUrl}/api";
|
||||
|
||||
private string LoginUrl => $"{ApiUrl}/login";
|
||||
|
||||
public override string Description => "SpeedApp is a ROMANIAN Private Torrent Tracker for MOVIES / TV / GENERAL";
|
||||
|
||||
public override string Language => "ro-RO";
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
private IIndexerRepository _indexerRepository;
|
||||
|
||||
public SpeedApp(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, IIndexerRepository indexerRepository)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger, indexerRepository)
|
||||
{
|
||||
_indexerRepository = indexerRepository;
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new SpeedAppRequestGenerator(Capabilities, Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new SpeedAppParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return Settings.ApiKey.IsNullOrWhiteSpace() || httpResponse.StatusCode == HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true,
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
var data = new SpeedAppAuthenticationRequest
|
||||
{
|
||||
Email = Settings.Email,
|
||||
Password = Settings.Password
|
||||
};
|
||||
|
||||
request.SetContent(JsonConvert.SerializeObject(data));
|
||||
|
||||
request.Headers.ContentType = MediaTypeNames.Application.Json;
|
||||
|
||||
var response = await ExecuteAuth(request);
|
||||
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
if (statusCode is < 200 or > 299)
|
||||
{
|
||||
throw new HttpException(response);
|
||||
}
|
||||
|
||||
var parsedResponse = JsonConvert.DeserializeObject<SpeedAppAuthenticationResponse>(response.Content);
|
||||
|
||||
Settings.ApiKey = parsedResponse.Token;
|
||||
|
||||
if (Definition.Id > 0)
|
||||
{
|
||||
_indexerRepository.UpdateSettings((IndexerDefinition)Definition);
|
||||
}
|
||||
|
||||
_logger.Debug("SpeedApp authentication succeeded.");
|
||||
}
|
||||
|
||||
protected override void ModifyRequest(IndexerRequest request)
|
||||
{
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
Cookies = GetCookies();
|
||||
|
||||
if (link.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(link.OriginalString);
|
||||
return Encoding.UTF8.GetBytes(link.OriginalString);
|
||||
}
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(link.AbsoluteUri);
|
||||
|
||||
if (Cookies != null)
|
||||
{
|
||||
requestBuilder.SetCookies(Cookies);
|
||||
}
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.AllowAutoRedirect = FollowRedirect;
|
||||
request.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
byte[] torrentData;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
torrentData = response.ResponseData;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed since it no longer exists ({0})", link.AbsoluteUri);
|
||||
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", link.AbsoluteUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Error("Downloading torrent failed");
|
||||
throw;
|
||||
}
|
||||
|
||||
return torrentData;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
@@ -253,356 +94,4 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private IndexerCapabilities Capabilities { get; }
|
||||
|
||||
private SpeedAppSettings Settings { get; }
|
||||
|
||||
public SpeedAppRequestGenerator(IndexerCapabilities capabilities, SpeedAppSettings settings)
|
||||
{
|
||||
Capabilities = capabilities;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId, searchCriteria.Season, searchCriteria.Episode);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
private IndexerPageableRequestChain GetSearch(SearchCriteriaBase searchCriteria, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, imdbId, season, episode));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var qc = new NameValueCollection();
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("imdbId", imdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
qc.Add("search", term);
|
||||
}
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
qc.Add("season", season.Value.ToString());
|
||||
}
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
qc.Add("episode", episode);
|
||||
}
|
||||
|
||||
var cats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
if (cats.Count > 0)
|
||||
{
|
||||
foreach (var cat in cats)
|
||||
{
|
||||
qc.Add("categories[]", cat);
|
||||
}
|
||||
}
|
||||
|
||||
var searchUrl = Settings.BaseUrl + "/api/torrent?" + qc.GetQueryString(duplicateKeysIfMulti: true);
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly SpeedAppSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public SpeedAppParser(SpeedAppSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
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 jsonResponse = new HttpResponse<List<SpeedAppTorrent>>(indexerResponse.HttpResponse);
|
||||
|
||||
return jsonResponse.Resource.Select(torrent => new TorrentInfo
|
||||
{
|
||||
Guid = torrent.Id.ToString(),
|
||||
Title = torrent.Name,
|
||||
Description = torrent.ShortDescription,
|
||||
Size = torrent.Size,
|
||||
ImdbId = ParseUtil.GetImdbID(torrent.ImdbId).GetValueOrDefault(),
|
||||
DownloadUrl = $"{_settings.BaseUrl}/api/torrent/{torrent.Id}/download",
|
||||
PosterUrl = torrent.Poster,
|
||||
InfoUrl = torrent.Url,
|
||||
Grabs = torrent.TimesCompleted,
|
||||
PublishDate = torrent.CreatedAt,
|
||||
Categories = _categories.MapTrackerCatToNewznab(torrent.Category.Id.ToString()),
|
||||
InfoHash = null,
|
||||
Seeders = torrent.Seeders,
|
||||
Peers = torrent.Leechers + torrent.Seeders,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
DownloadVolumeFactor = torrent.DownloadVolumeFactor,
|
||||
UploadVolumeFactor = torrent.UploadVolumeFactor,
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettingsValidator : AbstractValidator<SpeedAppSettings>
|
||||
{
|
||||
public SpeedAppSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Email).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
private static readonly SpeedAppSettingsValidator Validator = new ();
|
||||
|
||||
public SpeedAppSettings()
|
||||
{
|
||||
Email = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Email", HelpText = "Site Email", Privacy = PrivacyLevel.UserName)]
|
||||
public string Email { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "API Key", Hidden = HiddenType.Hidden)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppCategory
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppCountry
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("flag_image")]
|
||||
public string FlagImage { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppUploadedBy
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("class")]
|
||||
public int Class { get; set; }
|
||||
|
||||
[JsonProperty("avatar")]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
[JsonProperty("uploaded")]
|
||||
public int Uploaded { get; set; }
|
||||
|
||||
[JsonProperty("downloaded")]
|
||||
public int Downloaded { get; set; }
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("country")]
|
||||
public SpeedAppCountry Country { get; set; }
|
||||
|
||||
[JsonProperty("passkey")]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[JsonProperty("invites")]
|
||||
public int Invites { get; set; }
|
||||
|
||||
[JsonProperty("timezone")]
|
||||
public string Timezone { get; set; }
|
||||
|
||||
[JsonProperty("hit_and_run_count")]
|
||||
public int HitAndRunCount { get; set; }
|
||||
|
||||
[JsonProperty("snatch_count")]
|
||||
public int SnatchCount { get; set; }
|
||||
|
||||
[JsonProperty("need_seed")]
|
||||
public int NeedSeed { get; set; }
|
||||
|
||||
[JsonProperty("average_seed_time")]
|
||||
public int AverageSeedTime { get; set; }
|
||||
|
||||
[JsonProperty("free_leech_tokens")]
|
||||
public int FreeLeechTokens { get; set; }
|
||||
|
||||
[JsonProperty("double_upload_tokens")]
|
||||
public int DoubleUploadTokens { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTag
|
||||
{
|
||||
[JsonProperty("translated_name")]
|
||||
public string TranslatedName { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("match_list")]
|
||||
public List<string> MatchList { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTorrent
|
||||
{
|
||||
[JsonProperty("download_volume_factor")]
|
||||
public float DownloadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("upload_volume_factor")]
|
||||
public float UploadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("category")]
|
||||
public SpeedAppCategory Category { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("times_completed")]
|
||||
public int TimesCompleted { get; set; }
|
||||
|
||||
[JsonProperty("leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("uploaded_by")]
|
||||
public SpeedAppUploadedBy UploadedBy { get; set; }
|
||||
|
||||
[JsonProperty("short_description")]
|
||||
public string ShortDescription { get; set; }
|
||||
|
||||
[JsonProperty("poster")]
|
||||
public string Poster { get; set; }
|
||||
|
||||
[JsonProperty("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[JsonProperty("episode")]
|
||||
public int Episode { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public List<SpeedAppTag> Tags { get; set; }
|
||||
|
||||
[JsonProperty("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationRequest
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationResponse
|
||||
{
|
||||
[JsonProperty("token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
533
src/NzbDrone.Core/Indexers/Definitions/SpeedApp/SpeedAppBase.cs
Normal file
533
src/NzbDrone.Core/Indexers/Definitions/SpeedApp/SpeedAppBase.cs
Normal file
@@ -0,0 +1,533 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
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.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public abstract class SpeedAppBase : TorrentIndexerBase<SpeedAppSettings>
|
||||
{
|
||||
private string ApiUrl => $"{Settings.BaseUrl}/api";
|
||||
|
||||
private string LoginUrl => $"{ApiUrl}/login";
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
private IIndexerRepository _indexerRepository;
|
||||
|
||||
public SpeedAppBase(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, IIndexerRepository indexerRepository)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
_indexerRepository = indexerRepository;
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new SpeedAppRequestGenerator(Capabilities, Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new SpeedAppParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
return Settings.ApiKey.IsNullOrWhiteSpace() || httpResponse.StatusCode == HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
AllowAutoRedirect = true,
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
var data = new SpeedAppAuthenticationRequest
|
||||
{
|
||||
Email = Settings.Email,
|
||||
Password = Settings.Password
|
||||
};
|
||||
|
||||
request.SetContent(JsonConvert.SerializeObject(data));
|
||||
|
||||
request.Headers.ContentType = MediaTypeNames.Application.Json;
|
||||
|
||||
var response = await ExecuteAuth(request);
|
||||
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
if (statusCode is < 200 or > 299)
|
||||
{
|
||||
throw new HttpException(response);
|
||||
}
|
||||
|
||||
var parsedResponse = JsonConvert.DeserializeObject<SpeedAppAuthenticationResponse>(response.Content);
|
||||
|
||||
Settings.ApiKey = parsedResponse.Token;
|
||||
|
||||
if (Definition.Id > 0)
|
||||
{
|
||||
_indexerRepository.UpdateSettings((IndexerDefinition)Definition);
|
||||
}
|
||||
|
||||
_logger.Debug("SpeedApp authentication succeeded.");
|
||||
}
|
||||
|
||||
protected override void ModifyRequest(IndexerRequest request)
|
||||
{
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
}
|
||||
|
||||
public override async Task<byte[]> Download(Uri link)
|
||||
{
|
||||
Cookies = GetCookies();
|
||||
|
||||
if (link.Scheme == "magnet")
|
||||
{
|
||||
ValidateMagnet(link.OriginalString);
|
||||
return Encoding.UTF8.GetBytes(link.OriginalString);
|
||||
}
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(link.AbsoluteUri);
|
||||
|
||||
if (Cookies != null)
|
||||
{
|
||||
requestBuilder.SetCookies(Cookies);
|
||||
}
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.AllowAutoRedirect = FollowRedirect;
|
||||
request.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
byte[] torrentData;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
torrentData = response.ResponseData;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed since it no longer exists ({0})", link.AbsoluteUri);
|
||||
throw new ReleaseUnavailableException("Downloading torrent failed", ex);
|
||||
}
|
||||
|
||||
if (ex.Response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
_logger.Error("API Grab Limit reached for {0}", link.AbsoluteUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
}
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex, "Downloading torrent file for release failed ({0})", link.AbsoluteUri);
|
||||
|
||||
throw new ReleaseDownloadException("Downloading torrent failed", ex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_indexerStatusService.RecordFailure(Definition.Id);
|
||||
_logger.Error("Downloading torrent failed");
|
||||
throw;
|
||||
}
|
||||
|
||||
return torrentData;
|
||||
}
|
||||
|
||||
protected virtual IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities();
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private IndexerCapabilities Capabilities { get; }
|
||||
|
||||
private SpeedAppSettings Settings { get; }
|
||||
|
||||
public SpeedAppRequestGenerator(IndexerCapabilities capabilities, SpeedAppSettings settings)
|
||||
{
|
||||
Capabilities = capabilities;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria, searchCriteria.FullImdbId, searchCriteria.Season, searchCriteria.Episode);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
return GetSearch(searchCriteria);
|
||||
}
|
||||
|
||||
private IndexerPageableRequestChain GetSearch(SearchCriteriaBase searchCriteria, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, imdbId, season, episode));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int? season = null, string episode = null)
|
||||
{
|
||||
var qc = new NameValueCollection();
|
||||
|
||||
if (imdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
qc.Add("imdbId", imdbId);
|
||||
}
|
||||
else
|
||||
{
|
||||
qc.Add("search", term);
|
||||
}
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
qc.Add("season", season.Value.ToString());
|
||||
}
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
qc.Add("episode", episode);
|
||||
}
|
||||
|
||||
var cats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
if (cats.Count > 0)
|
||||
{
|
||||
foreach (var cat in cats)
|
||||
{
|
||||
qc.Add("categories[]", cat);
|
||||
}
|
||||
}
|
||||
|
||||
var searchUrl = Settings.BaseUrl + "/api/torrent?" + qc.GetQueryString(duplicateKeysIfMulti: true);
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.ApiKey}");
|
||||
|
||||
yield return request;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly SpeedAppSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public SpeedAppParser(SpeedAppSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
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 jsonResponse = new HttpResponse<List<SpeedAppTorrent>>(indexerResponse.HttpResponse);
|
||||
|
||||
return jsonResponse.Resource.Select(torrent => new TorrentInfo
|
||||
{
|
||||
Guid = torrent.Id.ToString(),
|
||||
Title = torrent.Name,
|
||||
Description = torrent.ShortDescription,
|
||||
Size = torrent.Size,
|
||||
ImdbId = ParseUtil.GetImdbID(torrent.ImdbId).GetValueOrDefault(),
|
||||
DownloadUrl = $"{_settings.BaseUrl}/api/torrent/{torrent.Id}/download",
|
||||
PosterUrl = torrent.Poster,
|
||||
InfoUrl = torrent.Url,
|
||||
Grabs = torrent.TimesCompleted,
|
||||
PublishDate = torrent.CreatedAt,
|
||||
Categories = _categories.MapTrackerCatToNewznab(torrent.Category.Id.ToString()),
|
||||
InfoHash = null,
|
||||
Seeders = torrent.Seeders,
|
||||
Peers = torrent.Leechers + torrent.Seeders,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800,
|
||||
DownloadVolumeFactor = torrent.DownloadVolumeFactor,
|
||||
UploadVolumeFactor = torrent.UploadVolumeFactor,
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettingsValidator : AbstractValidator<SpeedAppSettings>
|
||||
{
|
||||
public SpeedAppSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Email).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
private static readonly SpeedAppSettingsValidator Validator = new ();
|
||||
|
||||
public SpeedAppSettings()
|
||||
{
|
||||
Email = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Email", HelpText = "Site Email", Privacy = PrivacyLevel.UserName)]
|
||||
public string Email { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "API Key", Hidden = HiddenType.Hidden)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class SpeedAppCategory
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppCountry
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("flag_image")]
|
||||
public string FlagImage { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppUploadedBy
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("class")]
|
||||
public int Class { get; set; }
|
||||
|
||||
[JsonProperty("avatar")]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
[JsonProperty("uploaded")]
|
||||
public int Uploaded { get; set; }
|
||||
|
||||
[JsonProperty("downloaded")]
|
||||
public int Downloaded { get; set; }
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("country")]
|
||||
public SpeedAppCountry Country { get; set; }
|
||||
|
||||
[JsonProperty("passkey")]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[JsonProperty("invites")]
|
||||
public int Invites { get; set; }
|
||||
|
||||
[JsonProperty("timezone")]
|
||||
public string Timezone { get; set; }
|
||||
|
||||
[JsonProperty("hit_and_run_count")]
|
||||
public int HitAndRunCount { get; set; }
|
||||
|
||||
[JsonProperty("snatch_count")]
|
||||
public int SnatchCount { get; set; }
|
||||
|
||||
[JsonProperty("need_seed")]
|
||||
public int NeedSeed { get; set; }
|
||||
|
||||
[JsonProperty("average_seed_time")]
|
||||
public int AverageSeedTime { get; set; }
|
||||
|
||||
[JsonProperty("free_leech_tokens")]
|
||||
public int FreeLeechTokens { get; set; }
|
||||
|
||||
[JsonProperty("double_upload_tokens")]
|
||||
public int DoubleUploadTokens { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTag
|
||||
{
|
||||
[JsonProperty("translated_name")]
|
||||
public string TranslatedName { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("match_list")]
|
||||
public List<string> MatchList { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppTorrent
|
||||
{
|
||||
[JsonProperty("download_volume_factor")]
|
||||
public float DownloadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("upload_volume_factor")]
|
||||
public float UploadVolumeFactor { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("category")]
|
||||
public SpeedAppCategory Category { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("created_at")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty("times_completed")]
|
||||
public int TimesCompleted { get; set; }
|
||||
|
||||
[JsonProperty("leechers")]
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty("seeders")]
|
||||
public int Seeders { get; set; }
|
||||
|
||||
[JsonProperty("uploaded_by")]
|
||||
public SpeedAppUploadedBy UploadedBy { get; set; }
|
||||
|
||||
[JsonProperty("short_description")]
|
||||
public string ShortDescription { get; set; }
|
||||
|
||||
[JsonProperty("poster")]
|
||||
public string Poster { get; set; }
|
||||
|
||||
[JsonProperty("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[JsonProperty("episode")]
|
||||
public int Episode { get; set; }
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public List<SpeedAppTag> Tags { get; set; }
|
||||
|
||||
[JsonProperty("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationRequest
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public class SpeedAppAuthenticationResponse
|
||||
{
|
||||
[JsonProperty("token")]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -355,8 +355,6 @@ namespace NzbDrone.Core.Indexers
|
||||
request.HttpRequest.LogResponseContent = true;
|
||||
}
|
||||
|
||||
request.HttpRequest.AllowAutoRedirect = FollowRedirect;
|
||||
|
||||
var originalUrl = request.Url;
|
||||
|
||||
Cookies = GetCookies();
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers
|
||||
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is app's default", Advanced = true)]
|
||||
public double? SeedRatio { get; set; }
|
||||
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Advanced = true)]
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
|
||||
public int? SeedTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"BookSearch": "Buch Suche",
|
||||
"Id": "Id",
|
||||
"IndexerProxies": "Indexer-Proxies",
|
||||
"IndexerTagsHelpText": "Benutze Tags, um Indexer-Proxies zu spezifizieren oder um Indexer zu organisieren.",
|
||||
"IndexerTagsHelpText": "Benutze Tags, um Indexer-Proxies zu spezifizieren, mit welchen Apps der Indexer synchronisiert oder um Indexer zu organisieren.",
|
||||
"MovieSearch": "Film Suche",
|
||||
"QueryOptions": "Abfrage-Optionen",
|
||||
"Categories": "Kategorien",
|
||||
@@ -433,5 +433,32 @@
|
||||
"UnableToLoadIndexers": "Indexer konnten nicht geladen werden",
|
||||
"Yes": "Ja",
|
||||
"InstanceName": "Instanzname",
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname"
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
|
||||
"SyncProfiles": "Sync-Profile",
|
||||
"ThemeHelpText": "Prowlarr UI Theme ändern, inspiriert von {0}",
|
||||
"Duration": "Dauer",
|
||||
"EditSyncProfile": "Synchronisationsprofil bearbeiten",
|
||||
"ElapsedTime": "Vergangene Zeit",
|
||||
"EnabledRedirected": "Aktiviert, Weitergeleitet",
|
||||
"Ended": "Beendet",
|
||||
"GrabTitle": "Titel holen",
|
||||
"LastDuration": "Letzte Dauer",
|
||||
"LastExecution": "Letzte Ausführung",
|
||||
"MinimumSeeders": "Mindest-Seeder",
|
||||
"MinimumSeedersHelpText": "Mindest-Seeder sind benötigt von der App für den Indexer um zu holen",
|
||||
"NextExecution": "Nächste Ausführung",
|
||||
"Parameters": "Parameter",
|
||||
"Queued": "In der Warteschlange",
|
||||
"Started": "gestartet",
|
||||
"SyncProfile": "Sync-Profile",
|
||||
"IndexerSite": "Indexer-Seite",
|
||||
"MovieSearchTypes": "Film-Suchtypen",
|
||||
"MusicSearchTypes": "Musik-Suchtypen",
|
||||
"NotSupported": "Nicht unterstützt",
|
||||
"RawSearchSupported": "Raw-Suche unterstützt",
|
||||
"SearchCapabilities": "Suchfähigkeiten",
|
||||
"AddSyncProfile": "Synchronisationsprofil hinzufügen",
|
||||
"BookSearchTypes": "Buch-Suchtypen",
|
||||
"IndexerDetails": "Indexer-Details",
|
||||
"IndexerName": "Indexer-Name"
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
"AppDataDirectory": "AppData directory",
|
||||
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
|
||||
"Application": "Application",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "All applications are unavailable due to failures for more than 6 hours",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Applications unavailable due to failures for more than 6 hours: {0}",
|
||||
"Applications": "Applications",
|
||||
"ApplicationStatusCheckAllClientMessage": "All applications are unavailable due to failures",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Applications unavailable due to failures: {0}",
|
||||
|
||||
@@ -336,8 +336,8 @@
|
||||
"OnHealthIssue": "En Problema de Salud",
|
||||
"TestAllIndexers": "Comprobar Todos los Indexers",
|
||||
"NotificationTriggersHelpText": "Seleccione qué eventos deben activar esta notificación",
|
||||
"OnApplicationUpdate": "Al actualizar la aplicación",
|
||||
"OnApplicationUpdateHelpText": "Al actualizar la aplicación",
|
||||
"OnApplicationUpdate": "Al Actualizar La Aplicación",
|
||||
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
|
||||
"AddRemoveOnly": "Sólo añadir y eliminar",
|
||||
"AddedToDownloadClient": "Descarga añadida al cliente",
|
||||
"AddNewIndexer": "Añadir nuevo indexador",
|
||||
@@ -361,5 +361,8 @@
|
||||
"Notifications": "Notificaciones",
|
||||
"UnableToLoadIndexers": "No se pueden cargar los indexers",
|
||||
"Yes": "si",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent proporcionado por la aplicación llamó a la API"
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent proporcionado por la aplicación llamó a la API",
|
||||
"InstanceName": "Nombre de Instancia",
|
||||
"InstanceNameHelpText": "Nombre de instancia en pestaña y para nombre de aplicación en Syslog",
|
||||
"Database": "Base de Datos"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"UpdateCheckStartupTranslocationMessage": "Päivitystä ei voi asentaa, koska käynnistyskansio '{0}' sijaitsee 'App Translocation' -kansiossa.",
|
||||
"UpdateCheckUINotWritableMessage": "Päivitystä ei voi asentaa, koska käyttäjällä '{1}' ei ole kirjoitusoikeutta käyttöliittymäkansioon '{0}'.",
|
||||
"UpdateMechanismHelpText": "Käytä Prowlarrin sisäänrakennettua päivitystoimintoa tai omaa komentosarjaasi.",
|
||||
"ApplyTagsHelpTexts3": "– 'Poista' ainoastaan syötetyt tunnisteet",
|
||||
"ApplyTagsHelpTexts3": "- \"Poista\" tyhjentää syötetyt tunnisteet.",
|
||||
"Enable": "Käytä",
|
||||
"UI": "Käyttöliittymä",
|
||||
"UrlBaseHelpText": "Käänteisen välityspalvelimen tuki (esim. 'http://[host]:[port]/[urlBase]'). Käytä oletusta jättämällä tyhjäksi.",
|
||||
@@ -71,7 +71,7 @@
|
||||
"Username": "Käyttäjätunnus",
|
||||
"YesCancel": "Kyllä, peruuta",
|
||||
"NoTagsHaveBeenAddedYet": "Tunnisteita ei ole vielä lisätty.",
|
||||
"ApplyTags": "Toimenpide tunnisteille",
|
||||
"ApplyTags": "Tunnistetoimenpide",
|
||||
"Authentication": "Todennus",
|
||||
"AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana.",
|
||||
"BindAddressHelpText": "Toimiva IPv4-osoite tai jokerimerkkinä '*' (tähti) kaikille yhteyksille.",
|
||||
@@ -224,7 +224,7 @@
|
||||
"Wiki": "Wiki",
|
||||
"ApplyTagsHelpTexts1": "Tunnisteisiin kohdistettavat toimenpiteet:",
|
||||
"ApplyTagsHelpTexts2": "– 'Lisää' syötetyt tunnisteet aiempiin tunnisteisiin",
|
||||
"ApplyTagsHelpTexts4": "– 'Korvaa' kaikki aiemmat tunnisteet tai poista kaikki tunnisteet jättämällä tyhjäksi",
|
||||
"ApplyTagsHelpTexts4": "- \"Korvaa\" nykyiset tunnisteet syötetyillä tai tyhjennä kaikki tunnisteet jättämällä tyhjäksi.",
|
||||
"Port": "Portti",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Haluatko varmasti uudistaa API-avaimesi?",
|
||||
"Automatic": "Automaattinen",
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
"Description": "Description",
|
||||
"Donations": "Dons",
|
||||
"Enabled": "Activé",
|
||||
"Grabs": "Saisie",
|
||||
"Grabs": "Complétés",
|
||||
"Id": "Id",
|
||||
"Presets": "Préconfigurations",
|
||||
"Privacy": "Vie privée",
|
||||
@@ -440,5 +440,9 @@
|
||||
"MovieSearchTypes": "Types de recherches de films",
|
||||
"MusicSearchTypes": "Type de recherche de musiques",
|
||||
"NotSupported": "Non supporté",
|
||||
"SearchCapabilities": "Capacités de recherche"
|
||||
"SearchCapabilities": "Capacités de recherche",
|
||||
"Duration": "Durée",
|
||||
"LastDuration": "Dernière durée",
|
||||
"InstanceName": "Nom de l'instance",
|
||||
"InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog"
|
||||
}
|
||||
|
||||
1
src/NzbDrone.Core/Localization/Core/lv.json
Normal file
1
src/NzbDrone.Core/Localization/Core/lv.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -153,7 +153,7 @@
|
||||
"NetCore": ".NET",
|
||||
"Mode": "Modo",
|
||||
"Mechanism": "Mecanismo",
|
||||
"Logs": "Logs",
|
||||
"Logs": "Registos",
|
||||
"LogLevel": "Nível de log",
|
||||
"Interval": "Intervalo",
|
||||
"IndexerFlags": "Sinalizadores do indexador",
|
||||
@@ -392,7 +392,7 @@
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "Par Utilizador-Agente fornecido pela aplicação que chamou a API",
|
||||
"OnApplicationUpdate": "Quando a aplicação atualizar",
|
||||
"OnApplicationUpdateHelpText": "Quando a aplicação atualizar",
|
||||
"Database": "base de dados",
|
||||
"Database": "Base de dados",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Ficheiros na reciclagem serão eliminados automaticamente após o número de dias selecionado",
|
||||
"Application": "Aplicações",
|
||||
"Link": "Ligações",
|
||||
@@ -401,5 +401,6 @@
|
||||
"UnableToLoadIndexers": "Não foi possível carregar os indexadores",
|
||||
"Yes": "Sim",
|
||||
"GrabReleases": "Capturar versão",
|
||||
"InstanceName": "Nome da Instancia"
|
||||
"InstanceName": "Nome da Instancia",
|
||||
"InstanceNameHelpText": "Nome da instância na aba e nome da aplicação para Syslog"
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@
|
||||
"HistoryCleanupDaysHelpText": "Defina como 0 para desabilitar a limpeza automática",
|
||||
"OnApplicationUpdate": "Ao Atualizar o Aplicativo",
|
||||
"OnApplicationUpdateHelpText": "Ao Atualizar o Aplicativo",
|
||||
"OnGrab": "Ao Baixar",
|
||||
"OnGrab": "Em Espera",
|
||||
"OnHealthIssue": "Ao ter problema de integridade",
|
||||
"TestAllIndexers": "Testar todos os indexadores",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent fornecido pelo aplicativo que chamou a API",
|
||||
|
||||
@@ -19,25 +19,25 @@
|
||||
"Added": "Adăugat",
|
||||
"Actions": "Acțiuni",
|
||||
"About": "Despre",
|
||||
"IndexerStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită eșuărilor",
|
||||
"IndexerStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită erorilor",
|
||||
"Indexers": "Indexatori",
|
||||
"Indexer": "Indexator",
|
||||
"Host": "Gazdă",
|
||||
"History": "Istorie",
|
||||
"History": "Istoric",
|
||||
"HideAdvanced": "Ascunde Avansat",
|
||||
"Health": "Sănătate",
|
||||
"Grabbed": "Prins",
|
||||
"Grabbed": "În curs de descărcare",
|
||||
"GeneralSettingsSummary": "Port, SSL, utilizator/parolă, proxy, statistici și actualizări",
|
||||
"General": "General",
|
||||
"Folder": "Dosar",
|
||||
"Filter": "Filtru",
|
||||
"Files": "Fișiere",
|
||||
"Filename": "Numele Fișierului",
|
||||
"Filename": "Numele fișierului",
|
||||
"Failed": "Eșuat",
|
||||
"EventType": "Tip de eveniment",
|
||||
"Events": "Evenimente",
|
||||
"Edit": "Editează",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Clienții de descărcare sunt indisponibili datorită erorii: {0}",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Clienții de descărcare sunt indisponibili datorită erorilor: {0}",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Toți clienții de descărcare sunt indisponibili datorită erorilor",
|
||||
"Peers": "Parteneri",
|
||||
"PageSize": "Mărimea Paginii",
|
||||
@@ -56,11 +56,11 @@
|
||||
"Language": "Limbă",
|
||||
"KeyboardShortcuts": "Scurtături din tastatură",
|
||||
"Info": "Info",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexator indisponibil datorită erorilor: {0}",
|
||||
"HealthNoIssues": "Nicio problemă în configurare",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexatoare indisponibile datorită erorilor: {0}",
|
||||
"HealthNoIssues": "Nicio problemă de configurare",
|
||||
"Error": "Eroare",
|
||||
"ConnectionLostMessage": "Prowlarr a pierdut conexiunea cu backend-ul și trebuie reîncărcat pentru a restabili funcționalitatea.",
|
||||
"ConnectionLostAutomaticMessage": "Prowlarr va încerca să se reconecteze automat sau poți apăsa reîncarcă mai jos.",
|
||||
"ConnectionLostAutomaticMessage": "Prowlarr va încerca să se conecteze automat, sau poți apăsa reîncarcă mai jos.",
|
||||
"ConnectionLost": "Conexiune Pierdută",
|
||||
"Component": "Componentă",
|
||||
"Columns": "Coloane",
|
||||
@@ -68,7 +68,7 @@
|
||||
"Cancel": "Anulează",
|
||||
"Apply": "Aplică",
|
||||
"Age": "Vechime",
|
||||
"PtpOldSettingsCheckMessage": "Următorul indexer PassThePopcorn are setări depreciate și ar trebui actualizate: {0}",
|
||||
"PtpOldSettingsCheckMessage": "Următoarele indexatoare PassThePopcorn au setări depreciate și ar trebui actualizate: {0}",
|
||||
"ProxyCheckResolveIpMessage": "Nu am putut găsi adresa IP pentru Hostul Proxy Configurat {0}",
|
||||
"ProxyCheckFailedToTestMessage": "Nu am putut testa proxy: {0}",
|
||||
"ProxyCheckBadRequestMessage": "Testul proxy a eșuat. StatusCode: {0}",
|
||||
@@ -125,7 +125,7 @@
|
||||
"SettingsLongDateFormat": "Format de dată lungă",
|
||||
"SettingsShortDateFormat": "Format scurt de dată",
|
||||
"SettingsTimeFormat": "Format de timp",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS nu este acceptat cu acest indexer",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS nu este suportat de acest indexator",
|
||||
"ShowSearchHelpText": "Afișați butonul de căutare pe hover",
|
||||
"UILanguageHelpText": "Limba pe care Radarr o va folosi pentru interfața de utilizare",
|
||||
"UILanguageHelpTextWarning": "Reîncărcare browser necesară",
|
||||
@@ -137,35 +137,35 @@
|
||||
"AddingTag": "Se adaugă etichetă",
|
||||
"LaunchBrowserHelpText": " Deschideți un browser web și navigați la pagina de pornire Radarr la pornirea aplicației.",
|
||||
"AppDataDirectory": "Directorul AppData",
|
||||
"ApplicationStatusCheckAllClientMessage": "Toate listele sunt indisponibile datorită erorilor",
|
||||
"ApplicationStatusCheckAllClientMessage": "Toate aplicațiile sunt indisponibile datorită erorilor",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Sigur doriți să vă resetați cheia API?",
|
||||
"Authentication": "Autentificare",
|
||||
"AuthenticationMethodHelpText": "Solicitați numele de utilizator și parola pentru a accesa Radarr",
|
||||
"AuthenticationMethodHelpText": "Solicitați numele de utilizator și parola pentru a accesa Prowlarr",
|
||||
"AutomaticSearch": "Căutare automată",
|
||||
"BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Radarr",
|
||||
"BackupIntervalHelpText": "Interval între copiile de rezervă automate",
|
||||
"Backups": "Copii de rezervă",
|
||||
"BackupFolderHelpText": "Căile relative vor fi în directorul AppData al lui Prowlarr",
|
||||
"BackupIntervalHelpText": "Interval între crearea copiile de siguranță automate",
|
||||
"Backups": "Copii de siguranță",
|
||||
"BeforeUpdate": "Înainte de actualizare",
|
||||
"BindAddressHelpText": "Adresă IP4 validă sau „*” pentru toate interfețele",
|
||||
"BindAddressHelpText": "Adresă IPv4 validă sau '*' pentru toate interfațele",
|
||||
"Branch": "Ramură",
|
||||
"BranchUpdate": "Sucursală de utilizat pentru actualizarea Radarr",
|
||||
"BranchUpdate": "Ramură utilizată pentru a actualiza Prowlarr",
|
||||
"BranchUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare",
|
||||
"BypassProxyForLocalAddresses": "Bypass Proxy pentru adrese locale",
|
||||
"BypassProxyForLocalAddresses": "Nu folosiți Proxy pentru adrese locale",
|
||||
"CancelPendingTask": "Sigur doriți să anulați această sarcină în așteptare?",
|
||||
"CertificateValidation": "Validarea certificatului",
|
||||
"CertificateValidationHelpText": "Modificați cât de strictă este validarea certificării HTTPS",
|
||||
"ClientPriority": "Prioritatea clientului",
|
||||
"CloseCurrentModal": "Închideți modul curent",
|
||||
"EnableAutomaticSearch": "Activați Căutarea automată",
|
||||
"EnableAutomaticSearchHelpText": "Va fi utilizat atunci când căutările automate sunt efectuate prin interfața de utilizare sau de către Radarr",
|
||||
"EnableAutomaticSearch": "Activați căutarea automată",
|
||||
"EnableAutomaticSearchHelpText": "Va fi utilizat atunci când căutările automate sunt efectuate prin interfața de utilizare sau de către Prowlarr",
|
||||
"EnableInteractiveSearchHelpText": "Va fi utilizat atunci când este utilizată căutarea interactivă",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Pentru mai multe informații despre clienții individuali de descărcare, faceți clic pe butoanele de informații.",
|
||||
"IllRestartLater": "Voi reporni mai târziu",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită eșuărilor",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Liste indisponibile datorită erorilor: {0}",
|
||||
"Add": "Adăuga",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Niciun indexator nu este disponibil datorită erorilor",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Proxiuri indisponibile datorită erorilor: {0}",
|
||||
"Add": "Adaugă",
|
||||
"Custom": "Personalizat",
|
||||
"DeleteBackup": "Ștergeți Backup",
|
||||
"DeleteBackup": "Ștergeți copie de siguranță",
|
||||
"MovieIndexScrollBottom": "Index film: Derulați partea de jos",
|
||||
"NoLinks": "Fără legături",
|
||||
"NoUpdatesAreAvailable": "Nu sunt disponibile actualizări",
|
||||
@@ -189,8 +189,8 @@
|
||||
"URLBase": "Baza URL",
|
||||
"UrlBaseHelpText": "Pentru suport proxy invers, implicit este gol",
|
||||
"Usenet": "Usenet",
|
||||
"EditIndexer": "Editați Indexer",
|
||||
"Enable": "Permite",
|
||||
"EditIndexer": "Editați indexator",
|
||||
"Enable": "Activați",
|
||||
"EnableRss": "Activați RSS",
|
||||
"ErrorLoadingContents": "Eroare la încărcarea conținutului",
|
||||
"FocusSearchBox": "Caseta de căutare Focus",
|
||||
@@ -203,10 +203,10 @@
|
||||
"Torrents": "Torente",
|
||||
"UnableToAddANewApplicationPleaseTryAgain": "Imposibil de adăugat o nouă notificare, încercați din nou.",
|
||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Imposibil de adăugat un nou client de descărcare, încercați din nou.",
|
||||
"DownloadClientSettings": "Descărcați setările clientului",
|
||||
"DownloadClientSettings": "Setări client de descărcare",
|
||||
"Enabled": "Activat",
|
||||
"IncludeHealthWarningsHelpText": "Includeți avertismente de sănătate",
|
||||
"IndexerPriorityHelpText": "Prioritatea indexerului de la 1 (cea mai mare) la 50 (cea mai mică). Implicit: 25.",
|
||||
"IndexerPriorityHelpText": "Prioritatea indexatorului de la 1 (cea mai mare) la 50 (cea mai mică). Implicit: 25.",
|
||||
"InteractiveSearch": "Căutare interactivă",
|
||||
"LogLevel": "Nivel jurnal",
|
||||
"LogLevelTraceHelpTextWarning": "Înregistrarea urmăririi trebuie activată doar temporar",
|
||||
@@ -227,15 +227,15 @@
|
||||
"UnableToLoadTags": "Nu se pot încărca etichete",
|
||||
"UnableToLoadUISettings": "Nu se pot încărca setările UI",
|
||||
"UpdateMechanismHelpText": "Utilizați actualizatorul încorporat al lui Radarr sau un script",
|
||||
"AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele Radarr. Aceasta include informații despre browserul dvs., ce pagini WebUI Radarr utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.",
|
||||
"AnalyticsEnabledHelpText": "Trimiteți informații anonime privind utilizarea și erorile către serverele Prowlarr. Aceasta include informații despre browserul folosit, ce pagini WebUI Prowlarr utilizați, raportarea erorilor, precum și sistemul de operare și versiunea de execuție. Vom folosi aceste informații pentru a acorda prioritate caracteristicilor și remedierilor de erori.",
|
||||
"ApiKey": "Cheie API",
|
||||
"BackupRetentionHelpText": "Copiile de rezervă automate mai vechi de perioada de păstrare vor fi curățate automat",
|
||||
"BackupRetentionHelpText": "Copiile de siguranță automate mai vechi decât perioada de păstrare vor fi curățate automat",
|
||||
"BindAddress": "Adresa de legare",
|
||||
"ChangeHasNotBeenSavedYet": "Modificarea nu a fost încă salvată",
|
||||
"CloneProfile": "Profil de clonare",
|
||||
"CloneProfile": "Clonați profil",
|
||||
"NoLeaveIt": "Nu, lasă-l",
|
||||
"DBMigration": "Migrarea DB",
|
||||
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de rezervă „{0}”?",
|
||||
"DBMigration": "Migrarea BD",
|
||||
"DeleteBackupMessageText": "Sigur doriți să ștergeți copia de siguranță „{0}”?",
|
||||
"DeleteTagMessageText": "Sigur doriți să ștergeți eticheta „{0}”?",
|
||||
"EnableInteractiveSearch": "Activați căutarea interactivă",
|
||||
"EnableSSL": "Activați SSL",
|
||||
@@ -243,12 +243,12 @@
|
||||
"ExistingTag": "Etichetă existentă",
|
||||
"FeatureRequests": "Cereri de caracteristici",
|
||||
"HiddenClickToShow": "Ascuns, faceți clic pentru a afișa",
|
||||
"HomePage": "Pagina principala",
|
||||
"HomePage": "Pagina principală",
|
||||
"Hostname": "Numele gazdei",
|
||||
"IgnoredAddresses": "Adrese ignorate",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Toți indexatorii nu sunt disponibili din cauza unor eșecuri de mai mult de 6 ore",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexatori indisponibili din cauza unor eșecuri de mai mult de 6 ore: {0}",
|
||||
"IndexerPriority": "Prioritatea indexerului",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Toți indexatorii sunt indisponibili datorită erorilor de mai mult de 6 ore",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexatori indisponibili datorită erorilor de mai mult de 6 ore: {0}",
|
||||
"IndexerPriority": "Prioritatea indexatorului",
|
||||
"Mode": "Mod",
|
||||
"MovieIndexScrollTop": "Index film: Derulați sus",
|
||||
"NoBackupsAreAvailable": "Nu sunt disponibile copii de rezervă",
|
||||
@@ -266,14 +266,14 @@
|
||||
"ShowSearch": "Afișați Căutare",
|
||||
"TagCannotBeDeletedWhileInUse": "Nu poate fi șters în timpul utilizării",
|
||||
"TagIsNotUsedAndCanBeDeleted": "Eticheta nu este utilizată și poate fi ștearsă",
|
||||
"TagsHelpText": "Se aplică filmelor cu cel puțin o etichetă potrivită",
|
||||
"TagsHelpText": "Se aplică indexatoarelor cu cel puțin o etichetă potrivită",
|
||||
"Tomorrow": "Mâine",
|
||||
"Torrent": "Torente",
|
||||
"UILanguage": "Limbajul UI",
|
||||
"UISettings": "Setări UI",
|
||||
"UnableToAddANewAppProfilePleaseTryAgain": "Imposibil de adăugat un nou profil de calitate, încercați din nou.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.",
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Imposibil de adăugat un nou indexer, încercați din nou.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Nu se poate adăuga un nou indexator, vă rugăm să încercați din nou.",
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Nu se poate adăuga un nou proxy indexator, vă rugăm să încercați din nou.",
|
||||
"UpdateScriptPathHelpText": "Calea către un script personalizat care preia un pachet de actualizare extras și se ocupă de restul procesului de actualizare",
|
||||
"Uptime": "Timp de funcționare",
|
||||
"UseProxy": "Utilizarea proxy",
|
||||
@@ -281,10 +281,10 @@
|
||||
"Version": "Versiune",
|
||||
"Yesterday": "Ieri",
|
||||
"AcceptConfirmationModal": "Acceptați Modul de confirmare",
|
||||
"DeleteIndexerProxyMessageText": "Sigur doriți să ștergeți eticheta „{0}”?",
|
||||
"DeleteIndexerProxyMessageText": "Sigur doriți să ștergeți proxyul „{0}”?",
|
||||
"Disabled": "Dezactivat",
|
||||
"Discord": "Discordie",
|
||||
"Docker": "Docher",
|
||||
"Discord": "Discord",
|
||||
"Docker": "Docker",
|
||||
"Donations": "Donații",
|
||||
"Interval": "Interval",
|
||||
"Manual": "Manual",
|
||||
@@ -293,14 +293,14 @@
|
||||
"UnsavedChanges": "Modificări nesalvate",
|
||||
"UpdateAutomaticallyHelpText": "Descărcați și instalați automat actualizări. Veți putea în continuare să instalați din System: Updates",
|
||||
"AddDownloadClient": "Adăugați client de descărcare",
|
||||
"AddIndexer": "Adăugați Indexer",
|
||||
"AllIndexersHiddenDueToFilter": "Toate filmele sunt ascunse datorită filtrelor aplicate.",
|
||||
"AddIndexer": "Adăugați Indexator",
|
||||
"AllIndexersHiddenDueToFilter": "Toate indexatoarele sunt ascunse datorită filtrelor aplicate.",
|
||||
"DeleteDownloadClient": "Ștergeți clientul de descărcare",
|
||||
"DeleteDownloadClientMessageText": "Sigur doriți să ștergeți clientul de descărcare „{0}”?",
|
||||
"ConnectSettings": "Setări conectare",
|
||||
"CouldNotConnectSignalR": "Nu s-a putut conecta la SignalR, UI nu se va actualiza",
|
||||
"GeneralSettings": "setari generale",
|
||||
"Grabs": "Apuca",
|
||||
"GeneralSettings": "Setări generale",
|
||||
"Grabs": "Descărcări",
|
||||
"Port": "Port",
|
||||
"PortNumber": "Numarul portului",
|
||||
"Reset": "Resetați",
|
||||
@@ -313,27 +313,68 @@
|
||||
"StartTypingOrSelectAPathBelow": "Începeți să tastați sau selectați o cale de mai jos",
|
||||
"StartupDirectory": "Director de pornire",
|
||||
"SuggestTranslationChange": "Sugerează modificarea traducerii",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Liste indisponibile datorită erorilor: {0}",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Aplicații indisponibile datorită erorilor: {0}",
|
||||
"ApplyTags": "Aplicați etichete",
|
||||
"ApplyTagsHelpTexts1": "Cum se aplică etichete filmelor selectate",
|
||||
"ApplyTagsHelpTexts1": "Cum se aplică etichete indexatoarelor selectate",
|
||||
"ApplyTagsHelpTexts2": "Adăugare: adăugați etichetele la lista de etichete existentă",
|
||||
"ApplyTagsHelpTexts3": "Eliminați: eliminați etichetele introduse",
|
||||
"ApplyTagsHelpTexts4": "Înlocuire: înlocuiți etichetele cu etichetele introduse (nu introduceți etichete pentru a șterge toate etichetele)",
|
||||
"Automatic": "Automat",
|
||||
"DeleteApplicationMessageText": "Sigur doriți să ștergeți notificarea „{0}”?",
|
||||
"DeleteApplicationMessageText": "Sigur doriți să ștergeți aplicația „{0}”?",
|
||||
"Exception": "Excepție",
|
||||
"MaintenanceRelease": "Versiune de întreținere: remedieri de erori și alte îmbunătățiri. Consultați Istoricul comiterilor Github pentru mai multe detalii",
|
||||
"Filters": "Filtru",
|
||||
"Filters": "Filtre",
|
||||
"HistoryCleanupDaysHelpText": "Setați la 0 pentru a dezactiva curățarea automată",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Fișierele din coșul de reciclare mai vechi de numărul de zile selectat vor fi curățate automat",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Obiectele din istoric în coșul de reciclare mai vechi de numărul de zile selectat vor fi curățate automat",
|
||||
"OnGrab": "Pe Grab",
|
||||
"OnHealthIssue": "Cu privire la problema sănătății",
|
||||
"TestAllIndexers": "Testați toți indexatorii",
|
||||
"TestAllIndexers": "Testați toate indexatoarele",
|
||||
"Link": "Link-uri",
|
||||
"NetCore": ".NET Core",
|
||||
"UnableToLoadIndexers": "Imposibil de încărcat indexatori",
|
||||
"GrabReleases": "Grab Release",
|
||||
"UnableToLoadIndexers": "Nu se pot încărca indexatoarele",
|
||||
"GrabReleases": "Descarcă fișier(e)",
|
||||
"MappedDrivesRunningAsService": "Unitățile de rețea mapate nu sunt disponibile atunci când rulează ca serviciu Windows. Vă rugăm să consultați FAQ pentru mai multe informații",
|
||||
"No": "Nu",
|
||||
"Yes": "da"
|
||||
"Yes": "da",
|
||||
"IndexerSettingsSummary": "Configurați diverse setări globale indexatoare inclusiv proxiuri.",
|
||||
"EnableRssHelpText": "Activați flux RSS pentru indexator",
|
||||
"IndexerHealthCheckNoIndexers": "Niciun indexator nu este activat, Prowlarr nu va returna rezultate la căutare.",
|
||||
"IndexerProxy": "Proxy indexator",
|
||||
"IndexerVipCheckExpiredClientMessage": "Beneficiile VIP pentru indexator au expirat: {0}",
|
||||
"IndexerNoDefCheckMessage": "Indexatorii nu au definiție și nu vor funcționa: {0}. Vă rugăm să-i ștergeți și (sau) să-i adăugați din nou în Prowlarr",
|
||||
"IndexerRss": "RSS indexator",
|
||||
"EnabledRedirected": "Activat, Redirecționat",
|
||||
"Ended": "Finalizat",
|
||||
"EnableIndexer": "Activați indexator",
|
||||
"GrabTitle": "Descarcă titlu",
|
||||
"HistoryCleanup": "Istoric curățare",
|
||||
"Id": "Id",
|
||||
"FilterPlaceHolder": "Căutați folosind indexatoare",
|
||||
"IndexerAuth": "Autentificare indexator",
|
||||
"IndexerDetails": "Detalii indexator",
|
||||
"IndexerName": "Nume indexator",
|
||||
"IndexerInfo": "Informații indexator",
|
||||
"IndexerQuery": "Căutare indexator",
|
||||
"IndexerSite": "Site indexator",
|
||||
"IndexersSelectedInterp": "{0} Indexatoare selectate",
|
||||
"IndexerTagsHelpText": "Folosiți etichete pentru a specifica proxiurile indexatoarelor, cu ce aplicații sunt sincronizate indexatoarele, sau doar pentru a le organiza.",
|
||||
"SyncAppIndexers": "Sincronizați indexatoare aplicații",
|
||||
"SyncLevelAddRemove": "Doar Adaugă sau Șterge: Când indexatoarele sunt adăugate sau șterse din Prowlarr, va actualiza această aplicație.",
|
||||
"DeleteIndexerProxy": "Ștergeți proxy indexator",
|
||||
"UnableToLoadIndexerProxies": "Nu se pot încărca proxiurile indexatoarelor",
|
||||
"SettingsIndexerLoggingHelpText": "Logați informații adiționale despre indexatoare, inclusiv răspunsul",
|
||||
"AddIndexerProxy": "Adăugați proxy indexator",
|
||||
"AddNewIndexer": "Adăugați indexator nou",
|
||||
"IndexerAlreadySetup": "Cel puțin o instanță de indexator e deja configurată",
|
||||
"MinimumSeedersHelpText": "Seederi necesari pentru ca aplicația să descarce folosind indexatorul",
|
||||
"ProwlarrSupportsAnyIndexer": "Prowlarr suportă multe indexatoare pe lângă orice indexator ce folosește standardul Newznab/Torznab folosind 'Generic Newznab' (pentru usenet) sau 'Generic Torznab' (pentru torrent). Căutați și selectați-vă indexatorul mai jos.",
|
||||
"RedirectHelpText": "Redirecționați cererile de descărcare pentru indexator și predați descărcarea direct, in loc de a-o trece prin Prowlarr",
|
||||
"SearchIndexers": "Căutare folosind indexatoare",
|
||||
"SettingsIndexerLogging": "Logare îmbunătățită indexator",
|
||||
"SyncLevelFull": "Sincronizare Completă: Va păstra indexatoarele acestei aplicații sincronizate complet. Schimbările făcute indexatoarelor în Prowlarr sunt sincronizate cu această aplicație. Orice schimbare făcută indexatoarelor în această aplicație vor fi șterse la următoarea sincronizare cu Prowlarr.",
|
||||
"Encoding": "Encodare",
|
||||
"FullSync": "Sincronizare completă",
|
||||
"IndexerObsoleteCheckMessage": "Indexatorii sunt învechiți sau nu au fost actualizați: {0}. Vă rugăm să-i ștergeți și (sau) să-i adăugați din nou în Prowlarr",
|
||||
"IndexerProxies": "Proxiuri indexatoare",
|
||||
"IndexerVipCheckExpiringClientMessage": "Beneficiile VIP pentru indexator expiră în curând: {0}"
|
||||
}
|
||||
|
||||
@@ -347,5 +347,9 @@
|
||||
"Encoding": "Кодирование",
|
||||
"Applications": "Приложения",
|
||||
"Application": "Приложения",
|
||||
"Notifications": "Оповещения"
|
||||
"Notifications": "Оповещения",
|
||||
"InstanceName": "Имя экземпляра",
|
||||
"InstanceNameHelpText": "Имя экземпляра на вкладке и для имени приложения системного журнала",
|
||||
"Started": "Запущено",
|
||||
"Database": "База данных"
|
||||
}
|
||||
|
||||
@@ -44,5 +44,17 @@
|
||||
"AuthenticationMethodHelpText": "Vyžadovať používateľské meno a heslo pre prístup k Radarru",
|
||||
"BackupFolderHelpText": "Relatívne cesty budú v priečinku AppData Radarru",
|
||||
"BranchUpdate": "Vetva, ktorá sa má použiť k aktualizácií Radarru",
|
||||
"DeleteDownloadClientMessageText": "Naozaj chcete zmazať značku formátu {0} ?"
|
||||
"DeleteDownloadClientMessageText": "Naozaj chcete zmazať značku formátu {0} ?",
|
||||
"ChangeHasNotBeenSavedYet": "Zmena ešte nebola uložená",
|
||||
"Clear": "Vymazať",
|
||||
"Close": "Zatvoriť",
|
||||
"CertificateValidation": "Overenie certifikátu",
|
||||
"CloneProfile": "Klonovať profil",
|
||||
"BindAddress": "Viazať adresu",
|
||||
"CancelPendingTask": "Naozaj chcete zrušiť túto prebiehajúcu úlohu?",
|
||||
"ClientPriority": "Priorita klienta",
|
||||
"CloseCurrentModal": "Zatvoriť aktuálne okno",
|
||||
"Columns": "Stĺpce",
|
||||
"Component": "Komponent",
|
||||
"ConnectionLost": "Spojenie prerušené"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"About": "關於",
|
||||
"Add": "新增",
|
||||
"Added": "以新增",
|
||||
"Added": "已新增",
|
||||
"Actions": "執行",
|
||||
"Age": "年齡",
|
||||
"AddIndexer": "新增索引",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="AngleSharp.Xml" Version="0.17.0" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
||||
<PackageReference Include="MailKit" Version="3.3.0" />
|
||||
<PackageReference Include="MailKit" Version="3.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.11" />
|
||||
@@ -21,7 +21,7 @@
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.5" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="12.0.1" />
|
||||
<PackageReference Include="AngleSharp" Version="0.17.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.3.1" />
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.4.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Prowlarr.Common.csproj" />
|
||||
|
||||
@@ -96,11 +96,16 @@ namespace NzbDrone.Test.Common.AutoMoq
|
||||
return null;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(System.Text.Json.Serialization.JsonConverter))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the Mock object for the abstract class or interface
|
||||
if (serviceType.IsInterface || serviceType.IsAbstract)
|
||||
{
|
||||
var mockType = typeof(Mock<>).MakeGenericType(serviceType);
|
||||
var mockFactory = new DelegateFactory(r =>
|
||||
var mockFactory = DelegateFactory.Of(r =>
|
||||
{
|
||||
var mock = (Mock)r.Resolve(mockType);
|
||||
SetMock(serviceType, mock);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="4.8.8" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.2.2" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user