Compare commits

..

52 Commits

Author SHA1 Message Date
ilike2burnthing
c2797e132e thesceneplace: fix freeleech row selector. resolves #11691
also add 2x > 10x uploadvolumefactor cases
2021-05-08 06:32:20 +01:00
JigSaw
d34dbcb626 xthor-api: massive improvements (#11690) 2021-05-07 21:32:07 +02:00
ilike2burnthing
6740c7c40f assorted: add new nocensor proxies (#11687) 2021-05-07 07:41:14 +01:00
ilike2burnthing
17fc2d50cf pipelines: fix whitespace (#11680) 2021-05-05 23:39:51 +01:00
JigSaw
77af202e2c norbits: cleaning, removed legacy dev tools. (#11677) 2021-05-06 07:06:09 +12:00
JigSaw
cdbe24dfdf abnormal: cleaned login function (#11679) 2021-05-05 21:05:31 +02:00
JigSaw
7983bc9a57 abnormal: cleaning, removed old dev mode. (#11676) 2021-05-05 16:52:20 +02:00
Webster
381e674ac4 beyond-hd: add new indexer based on API (#11481)
Thanks to: PNWebster
2021-05-05 10:24:05 +02:00
ilike2burnthing
921093934f hdarea: add MR
Global MR ranges between 0.4 and 0.8 depending on amount downloaded - https://www.hdarea.co/faq.php

No individual MR or MST.
2021-05-04 22:52:41 +01:00
JigSaw
ca3466050c xthor: pagination support, cleaning, dedup of results, resolves #10635 #6392 (#11675) 2021-05-04 18:38:37 +02:00
Alessio Gogna
993116c96f [enhancement] Handle optional field without exceptions (#11669)
In the optional fields parsing, it is no exceptional case that the selector finds nothing.
Return null value instead throwing exception increase the performance.
2021-05-04 07:59:37 +12:00
Garfield69
98dad4c169 torrentqq: drop a comment as its now outdated. 2021-05-04 07:38:04 +12:00
Alessio Gogna
7789a72ffb [enhancement] Date parser handle dates without a year. resolves #11219 (#11656) 2021-05-03 20:26:43 +01:00
Garfield69
4de2124b98 assorted: unblockit.club -> *.onl 2021-05-03 18:09:12 +12:00
Ryan McDonald
00c1ffa8c6 MoreThanTv: Add support for Season, Ep params for Sonarr. resolves #11527 (#11666) 2021-05-03 05:05:46 +01:00
ilike2burnthing
c1cbede92f hdtime: add MR
Global MR ranges between 0.4 and 0.8 depending on amount downloaded - https://hdtime.org/faq.php

No individual MR or MST.
2021-05-03 05:00:04 +01:00
JigSaw
52be410655 indexer: fix xthor api state, cleaning and removing old dev mode (#11665)
* xthor: added new case for api state

* xthor: removed old dev mode

* xthor: fix name violations

* xthor: add forced request delay due to api limit

* xthor: added new case for api state

* xthor: removed old dev mode

* xthor: fix name violations

* xthor: add forced request delay due to api limit
2021-05-03 02:38:36 +02:00
ilike2burnthing
0b630cee7c anime-free: change keywordless search to all time rather than last 24hrs 2021-05-03 01:35:26 +01:00
Garfield69
86b369ef1e piratadigital: drop tvdbid support while they are on unit3d 2.7.0 resolves #11660 2021-05-03 11:43:35 +12:00
ilike2burnthing
1310206d9e pier720: update categories 2021-05-03 00:26:45 +01:00
ilike2burnthing
de6c365865 cilipro: remove dead domains 2021-05-03 00:13:22 +01:00
ilike2burnthing
cbd14c2b2d torrentqq: new domain *85.com 2021-05-03 00:12:12 +01:00
Garfield69
324abe94a3 shizaproject: site forcing https. #11659 2021-05-03 08:58:14 +12:00
ilike2burnthing
0126e20984 issue template: minor edit (#11658) 2021-05-03 06:37:14 +12:00
ilike2burnthing
22ef17fe5b tjangto: removed, down for 30 days. resolves #11216 (#11655) 2021-05-02 15:11:49 +01:00
Garfield69
be64500580 Update README.md 2021-05-02 15:08:03 +12:00
Alessio Gogna
47a4f0f422 core: Convert Parse/Catch to TryParse (#11641) 2021-05-02 14:43:24 +12:00
ilike2burnthing
db6a8d89a8 elitetracker: removed, dead, pt2 2021-05-01 21:39:49 +01:00
Garfield69
483a72babd tvvault: fix keywordless searches. resolves #11515 2021-05-02 08:30:37 +12:00
ilike2burnthing
7a94b8809e elitetracker: removed, dead. resolves #11245 (#11652) 2021-05-01 21:01:52 +01:00
Garfield69
dd894ed267 bit-titan: drop European Size processing. resolve #11643 2021-05-01 07:37:34 +12:00
Garfield69
f808a071da torrentz2k: now just a TPB reskinned. removed. resolves #11636 2021-04-30 18:29:41 +12:00
ilike2burnthing
2d207a482d extremlymtorrents: removed, down for 30 days. resolves #6668 (#11638) 2021-04-30 02:29:56 +01:00
Garfield69
38849f57f8 ilcorsaroblu: new domain *.online resolves #11635 2021-04-30 06:54:32 +12:00
ilike2burnthing
ce2e7d8d1a hdturk: change domain *.club. resolves #5958 2021-04-28 23:08:18 +01:00
Frederic Yesid Peña Sánchez
72585f9761 cinecalidad: switch to new site cine-calidad.com (#11627) resolves #6081 2021-04-29 08:50:03 +12:00
Garfield69
c862fabeb4 puntotorrent: add season ep to tv-search resolves #11620 2021-04-28 09:59:26 +12:00
Garfield69
5894372c49 redbits: detect vose resolves #11615
also handle single season releases
tidy up title word spacing
2021-04-28 06:53:05 +12:00
Garfield69
d9ed8b981d hdspain: detect vose #11615 2021-04-28 06:51:16 +12:00
Garfield69
86f185d345 unionfansub: update login. resolves #11609 2021-04-26 15:46:10 +12:00
Garfield69
9e9c56e4c9 piratadigital: switch to cookie method. resolves #11608 2021-04-26 12:42:55 +12:00
ilike2burnthing
853179fd72 torrentparadise: restore working legacylinks 2021-04-25 23:17:17 +01:00
Garfield69
0dd5026cb1 torrentparadise: domain back to *.cc resolves #6385 2021-04-26 09:52:10 +12:00
Chuck
35232b1a5c thesceneplace: back to https (#11606) #11433
Updated links from http to https, as site is no longer forcing http.
2021-04-26 07:00:44 +12:00
Garfield69
49a507f6a0 byrutor: change test search
any age rather than just 1 day
2021-04-25 17:12:43 +12:00
Garfield69
ce527439f2 torrentqq: new domain *84.com 2021-04-25 16:44:26 +12:00
Garfield69
1e07a196df thesceneplace: forcing http. #11433 2021-04-25 16:29:39 +12:00
Wayne Dupree
d8d88962c6 Gui: Add dropdown filters for Categories and Type (#11603)
cleaned up the on load to clear previous filters when the modal was closed
2021-04-25 13:19:13 +12:00
ilike2burnthing
5ac8095741 sdbits: lint fix 2021-04-25 00:00:03 +01:00
ilike2burnthing
b2c0cb6ca9 sdbits: add sorting & imdb search. resolves #10629
also results per page info
2021-04-24 23:38:08 +01:00
Wayne Dupree
62de0458e5 gui: Add the ability to filter the Add Indexer datatable (#11596) 2021-04-24 18:45:33 +12:00
Garfield69
e1aa849315 nordic+: add config option to choose download link. #11542 2021-04-24 07:31:32 +12:00
66 changed files with 1470 additions and 2568 deletions

View File

@@ -1,5 +1,8 @@
### Read and complete in full with information about your setup and issue before submitting.
### Do not delete the template.
**Please use the search bar** at the top of the page and make sure you are not creating an already submitted issue.
Check closed issues as well, because your issue may have already been fixed.
Check closed issues as well, because your issue may have already been fixed. Also check our [Troubleshooting](https://github.com/Jackett/Jackett/wiki/Troubleshooting) for steps to resolve common issues.
Please read our [Contributing Guidelines](https://github.com/Jackett/Jackett/blob/master/CONTRIBUTING.md) before submitting your issue to ensure a prompt response to your bug.

View File

@@ -131,7 +131,6 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
* sukebei-Pantsu
* sukebei.Nyaa.si
* The Pirate Bay (TPB)
* Tjangto (짱토)
* TNTfork
* Tokyo Tosho
* Torlock
@@ -158,7 +157,6 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
* Torrentv
* TorrentView (토렌트뷰)
* TorrentWhiz ( 토렌트위즈)
* Torrentz2k
* truPornolabs
* Underverse
* UnionDHT
@@ -191,7 +189,6 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
* Erzsebet
* Erzsebet.pl
* ExKinoRay
* ExtremlymTorrents (XTR)
* Genesis-Movement
* HamsterStudio
* HunTorrent
@@ -322,7 +319,6 @@ A third-party Golang SDK for Jackett is available from [webtor-io/go-jackett](ht
* EbookParadijs
* Ebooks-Shares
* EfectoDoppler
* Elite-Tracker
* Empornium (EMP)
* EpubLibre
* eShareNet
@@ -777,11 +773,13 @@ To use it, please just request a free API key on [OMDb](http://www.omdbapi.com/a
### Windows
* Install the .NET 5 [SDK](https://www.microsoft.com/net/download/windows)
* Clone Jackett
* Open PowerShell and from the `src` directory, run `dotnet restore`
* Open the Jackett solution in Visual Studio 2019 (version 16.4 or above)
* Right-click on the Jackett solution and click 'Rebuild Solution' to restore NuGet packages
* Select Jackett.Server as the startup project
* In the drop-down menu of the run button select "Jackett.Server" instead of "IIS Express"
* Open PowerShell and from the `src` directory:
* - run `dotnet msbuild /restore`
* - then run `dotnet restore`
* - and run `dotnet build`
* Open the Jackett solution in Visual Studio 2019 (version 16.9 or above)
* Select **Jackett.Server** as the startup project
* In the drop-down menu of the run button select **Jackett.Server** instead of _IIS Express_
* Build/Start the project
### OSX

View File

@@ -301,6 +301,14 @@ stages:
targetType: inline
failOnStderr: true
script: |
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/DateTimeRoutines
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.Common
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.IntegrationTests
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.Server
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.Service
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.Test
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.Tray
dotnet-format --fix-whitespace --verbosity diagnostic --folder ./src/Jackett.Updater
dotnet-format --check --verbosity diagnostic --folder ./src/DateTimeRoutines
dotnet-format --check --verbosity diagnostic --folder ./src/Jackett.Common
dotnet-format --check --verbosity diagnostic --folder ./src/Jackett.IntegrationTests

File diff suppressed because it is too large Load Diff

View File

@@ -375,7 +375,12 @@
</tbody>
<tfoot>
<tr>
<td colspan="6"></td>
<th>Indexer</th>
<th>Categories</th>
<th>Type</th>
<th>Type string</th>
<th>Language</th>
<th></th>
</tr>
</tfoot>
</table>
@@ -693,6 +698,6 @@
</script>
<script type="text/javascript" src="../libs/api.js?changed=2017083001"></script>
<script type="text/javascript" src="../custom.js?changed=20210420"></script>
<script type="text/javascript" src="../custom.js?changed=20210424"></script>
</body>
</html>

View File

@@ -14,7 +14,7 @@ links:
- https://x1337x.ws/
- https://x1337x.eu/
- https://x1337x.se/
- https://1337x.unblockit.club/
- https://1337x.unblockit.onl/
- https://1337x.nocensor.space/
legacylinks:
- https://1337x.unblocked.earth/
@@ -43,6 +43,7 @@ legacylinks:
- https://1337x.unblockit.link/
- https://1337x.unblockit.buzz/
- https://1337x.unblocked.monster/
- https://1337x.unblockit.club/
caps:
categorymappings:

View File

@@ -75,7 +75,7 @@ search:
all_word_seach: 1
# 0 article, 1 comments, 2 static pages, 3 article titles
titleonly: "{{ if .Keywords }}3{{ else }}0{{ end }}"
searchdate: "{{ if .Keywords }}0{{ else }}1{{ end }}"
searchdate: 0
searchuser: ""
story: "{{ if .Keywords }}{{ .Keywords }}{{ else }}{{ .Today.Year }}{{ end }}"
sortby: date

View File

@@ -9,7 +9,7 @@ links:
- http://audiobookbay.nl/ # site forces http, does not support https
- http://audiobookbay.net/
- http://audiobookbayabb.com/
- https://audiobookbay.unblockit.club/
- https://audiobookbay.unblockit.onl/
legacylinks:
- https://audiobookbay.la/
- https://audiobookbay.unblockit.lat/
@@ -18,6 +18,7 @@ legacylinks:
- https://audiobookbay.unblockit.ltd/
- https://audiobookbay.unblockit.link/
- https://audiobookbay.unblockit.buzz/
- https://audiobookbay.unblockit.club/
caps:
categorymappings:

View File

@@ -217,13 +217,6 @@ search:
args: "02.01.2006 15:04:05 -07:00"
size:
selector: td.size
filters:
- name: replace
args: ["\u00a0", ""]
- name: replace
args: [".", ""]
- name: replace
args: [",", "."]
downloadvolumefactor:
case:
"span:contains(\"OU\")": 0 # only upload is counted

View File

@@ -9,7 +9,7 @@ followredirect: true
links:
# update poster placeholder link too
- https://btdb.eu/
- https://btdb.unblockit.club/
- https://btdb.unblockit.onl/
legacylinks:
- https://btdb.to/
- https://btdb.unblocked.app/
@@ -35,6 +35,7 @@ legacylinks:
- https://btdb.unblockit.ltd/
- https://btdb.unblockit.link/
- https://btdb.unblockit.buzz/
- https://btdb.unblockit.club/
caps:
categories:

View File

@@ -262,10 +262,10 @@ download:
attribute: href
search:
# keywords (any age posts, article titles only search)
# keywords (article titles only search)
# do=search&subaction=search&story=five+freddy&titleonly=3&searchdate=0&sortby=date&resorder=desc&catlist[]=1&catlist[]=2&catlist[]=3
# keywordless (1 day old posts or newer + article body search) query=game
# do=search&subaction=search&story=game&titleonly=0&searchdate=1&sortby=date&resorder=desc&catlist[]=0
# keywordless (article body search) query=game
# do=search&subaction=search&story=game&titleonly=0&searchdate=0&sortby=date&resorder=desc&catlist[]=0
paths:
- path: index.php
inputs:
@@ -278,7 +278,7 @@ search:
showposts: 1
# 0 article, 1 comments, 2 static pages, 3 article titles
titleonly: "{{ if .Keywords }}3{{ else }}0{{ end }}"
searchdate: "{{ if .Keywords }}0{{ else }}1{{ end }}"
searchdate: 0
story: "{{ if .Keywords }}{{ .Keywords }}{{ else }}game{{ end }}"
sortby: date
resorder: desc

View File

@@ -6,20 +6,9 @@ language: en-us
type: public
encoding: UTF-8
links:
- http://www.cilipro1.xyz/
- http://www.cilipro2.xyz/
- http://www.cilipro3.xyz/
- http://www.cilipro4.xyz/
- http://www.cilipro5.xyz/
- http://www.cilipro6.xyz/
- http://www.cilipro7.xyz/
- http://www.cilipro8.xyz/
- http://www.cilipro9.xyz/
- http://www.cilipro10.xyz/
- http://www.cilinb1.xyz/
- http://www.cilinb2.xyz/
- http://www.cilinb3.xyz/
- http://www.cilinb4.xyz/
- http://www.cilinb5.xyz/
- http://www.cilinb6.xyz/
- http://www.cilinb7.xyz/
@@ -80,6 +69,17 @@ legacylinks:
- http://www.lrsoso9.xyz/
- http://www.lrsoso10.xyz/
- http://www.cilijj.xyz/
- http://www.cilipro1.xyz/
- http://www.cilipro2.xyz/
- http://www.cilipro3.xyz/
- http://www.cilipro4.xyz/
- http://www.cilipro5.xyz/
- http://www.cilipro6.xyz/
- http://www.cilipro7.xyz/
- http://www.cilipro8.xyz/
- http://www.cilipro9.xyz/
- http://www.cilipro10.xyz/
- http://www.cilinb4.xyz/
caps:
categories:

View File

@@ -10,7 +10,7 @@ links:
- https://www.demonoid.is/
- https://www.dnoid.to/
- https://www.dnoid.pw/
- https://demonoid.unblockit.club/
- https://demonoid.unblockit.onl/
- https://demonoid.torrentbay.to/
- https://demonoid.nocensor.space/
legacylinks:
@@ -32,6 +32,7 @@ legacylinks:
- https://demonoid.unblockit.ltd/
- https://demonoid.unblockit.link/
- https://demonoid.unblockit.buzz/
- https://demonoid.unblockit.club/
caps:
categorymappings:

View File

@@ -9,7 +9,7 @@ followredirect: true
links:
- https://www.ettvcentral.com/
- https://ettv.unblockninja.com/
- https://ettv.unblockit.club/
- https://ettv.unblockit.onl/
- https://ettv.nocensor.space/
legacylinks:
- https://www.ettv.tv/
@@ -40,6 +40,7 @@ legacylinks:
- https://ettv.unblockit.link/
- https://ettv.unblockit.buzz/
- https://ettv.unblocked.monster/
- https://ettv.unblockit.club/
caps:
categorymappings:

View File

@@ -7,7 +7,7 @@ type: public
encoding: UTF-8
links:
- https://extratorrents.it/
- https://extratorrent.unblockit.club/
- https://extratorrent.unblockit.onl/
- https://extratorrent.nocensor.space/
legacylinks:
- https://extratorrent.ag/
@@ -17,6 +17,7 @@ legacylinks:
- https://extratorrent.unblockit.link/
- https://extratorrent2.unblockninja.com/
- https://extratorrent.unblockit.buzz/
- https://extratorrent.unblockit.club/
caps:
categorymappings:

View File

@@ -1,179 +0,0 @@
---
id: extremlymtorrents
name: ExtremlymTorrents
description: "ExtremlymTorrents (XTR) is a Semi-Private tracker for MOVIES / TV / GENERAL"
language: en-us
type: semi-private
encoding: UTF-8
links:
- https://extremlymtorrents.ws/
caps:
categorymappings:
- {id: 22, cat: Movies/HD, desc: "720p HD"}
- {id: 15, cat: Movies/HD, desc: "1080p HD"}
- {id: 40, cat: Movies/UHD, desc: "4K UHD 2160p"}
- {id: 12, cat: Movies/BluRay, desc: "BluRay"}
- {id: 5, cat: Movies/DVD, desc: "DVDRip"}
- {id: 16, cat: Movies/3D, desc: "BluRay 3D"}
- {id: 13, cat: TV/HD, desc: "HDTV"}
- {id: 47, cat: XXX, desc: "Porn UHD 4K -[+18]- xXx"}
- {id: 11, cat: XXX, desc: "Porn -[+18]- xXx"}
- {id: 50, cat: XXX, desc: "xXx iMAGESET (+18)"}
- {id: 41, cat: TV, desc: "TVRip"}
- {id: 6, cat: Audio, desc: "Music Mp3 | FLAC"}
- {id: 9, cat: TV, desc: "Kidz | Cartoons"}
- {id: 8, cat: Books/EBook, desc: "Comics | EBook"}
- {id: 10, cat: TV, desc: "TV Episode | Season Complete"}
- {id: 27, cat: Movies/DVD, desc: "DVD | PAL | NTSC"}
- {id: 25, cat: Movies/WEB-DL, desc: "WEBRip | WEB-DL"}
- {id: 35, cat: Movies, desc: "BRRip | BDRip | HDRip"}
- {id: 3, cat: PC, desc: "Applications"}
- {id: 17, cat: Console/PSP, desc: "PSP | Playstation "}
- {id: 30, cat: TV/SD, desc: "PDTV | SDTV"}
- {id: 18, cat: Console/PS3, desc: "PS3 | Playstation 3 "}
- {id: 46, cat: Console/PS4, desc: "PS4 | PlayStation 4"}
- {id: 20, cat: PC/Mobile-iOS, desc: "Iphone iOS"}
- {id: 19, cat: PC/Mobile-Android, desc: "Android Apk"}
- {id: 21, cat: Movies, desc: "Pack"}
- {id: 49, cat: TV/UHD, desc: "TV UHD | 2160p | Episodes"}
- {id: 24, cat: Audio/Video, desc: "VideoClip"}
- {id: 26, cat: Console/Wii, desc: "Wii Games"}
- {id: 31, cat: TV/Documentary, desc: "DOC's"}
- {id: 36, cat: Movies, desc: "CAMRip | REC"}
- {id: 38, cat: Movies, desc: "TS: TeleSync | HD-TS"}
- {id: 48, cat: Audio/Video, desc: "4K | 2160p | Music Video"}
- {id: 28, cat: TV/Anime, desc: "Anime | Japanese"}
- {id: 43, cat: XXX, desc: "Hentai | Manga"}
- {id: 29, cat: PC/0day, desc: "Windows PC"}
- {id: 7, cat: PC/Mac, desc: "Mac"}
- {id: 23, cat: PC, desc: "Linux"}
- {id: 32, cat: PC/Mobile-Other, desc: "GPS Navigation"}
- {id: 45, cat: Audio, desc: "Vinyl Rip"}
- {id: 2, cat: Console/XBox 360, desc: "XBOX 360"}
- {id: 1, cat: PC/Games, desc: "Games PC"}
- {id: 14, cat: Other, desc: "Wallpapers"}
- {id: 44, cat: Movies, desc: "Bollywood"}
- {id: 42, cat: Other/Misc, desc: "X EXTERN ONLY MAGNET"}
- {id: 39, cat: TV/Sport, desc: "Sport TV"}
modes:
search: [q]
tv-search: [q, season, ep]
movie-search: [q]
music-search: [q]
book-search: [q]
settings:
- name: username
type: text
label: Username
- name: password
type: password
label: Password
- name: sort
type: select
label: Sort requested from site
default: id
options:
id: created
seeders: seeders
size: size
name: title
- name: type
type: select
label: Order requested from site
default: desc
options:
desc: desc
asc: asc
login:
path: account-login.php
method: post
inputs:
username: "{{ .Config.username }}"
password: "{{ .Config.password }}"
error:
- selector: span.titlebar:contains("Access Denied")
message:
selector: td.text
test:
path: index.php
selector: a[href="account-logout.php"]
search:
paths:
- path: torrents-search.php
inputs:
$raw: "{{ range .Categories }}c{{.}}=1&{{end}}"
search: "{{ .Keywords }}"
# 0 all 1 English 2 etc...
lang: 0
sort: "{{ .Config.sort }}"
order: "{{ .Config.type }}"
keywordsfilters:
- name: re_replace
args: ["(\\w+)", " +$1"] # prepend + to each word
rows:
selector: table.xtrz > tbody > tr[class^="ttable_col"]
fields:
category:
selector: a[href^="torrents.php?cat="]
attribute: href
filters:
- name: querystring
args: cat
language:
selector: td:nth-last-child(5)
description:
optional: true
selector: img[src="/images/vip-icon.png"]
attribute: src
filters:
- name: replace
args: ["/images/vip-icon.png", " VIP ONLY"]
title:
selector: a[href^="file.php?id="] b
filters:
- name: append
args: " {{ .Result.language }}{{ .Result.description }}"
details:
selector: a[href^="file.php?id="]
attribute: href
download:
selector: a[href^="download.php?id="]
attribute: href
poster:
selector: a[href^="file.php?id="]
attribute: onmouseover
filters:
- name: regexp
args: "src=(.+?) "
date:
selector: td:nth-last-child(1)
filters:
- name: append
args: " +00:00" # auto adjusted by site account profile
- name: dateparse
args: "02.01.200615:04:05 -07:00"
leechers:
selector: td:nth-last-child(2)
seeders:
selector: td:nth-last-child(3)
size:
selector: td:nth-last-child(4)
downloadvolumefactor:
case:
img[src="/images/free.png"]: 0
"*": 1
uploadvolumefactor:
text: 1
minimumratio:
text: 1.0
minimumseedtime:
# 1 day (as seconds = 24 x 60 x 60)
text: 86400
# engine n/a

View File

@@ -12,7 +12,7 @@ links:
- https://eztv.tf/
- https://eztv.yt/
- https://eztv.unblockninja.com/
- https://eztv.unblockit.club/
- https://eztv.unblockit.onl/
- https://eztv.nocensor.space/
legacylinks:
- https://eztv.ag/ # redirects to .re
@@ -43,6 +43,7 @@ legacylinks:
- https://eztv.unblockit.link/
- https://eztv.unblockit.buzz/
- https://eztv.unblocked.monster/
- https://eztv.unblockit.club/
caps:
categories:

View File

@@ -9,7 +9,7 @@ followredirect: true
links:
- https://www.gtdb.to/
- https://glodls.to/
- https://glotorrents.unblockit.club/
- https://glotorrents.unblockit.onl/
- https://glotorrents.nocensor.space/
legacylinks:
- https://glodls.rocks/
@@ -37,6 +37,7 @@ legacylinks:
- https://glotorrents.unblockit.link/
- https://glotorrents.unblockit.buzz/
- https://glodls.unblocked.monster/
- https://glotorrents.unblockit.club/
caps:
categorymappings:

View File

@@ -162,6 +162,8 @@ search:
img.pro_free2up: 2
img.pro_2up: 2
"*": 1
minimumratio:
text: 0.8
description:
selector: td:nth-child(2)
remove: a, img

View File

@@ -78,7 +78,18 @@ search:
- name: append
args: "]"
title:
selector: td.titulo a[id]
selector: td.titulo a[id]:contains("VOSE")
optional: true
filters:
- name: prepend
args: "{{ .Result.extras }} "
- name: append
args: " English"
- name: re_replace
args: ["(?i)T(\\d{1,2})\\b", "S$1"]
title:
selector: td.titulo a[id]:not(:contains("VOSE"))
optional: true
filters:
- name: prepend
args: "{{ .Result.extras }} "

View File

@@ -162,6 +162,8 @@ search:
img.pro_free2up: 2
img.pro_2up: 2
"*": 1
minimumratio:
text: 0.8
description:
selector: td:nth-child(2)
remove: a, img

View File

@@ -6,9 +6,10 @@ language: tr-TR
type: private
encoding: UTF-8
links:
- https://hdturk.de/
- https://hdturk.club/
legacylinks:
- http://hdturk.de/
- https://hdturk.de/
caps:
categorymappings:

View File

@@ -6,11 +6,12 @@ language: it-it
type: semi-private
encoding: UTF-8
links:
- https://ilcorsaroblu.org/
- https://ilcorsaroblu.online/
legacylinks:
- http://ilcorsaroblu.org/
- https://www.ilcorsaroblu.info/
- https://www.ilcorsaroblu.org/
- https://ilcorsaroblu.org/
caps:
categorymappings:

View File

@@ -12,7 +12,7 @@ links:
- https://www.limetorrents.co/
- https://limetor.com/
- https://www.limetor.pro/
- https://limetorrents.unblockit.club/
- https://limetorrents.unblockit.onl/
- https://limetorrents.unblockninja.com/
- https://limetorrents.nocensor.space/
legacylinks:
@@ -41,6 +41,7 @@ legacylinks:
- https://limetorrents.unblockit.ltd/
- https://limetorrents.unblockit.link/
- https://limetorrents.unblockit.buzz/
- https://limetorrents.unblockit.club/
caps:
categorymappings:

View File

@@ -37,6 +37,10 @@ settings:
- name: password
type: password
label: Password
- name: usemagnet
type: checkbox
label: "Use Magnet Download Link"
default: false
- name: freeleech
type: checkbox
label: Search freeleech only
@@ -111,15 +115,17 @@ search:
args: "/categories/(\\d+)"
title:
selector: a[href*="/torrents/"]
download:
selector: a[href*="/download/"]
attribute: href
magnet:
selector: a[href^="magnet:?"]
attribute: href
details:
selector: a[href*="/torrents/"]
attribute: href
download1:
selector: a[href*="/download/"]
attribute: href
download2:
selector: a[href^="magnet:?"]
attribute: href
download:
text: "{{ if .Config.usemagnet }}{{ .Result.download2 }}{{ else }}{{ .Result.download1 }}{{ end }}"
poster:
selector: div.torrent-poster img
attribute: src

View File

@@ -42,7 +42,7 @@ caps:
- {id: 46, cat: TV/Sport, desc: "Hockey"}
- {id: 48, cat: TV/Sport, desc: "Hockey - NHL"}
- {id: 88, cat: TV/Sport, desc: "Hockey - NHL Playoffs"}
- {id: 102, cat: TV/Sport, desc: "Hockey - NHL Playoffs 2018-2019"}
- {id: 102, cat: TV/Sport, desc: "Hockey - NHL Playoffs 2018-2020"}
- {id: 93, cat: TV/Sport, desc: "Hockey - NHL Playoffs - 2017"}
- {id: 80, cat: TV/Sport, desc: "Hockey - NHL Playoffs - 2016"}
- {id: 65, cat: TV/Sport, desc: "Hockey - Stanley Cup Finals"}
@@ -53,6 +53,7 @@ caps:
- {id: 50, cat: TV/Sport, desc: "Hockey - Other"}
- {id: 55, cat: TV/Sport, desc: "Baseball"}
- {id: 71, cat: TV/Sport, desc: "Baseball - MLB"}
- {id: 107, cat: TV/Sport, desc: "Baseball - MLB World Series"}
- {id: 72, cat: TV/Sport, desc: "Baseball - Other"}
- {id: 85, cat: TV/Sport, desc: "Baseball - Reviews, highlights, documentaries"}
- {id: 45, cat: TV/Sport, desc: "Other sports"}
@@ -63,6 +64,7 @@ caps:
- {id: 75, cat: TV/Sport, desc: "Other sports - Tennis"}
- {id: 74, cat: TV/Sport, desc: "Other sports - Fighting"}
- {id: 94, cat: TV/Sport, desc: "Other sports - Misc"}
- {id: 73, cat: TV/Sport, desc: "Other sports - Auto, moto racing"}
- {id: 100, cat: TV/Sport, desc: "Soccer"}
- {id: 98, cat: TV/Sport, desc: "Soccer - FIFA World Cup"}
- {id: 56, cat: TV/Sport, desc: "Sports on tv"}

View File

@@ -18,16 +18,17 @@ caps:
modes:
search: [q]
tv-search: [q, season, ep, imdbid, tvdbid]
tv-search: [q, season, ep, imdbid]
movie-search: [q, imdbid, tmdbid]
settings:
- name: username
- name: cookie
type: text
label: Username
- name: password
type: password
label: Password
label: Cookie
- name: info
type: info
label: How to get the Cookie
default: "<ol><li>Login to this tracker with your browser<li>Open the <b>DevTools</b> panel by pressing <b>F12</b><li>Select the <b>Network</b> tab<li>Click on the <b>Doc</b> button (Chrome Browser) or <b>HTML</b> button (FireFox)<li>Refresh the page by pressing <b>F5</b><li>Click on the first row entry<li>Select the <b>Headers</b> tab on the Right panel<li>Find <b>'cookie:'</b> in the <b>Request Headers</b> section<li><b>Select</b> and <b>Copy</b> the whole cookie string <i>(everything after 'cookie: ')</i> and <b>Paste</b> here.</ol>"
- name: freeleech
type: checkbox
label: Search freeleech only
@@ -50,22 +51,9 @@ settings:
asc: asc
login:
path: login
method: form
form: form[action$="/login"]
method: cookie
inputs:
username: "{{ .Config.username }}"
password: "{{ .Config.password }}"
remember: on
selectorinputs:
_token:
selector: input[name="_token"]
attribute: value
error:
- selector: div#ERROR_COPY
# test:
# path: /
# selector: a[href$="/logout"]
cookie: "{{ .Config.cookie }}"
search:
paths:
@@ -76,7 +64,7 @@ search:
description: ""
uploader: ""
imdb: "{{ .Query.IMDBIDShort }}"
tvdb: "{{ .Query.TVDBID }}"
tvdb: ""
tmdb: "{{ .Query.TMDBID }}"
mal: ""
igdb: ""

View File

@@ -9,7 +9,7 @@ followredirect: true
links:
- https://pirateiro.com/
- https://pirateiro.eu/
- https://pirateiro.unblockit.club/
- https://pirateiro.unblockit.onl/
legacylinks:
- http://pirateiro.com/
- https://pirateiro.unblockit.pro/
@@ -25,6 +25,7 @@ legacylinks:
- https://pirateiro.unblockit.ltd/
- https://pirateiro.unblockit.link/
- https://pirateiro.unblockit.buzz/
- https://pirateiro.unblockit.club/
caps:
categorymappings:

View File

@@ -96,7 +96,7 @@ caps:
modes:
search: [q]
tv-search: [q]
tv-search: [q, season, ep]
movie-search: [q]
music-search: [q]
book-search: [q]

View File

@@ -68,6 +68,9 @@ login:
search:
paths:
- path: torrents/filter
keywordsfilters:
- name: re_replace
args: ["(?i)\\bS0*(\\d+)\\b", "T$1"]
inputs:
$raw: "{{ range .Categories }}categories[]={{.}}&{{end}}"
search: "{{ if .Query.IMDBID }}{{ else }}{{ .Keywords }}{{ end }}" # for dashboard imdbid search
@@ -99,23 +102,42 @@ search:
- name: regexp
args: "/categories/(\\d+)"
title:
selector: a[href*="/torrents/"]
selector: a.view-torrent:contains("VOSE")
optional: true
filters:
- name: re_replace
args: ["\\[", " "]
args: ["(?i)bdfull", "BluRay"] # BDfull -> BluRay
- name: re_replace
args: ["\\]", " "]
args: ["(?i)RedBits", ""] # Delete RedBits
- name: append
args: " English" # Add english to fix Sonarr/Radarr language
- name: re_replace
args: ["\\[|\\]", " "]
- name: re_replace
args: [" ", " "]
- name: re_replace
args: ["(?i)T(\\d{1,2})\\b", "S$1"]
title:
selector: a.view-torrent:not(:contains("VOSE"))
optional: true
filters:
- name: re_replace
args: ["(?i)bdfull", "BluRay"] # BDfull -> BluRay
- name: re_replace
args: ["(?i)RedBits", ""] # Delete RedBits
- name: append
args: " Spanish" # Add spanish to fix Sonarr/Radarr language
- name: re_replace
args: ["\\[|\\]", " "]
- name: re_replace
args: [" ", " "]
- name: re_replace
args: ["(?i)T(\\d{1,2})\\b", "S$1"]
download:
selector: a[href*="/download/"]
attribute: href
details:
selector: a[href*="/torrents/"]
selector: a.view-torrent
attribute: href
poster:
selector: div.torrent-poster img

View File

@@ -22,10 +22,38 @@ caps:
modes:
search: [q]
tv-search: [q, season, ep]
movie-search: [q]
tv-search: [q, season, ep, imdbid]
movie-search: [q, imdbid]
music-search: [q]
settings:
- name: username
type: text
label: Username
- name: password
type: password
label: Password
- name: sort
type: select
label: Sort requested from site
default: added
options:
added: created
seeders: seeders
size: size
name: title
- name: type
type: select
label: Order requested from site
default: DESC
options:
DESC: desc
ASC: asc
- name: info
type: info
label: Results Per Page
default: For best results, change the <b>Torrents per page:</b> setting to <b>100</b> on your account profile.
login:
path: takeloginn3.php
method: post
@@ -44,9 +72,14 @@ search:
- path: browse.php
inputs:
$raw: "{{ range .Categories }}c{{.}}=1&{{end}}"
search: "{{ .Keywords }}"
search: "{{ if .Query.IMDBID }}{{ else }}{{ .Keywords }}{{ end }}"
incldead: 1
descriptions: 0
imdbgt: 0
imdblt: 10
imdb: "{{ .Query.IMDBID }}"
sort: "{{ .Config.sort }}"
d: "{{ .Config.type }}"
rows:
selector: table#torrent-list > tbody > tr[id]

View File

@@ -7,6 +7,8 @@ type: private
encoding: UTF-8
links:
- https://www.thesceneplace.com/
legacylinks:
- http://www.thesceneplace.com/ # site is no longer forcing http
caps:
categorymappings:
@@ -98,7 +100,7 @@ search:
# does not support imdbid search and does not return imdb link in results
rows:
selector: "table.lista > tbody > tr:has(a[href^=\"index.php?page=torrent-details&id=\"]){{ if .Config.freeleech }}:has(img[src=\"images/freeleech.gif\"]){{ else }}{{ end }}"
selector: "table.lista > tbody > tr:has(a[href^=\"index.php?page=torrent-details&id=\"]){{ if .Config.freeleech }}:has(img[src=\"images/freeleech.gif\"]){{ else }}{{ end }}{{ if .Config.freeleech }}, table.lista > tbody > tr:has(a[href^=\"index.php?page=torrent-details&id=\"]):has(img[src=\"images/gold.gif\"]){{ else }}{{ end }}"
fields:
category:
@@ -149,7 +151,17 @@ search:
img[src="images/bronze.gif"]: 0.75
"*": 1
uploadvolumefactor:
text: 1
case:
img[src="images/2x.gif"]: 2
img[src="images/3x.gif"]: 3
img[src="images/4x.gif"]: 4
img[src="images/5x.gif"]: 5
img[src="images/6x.gif"]: 6
img[src="images/7x.gif"]: 7
img[src="images/8x.gif"]: 8
img[src="images/9x.gif"]: 9
img[src="images/10x.gif"]: 10
"*": 1
minimumratio:
text: 1.0
minimumseedtime:

View File

@@ -1,95 +0,0 @@
---
id: tjangto
name: Tjangto
description: "Tjangto (짱토) is a Public KOREAN tracker for Korean media."
language: ko-KR
type: public
encoding: UTF-8
followredirect: true
links:
- https://www.jjangt.com/
legacylinks:
- https://www.jjangtorrent.com/
caps:
categorymappings:
- {id: "tani", cat: TV/Anime, desc: "애니 (Animation)"}
- {id: "tv", cat: TV, desc: "TV프로 (TV)"}
- {id: "tmovie", cat: Movies, desc: "영화 (Movie)"}
- {id: "tdrama", cat: TV, desc: "드라마 (Drama)"}
- {id: "tent", cat: TV, desc: "예능 (Entertainment)"}
- {id: "tmusic", cat: Audio, desc: "음악 (Music)"}
- {id: "util", cat: PC, desc: "유틸 (Software)"}
- {id: "torrent_amav", cat: XXX, desc: "서양 (Adult)"}
- {id: "torrent_nmav", cat: XXX, desc: "일본노모 (Adult)"}
- {id: "torrent_ymav", cat: XXX, desc: "일본유모 (Adult)"}
modes:
search: [q]
tv-search: [q]
movie-search: [q]
music-search: [q]
book-search: [q]
settings:
- name: flaresolverr
type: info
label: FlareSolverr
default: This site may use Cloudflare DDoS Protection, therefore Jackett requires <a href="https://github.com/Jackett/Jackett#configuring-flaresolverr" target="_blank">FlareSolver</a> to access it.
download:
selector: a[href*="magnet:?xt="]
attribute: href
filters:
- name: append
args: "&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969&tr=udp%3A%2F%2Fp4p.arenabg.com%3A1337%2Fannounce&tr=udp%3A%2F%2Fexplodie.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.tiny-vps.com%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.demonii.si%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.pirateparty.gr%3A6969%2Fannounce&tr=udp%3A%2F%2Fipv4.tracker.harry.lu%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.cyberia.is%3A6969%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2710%2Fannounce&tr=udp%3A%2F%2Fdenis.stalker.upeer.me%3A6969%2Fannounce"
search:
paths:
# https://www.jjangtorrent.com/bbs/search.php?site=1&sfl=wr_subject%7C%7Cwr_content&sop=and&stx=sin
- path: bbs/search.php
inputs:
stx: "{{ if .Keywords }}{{ .Keywords }}{{ else }}{{ .Today.Year }}{{ end }}"
srows: 100
gr_id: ""
# wr_subject||wr_content, wr_subject, wr_content, mb_id, wr_name
sfl: wr_subject
# or, and
sop: and
site: 1
rows:
selector: section.sch_res_list ul li
fields:
category:
selector: div.sch_tit a
attribute: href
filters:
- name: querystring
args: bo_table
title:
selector: div.sch_tit a
details:
selector: div.sch_tit a
attribute: href
download:
selector: div.sch_tit a
attribute: href
date:
# 2020-05-12 12:00:52
selector: span.sch_datetime
filters:
- name: dateparse
args: "2006-01-02 15:04:05"
size:
text: "512 MB"
seeders:
text: 1
leechers:
text: 1
downloadvolumefactor:
text: 0
uploadvolumefactor:
text: 1
# engine n/a

View File

@@ -10,7 +10,8 @@ links:
- https://www.torlock.com/
- https://www.torlock2.com/
- https://www.torlock.icu/
- https://torlock.unblockit.club/
- https://torlock.unblockit.onl/
- https://torlock.nocensor.space/
legacylinks:
- https://torlock.com/
- https://torlock.unblockit.pro/
@@ -26,6 +27,7 @@ legacylinks:
- https://torlock.unblockit.ltd/
- https://torlock.unblockit.link/
- https://torlock.unblockit.buzz/
- https://torlock.unblockit.club/
caps:
categorymappings:

View File

@@ -7,6 +7,7 @@ type: public
encoding: UTF-8
links:
- https://www.toros.co/
- https://toros.nocensor.space/
caps:
categorymappings:

View File

@@ -8,7 +8,7 @@ encoding: UTF-8
followredirect: true
links:
- https://www.torrentdownload.info/
- https://torrentdownload.unblockit.club/
- https://torrentdownload.unblockit.onl/
- https://torrentdownload.nocensor.space/
legacylinks:
- https://torrentdownload.unblockit.pro/
@@ -34,6 +34,7 @@ legacylinks:
- https://torrentdownload.unblockit.link/
- https://torrentdownload.unblockit.buzz/
- https://torrentdownload.unblocked.monster/
- https://torrentdownload.unblockit.club/
caps:
categorymappings:

View File

@@ -9,7 +9,7 @@ followredirect: true
links:
- https://www.torrentdownloads.info/
- https://www.torrentdownloads.me/
- https://torrentdownloads.unblockit.club/
- https://torrentdownloads.unblockit.onl/
- https://torrentdownloads.nocensor.space/
legacylinks:
- https://torrentdownloads.unblockit.pro/
@@ -35,6 +35,7 @@ legacylinks:
- https://torrentdownloads.unblockit.link/
- https://torrentdownloads.unblockit.buzz/
- https://torrentdownloads.unblocked.monster/
- https://torrentdownloads.unblockit.club/
caps:
categorymappings:

View File

@@ -9,7 +9,8 @@ followredirect: true
links:
- https://www.torrentfunk.com/
- https://www.torrentfunk2.com/
- https://torrentfunk.unblockit.club/
- https://torrentfunk.unblockit.onl/
- https://torrentfunk.nocensor.space/
legacylinks:
- https://torrentfunk.unblockit.pro/
- https://torrentfunk.unblockit.one/
@@ -24,6 +25,7 @@ legacylinks:
- https://torrentfunk.unblockit.ltd/
- https://torrentfunk.unblockit.link/
- https://torrentfunk.unblockit.buzz/
- https://torrentfunk.unblockit.club/
caps:
categorymappings:

View File

@@ -11,7 +11,7 @@ links:
- https://torrentgalaxy.mx/
- https://torrentgalaxy.su/
- https://torrentgalaxy.unblockninja.com/
- https://torrentgalaxy.unblockit.club/
- https://torrentgalaxy.unblockit.onl/
- https://torrentgalaxy.nocensor.space/
legacylinks:
- https://torrentgalaxy.org/ # redirects to *.to
@@ -40,6 +40,7 @@ legacylinks:
- https://torrentgalaxy.unblockit.link/
- https://torrentgalaxy.unblockit.buzz/
- https://tgx.unblocked.monster/
- https://torrentgalaxy.unblockit.club/
caps:
categorymappings:

View File

@@ -6,12 +6,11 @@ language: en-us
type: public
encoding: UTF-8
links:
- https://torrentparadise.org/
legacylinks:
- https://torrentparadise.to/
- https://torrentparadise.cc/
- https://torrentparadise.la/
- https://torrentparadise.cl/
- https://torrentparadise.org/
caps:
categories:

View File

@@ -7,7 +7,7 @@ type: public
encoding: UTF-8
followredirect: true
links:
- https://torrentqq83.com/
- https://torrentqq85.com/
legacylinks:
- https://torrentqq76.com/
- https://torrentqq77.com/
@@ -16,6 +16,8 @@ legacylinks:
- https://torrentqq80.com/
- https://torrentqq81.com/
- https://torrentqq82.com/
- https://torrentqq83.com/
- https://torrentqq84.com/
caps:
categorymappings:
@@ -77,7 +79,6 @@ search:
selector: a[href$=".html"][title]
attribute: href
date:
# note: this will cause 0m date results for MM-dd dates that are higher than current date, as Jackett dateparse assumes year is now.
selector: div.wr-date:contains("-")
optional: true
filters:

View File

@@ -1,113 +0,0 @@
---
id: torrentz2k
name: Torrentz2k
description: "Torrentz2k is a Public torrent indexer"
language: en-us
type: public
encoding: UTF-8
links:
- https://torrentz2is.me/
legacylinks:
- https://torrentz2k.xyz/
- https://torrentz2k.pw/
caps:
categorymappings:
- {id: book, cat: Books, desc: Books}
- {id: film, cat: Movies, desc: Movies}
- {id: gamepad, cat: PC/Games, desc: Games}
- {id: list, cat: Other, desc: Other}
- {id: close, cat: XXX, desc: XXX}
- {id: music, cat: Audio, desc: "Music MP3"}
- {id: sellsy, cat: Audio/Lossless, desc: "Music Lossless"}
- {id: play-circle, cat: TV, desc: WEBTV}
- {id: smile-o, cat: TV/Anime, desc: Anime}
- {id: television, cat: TV, desc: TV}
- {id: wrench, cat: PC, desc: Apps}
modes:
search: [q]
tv-search: [q, season, ep]
movie-search: [q]
music-search: [q]
book-search: [q]
settings:
- name: flaresolverr
type: info
label: FlareSolverr
default: This site may use Cloudflare DDoS Protection, therefore Jackett requires <a href="https://github.com/Jackett/Jackett#configuring-flaresolverr" target="_blank">FlareSolver</a> to access it.
search:
# https://torrentz2is.me/search/
paths:
- path: search/
method: post
- path: "search/{{ if .Keywords }}{{ .Keywords }}{{ else }}:latest:{{ end }}/category/all/page/2"
method: post
inputs:
page: 2
inputs:
q: "{{ if .Keywords }}{{ .Keywords }}{{ else }}:latest:{{ end }}"
category: all
keywordsfilters:
# the site uses % for wildcard
- name: re_replace
args: ["[^a-zA-Z0-9]+", "%"]
rows:
selector: table > tbody > tr:has(i)
filters:
- name: andmatch
fields:
category:
selector: i
attribute: class
filters:
- name: append
args: " list"
# extract the second class
- name: split
args: [" ", 1]
# remove fa- prefix
- name: replace
args: ["fa-", ""]
title:
selector: div.btntitle a
attribute: title
details:
# details page is only accessible via form and post which Cardigann does not support.
text: "{{ .Config.sitelink }}"
infohash:
selector: input[name="id"]
attribute: value
date:
selector: td:nth-child(6)
filters:
- name: re_replace
args: ["(?i)sec", "seconds"]
- name: re_replace
args: ["(?i)min", "minutes"]
- name: re_replace
args: ["(?i)hr", "hours"]
- name: re_replace
args: ["(?i)mon", "months"]
- name: re_replace
args: ["(?i)yr", "years"]
- name: append
args: " ago"
- name: timeago
_size:
selector: td:nth-child(5)
size:
text: "{{ if .Result._size }}{{ .Result._size }}{{ else }}0 B{{ end }}"
seeders:
selector: td:nth-child(3)
leechers:
selector: td:nth-child(4)
downloadvolumefactor:
text: 0
uploadvolumefactor:
text: 1
# engine n/a

View File

@@ -63,13 +63,13 @@ settings:
asc: asc
login:
path: http://foro.unionfansub.com/member.php
path: https://foro.unionfansub.com/member.php
method: post
inputs:
quick_username: "{{ .Config.username }}"
quick_password: "{{ .Config.password }}"
action: do_login
url: "http://torrent.unionfansub.com/browse.php"
url: "https://torrent.unionfansub.com/"
quick_login: 1
quick_remember: yes
error:

View File

@@ -8,6 +8,7 @@ encoding: UTF-8
links:
- https://yourbittorrent.com/
- https://yourbittorrent2.com/
- https://yourbittorrent.nocensor.space/
legacylinks:
- https://yourbittorrent.host/

View File

@@ -9,7 +9,7 @@ followredirect: true
links:
- https://zooqle.com/
- https://zooqle.unblockninja.com/
- https://zooqle.unblockit.club/
- https://zooqle.unblockit.onl/
- https://zooqle.nocensor.space/
legacylinks:
- https://zooqle.unblockit.pro/
@@ -35,6 +35,7 @@ legacylinks:
- https://zooqle.unblockit.link/
- https://zooqle.unblockit.buzz/
- https://zooqle.unblocked.monster/
- https://zooqle.unblockit.club/
caps:
categorymappings:

View File

@@ -3,10 +3,8 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -17,8 +15,6 @@ using Jackett.Common.Models;
using Jackett.Common.Models.IndexerConfig.Bespoke;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using WebClient = Jackett.Common.Utils.Clients.WebClient;
@@ -36,12 +32,6 @@ namespace Jackett.Common.Indexers
private string SearchUrl => SiteLink + "torrents.php";
private string DetailsUrl => SiteLink + "torrents.php?id=";
private string ReplaceMulti => ConfigData.ReplaceMulti.Value;
private bool Latency => ConfigData.Latency.Value;
private bool DevMode => ConfigData.DevMode.Value;
private bool CacheMode => ConfigData.HardDriveCache.Value;
private static string Directory => Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name.ToLower(), MethodBase.GetCurrentMethod().DeclaringType?.Name.ToLower());
private readonly Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
private ConfigurationDataAbnormal ConfigData
{
@@ -76,8 +66,6 @@ namespace Jackett.Common.Indexers
Language = "fr-fr";
Encoding = Encoding.UTF8;
Type = "private";
// NET::ERR_CERT_DATE_INVALID expired 29 July 2020
w.AddTrustedCertificate(new Uri(SiteLink).Host, "9cb32582b564256146616afddbdb8e7c94c428ed");
AddCategoryMapping("MOVIE|DVDR", TorznabCatType.MoviesDVD, "DVDR");
AddCategoryMapping("MOVIE|DVDRIP", TorznabCatType.MoviesSD, "DVDRIP");
@@ -115,34 +103,7 @@ namespace Jackett.Common.Indexers
LoadValuesFromJson(configJson);
// Check & Validate Config
validateConfig();
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// If we want to simulate a browser
if (ConfigData.Browser.Value)
{
// Clean headers
emulatedBrowserHeaders.Clear();
// Inject headers
emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
}
// Getting login form to retrieve CSRF token
var myRequest = new Utils.Clients.WebRequest
{
Url = LoginUrl
};
// Add our headers to request
myRequest.Headers = emulatedBrowserHeaders;
ValidateConfig();
// Building login form data
var pairs = new Dictionary<string, string> {
@@ -152,20 +113,9 @@ namespace Jackett.Common.Indexers
{ "login", "Connexion" }
};
// Do the login
var request = new Utils.Clients.WebRequest
{
PostData = pairs,
Referer = LoginUrl,
Type = RequestType.POST,
Url = LoginUrl,
Headers = emulatedBrowserHeaders
};
// Perform loggin
latencyNow();
output("\nPerform loggin.. with " + LoginUrl);
var response = await webclient.GetResultAsync(request);
logger.Info("\nAbnormal - Perform loggin.. with " + LoginUrl);
var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, LoginUrl, true);
// Test if we are logged in
await ConfigureIfOK(response.Cookies, response.Cookies.Contains("session="), () =>
@@ -179,11 +129,11 @@ namespace Jackett.Common.Indexers
var left = dom.QuerySelector(".info").TextContent.Trim();
// Oops, unable to login
output("-> Login failed: \"" + message + "\" and " + left + " tries left before being banned for 6 hours !", "error");
throw new ExceptionWithConfigData("Login failed: " + message, configData);
logger.Info("Abnormal - Login failed: \"" + message + "\" and " + left + " tries left before being banned for 6 hours !", "error");
throw new ExceptionWithConfigData("Abnormal - Login failed: " + message, configData);
});
output("-> Login Success");
logger.Info("-> Login Success");
return IndexerConfigurationStatus.RequiresTesting;
}
@@ -206,41 +156,26 @@ namespace Jackett.Common.Indexers
var qRowList = new List<IElement>();
var searchTerm = query.GetQueryString();
var searchUrl = SearchUrl;
var nbResults = 0;
var pageLinkCount = 0;
// Check cache first so we don't query the server (if search term used or not in dev mode)
if (!DevMode && !string.IsNullOrEmpty(searchTerm))
{
lock (cache)
{
// Remove old cache items
CleanCache();
// Search in cache
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
// Build our query
var request = buildQuery(searchTerm, query, searchUrl);
var request = BuildQuery(searchTerm, query, searchUrl);
// Getting results & Store content
var parser = new HtmlParser();
var dom = parser.ParseDocument(await queryExec(request));
var dom = parser.ParseDocument(await QueryExecAsync(request));
try
{
// Find torrent rows
var firstPageRows = findTorrentRows(dom);
var firstPageRows = FindTorrentRows(dom);
// Add them to torrents list
qRowList.AddRange(firstPageRows);
// Check if there are pagination links at bottom
var qPagination = dom.QuerySelectorAll(".linkbox > a");
int pageLinkCount;
int nbResults;
if (qPagination.Length > 0)
{
// Calculate numbers of pages available for this search query (Based on number results and number of torrents on first page)
@@ -260,13 +195,13 @@ namespace Jackett.Common.Indexers
}
else
{
output("\nNo result found for your query, please try another search term ...\n", "info");
logger.Info("\nAbnormal - No result found for your query, please try another search term ...\n", "info");
// No result found for this query
return releases;
}
}
output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
output("\nThere are " + firstPageRows.Length + " results on the first page !");
logger.Info("\nAbnormal - Found " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
logger.Info("\nAbnormal - There are " + firstPageRows.Length + " results on the first page !");
// If we have a term used for search and pagination result superior to one
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
@@ -274,20 +209,17 @@ namespace Jackett.Common.Indexers
// Starting with page #2
for (var i = 2; i <= Math.Min(int.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
{
output("\nProcessing page #" + i);
// Request our page
latencyNow();
logger.Info("\nAbnormal - Processing page #" + i);
// Build our query
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
var pageRequest = BuildQuery(searchTerm, query, searchUrl, i);
// Getting results & Store content
parser = new HtmlParser();
dom = parser.ParseDocument(await queryExec(pageRequest));
dom = parser.ParseDocument(await QueryExecAsync(pageRequest));
// Process page results
var additionalPageRows = findTorrentRows(dom);
var additionalPageRows = FindTorrentRows(dom);
// Add them to torrents list
qRowList.AddRange(additionalPageRows);
@@ -297,11 +229,8 @@ namespace Jackett.Common.Indexers
// Loop on results
foreach (var row in qRowList)
{
output("\n=>> Torrent #" + (releases.Count + 1));
// ID
var id = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(2) > a").GetAttribute("href"), @"\d+").Value);
output("ID: " + id);
// Release Name
var name = row.QuerySelector("td:nth-of-type(2) > a").TextContent;
@@ -311,39 +240,22 @@ namespace Jackett.Common.Indexers
var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])");
name = regex.Replace(name, "$1" + ReplaceMulti + "$2");
}
output("Release: " + name);
// Category
var categoryId = row.QuerySelector("td:nth-of-type(1) > a").GetAttribute("href").Replace("torrents.php?cat[]=", string.Empty);
var newznab = MapTrackerCatToNewznab(categoryId);
output("Category: " + MapTrackerCatToNewznab(categoryId).First().ToString() + " (" + categoryId + ")");
// Seeders
var seeders = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(6)").TextContent, @"\d+").Value);
output("Seeders: " + seeders);
// Leechers
var leechers = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(7)").TextContent, @"\d+").Value);
output("Leechers: " + leechers);
// Completed
var completed = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(6)").TextContent, @"\d+").Value);
output("Completed: " + completed);
// Size
var sizeStr = row.QuerySelector("td:nth-of-type(5)").TextContent.Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb");
var size = ReleaseInfo.GetBytes(sizeStr);
output("Size: " + sizeStr + " (" + size + " bytes)");
var categoryId = row.QuerySelector("td:nth-of-type(1) > a").GetAttribute("href").Replace("torrents.php?cat[]=", string.Empty); // Category
var newznab = MapTrackerCatToNewznab(categoryId); // Newznab Category
var seeders = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(6)").TextContent, @"\d+").Value); // Seeders
var leechers = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(7)").TextContent, @"\d+").Value); // Leechers
var completed = ParseUtil.CoerceInt(Regex.Match(row.QuerySelector("td:nth-of-type(6)").TextContent, @"\d+").Value); // Completed
var sizeStr = row.QuerySelector("td:nth-of-type(5)").TextContent.Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb"); // Size
var size = ReleaseInfo.GetBytes(sizeStr); // Size in bytes
// Publish DateToString
var datestr = row.QuerySelector("span.time").GetAttribute("title");
var dateLocal = DateTime.SpecifyKind(DateTime.ParseExact(datestr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture), DateTimeKind.Unspecified);
var date = TimeZoneInfo.ConvertTimeToUtc(dateLocal, FranceTz);
output("Released on: " + date);
// Torrent Details URL
var details = new Uri(DetailsUrl + id);
output("Details: " + details.AbsoluteUri);
// Torrent Download URL
Uri downloadLink = null;
@@ -352,12 +264,11 @@ namespace Jackett.Common.Indexers
{
// Download link available
downloadLink = new Uri(SiteLink + link);
output("Download Link: " + downloadLink.AbsoluteUri);
}
else
{
// No download link available -- Must be on pending ( can't be downloaded now...)
output("Download Link: Not available, torrent pending ? Skipping ...");
logger.Info("Abnormal - Download Link: Not available, torrent pending ? Skipping ...");
continue;
}
@@ -366,7 +277,6 @@ namespace Jackett.Common.Indexers
if (row.QuerySelector("img[alt=\"Freeleech\"]") != null)
{
downloadVolumeFactor = 0;
output("FreeLeech =)");
}
// Building release infos
@@ -387,6 +297,7 @@ namespace Jackett.Common.Indexers
DownloadVolumeFactor = downloadVolumeFactor
};
releases.Add(release);
logger.Info("Abnormal - Found Release: " + release.Title + "(" + id + ")");
}
}
catch (Exception ex)
@@ -406,7 +317,7 @@ namespace Jackett.Common.Indexers
/// <param name="url">Search url for provider</param>
/// <param name="page">Page number to request</param>
/// <returns>URL to query for parsing and processing results</returns>
private string buildQuery(string term, TorznabQuery query, string url, int page = 0)
private string BuildQuery(string term, TorznabQuery query, string url, int page = 0)
{
var parameters = new NameValueCollection();
var categoriesList = MapTorznabCapsToTrackers(query);
@@ -451,7 +362,7 @@ namespace Jackett.Common.Indexers
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong cat[] param)
url += "?" + string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
output("\nBuilded query for \"" + term + "\"... " + url);
logger.Info("\nAbnormal - Builded query for \"" + term + "\"... " + url);
// Return our search url
return url;
@@ -462,77 +373,13 @@ namespace Jackett.Common.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<string> queryExec(string request)
private async Task<string> QueryExecAsync(string request)
{
string results = null;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await queryCache(request);
}
else
{
// Querying tracker directly
results = await queryTracker(request);
}
return results;
}
// Querying tracker directly
results = await QueryTrackerAsync(request);
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<string> queryCache(string request)
{
string results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
cleanCacheStorage();
// File Name
var fileName = StringUtil.HashSHA1(request) + ".json";
// Create fingerprint for request
var file = Path.Combine(Directory, fileName);
// Checking modes states
if (File.Exists(file))
{
// File exist... loading it right now !
output("Loading results from hard drive cache ..." + fileName);
try
{
using (var fileReader = File.OpenText(file))
{
var serializer = new JsonSerializer();
results = (string)serializer.Deserialize(fileReader, typeof(string));
}
}
catch (Exception e)
{
output("Error loading cached results ! " + e.Message, "error");
results = null;
}
}
else
{
// No cached file found, querying tracker directly
results = await queryTracker(request);
// Cached file didn't exist for our query, writing it right now !
output("Writing results to hard drive cache ..." + fileName);
using (var fileWriter = File.CreateText(file))
{
var serializer = new JsonSerializer();
serializer.Serialize(fileWriter, results);
}
}
return results;
}
@@ -541,142 +388,31 @@ namespace Jackett.Common.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<string> queryTracker(string request)
private async Task<string> QueryTrackerAsync(string request)
{
// Cache mode not enabled or cached file didn't exist for our query
output("\nQuerying tracker for results....");
logger.Info("\nAbnormal - Querying tracker for results....");
// Request our first page
latencyNow();
var results = await RequestWithCookiesAndRetryAsync(request, headers: emulatedBrowserHeaders);
var results = await RequestWithCookiesAndRetryAsync(request);
// Return results from tracker
return results.ContentString;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void cleanCacheStorage(bool force = false)
{
// Check cleaning method
if (force)
{
// Deleting Provider Storage folder and all files recursively
output("\nDeleting Provider Storage folder and all files recursively ...");
// Check if directory exist
if (System.IO.Directory.Exists(Directory))
{
// Delete storage directory of provider
System.IO.Directory.Delete(Directory, true);
output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
output("-> No Storage folder found for this provider !");
}
}
else
{
var i = 0;
// Check if there is file older than ... and delete them
output("\nCleaning Provider Storage folder... in progress.");
System.IO.Directory.GetFiles(Directory)
.Select(f => new FileInfo(f))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f =>
{
output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if (i > 0)
{
output("-> Deleted " + i + " cached files during cleaning.");
}
else
{
output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Generate a random fake latency to avoid detection on tracker side
/// </summary>
private void latencyNow()
{
// Need latency ?
if (Latency)
{
// Generate a random value in our range
var random = new Random(DateTime.Now.Millisecond);
var waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
output("\nLatency Faker => Sleeping for " + waiting + " ms...");
// Sleep now...
System.Threading.Thread.Sleep(waiting);
}
}
/// <summary>
/// Find torrent rows in search pages
/// </summary>
/// <returns>List of rows</returns>
private IHtmlCollection<IElement> findTorrentRows(IHtmlDocument dom) =>
private IHtmlCollection<IElement> FindTorrentRows(IHtmlDocument dom) =>
dom.QuerySelectorAll(".torrent_table > tbody > tr:not(.colhead)");
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void output(string message, string level = "debug")
{
// Check if we are in dev mode
if (DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if (logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void validateConfig()
private void ValidateConfig()
{
output("\nValidating Settings ... \n");
logger.Info("\nAbnormal - Validating Settings ... \n");
// Check Username Setting
if (string.IsNullOrEmpty(ConfigData.Username.Value))
@@ -685,7 +421,7 @@ namespace Jackett.Common.Indexers
}
else
{
output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
logger.Info("Abnormal - Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
}
// Check Password Setting
@@ -695,7 +431,7 @@ namespace Jackett.Common.Indexers
}
else
{
output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
logger.Info("Abnormal - Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
}
// Check Max Page Setting
@@ -703,7 +439,7 @@ namespace Jackett.Common.Indexers
{
try
{
output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
logger.Info("Abnormal - Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
}
catch (Exception)
{
@@ -714,116 +450,6 @@ namespace Jackett.Common.Indexers
{
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
}
// Check Latency Setting
if (ConfigData.Latency.Value)
{
output("\nValidated Setting -- Latency Simulation enabled");
// Check Latency Start Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
{
try
{
output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
}
// Check Latency End Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
{
try
{
output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
}
}
// Check Browser Setting
if (ConfigData.Browser.Value)
{
output("\nValidated Setting -- Browser Simulation enabled");
// Check ACCEPT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
}
else
{
output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value.ToString());
}
// Check ACCEPT-LANG header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
}
else
{
output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value.ToString());
}
// Check USER-AGENT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
}
else
{
output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value.ToString());
}
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value == true)
{
output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
cleanCacheStorage(true);
}
}
}
}

View File

@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Jackett.Common.Models;
using Jackett.Common.Models.IndexerConfig;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Common.Indexers
{
[ExcludeFromCodeCoverage]
public class BeyondHDAPI : BaseWebIndexer
{
private readonly string APIBASE = "https://beyond-hd.me/api/torrents/";
private new ConfigurationDataAPIKeyAndRSSKey configData
{
get => (ConfigurationDataAPIKeyAndRSSKey)base.configData;
set => base.configData = value;
}
public BeyondHDAPI(IIndexerConfigurationService configService, WebClient wc, Logger l,
IProtectionService ps, ICacheService cs)
: base(id: "beyond-hd-api",
name: "Beyond-HD (API)",
description: "Without BeyondHD, your HDTV is just a TV",
link: "https://beyond-hd.me/",
caps: new TorznabCapabilities
{
LimitsDefault = 100,
LimitsMax = 100,
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
}
},
configService: configService,
client: wc,
logger: l,
p: ps,
cacheService: cs,
configData: new ConfigurationDataAPIKeyAndRSSKey())
{
Encoding = Encoding.UTF8;
Language = "en-us";
Type = "private";
AddCategoryMapping("Movies", TorznabCatType.Movies);
AddCategoryMapping("TV", TorznabCatType.TV);
}
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
LoadValuesFromJson(configJson);
IsConfigured = false;
try
{
var results = await PerformQuery(new TorznabQuery());
if (results.Count() == 0)
throw new Exception("Testing returned no results!");
IsConfigured = true;
SaveConfig();
}
catch (Exception e)
{
throw new ExceptionWithConfigData(e.Message, configData);
}
return IndexerConfigurationStatus.Completed;
}
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var apiKey = configData.ApiKey.Value;
var apiUrl = $"{APIBASE}{apiKey}";
Dictionary<string, string> postData = new Dictionary<string, string>
{
{ BHDParams.action, "search" },
{ BHDParams.rsskey, configData.RSSKey.Value },
{ BHDParams.search, query.SanitizedSearchTerm },
};
if (query.IsTVSearch)
{
postData.Add(BHDParams.categories, "TV");
if (query.Season != 0)
postData[BHDParams.search] = $"{query.SanitizedSearchTerm} {query.GetEpisodeSearchString()}";
}
else if (query.IsMovieSearch)
{
postData.Add(BHDParams.categories, "Movies");
}
var imdbId = ParseUtil.GetImdbID(query.ImdbID);
if (imdbId != null)
postData.Add(BHDParams.imdb_id, imdbId.ToString());
if (query.IsTmdbQuery)
postData.Add(BHDParams.tmdb_id, query.TmdbID.Value.ToString());
var bhdResponse = await GetBHDResponse(apiUrl, postData);
var releaseInfos = bhdResponse.results.Select(mapToReleaseInfo);
return releaseInfos;
}
private ReleaseInfo mapToReleaseInfo(BHDResult bhdResult)
{
var uri = new Uri(bhdResult.url);
var downloadUri = new Uri(bhdResult.download_url);
var releaseInfo = new ReleaseInfo
{
Title = bhdResult.name,
Seeders = bhdResult.seeders,
Guid = new Uri(bhdResult.url),
Details = new Uri(bhdResult.url),
Link = downloadUri,
InfoHash = bhdResult.info_hash,
Peers = bhdResult.leechers + bhdResult.seeders,
Grabs = bhdResult.times_completed,
PublishDate = bhdResult.created_at,
Size = bhdResult.size,
Category = MapTrackerCatToNewznab(bhdResult.category)
};
if (!string.IsNullOrEmpty(bhdResult.imdb_id))
releaseInfo.Imdb = ParseUtil.GetImdbID(bhdResult.imdb_id);
releaseInfo.DownloadVolumeFactor = 1;
releaseInfo.UploadVolumeFactor = 1;
if (bhdResult.freeleech == 1 || bhdResult.limited == 1)
releaseInfo.DownloadVolumeFactor = 0;
if (bhdResult.promo25 == 1)
releaseInfo.DownloadVolumeFactor = .75;
if (bhdResult.promo50 == 1)
releaseInfo.DownloadVolumeFactor = .50;
if (bhdResult.promo75 == 1)
releaseInfo.DownloadVolumeFactor = .25;
return releaseInfo;
}
private async Task<BHDResponse> GetBHDResponse(string apiUrl, Dictionary<string, string> postData)
{
var request = new WebRequest
{
PostData = postData,
Type = RequestType.POST,
Url = apiUrl
};
var response = await webclient.GetResultAsync(request);
var bhdresponse = JsonConvert.DeserializeObject<BHDResponse>(response.ContentString);
return bhdresponse;
}
internal class BHDParams
{
internal const string action = "action"; // string - The torrents endpoint action you wish to perform. (search)
internal const string rsskey = "rsskey"; // string - Your personal RSS key (RID) if you wish for results to include the uploaded_by and download_url fields
internal const string page = "page"; // int - The page number of the results. Only if the result set has more than 100 total matches.
internal const string search = "search"; // string - The torrent name. It does support !negative searching. Example: Christmas Movie
internal const string info_hash = "info_hash"; // string - The torrent info_hash. This is an exact match.
internal const string folder_name = "folder_name"; // string - The torrent folder name. This is an exact match.file_name string The torrent included file names. This is an exact match.
internal const string size = "size"; // int - The torrent size. This is an exact match.
internal const string uploaded_by = "uploaded_by"; // string - The uploaders username. Only non anonymous results will be returned.
internal const string imdb_id = "imdb_id"; // int - The ID of the matching IMDB page.
internal const string tmdb_id = "tmdb_id"; // int - The ID of the matching TMDB page.
internal const string categories = "categories"; // string - Any categories separated by comma(s). TV, Movies)
internal const string types = "types"; // string - Any types separated by comma(s). BD Remux, 1080p, etc.)
internal const string sources = "sources"; // string - Any sources separated by comma(s). Blu-ray, WEB, DVD, etc.)
internal const string genres = "genres"; // string - Any genres separated by comma(s). Action, Anime, StandUp, Western, etc.)
internal const string groups = "groups"; // string - Any internal release groups separated by comma(s).FraMeSToR, BHDStudio, BeyondHD, RPG, iROBOT, iFT, ZR, MKVULTRA
internal const string freeleech = "freeleech"; // int - The torrent freeleech status. 1 = Must match.
internal const string limited = "limited"; // int - The torrent limited UL promo. 1 = Must match.
internal const string promo25 = "promo25"; // int - The torrent 25% promo. 1 = Must match.
internal const string promo50 = "promo50"; // int - The torrent 50% promo. 1 = Must match.
internal const string promo75 = "promo75"; // int - The torrent 75% promo. 1 = Must match.
internal const string refund = "refund"; // int - The torrent refund promo. 1 = Must match.
internal const string rescue = "rescue"; // int - The torrent rescue promo. 1 = Must match.
internal const string rewind = "rewind"; // int - The torrent rewind promo. 1 = Must match.
internal const string stream = "stream"; // int - The torrent Stream Optimized flag. 1 = Must match.
internal const string sd = "sd"; // int - The torrent SD flag. 1 = Must match.
internal const string pack = "pack"; // int - The torrent TV pack flag. 1 = Must match.
internal const string h264 = "h264"; // int - The torrent x264/h264 codec flag. 1 = Must match.
internal const string h265 = "h265"; // int - The torrent x265/h265 codec flag. 1 = Must match.
internal const string alive = "alive"; // int - The torrent has at least 1 seeder. 1 = Must match.
internal const string dying = "dying"; // int - The torrent has less than 3 seeders. 1 = Must match.
internal const string dead = "dead"; // int - The torrent has no seeders. 1 = Must match.
internal const string reseed = "reseed"; // int - The torrent has no seeders and an active reseed request. 1 = Must match.
internal const string seeding = "seeding"; // int - The torrent is seeded by you. 1 = Must match.
internal const string leeching = "leeching"; // int - The torrent is being leeched by you. 1 = Must match.
internal const string completed = "completed"; // int - The torrent has been completed by you. 1 = Must match.
internal const string incomplete = "incomplete"; // int - The torrent has not been completed by you. 1 = Must match.
internal const string notdownloaded = "notdownloaded"; // int - The torrent has not been downloaded you. 1 = Must match.
internal const string min_bhd = "min_bhd"; // int - The minimum BHD rating.
internal const string vote_bhd = "vote_bhd"; // int - The minimum number of BHD votes.
internal const string min_imdb = "min_imdb"; // int - The minimum IMDb rating.
internal const string vote_imdb = "vote_imdb"; // int - The minimum number of IMDb votes.
internal const string min_tmdb = "min_tmdb"; // int - The minimum TMDb rating.
internal const string vote_tmdb = "vote_tmdb"; // int - The minimum number of TDMb votes.
internal const string min_year = "min_year"; // int - The earliest release year.
internal const string max_year = "max_year"; // int - The latest release year.
internal const string sort = "sort"; // string - Field to sort results by. (bumped_at, created_at, seeders, leechers, times_completed, size, name, imdb_rating, tmdb_rating, bhd_rating). Default is bumped_at
internal const string order = "order"; // string - The direction of the sort of results. (asc, desc). Default is desc
// Most of the comma separated fields are OR searches.
internal const string features = "features"; // string - Any features separated by comma(s). DV, HDR10, HDR10P, Commentary)
internal const string countries = "countries"; // string - Any production countries separated by comma(s). France, Japan, etc.)
internal const string languages = "languages"; // string - Any spoken languages separated by comma(s). French, English, etc.)
internal const string audios = "audios"; // string - Any audio tracks separated by comma(s). English, Japanese,etc.)
internal const string subtitles = "subtitles"; // string - Any subtitles separated by comma(s). Dutch, Finnish, Swedish, etc.)
}
class BHDResponse
{
public int status_code { get; set; } // The status code of the post request. (0 = Failed and 1 = Success)
public int page { get; set; } // The current page of results that you're on.
public int total_pages { get; set; } // int The total number of pages of results matching your query.
public int total_results { get; set; } // The total number of results matching your query.
public bool success { get; set; } // The status of the call. (True = Success, False = Error)
public BHDResult[] results { get; set; } // The results that match your query.
}
class BHDResult
{
public int id { get; set; }
public string name { get; set; }
public string folder_name { get; set; }
public string info_hash { get; set; }
public long size { get; set; }
public string uploaded_by { get; set; }
public string category { get; set; }
public string type { get; set; }
public int seeders { get; set; }
public int leechers { get; set; }
public int times_completed { get; set; }
public string imdb_id { get; set; }
public string tmdb_id { get; set; }
public decimal bhd_rating { get; set; }
public decimal tmdb_rating { get; set; }
public decimal imdb_rating { get; set; }
public int tv_pack { get; set; }
public int promo25 { get; set; }
public int promo50 { get; set; }
public int promo75 { get; set; }
public int freeleech { get; set; }
public int rewind { get; set; }
public int refund { get; set; }
public int limited { get; set; }
public int rescue { get; set; }
public DateTime bumped_at { get; set; }
public DateTime created_at { get; set; }
public string url { get; set; }
public string download_url { get; set; }
}
}
}

View File

@@ -1129,7 +1129,7 @@ namespace Jackett.Common.Indexers
return Element.QuerySelector(Selector);
}
protected string handleSelector(selectorBlock Selector, IElement Dom, Dictionary<string, object> variables = null)
protected string handleSelector(selectorBlock Selector, IElement Dom, Dictionary<string, object> variables = null, bool required = true)
{
if (Selector.Text != null)
{
@@ -1147,7 +1147,9 @@ namespace Jackett.Common.Indexers
selection = QuerySelector(Dom, Selector.Selector);
if (selection == null)
{
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", Selector.Selector, Dom.ToHtmlPretty()));
if (required)
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", Selector.Selector, Dom.ToHtmlPretty()));
return null;
}
}
@@ -1170,13 +1172,21 @@ namespace Jackett.Common.Indexers
}
}
if (value == null)
throw new Exception(string.Format("None of the case selectors \"{0}\" matched {1}", string.Join(",", Selector.Case), selection.ToHtmlPretty()));
{
if (required)
throw new Exception(string.Format("None of the case selectors \"{0}\" matched {1}", string.Join(",", Selector.Case), selection.ToHtmlPretty()));
return null;
}
}
else if (Selector.Attribute != null)
{
value = selection.GetAttribute(Selector.Attribute);
if (value == null)
throw new Exception(string.Format("Attribute \"{0}\" is not set for element {1}", Selector.Attribute, selection.ToHtmlPretty()));
{
if (required)
throw new Exception(string.Format("Attribute \"{0}\" is not set for element {1}", Selector.Attribute, selection.ToHtmlPretty()));
return null;
}
}
else
{
@@ -1401,9 +1411,16 @@ namespace Jackett.Common.Indexers
string value = null;
var variablesKey = ".Result." + FieldName;
var isOptional = OptionalFields.Contains(Field.Key) || FieldModifiers.Contains("optional") || Field.Value.Optional;
try
{
value = handleSelector(Field.Value, Row, variables);
value = handleSelector(Field.Value, Row, variables, !isOptional);
if (isOptional && value == null)
{
variables[variablesKey] = null;
continue;
}
switch (FieldName)
{
case "download":
@@ -1560,7 +1577,7 @@ namespace Jackett.Common.Indexers
{
if (!variables.ContainsKey(variablesKey))
variables[variablesKey] = null;
if (OptionalFields.Contains(Field.Key) || FieldModifiers.Contains("optional") || Field.Value.Optional)
if (isOptional)
{
variables[variablesKey] = null;
continue;

View File

@@ -23,9 +23,10 @@ namespace Jackett.Common.Indexers
{
private const int MaxItemsPerPage = 15;
private const int MaxSearchPageLimit = 6; // 15 items per page * 6 pages = 90
private string _language;
public override string[] AlternativeSiteLinks { get; protected set; } = {
public override string[] LegacySiteLinks { get; protected set; } = {
"https://www.cinecalidad.to/",
"https://www.cinecalidad.im/", // working but outdated, maybe copycat
"https://www.cinecalidad.is/",
"https://www.cinecalidad.li/",
"https://www.cinecalidad.eu/",
@@ -34,18 +35,14 @@ namespace Jackett.Common.Indexers
"https://cinecalidad.mrunblock.icu/"
};
public override string[] LegacySiteLinks { get; protected set; } = {
"https://www.cinecalidad.to/",
"https://www.cinecalidad.im/" // working but outdated, maybe copycat
};
public Cinecalidad(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps,
ICacheService cs)
: base(id: "cinecalidad",
name: "Cinecalidad",
description: "Películas Full HD en Castellano y Latino Dual.",
link: "https://www.cinecalidad.is/",
caps: new TorznabCapabilities {
description: "Películas Full HD en Latino y Inglés Dual.",
link: "https://www.cine-calidad.com/",
caps: new TorznabCapabilities
{
MovieSearchParams = new List<MovieSearchParam> { MovieSearchParam.Q }
},
configService: configService,
@@ -59,24 +56,12 @@ namespace Jackett.Common.Indexers
Language = "es-es";
Type = "public";
var language = new SingleSelectConfigurationItem("Select language", new Dictionary<string, string>
{
{"castellano", "Castilian Spanish"},
{"latino", "Latin American Spanish"}
})
{
Value = "castellano"
};
configData.AddDynamic("language", language);
AddCategoryMapping(1, TorznabCatType.MoviesHD);
}
public override void LoadValuesFromJson(JToken jsonConfig, bool useProtectionService = false)
{
base.LoadValuesFromJson(jsonConfig, useProtectionService);
var language = (SingleSelectConfigurationItem)configData.GetDynamic("language");
_language = language?.Value ?? "castellano";
}
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
@@ -95,8 +80,6 @@ namespace Jackett.Common.Indexers
var releases = new List<ReleaseInfo>();
var templateUrl = SiteLink;
if (_language.Equals("castellano"))
templateUrl += "espana/";
templateUrl += "{0}"; // placeholder for page
var maxPages = 2; // we scrape only 2 pages for recent torrents
@@ -137,7 +120,7 @@ namespace Jackett.Common.Indexers
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(results.ContentString);
var protectedLink = dom.QuerySelector("a[service=BitTorrent]").GetAttribute("href");
var protectedLink = dom.QuerySelector("li:contains('Torrent')").ParentElement.GetAttribute("href");
if (protectedLink.Contains("/ouo.io/"))
{
// protected link =>
@@ -178,11 +161,11 @@ namespace Jackett.Common.Indexers
var title = qImg.GetAttribute("title");
if (!CheckTitleMatchWords(query.GetQueryString(), title))
continue; // skip if it doesn't contain all words
title += _language.Equals("castellano") ? " MULTi/SPANiSH" : " MULTi/LATiN SPANiSH";
title += " 1080p BDRip x264";
title += " MULTi LATiN SPANiSH 1080p BDRip x264";
var poster = new Uri(GetAbsoluteUrl(qImg.GetAttribute("src")));
var link = new Uri(GetAbsoluteUrl(row.QuerySelector("a").GetAttribute("href")));
var extract = row.QuerySelector("noscript").InnerHtml.Split('\'');
var link = new Uri(GetAbsoluteUrl(extract[1]));
var release = new ReleaseInfo
{

View File

@@ -1,307 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using Jackett.Common.Models;
using Jackett.Common.Models.IndexerConfig.Bespoke;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Common.Indexers
{
[ExcludeFromCodeCoverage]
internal class EliteTracker : BaseWebIndexer
{
private string LoginUrl => SiteLink + "takelogin.php";
private string BrowseUrl => SiteLink + "browse.php";
private new ConfigurationDataEliteTracker configData => (ConfigurationDataEliteTracker)base.configData;
public EliteTracker(IIndexerConfigurationService configService, WebClient webClient, Logger logger,
IProtectionService ps, ICacheService cs)
: base(id: "elitetracker",
name: "Elite-Tracker",
description: "French Torrent Tracker",
link: "https://elite-tracker.net/",
caps: new TorznabCapabilities
{
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
}
},
configService: configService,
logger: logger,
p: ps,
cacheService: cs,
client: webClient,
configData: new ConfigurationDataEliteTracker()
)
{
Encoding = Encoding.UTF8;
Language = "fr-fr";
Type = "private";
AddCategoryMapping(27, TorznabCatType.TVAnime, "Animation/Animes");
AddCategoryMapping(90, TorznabCatType.TVAnime, "Animes - 3D");
AddCategoryMapping(99, TorznabCatType.TVAnime, "Animes - 4K");
AddCategoryMapping(63, TorznabCatType.TVAnime, "Animes - DVD");
AddCategoryMapping(56, TorznabCatType.TVAnime, "Animes - HD");
AddCategoryMapping(89, TorznabCatType.TVAnime, "Animes - HDRip");
AddCategoryMapping(87, TorznabCatType.TVAnime, "Animes - Pack");
AddCategoryMapping(88, TorznabCatType.TVAnime, "Animes - SD");
AddCategoryMapping(59, TorznabCatType.TVAnime, "Animes - Serie");
AddCategoryMapping(3, TorznabCatType.PC0day, "APPLICATION");
AddCategoryMapping(74, TorznabCatType.PCMobileAndroid, "APPLICATION - ANDROID");
AddCategoryMapping(57, TorznabCatType.PCMobileiOS, "APPLICATION - IPHONE");
AddCategoryMapping(6, TorznabCatType.PC0day, "APPLICATION - LINUX");
AddCategoryMapping(5, TorznabCatType.PCMac, "APPLICATION - MAC");
AddCategoryMapping(4, TorznabCatType.PC0day, "APPLICATION - WINDOWS");
AddCategoryMapping(38, TorznabCatType.TVDocumentary, "DOCUMENTAIRES");
AddCategoryMapping(97, TorznabCatType.TVDocumentary, "DOCUMENTAIRES - PACK");
AddCategoryMapping(34, TorznabCatType.Books, "EBOOKS");
AddCategoryMapping(86, TorznabCatType.Books, "EBOOKS - ABOOKS");
AddCategoryMapping(48, TorznabCatType.MoviesHD, "FiLMS HD");
AddCategoryMapping(51, TorznabCatType.MoviesHD, "FiLMS HD - 1080p");
AddCategoryMapping(98, TorznabCatType.MoviesUHD, "FiLMS HD - 2160p");
AddCategoryMapping(70, TorznabCatType.Movies3D, "FiLMS HD - 3D");
AddCategoryMapping(84, TorznabCatType.MoviesUHD, "FiLMS HD - 4K");
AddCategoryMapping(50, TorznabCatType.MoviesHD, "FiLMS HD - 720P");
AddCategoryMapping(49, TorznabCatType.MoviesBluRay, "FiLMS HD - BluRay");
AddCategoryMapping(78, TorznabCatType.MoviesHD, "FiLMS HD - HDRip");
AddCategoryMapping(105, TorznabCatType.MoviesUHD, "FiLMS HD - VOSTFR 4k");
AddCategoryMapping(95, TorznabCatType.MoviesHD, "FiLMS HD - VOSTFR HD");
AddCategoryMapping(85, TorznabCatType.MoviesHD, "FiLMS HD - x265");
AddCategoryMapping(7, TorznabCatType.Movies, "FiLMS SD");
AddCategoryMapping(91, TorznabCatType.Movies3D, "FiLMS SD - 3D");
AddCategoryMapping(11, TorznabCatType.MoviesDVD, "FiLMS SD - DVD");
AddCategoryMapping(53, TorznabCatType.MoviesSD, "FiLMS SD - DVD-SCREENER");
AddCategoryMapping(9, TorznabCatType.MoviesDVD, "FiLMS SD - R5");
AddCategoryMapping(8, TorznabCatType.MoviesSD, "FiLMS SD - SCREENER");
AddCategoryMapping(10, TorznabCatType.MoviesSD, "FiLMS SD - SDRip");
AddCategoryMapping(40, TorznabCatType.Movies, "FiLMS SD - VO");
AddCategoryMapping(39, TorznabCatType.Movies, "FiLMS SD - VOSTFR");
AddCategoryMapping(15, TorznabCatType.Console, "JEUX VIDEO");
AddCategoryMapping(76, TorznabCatType.Console3DS, "JEUX VIDEO - 3DS");
AddCategoryMapping(18, TorznabCatType.ConsoleNDS, "JEUX VIDEO - DS");
AddCategoryMapping(55, TorznabCatType.PCMobileiOS, "JEUX VIDEO - IPHONE");
AddCategoryMapping(80, TorznabCatType.PCGames, "JEUX VIDEO - LINUX");
AddCategoryMapping(96, TorznabCatType.ConsoleOther, "JEUX VIDEO - NSW");
AddCategoryMapping(79, TorznabCatType.PCMac, "JEUX VIDEO - OSX");
AddCategoryMapping(22, TorznabCatType.PCGames, "JEUX VIDEO - PC");
AddCategoryMapping(66, TorznabCatType.ConsolePS3, "JEUX VIDEO - PS2");
AddCategoryMapping(58, TorznabCatType.ConsolePS3, "JEUX VIDEO - PS3");
AddCategoryMapping(81, TorznabCatType.ConsolePS4, "JEUX VIDEO - PS4");
AddCategoryMapping(20, TorznabCatType.ConsolePSP, "JEUX VIDEO - PSP");
AddCategoryMapping(75, TorznabCatType.ConsolePS3, "JEUX VIDEO - PSX");
AddCategoryMapping(19, TorznabCatType.ConsoleWii, "JEUX VIDEO - WII");
AddCategoryMapping(83, TorznabCatType.ConsoleWiiU, "JEUX VIDEO - WiiU");
AddCategoryMapping(16, TorznabCatType.ConsoleXBox, "JEUX VIDEO - XBOX");
AddCategoryMapping(82, TorznabCatType.ConsoleXBoxOne, "JEUX VIDEO - XBOX ONE");
AddCategoryMapping(17, TorznabCatType.ConsoleXBox360, "JEUX VIDEO - XBOX360");
AddCategoryMapping(23, TorznabCatType.Audio, "MUSIQUES");
AddCategoryMapping(26, TorznabCatType.Audio, "MUSIQUES - CLIP/CONCERT");
AddCategoryMapping(61, TorznabCatType.AudioLossless, "MUSIQUES - FLAC");
AddCategoryMapping(60, TorznabCatType.AudioMP3, "MUSIQUES - MP3");
AddCategoryMapping(30, TorznabCatType.TV, "SERIES");
AddCategoryMapping(77, TorznabCatType.TVSD, "SERIES - DVD");
AddCategoryMapping(100, TorznabCatType.TVUHD, "SERIES - 4k");
AddCategoryMapping(67, TorznabCatType.TVHD, "SERIES - FR HD");
AddCategoryMapping(31, TorznabCatType.TVSD, "SERIES - FR SD");
AddCategoryMapping(102, TorznabCatType.TVUHD, "SERIES - Pack 4k");
AddCategoryMapping(92, TorznabCatType.TVHD, "SERIES - Pack FR HD");
AddCategoryMapping(73, TorznabCatType.TVSD, "SERIES - Pack FR SD");
AddCategoryMapping(94, TorznabCatType.TVHD, "SERIES - Pack VOSTFR HD");
AddCategoryMapping(93, TorznabCatType.TVSD, "SERIES - Pack VOSTFR SD");
AddCategoryMapping(68, TorznabCatType.TVHD, "SERIES - VO HD");
AddCategoryMapping(32, TorznabCatType.TVSD, "SERIES - VO SD");
AddCategoryMapping(101, TorznabCatType.TVUHD, "SERIES - 4k");
AddCategoryMapping(69, TorznabCatType.TVHD, "SERIES - VOSTFR HD");
AddCategoryMapping(33, TorznabCatType.TVSD, "SERIES - VOSTFR SD");
AddCategoryMapping(47, TorznabCatType.TV, "SPECTACLES/EMISSIONS");
AddCategoryMapping(71, TorznabCatType.TV, "SPECTACLES/EMISSIONS - Emissions");
AddCategoryMapping(103, TorznabCatType.TV, "SPECTACLES/EMISSIONS - Emissions Pack");
AddCategoryMapping(72, TorznabCatType.TV, "SPECTACLES/EMISSIONS - Spectacles");
AddCategoryMapping(35, TorznabCatType.TVSport, "SPORT");
AddCategoryMapping(36, TorznabCatType.TVSport, "SPORT - CATCH");
AddCategoryMapping(65, TorznabCatType.TVSport, "SPORT - UFC");
AddCategoryMapping(37, TorznabCatType.XXX, "XXX");
}
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
{
LoadValuesFromJson(configJson);
var pairs = new Dictionary<string, string>
{
{ "username", configData.Username.Value },
{ "password", configData.Password.Value }
};
var result = await RequestWithCookiesAsync(LoginUrl, "", RequestType.POST, data: pairs);
await ConfigureIfOK(result.Cookies, result.Cookies != null, () =>
{
var errorMessage = result.ContentString;
throw new ExceptionWithConfigData(errorMessage, configData);
});
return IndexerConfigurationStatus.RequiresTesting;
}
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var pairs = new Dictionary<string, string>
{
{"do", "search"},
{"search_type", query.IsImdbQuery ? "t_genre" : "t_name"},
{"keywords", query.IsImdbQuery ? query.ImdbID : query.GetQueryString()},
{"category", "0"} // multi cat search not supported
};
var results = await RequestWithCookiesAsync(BrowseUrl, method: RequestType.POST, data: pairs);
if (results.IsRedirect)
{
// re-login
await ApplyConfiguration(null);
results = await RequestWithCookiesAsync(BrowseUrl, method: RequestType.POST, data: pairs);
}
try
{
var lastDate = DateTime.Now;
var parser = new HtmlParser();
var doc = parser.ParseDocument(results.ContentString);
var rows = doc.QuerySelectorAll("table[id='sortabletable'] > tbody > tr");
foreach (var row in rows.Skip(1))
{
if (row.Children.Length != 9)
continue; // not a torrent line
var cat = row.Children[0].QuerySelector("a").GetAttribute("href").Split('=')[1];
var title = row.Children[1].QuerySelector("a").TextContent;
var qLinks = row.Children[2].QuerySelectorAll("a");
var link = new Uri(configData.TorrentHTTPSMode.Value ? qLinks[1].GetAttribute("href") : qLinks[0].GetAttribute("href"));
var details = new Uri(row.Children[1].QuerySelector("a").GetAttribute("href"));
var size = row.Children[4].TextContent;
var grabs = row.Children[5].QuerySelector("a").TextContent;
var seeders = ParseUtil.CoerceInt(row.Children[6].QuerySelector("a").TextContent);
var leechers = ParseUtil.CoerceInt(row.Children[7].QuerySelector("a").TextContent);
var qTags = row.Children[1].QuerySelector("div:has(span[style=\"float: right;\"])");
var dlVolumeFactor = 1.0;
if (qTags.QuerySelector("img[alt^=\"TORRENT GRATUIT\"]") != null)
dlVolumeFactor = 0.0;
else if (qTags.QuerySelector("img[alt^=\"TORRENT SILVER\"]") != null)
dlVolumeFactor = 0.5;
var upVolumeFactor = qTags.QuerySelector("img[alt^=\"TORRENT X2\"]") != null ? 2.0 : 1.0;
var release = new ReleaseInfo
{
MinimumRatio = 1,
MinimumSeedTime = 172800,
Category = MapTrackerCatToNewznab(cat),
Title = title,
Link = link,
Details = details,
Size = ReleaseInfo.GetBytes(size),
Seeders = seeders,
Grabs = ParseUtil.CoerceLong(grabs),
DownloadVolumeFactor = dlVolumeFactor,
UploadVolumeFactor = upVolumeFactor,
Peers = leechers + seeders,
Guid = link
};
var qTooltip = row.Children[1].QuerySelector("div.tooltip-content");
if (qTooltip != null)
{
var qPoster = qTooltip.QuerySelector("img");
if (qPoster != null)
{
release.Poster = new Uri(qPoster.GetAttribute("src"));
qPoster.Remove();
}
qTooltip.QuerySelector("div:contains(\"Total Hits\")").Remove();
var qLongTitle = qTooltip.QuerySelector("div");
release.Title = qLongTitle.TextContent;
qLongTitle.Remove();
var description = qTooltip.TextContent.Trim();
if (!string.IsNullOrWhiteSpace(description))
release.Description = description;
}
// issue #5064 replace multi keyword
if (!string.IsNullOrEmpty(configData.ReplaceMulti.Value))
{
var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])");
release.Title = regex.Replace(release.Title, "$1" + configData.ReplaceMulti.Value + "$2");
}
// issue #6855 Replace VOSTFR with ENGLISH
if (configData.Vostfr.Value)
release.Title = release.Title.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH");
var qPretime = qTags.QuerySelector("font.mkprettytime");
if (qPretime != null)
{
if (release.Description == null)
release.Description = qPretime.TextContent;
else
release.Description += "<br>\n" + qPretime.TextContent;
release.PublishDate = lastDate;
}
else
{
release.PublishDate = DateTime.ParseExact(qTags.TextContent.Trim(), "dd-MM-yyyy HH:mm", CultureInfo.InvariantCulture);
lastDate = release.PublishDate;
}
releases.Add(release);
}
}
catch (Exception ex)
{
OnParseError(results.ContentString, ex);
}
return releases;
}
}
}

View File

@@ -45,7 +45,7 @@ namespace Jackett.Common.Indexers
public override string[] AlternativeSiteLinks { get; protected set; } = {
"https://www.epublibre.org/",
"https://epublibre.unblockit.club/"
"https://epublibre.unblockit.onl/"
};
public override string[] LegacySiteLinks { get; protected set; } = {
@@ -55,6 +55,7 @@ namespace Jackett.Common.Indexers
"https://epublibre.unblockit.ltd/",
"https://epublibre.unblockit.link/",
"https://epublibre.unblockit.buzz/",
"https://epublibre.unblockit.club/",
"https://epublibre.org/"
};

View File

@@ -51,7 +51,7 @@ namespace Jackett.Common.Indexers
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{

View File

@@ -3,9 +3,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -18,7 +16,6 @@ using Jackett.Common.Models.IndexerConfig.Bespoke;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
@@ -32,12 +29,6 @@ namespace Jackett.Common.Indexers
private string SearchUrl => SiteLink + "browse.php";
private string TorrentDetailsUrl => SiteLink + "details.php?id={id}";
private string TorrentDownloadUrl => SiteLink + "download.php?id={id}&passkey={passkey}";
private bool Latency => ConfigData.Latency.Value;
private bool DevMode => ConfigData.DevMode.Value;
private bool CacheMode => ConfigData.HardDriveCache.Value;
private static string Directory => Path.Combine(Path.GetTempPath(), "Jackett", MethodBase.GetCurrentMethod().DeclaringType?.Name);
private readonly Dictionary<string, string> _emulatedBrowserHeaders = new Dictionary<string, string>();
private ConfigurationDataNorbits ConfigData => (ConfigurationDataNorbits)configData;
@@ -113,26 +104,7 @@ namespace Jackett.Common.Indexers
// Check & Validate Config
ValidateConfig();
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// If we want to simulate a browser
if (ConfigData.Browser.Value)
{
// Clean headers
_emulatedBrowserHeaders.Clear();
// Inject headers
_emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
_emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
_emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDnt.Value).ToString());
_emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
_emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
_emulatedBrowserHeaders.Add("Referer", LoginUrl);
}
await DoLogin();
await DoLoginAsync();
return IndexerConfigurationStatus.RequiresTesting;
}
@@ -141,19 +113,18 @@ namespace Jackett.Common.Indexers
/// Perform login to racker
/// </summary>
/// <returns></returns>
private async Task DoLogin()
private async Task DoLoginAsync()
{
// Build WebRequest for index
var myIndexRequest = new WebRequest
{
Type = RequestType.GET,
Url = SiteLink,
Headers = _emulatedBrowserHeaders,
Encoding = Encoding
};
// Get index page for cookies
Output("\nGetting index page (for cookies).. with " + SiteLink);
logger.Info("\nNorBits - Getting index page (for cookies).. with " + SiteLink);
var indexPage = await webclient.GetResultAsync(myIndexRequest);
// Building login form data
@@ -167,15 +138,13 @@ namespace Jackett.Common.Indexers
{
Type = RequestType.GET,
Url = LoginUrl,
Headers = _emulatedBrowserHeaders,
Cookies = indexPage.Cookies,
Referer = SiteLink,
Encoding = Encoding
};
// Get login page -- (not used, but simulation needed by tracker security's checks)
LatencyNow();
Output("\nGetting login page (user simulation).. with " + LoginUrl);
logger.Info("\nNorBits - Getting login page (user simulation).. with " + LoginUrl);
await webclient.GetResultAsync(myRequestLogin);
// Build WebRequest for submitting authentification
@@ -185,14 +154,12 @@ namespace Jackett.Common.Indexers
Referer = LoginUrl,
Type = RequestType.POST,
Url = LoginCheckUrl,
Headers = _emulatedBrowserHeaders,
Cookies = indexPage.Cookies,
Encoding = Encoding
};
// Perform loggin
LatencyNow();
Output("\nPerform loggin.. with " + LoginCheckUrl);
logger.Info("\nPerform loggin.. with " + LoginCheckUrl);
var response = await webclient.GetResultAsync(request);
// Test if we are logged in
@@ -204,36 +171,36 @@ namespace Jackett.Common.Indexers
var redirectTo = response.RedirectingTo;
// Oops, unable to login
Output("-> Login failed: " + message, "error");
logger.Info("NorBits - Login failed: " + message, "error");
throw new ExceptionWithConfigData("Login failed: " + message, configData);
});
Output("\nCookies saved for future uses...");
logger.Info("\nNorBits - Cookies saved for future uses...");
ConfigData.CookieHeader.Value = indexPage.Cookies + " " + response.Cookies + " ts_username=" + ConfigData.Username.Value;
Output("\n-> Login Success\n");
logger.Info("\nNorBits - Login Success\n");
}
/// <summary>
/// Check logged-in state for provider
/// </summary>
/// <returns></returns>
private async Task CheckLogin()
private async Task CheckLoginAsync()
{
// Checking ...
Output("\n-> Checking logged-in state....");
logger.Info("\nNorBits - Checking logged-in state....");
var loggedInCheck = await RequestWithCookiesAsync(SearchUrl);
if (!loggedInCheck.ContentString.Contains("logout.php"))
{
// Cookie expired, renew session on provider
Output("-> Not logged, login now...\n");
logger.Info("NorBits - Not logged, login now...\n");
await DoLogin();
await DoLoginAsync();
}
else
{
// Already logged, session active
Output("-> Already logged, continue...\n");
logger.Info("NorBits - Already logged, continue...\n");
}
}
@@ -249,22 +216,7 @@ namespace Jackett.Common.Indexers
var searchUrl = SearchUrl;
// Check login before performing a query
await CheckLogin();
// Check cache first so we don't query the server (if search term used or not in dev mode)
if (!DevMode && !string.IsNullOrEmpty(exactSearchTerm))
{
lock (cache)
{
// Remove old cache items
CleanCache();
// Search in cache
var cachedResult = cache.FirstOrDefault(i => i.Query == exactSearchTerm);
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
await CheckLoginAsync();
var SearchTerms = new List<string> { exactSearchTerm };
@@ -300,76 +252,41 @@ namespace Jackett.Common.Indexers
else
{
// No result found for this query
Output("\nNo result found for your query, please try another search term ...\n", "info");
logger.Info("\nNorBits - No result found for your query, please try another search term ...\n", "info");
break;
}
Output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
Output("\nThere are " + firstPageRows.Length + " results on the first page !");
logger.Info("\nNorBits - Found " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
logger.Info("\nNorBits - There are " + firstPageRows.Length + " results on the first page !");
// Loop on results
foreach (var row in firstPageRows)
{
Output("Torrent #" + (releases.Count + 1));
// ID
var id = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)").GetAttribute("href").Split('=').Last();
Output("ID: " + id);
// Release Name
var name = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)").GetAttribute("title");
// Category
var categoryName = row.QuerySelector("td:nth-of-type(1) > div > a:nth-of-type(1)").GetAttribute("title");
var id = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)").GetAttribute("href").Split('=').Last(); // ID
var name = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)").GetAttribute("title"); // Release Name
var categoryName = row.QuerySelector("td:nth-of-type(1) > div > a:nth-of-type(1)").GetAttribute("title"); // Category
var mainCat = row.QuerySelector("td:nth-of-type(1) > div > a:nth-of-type(1)").GetAttribute("href").Split('?').Last();
var qSubCat2 = row.QuerySelector("td:nth-of-type(1) > div > a[href^=\"/browse.php?sub2_cat[]=\"]");
var cat = mainCat;
if (qSubCat2 != null)
cat += '&' + qSubCat2.GetAttribute("href").Split('?').Last();
Output("Category: " + cat + " - " + categoryName);
// Seeders
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)").TextContent);
Output("Seeders: " + seeders);
// Leechers
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)").TextContent);
Output("Leechers: " + leechers);
// Completed
var regexObj = new Regex(@"[^\d]");
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)").TextContent); // Seeders
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)").TextContent); // Leechers
var regexObj = new Regex(@"[^\d]"); // Completed
var completed2 = row.QuerySelector("td:nth-of-type(8)").TextContent;
var completed = ParseUtil.CoerceLong(regexObj.Replace(completed2, ""));
Output("Completed: " + completed);
// Files
var qFiles = row.QuerySelector("td:nth-of-type(3) > a");
var qFiles = row.QuerySelector("td:nth-of-type(3) > a"); // Files
var files = qFiles != null ? ParseUtil.CoerceInt(Regex.Match(qFiles.TextContent, @"\d+").Value) : 1;
Output("Files: " + files);
// Size
var humanSize = row.QuerySelector("td:nth-of-type(7)").TextContent.ToLowerInvariant();
var size = ReleaseInfo.GetBytes(humanSize);
Output("Size: " + humanSize + " (" + size + " bytes)");
// --> Date
var humanSize = row.QuerySelector("td:nth-of-type(7)").TextContent.ToLowerInvariant(); // Size
var size = ReleaseInfo.GetBytes(humanSize); // Date
var dateTimeOrig = row.QuerySelector("td:nth-of-type(5)").TextContent;
var dateTime = Regex.Replace(dateTimeOrig, @"<[^>]+>|&nbsp;", "").Trim();
var date = DateTime.ParseExact(dateTime, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
Output("Released on: " + date);
// Torrent Details URL
var details = new Uri(TorrentDetailsUrl.Replace("{id}", id.ToString()));
Output("Details: " + details.AbsoluteUri);
// Torrent Download URL
var passkey = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(2)").GetAttribute("href");
var details = new Uri(TorrentDetailsUrl.Replace("{id}", id.ToString())); // Description Link
var passkey = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(2)").GetAttribute("href"); // Download Link
var key = Regex.Match(passkey, "(?<=passkey\\=)([a-zA-z0-9]*)");
var downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()).Replace("{passkey}", key.ToString()));
Output("Download Link: " + downloadLink.AbsoluteUri);
// Building release infos
var release = new ReleaseInfo
@@ -462,7 +379,7 @@ namespace Jackett.Common.Indexers
// Building our query
url += "?" + searchterm + "&" + parameters.GetQueryString() + "&" + CatQryStr;
Output("\nBuilded query for \"" + term + "\"... " + url);
logger.Info("\nBuilded query for \"" + term + "\"... " + url);
// Return our search url
return url;
@@ -473,58 +390,10 @@ namespace Jackett.Common.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebResult> QueryExec(string request)
private async Task<WebResult> QueryExecAsync(string request)
{
WebResult results;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await QueryCache(request);
}
else
{
// Querying tracker directly
results = await QueryTracker(request);
}
return results;
}
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebResult> QueryCache(string request)
{
WebResult results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
CleanCacheStorage();
// Create fingerprint for request
var file = Directory + request.GetHashCode() + ".json";
// Checking modes states
if (File.Exists(file))
{
// File exist... loading it right now !
Output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
results = JsonConvert.DeserializeObject<WebResult>(File.ReadAllText(file));
}
else
{
// No cached file found, querying tracker directly
results = await QueryTracker(request);
// Cached file didn't exist for our query, writing it right now !
Output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
File.WriteAllText(file, JsonConvert.SerializeObject(results));
}
results = await QueryTrackerAsync(request);
return results;
}
@@ -533,91 +402,18 @@ namespace Jackett.Common.Indexers
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<WebResult> QueryTracker(string request)
private async Task<WebResult> QueryTrackerAsync(string request)
{
// Cache mode not enabled or cached file didn't exist for our query
Output("\nQuerying tracker for results....");
logger.Info("\nNorBits - Querying tracker for results....");
// Request our first page
LatencyNow();
var results = await RequestWithCookiesAndRetryAsync(request, ConfigData.CookieHeader.Value, RequestType.GET, SearchUrl, null, _emulatedBrowserHeaders);
var results = await RequestWithCookiesAndRetryAsync(request, ConfigData.CookieHeader.Value, RequestType.GET, SearchUrl, null);
// Return results from tracker
return results;
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void CleanCacheStorage(bool force = false)
{
// Check cleaning method
if (force)
{
// Deleting Provider Storage folder and all files recursively
Output("\nDeleting Provider Storage folder and all files recursively ...");
// Check if directory exist
if (System.IO.Directory.Exists(Directory))
{
// Delete storage directory of provider
System.IO.Directory.Delete(Directory, true);
Output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
Output("-> No Storage folder found for this provider !");
}
}
else
{
var i = 0;
// Check if there is file older than ... and delete them
Output("\nCleaning Provider Storage folder... in progress.");
System.IO.Directory.GetFiles(Directory)
.Select(f => new FileInfo(f))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f =>
{
Output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if (i > 0)
{
Output("-> Deleted " + i + " cached files during cleaning.");
}
else
{
Output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Generate a random fake latency to avoid detection on tracker side
/// </summary>
private void LatencyNow()
{
// Need latency ?
if (Latency)
{
var random = new Random(DateTime.Now.Millisecond);
var waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value),
Convert.ToInt32(ConfigData.LatencyEnd.Value));
Output("\nLatency Faker => Sleeping for " + waiting + " ms...");
// Sleep now...
System.Threading.Thread.Sleep(waiting);
}
// Generate a random value in our range
}
/// <summary>
/// Find torrent rows in search pages
/// </summary>
@@ -634,7 +430,7 @@ namespace Jackett.Common.Indexers
{
// Retrieving ID from link provided
var id = ParseUtil.CoerceInt(Regex.Match(link.AbsoluteUri, @"\d+").Value);
Output("Torrent Requested ID: " + id);
logger.Info("NorBits - Torrent Requested ID: " + id);
// Building login form data
var pairs = new Dictionary<string, string> {
@@ -642,67 +438,19 @@ namespace Jackett.Common.Indexers
{ "_", string.Empty } // ~~ Strange, blank param...
};
// Add emulated XHR request
_emulatedBrowserHeaders.Add("X-Prototype-Version", "1.6.0.3");
_emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest");
// Get torrent file now
Output("Getting torrent file now....");
var response = await base.Download(link);
// Remove our XHR request header
_emulatedBrowserHeaders.Remove("X-Prototype-Version");
_emulatedBrowserHeaders.Remove("X-Requested-With");
// Return content
return response;
}
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void Output(string message, string level = "debug")
{
// Check if we are in dev mode
if (DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if (logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void ValidateConfig()
{
Output("\nValidating Settings ... \n");
logger.Info("\nNorBits - Validating Settings ... \n");
// Check Username Setting
if (string.IsNullOrEmpty(ConfigData.Username.Value))
@@ -711,7 +459,7 @@ namespace Jackett.Common.Indexers
}
else
{
Output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value);
logger.Info("NorBits - Validated Setting -- Username (auth) => " + ConfigData.Username.Value);
}
// Check Password Setting
@@ -721,7 +469,7 @@ namespace Jackett.Common.Indexers
}
else
{
Output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value);
logger.Info("NorBits - Validated Setting -- Password (auth) => " + ConfigData.Password.Value);
}
// Check Max Page Setting
@@ -729,7 +477,7 @@ namespace Jackett.Common.Indexers
{
try
{
Output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
logger.Info("NorBits - Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
}
catch (Exception)
{
@@ -740,121 +488,6 @@ namespace Jackett.Common.Indexers
{
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
}
// Check Latency Setting
if (ConfigData.Latency.Value)
{
Output("\nValidated Setting -- Latency Simulation enabled");
// Check Latency Start Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
{
try
{
Output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
}
// Check Latency End Setting
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
{
try
{
Output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
}
}
// Check Browser Setting
if (ConfigData.Browser.Value)
{
Output("\nValidated Setting -- Browser Simulation enabled");
// Check ACCEPT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
}
else
{
Output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value);
}
// Check ACCEPT-LANG header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
}
else
{
Output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value);
}
// Check USER-AGENT header Setting
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
{
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
}
else
{
Output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value);
}
}
else
{
// Browser simulation must be enabled (otherwhise, this provider will not work due to tracker's security)
throw new ExceptionWithConfigData("Browser Simulation must be enabled for this provider to work, please enable it !", ConfigData);
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value)
{
Output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
Output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
CleanCacheStorage(true);
}
}
}
}

View File

@@ -19,12 +19,16 @@ namespace Jackett.Common.Indexers
[ExcludeFromCodeCoverage]
internal class ShizaProject : BaseWebIndexer
{
public override string[] LegacySiteLinks { get; protected set; } = {
"http://shiza-project.com/" // site is forcing https
};
public ShizaProject(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps,
ICacheService cs)
: base(id: "ShizaProject",
name: "ShizaProject",
description: "ShizaProject Tracker is a semi-private russian tracker and release group for anime",
link: "http://shiza-project.com/",
link: "https://shiza-project.com/",
caps: new TorznabCapabilities
{
TvSearchParams = new List<TvSearchParam>

View File

@@ -20,6 +20,11 @@ namespace Jackett.Common.Indexers
[ExcludeFromCodeCoverage]
public class SubsPlease : BaseWebIndexer
{
public override string[] AlternativeSiteLinks { get; protected set; } = {
"https://subsplease.org/",
"https://subsplease.nocensor.space/"
};
private string ApiEndpoint => SiteLink + "/api/?";
public SubsPlease(IIndexerConfigurationService configService, Utils.Clients.WebClient wc, Logger l, IProtectionService ps, ICacheService cs)

View File

@@ -92,7 +92,7 @@ namespace Jackett.Common.Indexers
var qc = new NameValueCollection
{
{ "order_by", "s3" },
{ "order_way", "desc" },
{ "order_way", "DESC" },
{ "disablegrouping", "1" }
};
@@ -101,7 +101,7 @@ namespace Jackett.Common.Indexers
qc.Add("action", "advanced");
qc.Add("imdbid", query.ImdbID);
}
else
else if (!string.IsNullOrWhiteSpace(query.GetQueryString()))
qc.Add("searchstr", StripSearchString(query.GetQueryString()));
var searchUrl = BrowseUrl + "?" + qc.GetQueryString();
@@ -115,7 +115,7 @@ namespace Jackett.Common.Indexers
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
foreach (var row in rows)
{
var qDetailsLink = row.QuerySelector("a[href*=\"torrents.php?id=\"]");
var qDetailsLink = row.QuerySelector("a[href^=\"torrents.php?id=\"]");
var title = qDetailsLink.TextContent;
// if it's a season search, we filter results. the trailing space is to match regex
if (query.Season > 0 && !seasonRegEx.Match($"{title} ").Success)
@@ -123,7 +123,7 @@ namespace Jackett.Common.Indexers
var description = qDetailsLink.NextSibling.TextContent.Trim();
title += " " + description;
var details = new Uri(qDetailsLink.GetAttribute("href"));
var details = new Uri(SiteLink + qDetailsLink.GetAttribute("href"));
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
var link = new Uri(SiteLink + "torrents.php?action=download&id=" + torrentId);

View File

@@ -2,21 +2,20 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Jackett.Common.Models;
using Jackett.Common.Models.IndexerConfig.Bespoke;
using Jackett.Common.Models.IndexerConfig;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using static Jackett.Common.Models.IndexerConfig.ConfigurationData;
using WebRequest = Jackett.Common.Utils.Clients.WebRequest;
namespace Jackett.Common.Indexers
@@ -25,25 +24,27 @@ namespace Jackett.Common.Indexers
public class Xthor : BaseCachingWebIndexer
{
private static string ApiEndpoint => "https://api.xthor.tk/";
private int MaxPagesHardLimit => 4;
private string TorrentDetailsUrl => SiteLink + "details.php?id={id}";
private string WebRequestDelay => ((SingleSelectConfigurationItem)configData.GetDynamic("webRequestDelay")).Value;
private int MaxPages => Convert.ToInt32(((SingleSelectConfigurationItem)configData.GetDynamic("maxPages")).Value);
private bool MaxPagesBypassForTMDB => ((BoolConfigurationItem)configData.GetDynamic("maxPagesBypassForTMDB")).Value;
private string MultiReplacement => ((StringConfigurationItem)configData.GetDynamic("multiReplacement")).Value;
private bool SubReplacement => ((BoolConfigurationItem)configData.GetDynamic("subReplacement")).Value;
private bool EnhancedAnimeSearch => ((BoolConfigurationItem)configData.GetDynamic("enhancedAnimeSearch")).Value;
private string SpecificLanguageAccent => ((SingleSelectConfigurationItem)configData.GetDynamic("specificLanguageAccent")).Value;
private bool FreeleechOnly => ((BoolConfigurationItem)configData.GetDynamic("freeleechOnly")).Value;
public override string[] LegacySiteLinks { get; protected set; } = {
"https://xthor.bz/",
"https://xthor.to"
};
private string TorrentDetailsUrl => SiteLink + "details.php?id={id}";
private string ReplaceMulti => ConfigData.ReplaceMulti.Value;
private bool EnhancedAnime => ConfigData.EnhancedAnime.Value;
private bool DevMode => ConfigData.DevMode.Value;
private bool CacheMode => ConfigData.HardDriveCache.Value;
private static string Directory => Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name.ToLower(), MethodBase.GetCurrentMethod().DeclaringType?.Name.ToLower());
public Dictionary<string, string> EmulatedBrowserHeaders { get; } = new Dictionary<string, string>();
private ConfigurationDataXthor ConfigData => (ConfigurationDataXthor)configData;
private ConfigurationDataPasskey ConfigData => (ConfigurationDataPasskey)configData;
public Xthor(IIndexerConfigurationService configService, Utils.Clients.WebClient w, Logger l,
IProtectionService ps, ICacheService cs)
: base(id: "xthor",
name: "Xthor",
: base(id: "xthor-api",
name: "Xthor API",
description: "General French Private Tracker",
link: "https://xthor.tk/",
caps: new TorznabCapabilities
@@ -54,7 +55,7 @@ namespace Jackett.Common.Indexers
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
MovieSearchParam.Q, MovieSearchParam.TmdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
@@ -71,7 +72,8 @@ namespace Jackett.Common.Indexers
p: ps,
cacheService: cs,
downloadBase: "https://xthor.tk/download.php?torrent=",
configData: new ConfigurationDataXthor())
configData: new ConfigurationDataPasskey()
)
{
Encoding = Encoding.UTF8;
Language = "fr-fr";
@@ -145,6 +147,106 @@ namespace Jackett.Common.Indexers
AddCategoryMapping(21, TorznabCatType.PC, "Logiciels Applis PC");
AddCategoryMapping(22, TorznabCatType.PCMac, "Logiciels Applis Mac");
AddCategoryMapping(23, TorznabCatType.PCMobileAndroid, "Logiciels Smartphone");
// Dynamic Configuration
ConfigData.AddDynamic("optionsConfigurationWarning", new DisplayInfoConfigurationItem(string.Empty, "<center><b>Available Options</b></center>,<br /><br /> <ul><li><b>Freeleech Only</b>: (<i>Restrictive</i>) If you want to discover only freeleech torrents to not impact your ratio, check the related box. So only torrents marked as freeleech will be returned instead of all.</li><br /><li><b>Specific Language</b>: (<i>Restrictive</i>) You can scope your searches with a specific language / accent.</li></ul>"));
var ConfigFreeleechOnly = new BoolConfigurationItem("Do you want to discover only freeleech tagged torrents ?");
ConfigData.AddDynamic("freeleechOnly", ConfigFreeleechOnly);
var ConfigSpecificLanguageAccent = new SingleSelectConfigurationItem("Do you want to scope your searches with a specific language ? (Accent)", new Dictionary<string, string>
{
{"0", "All Voices (default)"},
{"1", "Françaises"},
{"2", "Quebecoises"},
{"47", "Françaises et Québécoises"},
{"3", "Anglaises"},
{"4", "Japonaises"},
{"5", "Espagnoles"},
{"6", "Allemandes"},
{"7", "Chinoises"},
{"8", "Italiennes"},
{"9", "Coréennes"},
{"10", "Danoises"},
{"11", "Russes"},
{"12", "Portugaises"},
{"13", "Hindi"},
{"14", "Hollandaises"},
{"15", "Suédoises"},
{"16", "Norvégiennes"},
{"17", "Thaïlandaises"},
{"18", "Hébreu"},
{"19", "Persanes"},
{"20", "Arabes"},
{"21", "Turques"},
{"22", "Hongroises"},
{"23", "Polonaises"},
{"24", "Finnoises"},
{"25", "Indonésiennes"},
{"26", "Roumaines"},
{"27", "Malaisiennes"},
{"28", "Estoniennes"},
{"29", "Islandaises"},
{"30", "Grecques"},
{"31", "Serbes"},
{"32", "Norvégiennes"},
{"33", "Ukrainiennes"},
{"34", "Bulgares"},
{"35", "Tagalogues"},
{"36", "Xhosa"},
{"37", "Kurdes"},
{"38", "Bengali"},
{"39", "Amhariques"},
{"40", "Bosniaques"},
{"41", "Malayalam"},
{"42", "Télougou"},
{"43", "Bambara"},
{"44", "Catalanes"},
{"45", "Tchèques"},
{"46", "Afrikaans"}
})
{ Value = "0" };
ConfigData.AddDynamic("specificLanguageAccent", ConfigSpecificLanguageAccent);
ConfigData.AddDynamic("advancedConfigurationWarning", new DisplayInfoConfigurationItem(string.Empty, "<center><b>Advanced Configuration</b></center>,<br /><br /> <center><b><u>WARNING !</u></b> <i>Be sure to read instructions before editing options bellow, you can <b>drastically reduce performance</b> of queries or have <b>non-accurate results</b>.</i></center><br/><br/><ul><li><b>Delay betwwen Requests</b>: (<i>not recommended</i>) you can increase delay to requests made to the tracker, but a minimum of 2.1s is enforced as there is an anti-spam protection.</li><br /><li><b>Max Pages</b>: (<i>not recommended</i>) you can increase max pages to follow when making a request. But be aware that others apps can consider this indexer not working if jackett take too many times to return results. Another thing is that API is very buggy on tracker side, most of time, results of next pages are same ... as the first page. Even if we deduplicate rows, you will loose performance for the same results. You can check logs to see if an higher pages following is not benefical, you will see an error percentage (duplicates) with recommandations.</li><br /><li><b>Bypass for TMDB</b>: (<i>recommended</i>) this indexer is compatible with TMDB queries (<i>for movies only</i>), so when requesting content with an TMDB ID, we will search directly ID on API instead of name. Results will be more accurate, so you can enable a max pages bypass for this query type. You will be at least limited by the hard limit of 4 pages.</li><br /><li><b>Enhanced Anime</b>: if you have \"Anime\", this will improve queries made to this tracker related to this type when making searches.</li><br /><li><b>Multi Replacement</b>: you can dynamically replace the word \"MULTI\" with another of your choice like \"MULTI.FRENCH\" for better analysis of 3rd party softwares.</li><li><b>Sub Replacement</b>: you can dynamically replace the word \"VOSTFR\" or \"SUBFRENCH\" with the word \"ENGLISH\" for better analysis of 3rd party softwares.</li></ul>"));
var ConfigWebRequestDelay = new SingleSelectConfigurationItem("Which delay do you want to apply between each requests made to tracker ?", new Dictionary<string, string>
{
{"2.1", "2.1s (minimum)"},
{"2.2", "2.2s"},
{"2.3", "2.3s"},
{"2.4", "2.4s" },
{"2.5", "2.5s"},
{"2.6", "2.6s"}
})
{ Value = "2.1" };
ConfigData.AddDynamic("webRequestDelay", ConfigWebRequestDelay);
var ConfigMaxPages = new SingleSelectConfigurationItem("How many pages do you want to follow ?", new Dictionary<string, string>
{
{"1", "1 (32 results - default / best perf.)"},
{"2", "2 (64 results)"},
{"3", "3 (96 results)"},
{"4", "4 (128 results - hard limit max)" },
})
{ Value = "1" };
ConfigData.AddDynamic("maxPages", ConfigMaxPages);
var ConfigMaxPagesBypassForTMDB = new BoolConfigurationItem("Do you want to bypass max pages for TMDB searches ? (Radarr) - Hard limit of 4") { Value = true };
ConfigData.AddDynamic("maxPagesBypassForTMDB", ConfigMaxPagesBypassForTMDB);
var ConfigEnhancedAnimeSearch = new BoolConfigurationItem("Do you want to use enhanced ANIME search ?") { Value = false };
ConfigData.AddDynamic("enhancedAnimeSearch", ConfigEnhancedAnimeSearch);
var ConfigMultiReplacement = new StringConfigurationItem("Do you want to replace \"MULTI\" keyword in release title by another word ?") { Value = "MULTI.FRENCH" };
ConfigData.AddDynamic("multiReplacement", ConfigMultiReplacement);
var ConfigSubReplacement = new BoolConfigurationItem("Do you want to replace \"VOSTFR\" and \"SUBFRENCH\" with \"ENGLISH\" word ?") { Value = false };
ConfigData.AddDynamic("subReplacement", ConfigSubReplacement);
// Api has 1req/2s limit (minimum)
webclient.requestDelay = Convert.ToDouble(WebRequestDelay);
}
/// <summary>
@@ -156,10 +258,9 @@ namespace Jackett.Common.Indexers
// Warning 1998 is async method with no await calls inside
// TODO: Remove pragma by wrapping return in Task.FromResult and removing async
#pragma warning disable 1998
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
#pragma warning restore 1998
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
// Provider not yet configured
IsConfigured = false;
@@ -167,19 +268,18 @@ namespace Jackett.Common.Indexers
// Retrieve config values set by Jackett's user
LoadValuesFromJson(configJson);
// Check & Validate Config
ValidateConfig();
logger.Debug("\nXthor - Validating Settings ... \n");
// Setting our data for a better emulated browser (maximum security)
// TODO: Encoded Content not supported by Jackett at this time
// EmulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
// Check Passkey Setting
if (string.IsNullOrEmpty(ConfigData.Passkey.Value))
{
throw new ExceptionWithConfigData("You must provide your passkey for this tracker to be allowed to use API !", ConfigData);
}
else
{
logger.Debug("Xthor - Validated Setting -- PassKey (auth) => " + ConfigData.Passkey.Value);
}
// Clean headers
EmulatedBrowserHeaders.Clear();
// Inject headers
EmulatedBrowserHeaders.Add("Accept", "application/json-rpc, application/json");
EmulatedBrowserHeaders.Add("Content-Type", "application/json-rpc");
// Tracker is now configured
IsConfigured = true;
@@ -198,106 +298,145 @@ namespace Jackett.Common.Indexers
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
{
var releases = new List<ReleaseInfo>();
var searchTerm = query.GetEpisodeSearchString() + " " + query.SanitizedSearchTerm; // use episode search string first, see issue #1202
var searchTerm = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
searchTerm = searchTerm.Trim();
searchTerm = searchTerm.ToLower();
searchTerm = searchTerm.Replace(" ", ".");
if (EnhancedAnime && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110)))
if (EnhancedAnimeSearch && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110)))
{
var regex = new Regex(" ([0-9]+)");
searchTerm = regex.Replace(searchTerm, " E$1");
}
// Check cache first so we don't query the server (if search term used or not in dev mode)
if (!DevMode && !string.IsNullOrEmpty(searchTerm))
// Multiple page support
var nextPage = 1; var followingPages = true;
do
{
lock (cache)
// Build our query
var request = BuildQuery(searchTerm, query, ApiEndpoint, nextPage);
// Getting results
logger.Info("\nXthor - Querying API page " + nextPage);
var results = await QueryTrackerAsync(request);
// Torrents Result Count
var torrentsCount = 0;
try
{
// Remove old cache items
CleanCache();
// Deserialize our Json Response
var xthorResponse = JsonConvert.DeserializeObject<XthorResponse>(results);
// Search in cache
var cachedResult = cache.FirstOrDefault(i => i.Query == searchTerm);
if (cachedResult != null)
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
}
}
// Check Tracker's State
CheckApiState(xthorResponse.Error);
// Build our query
var request = BuildQuery(searchTerm, query, ApiEndpoint);
// Getting results & Store content
var results = await QueryExec(request);
try
{
// Deserialize our Json Response
var xthorResponse = JsonConvert.DeserializeObject<XthorResponse>(results);
// Check Tracker's State
CheckApiState(xthorResponse.error);
// If contains torrents
if (xthorResponse.torrents != null)
{
// Adding each torrent row to releases
// Exclude hidden torrents (category 106, example => search 'yoda' in the API) #10407
releases.AddRange(xthorResponse.torrents
.Where(torrent => torrent.category != 106).Select(torrent =>
// If contains torrents
if (xthorResponse.Torrents != null)
{
//issue #3847 replace multi keyword
if (!string.IsNullOrEmpty(ReplaceMulti))
{
var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])");
torrent.name = regex.Replace(torrent.name, "$1" + ReplaceMulti + "$2");
}
// Store torrents rows count result
torrentsCount = xthorResponse.Torrents.Count();
logger.Info("\nXthor - Found " + torrentsCount + " torrents on current page.");
// issue #8759 replace vostfr and subfrench with English
if (ConfigData.Vostfr.Value) torrent.name = torrent.name.Replace("VOSTFR","ENGLISH").Replace("SUBFRENCH","ENGLISH");
// Adding each torrent row to releases
// Exclude hidden torrents (category 106, example => search 'yoda' in the API) #10407
releases.AddRange(xthorResponse.Torrents
.Where(torrent => torrent.Category != 106).Select(torrent =>
{
//issue #3847 replace multi keyword
if (!string.IsNullOrEmpty(MultiReplacement))
{
var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])");
torrent.Name = regex.Replace(torrent.Name, "$1" + MultiReplacement + "$2");
}
var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.added);
//TODO replace with download link?
var guid = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.id.ToString()));
var details = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.id.ToString()));
var link = new Uri(torrent.download_link);
var release = new ReleaseInfo
{
// Mapping data
Category = MapTrackerCatToNewznab(torrent.category.ToString()),
Title = torrent.name,
Seeders = torrent.seeders,
Peers = torrent.seeders + torrent.leechers,
MinimumRatio = 1,
MinimumSeedTime = 345600,
PublishDate = publishDate,
Size = torrent.size,
Grabs = torrent.times_completed,
Files = torrent.numfiles,
UploadVolumeFactor = 1,
DownloadVolumeFactor = (torrent.freeleech == 1 ? 0 : 1),
Guid = guid,
Details = details,
Link = link,
TMDb = torrent.tmdb_id
};
// issue #8759 replace vostfr and subfrench with English
if (SubReplacement)
torrent.Name = torrent.Name.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH");
//TODO make consistent with other trackers
if (DevMode)
{
Output(release.ToString());
}
var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.Added);
//TODO replace with download link?
var guid = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString()));
var details = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString()));
var link = new Uri(torrent.Download_link);
var release = new ReleaseInfo
{
// Mapping data
Category = MapTrackerCatToNewznab(torrent.Category.ToString()),
Title = torrent.Name,
Seeders = torrent.Seeders,
Peers = torrent.Seeders + torrent.Leechers,
MinimumRatio = 1,
MinimumSeedTime = 345600,
PublishDate = publishDate,
Size = torrent.Size,
Grabs = torrent.Times_completed,
Files = torrent.Numfiles,
UploadVolumeFactor = 1,
DownloadVolumeFactor = (torrent.Freeleech == 1 ? 0 : 1),
Guid = guid,
Details = details,
Link = link,
TMDb = torrent.Tmdb_id
};
return release;
}));
return release;
}));
nextPage++;
}
else
{
logger.Info("\nXthor - No results found on page " + nextPage + ", stopping follow of next page.");
// No results or no more results available
followingPages = false;
break;
}
}
catch (Exception ex)
{
OnParseError("Unable to parse result \n" + ex.StackTrace, ex);
}
}
catch (Exception ex)
{
OnParseError("Unable to parse result \n" + ex.StackTrace, ex);
}
// Stop ?
if(query.IsTmdbQuery && MaxPagesBypassForTMDB)
{
if(nextPage > MaxPagesHardLimit)
{
logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to page hard limit reached.");
break;
}
logger.Info("\nXthor - Continue to next page " + nextPage + " due to TMDB request and activated max page bypass for this type of query. Max page hard limit: 4.");
continue;
}
else
{
if(torrentsCount < 32)
{
logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due max available results reached.");
break;
} else if(nextPage > MaxPages)
{
logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to page limit reached.");
break;
} else if (query.IsTest)
{
logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to index test query.");
break;
}
}
} while (followingPages);
// Check if there is duplicate and return unique rows - Xthor API can be very buggy !
var uniqReleases = releases.GroupBy(x => x.Guid).Select(x => x.First()).ToList();
var errorPercentage = 1 - ((double) uniqReleases.Count() / releases.Count());
if(errorPercentage >= 0.25)
{
logger.Warn("\nXthor - High percentage error detected: " + string.Format("{0:0.0%}", errorPercentage) + "\nWe strongly recommend that you lower max page to 1, as there is no benefit to grab additionnals.\nTracker API sent us duplicated pages with same results, even if we deduplicate returned rows, please consider to lower as it's unnecessary and increase time used for query for the same result.");
}
// Return found releases
return releases;
return uniqReleases;
}
/// <summary>
@@ -305,9 +444,9 @@ namespace Jackett.Common.Indexers
/// </summary>
public class XthorResponse
{
public XthorError error { get; set; }
public XthorUser user { get; set; }
public List<XthorTorrent> torrents { get; set; }
public XthorError Error { get; set; }
public XthorUser User { get; set; }
public List<XthorTorrent> Torrents { get; set; }
}
/// <summary>
@@ -315,8 +454,8 @@ namespace Jackett.Common.Indexers
/// </summary>
public class XthorError
{
public int code { get; set; }
public string descr { get; set; }
public int Code { get; set; }
public string Descr { get; set; }
}
/// <summary>
@@ -324,14 +463,14 @@ namespace Jackett.Common.Indexers
/// </summary>
public class XthorUser
{
public int id { get; set; }
public string username { get; set; }
public long uploaded { get; set; }
public long downloaded { get; set; }
public int uclass { get; set; } // Class is a reserved keyword.
public decimal bonus_point { get; set; }
public int hits_and_run { get; set; }
public string avatar_url { get; set; }
public int Id { get; set; }
public string Username { get; set; }
public long Uploaded { get; set; }
public long Downloaded { get; set; }
public int Uclass { get; set; } // Class is a reserved keyword.
public decimal Bonus_point { get; set; }
public int Hits_and_run { get; set; }
public string Avatar_url { get; set; }
}
/// <summary>
@@ -339,21 +478,21 @@ namespace Jackett.Common.Indexers
/// </summary>
public class XthorTorrent
{
public int id { get; set; }
public int category { get; set; }
public int seeders { get; set; }
public int leechers { get; set; }
public string name { get; set; }
public int times_completed { get; set; }
public long size { get; set; }
public int added { get; set; }
public int freeleech { get; set; }
public int numfiles { get; set; }
public string release_group { get; set; }
public string download_link { get; set; }
public int tmdb_id { get; set; }
public int Id { get; set; }
public int Category { get; set; }
public int Seeders { get; set; }
public int Leechers { get; set; }
public string Name { get; set; }
public int Times_completed { get; set; }
public long Size { get; set; }
public int Added { get; set; }
public int Freeleech { get; set; }
public int Numfiles { get; set; }
public string Release_group { get; set; }
public string Download_link { get; set; }
public int Tmdb_id { get; set; }
public override string ToString() => string.Format("[XthorTorrent: id={0}, category={1}, seeders={2}, leechers={3}, name={4}, times_completed={5}, size={6}, added={7}, freeleech={8}, numfiles={9}, release_group={10}, download_link={11}, tmdb_id={12}]", id, category, seeders, leechers, name, times_completed, size, added, freeleech, numfiles, release_group, download_link, tmdb_id);
public override string ToString() => string.Format("[XthorTorrent: id={0}, category={1}, seeders={2}, leechers={3}, name={4}, times_completed={5}, size={6}, added={7}, freeleech={8}, numfiles={9}, release_group={10}, download_link={11}, tmdb_id={12}]", Id, Category, Seeders, Leechers, Name, Times_completed, Size, Added, Freeleech, Numfiles, Release_group, Download_link, Tmdb_id);
}
/// <summary>
@@ -363,26 +502,33 @@ namespace Jackett.Common.Indexers
/// <param name="query">Torznab Query for categories mapping</param>
/// <param name="url">Search url for provider</param>
/// <returns>URL to query for parsing and processing results</returns>
private string BuildQuery(string term, TorznabQuery query, string url)
private string BuildQuery(string term, TorznabQuery query, string url, int page = 1)
{
var parameters = new NameValueCollection();
var categoriesList = MapTorznabCapsToTrackers(query);
// Passkey
parameters.Add("passkey", ConfigData.PassKey.Value);
parameters.Add("passkey", ConfigData.Passkey.Value);
// If search term provided
if (!string.IsNullOrWhiteSpace(term))
if (query.IsTmdbQuery)
{
// Add search term
// ReSharper disable once AssignNullToNotNullAttribute
parameters.Add("search", WebUtility.UrlEncode(term));
logger.Info("\nXthor - Search requested for movie with TMDB ID n°" + query.TmdbID.ToString());
parameters.Add("tmdbid", query.TmdbID.ToString());
}
else
{
parameters.Add("search", string.Empty);
// Showing all torrents (just for output function)
term = "all";
if (!string.IsNullOrWhiteSpace(term))
{
// Add search term
logger.Info("\nXthor - Search requested for movie with title \"" + term + "\"");
parameters.Add("search", WebUtility.UrlEncode(term));
}
else
{
logger.Info("\nXthor - Global search requested without term");
parameters.Add("search", string.Empty);
// Showing all torrents
}
}
// Loop on Categories needed
@@ -392,121 +538,48 @@ namespace Jackett.Common.Indexers
}
// If Only Freeleech Enabled
if (ConfigData.Freeleech.Value)
if (FreeleechOnly)
{
parameters.Add("freeleech", "1");
}
if (!string.IsNullOrEmpty(ConfigData.Accent.Value))
// If Specific Language Accent Requested
if (!string.IsNullOrEmpty(SpecificLanguageAccent) && SpecificLanguageAccent != "0")
{
parameters.Add("accent", ConfigData.Accent.Value);
parameters.Add("accent", SpecificLanguageAccent);
}
// Pages handling
if (page > 1 && !query.IsTest)
{
parameters.Add("page", page.ToString());
}
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong category param)
url += "?" + string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
Output("\nBuilded query for \"" + term + "\"... " + url);
logger.Info("\nXthor - Builded query: " + url);
// Return our search url
return url;
}
/// <summary>
/// Switch Method for Querying
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<string> QueryExec(string request)
{
string results;
// Switch in we are in DEV mode with Hard Drive Cache or not
if (DevMode && CacheMode)
{
// Check Cache before querying and load previous results if available
results = await QueryCache(request);
}
else
{
// Querying tracker directly
results = await QueryTracker(request);
}
return results;
}
/// <summary>
/// Get Torrents Page from Cache by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<string> QueryCache(string request)
{
string results;
// Create Directory if not exist
System.IO.Directory.CreateDirectory(Directory);
// Clean Storage Provider Directory from outdated cached queries
CleanCacheStorage();
// File Name
var fileName = StringUtil.HashSHA1(request) + ".json";
// Create fingerprint for request
var file = Path.Combine(Directory, fileName);
// Checking modes states
if (File.Exists(file))
{
// File exist... loading it right now !
Output("Loading results from hard drive cache ..." + fileName);
try
{
using (var fileReader = File.OpenText(file))
{
var serializer = new JsonSerializer();
results = (string)serializer.Deserialize(fileReader, typeof(string));
}
}
catch (Exception e)
{
Output("Error loading cached results ! " + e.Message, "error");
results = null;
}
}
else
{
// No cached file found, querying tracker directly
results = await QueryTracker(request);
// Cached file didn't exist for our query, writing it right now !
Output("Writing results to hard drive cache ..." + fileName);
using (var fileWriter = File.CreateText(file))
{
var serializer = new JsonSerializer();
serializer.Serialize(fileWriter, results);
}
}
return results;
}
/// <summary>
/// Get Torrents Page from Tracker by Query Provided
/// </summary>
/// <param name="request">URL created by Query Builder</param>
/// <returns>Results from query</returns>
private async Task<string> QueryTracker(string request)
private async Task<string> QueryTrackerAsync(string request)
{
// Cache mode not enabled or cached file didn't exist for our query
Output("\nQuerying tracker for results....");
logger.Debug("\nQuerying tracker for results....");
// Build WebRequest for index
var myIndexRequest = new WebRequest
{
Type = RequestType.GET,
Url = request,
Encoding = Encoding,
Headers = EmulatedBrowserHeaders
Encoding = Encoding
};
// Request our first page
@@ -525,188 +598,40 @@ namespace Jackett.Common.Indexers
private void CheckApiState(XthorError state)
{
// Switch on state
switch (state.code)
switch (state.Code)
{
case 0:
// Everything OK
Output("\nAPI State : Everything OK ... -> " + state.descr);
logger.Debug("\nXthor - API State : Everything OK ... -> " + state.Descr);
break;
case 1:
// Passkey not found
Output("\nAPI State : Error, Passkey not found in tracker's database, aborting... -> " + state.descr);
logger.Error("\nXthor - API State : Error, Passkey not found in tracker's database, aborting... -> " + state.Descr);
throw new Exception("Passkey not found in tracker's database");
case 2:
// No results
Output("\nAPI State : No results for query ... -> " + state.descr);
logger.Info("\nXthor - API State : No results for query ... -> " + state.Descr);
break;
case 3:
// Power Saver
Output("\nAPI State : Power Saver mode, only cached query with no parameters available ... -> " + state.descr);
logger.Warn("\nXthor - API State : Power Saver mode, only cached query with no parameters available ... -> " + state.Descr);
break;
case 4:
// DDOS Attack, API disabled
Output("\nAPI State : Tracker is under DDOS attack, API disabled, aborting ... -> " + state.descr);
logger.Error("\nXthor - API State : Tracker is under DDOS attack, API disabled, aborting ... -> " + state.Descr);
throw new Exception("Tracker is under DDOS attack, API disabled");
case 8:
// AntiSpam Protection
logger.Warn("\nXthor - API State : Triggered AntiSpam Protection -> " + state.Descr);
throw new Exception("Triggered AntiSpam Protection, please delay your requests !");
default:
// Unknown state
Output("\nAPI State : Unknown state, aborting querying ... -> " + state.descr);
logger.Error("\nXthor - API State : Unknown state, aborting querying ... -> " + state.Descr);
throw new Exception("Unknown state, aborting querying");
}
}
/// <summary>
/// Clean Hard Drive Cache Storage
/// </summary>
/// <param name="force">Force Provider Folder deletion</param>
private void CleanCacheStorage(bool force = false)
{
// Check cleaning method
if (force)
{
// Deleting Provider Storage folder and all files recursively
Output("\nDeleting Provider Storage folder and all files recursively ...");
// Check if directory exist
if (System.IO.Directory.Exists(Directory))
{
// Delete storage directory of provider
System.IO.Directory.Delete(Directory, true);
Output("-> Storage folder deleted successfully.");
}
else
{
// No directory, so nothing to do
Output("-> No Storage folder found for this provider !");
}
}
else
{
var i = 0;
// Check if there is file older than ... and delete them
Output("\nCleaning Provider Storage folder... in progress.");
System.IO.Directory.GetFiles(Directory)
.Select(f => new FileInfo(f))
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
.ToList()
.ForEach(f =>
{
Output("Deleting cached file << " + f.Name + " >> ... done.");
f.Delete();
i++;
});
// Inform on what was cleaned during process
if (i > 0)
{
Output("-> Deleted " + i + " cached files during cleaning.");
}
else
{
Output("-> Nothing deleted during cleaning.");
}
}
}
/// <summary>
/// Output message for logging or developpment (console)
/// </summary>
/// <param name="message">Message to output</param>
/// <param name="level">Level for Logger</param>
private void Output(string message, string level = "debug")
{
// Check if we are in dev mode
if (DevMode)
{
// Output message to console
Console.WriteLine(message);
}
else
{
// Send message to logger with level
switch (level)
{
default:
goto case "debug";
case "debug":
// Only if Debug Level Enabled on Jackett
if (logger.IsDebugEnabled)
{
logger.Debug(message);
}
break;
case "info":
logger.Info(message);
break;
case "error":
logger.Error(message);
break;
}
}
}
/// <summary>
/// Validate Config entered by user on Jackett
/// </summary>
private void ValidateConfig()
{
Output("\nValidating Settings ... \n");
// Check Passkey Setting
if (string.IsNullOrEmpty(ConfigData.PassKey.Value))
{
throw new ExceptionWithConfigData("You must provide your passkey for this tracker to be allowed to use API !", ConfigData);
}
else
{
Output("Validated Setting -- PassKey (auth) => " + ConfigData.PassKey.Value);
}
if (!string.IsNullOrEmpty(ConfigData.Accent.Value) && !string.Equals(ConfigData.Accent.Value, "1") && !string.Equals(ConfigData.Accent.Value, "2"))
{
throw new ExceptionWithConfigData("Only '1' or '2' are available in the Accent parameter.", ConfigData);
}
else
{
Output("Validated Setting -- Accent (audio) => " + ConfigData.Accent.Value);
}
// Check Dev Cache Settings
if (ConfigData.HardDriveCache.Value)
{
Output("\nValidated Setting -- DEV Hard Drive Cache enabled");
// Check if Dev Mode enabled !
if (!ConfigData.DevMode.Value)
{
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
}
// Check Cache Keep Time Setting
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
{
try
{
Output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
}
catch (Exception)
{
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
}
}
else
{
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
}
}
else
{
// Delete cache if previously existed
CleanCacheStorage(true);
}
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Jackett.Common.Indexers
{
public override string[] AlternativeSiteLinks { get; protected set; } = {
"https://yts.mx/",
"https://yts.unblockit.club/",
"https://yts.unblockit.onl/",
"https://yts.unblockninja.com/",
"https://yts.nocensor.space/"
};
@@ -34,6 +34,7 @@ namespace Jackett.Common.Indexers
"https://yts.root.yt/",
"https://yts.unblockit.ltd/",
"https://yts.unblockit.buzz/",
"https://yts.unblockit.club/",
"https://yts.unblockit.link/"
};

View File

@@ -13,22 +13,6 @@ namespace Jackett.Common.Models.IndexerConfig.Bespoke
public DisplayInfoConfigurationItem PagesWarning { get; private set; }
public StringConfigurationItem ReplaceMulti { get; private set; }
public StringConfigurationItem Pages { get; private set; }
public DisplayInfoConfigurationItem SecurityWarning { get; private set; }
public BoolConfigurationItem Latency { get; private set; }
public BoolConfigurationItem Browser { get; private set; }
public DisplayInfoConfigurationItem LatencyWarning { get; private set; }
public StringConfigurationItem LatencyStart { get; private set; }
public StringConfigurationItem LatencyEnd { get; private set; }
public DisplayInfoConfigurationItem HeadersWarning { get; private set; }
public StringConfigurationItem HeaderAccept { get; private set; }
public StringConfigurationItem HeaderAcceptLang { get; private set; }
public BoolConfigurationItem HeaderDNT { get; private set; }
public BoolConfigurationItem HeaderUpgradeInsecure { get; private set; }
public StringConfigurationItem HeaderUserAgent { get; private set; }
public DisplayInfoConfigurationItem DevWarning { get; private set; }
public BoolConfigurationItem DevMode { get; private set; }
public BoolConfigurationItem HardDriveCache { get; private set; }
public StringConfigurationItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataAbnormal()
: base()
@@ -41,22 +25,6 @@ namespace Jackett.Common.Models.IndexerConfig.Bespoke
PagesWarning = new DisplayInfoConfigurationItem("Preferences", "<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Replace MULTI</b>, replace multi keyword in the resultset (leave empty to deactivate)</li><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li></ul>");
Pages = new StringConfigurationItem("Max Pages to Process (Required)") { Value = "4" };
ReplaceMulti = new StringConfigurationItem("Replace MULTI") { Value = "MULTI.FRENCH" };
SecurityWarning = new DisplayInfoConfigurationItem("Security", "<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>");
Latency = new BoolConfigurationItem("Latency Simulation (Optional)") { Value = false };
Browser = new BoolConfigurationItem("Browser Simulation (Optional)") { Value = true };
LatencyWarning = new DisplayInfoConfigurationItem("Simulate Latency", "<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>");
LatencyStart = new StringConfigurationItem("Minimum Latency (ms)") { Value = "1589" };
LatencyEnd = new StringConfigurationItem("Maximum Latency (ms)") { Value = "3674" };
HeadersWarning = new DisplayInfoConfigurationItem("Injecting headers", "<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>");
HeaderAccept = new StringConfigurationItem("Accept") { Value = "" };
HeaderAcceptLang = new StringConfigurationItem("Accept-Language") { Value = "" };
HeaderDNT = new BoolConfigurationItem("DNT") { Value = false };
HeaderUpgradeInsecure = new BoolConfigurationItem("Upgrade-Insecure-Requests") { Value = false };
HeaderUserAgent = new StringConfigurationItem("User-Agent") { Value = "" };
DevWarning = new DisplayInfoConfigurationItem("Development", "<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>");
DevMode = new BoolConfigurationItem("Enable DEV MODE (Developers ONLY)") { Value = false };
HardDriveCache = new BoolConfigurationItem("Enable HARD DRIVE CACHE (Developers ONLY)") { Value = false };
HardDriveCacheKeepTime = new StringConfigurationItem("Keep Cached files for (ms)") { Value = "300000" };
}
}
}

View File

@@ -1,23 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Jackett.Common.Models.IndexerConfig.Bespoke
{
[ExcludeFromCodeCoverage]
internal class ConfigurationDataEliteTracker : ConfigurationDataBasicLogin
{
public BoolConfigurationItem TorrentHTTPSMode { get; }
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once UnusedAutoPropertyAccessor.Global
public DisplayInfoConfigurationItem PagesWarning { get; }
public StringConfigurationItem ReplaceMulti { get; }
public BoolConfigurationItem Vostfr { get; }
public ConfigurationDataEliteTracker()
{
TorrentHTTPSMode = new BoolConfigurationItem("Use HTTPS for tracker URL") { Value = false };
PagesWarning = new DisplayInfoConfigurationItem("Preferences", "<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Replace MULTI</b>, replace multi keyword in the resultset (leave empty to deactivate)</li><li><b>Replace VOSTFR with ENGLISH</b> lets you change the titles by replacing VOSTFR with ENGLISH.</li></ul>");
ReplaceMulti = new StringConfigurationItem("Replace MULTI") { Value = "MULTI.FRENCH" };
Vostfr = new BoolConfigurationItem("Replace VOSTFR with ENGLISH") { Value = false };
}
}
}

View File

@@ -11,22 +11,6 @@ namespace Jackett.Common.Models.IndexerConfig.Bespoke
public DisplayInfoConfigurationItem PagesWarning { get; private set; }
public StringConfigurationItem Pages { get; private set; }
public BoolConfigurationItem UseFullSearch { get; private set; }
public DisplayInfoConfigurationItem SecurityWarning { get; private set; }
public BoolConfigurationItem Latency { get; private set; }
public BoolConfigurationItem Browser { get; private set; }
public DisplayInfoConfigurationItem LatencyWarning { get; private set; }
public StringConfigurationItem LatencyStart { get; private set; }
public StringConfigurationItem LatencyEnd { get; private set; }
public DisplayInfoConfigurationItem HeadersWarning { get; private set; }
public StringConfigurationItem HeaderAccept { get; private set; }
public StringConfigurationItem HeaderAcceptLang { get; private set; }
public BoolConfigurationItem HeaderDnt { get; private set; }
public BoolConfigurationItem HeaderUpgradeInsecure { get; private set; }
public StringConfigurationItem HeaderUserAgent { get; private set; }
public DisplayInfoConfigurationItem DevWarning { get; private set; }
public BoolConfigurationItem DevMode { get; private set; }
public BoolConfigurationItem HardDriveCache { get; private set; }
public StringConfigurationItem HardDriveCacheKeepTime { get; private set; }
public ConfigurationDataNorbits()
{
@@ -36,22 +20,6 @@ namespace Jackett.Common.Models.IndexerConfig.Bespoke
PagesWarning = new DisplayInfoConfigurationItem("Preferences", "<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li></ul>");
Pages = new StringConfigurationItem("Max Pages to Process (Required)") { Value = "4" };
UseFullSearch = new BoolConfigurationItem("Enable search in description.") { Value = false };
SecurityWarning = new DisplayInfoConfigurationItem("Security", "<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.<b>You must enable it to use this provider!</b></li></ul>");
Latency = new BoolConfigurationItem("Latency Simulation (Optional)") { Value = false };
Browser = new BoolConfigurationItem("Browser Simulation (Forced)") { Value = true };
LatencyWarning = new DisplayInfoConfigurationItem("Simulate Latency", "<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>");
LatencyStart = new StringConfigurationItem("Minimum Latency (ms)") { Value = "1589" };
LatencyEnd = new StringConfigurationItem("Maximum Latency (ms)") { Value = "3674" };
HeadersWarning = new DisplayInfoConfigurationItem("Injecting headers", "<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>");
HeaderAccept = new StringConfigurationItem("Accept") { Value = "" };
HeaderAcceptLang = new StringConfigurationItem("Accept-Language") { Value = "" };
HeaderDnt = new BoolConfigurationItem("DNT") { Value = false };
HeaderUpgradeInsecure = new BoolConfigurationItem("Upgrade-Insecure-Requests") { Value = false };
HeaderUserAgent = new StringConfigurationItem("User-Agent") { Value = "" };
DevWarning = new DisplayInfoConfigurationItem("Development", "<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>");
DevMode = new BoolConfigurationItem("Enable DEV MODE (Developers ONLY)") { Value = false };
HardDriveCache = new BoolConfigurationItem("Enable HARD DRIVE CACHE (Developers ONLY)") { Value = false };
HardDriveCacheKeepTime = new StringConfigurationItem("Keep Cached files for (ms)") { Value = "300000" };
}
}
}

View File

@@ -1,39 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Jackett.Common.Models.IndexerConfig.Bespoke
{
[ExcludeFromCodeCoverage]
internal class ConfigurationDataXthor : ConfigurationData
{
public DisplayInfoConfigurationItem CredentialsWarning { get; private set; }
public StringConfigurationItem PassKey { get; set; }
public DisplayInfoConfigurationItem PagesWarning { get; private set; }
public StringConfigurationItem Accent { get; set; }
public BoolConfigurationItem Freeleech { get; private set; }
public StringConfigurationItem ReplaceMulti { get; private set; }
public BoolConfigurationItem EnhancedAnime { get; private set; }
public DisplayInfoConfigurationItem DevWarning { get; private set; }
public BoolConfigurationItem DevMode { get; private set; }
public BoolConfigurationItem HardDriveCache { get; private set; }
public StringConfigurationItem HardDriveCacheKeepTime { get; private set; }
public BoolConfigurationItem Vostfr { get; private set; }
public ConfigurationDataXthor()
: base()
{
CredentialsWarning = new DisplayInfoConfigurationItem("Credentials", "<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>PassKey</b> is your private key on your account</li></ul>");
PassKey = new StringConfigurationItem("PassKey") { Value = "" };
Accent = new StringConfigurationItem("Accent") { Value = "" };
PagesWarning = new DisplayInfoConfigurationItem("Preferences", "<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Replace MULTI</b>, replace multi keyword in the resultset (leave empty to deactivate)</li><li><b>Enhanced anime search</b>, Enhance sonarr compatibility with Xthor. Only effective on requests with the <u>TVAnime Torznab category</u>.</li><li><b>Accent</b> is the french accent you want. 1 for VFF (Truefrench) 2 for VFQ (FRENCH, canada). When one is selected, the other will not be searched.</li></ul>");
Freeleech = new BoolConfigurationItem("Freeleech Only (Optional)") { Value = false };
ReplaceMulti = new StringConfigurationItem("Replace MULTI") { Value = "MULTI.FRENCH" };
EnhancedAnime = new BoolConfigurationItem("Enhanced anime search") { Value = false };
DevWarning = new DisplayInfoConfigurationItem("Development", "<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>");
DevMode = new BoolConfigurationItem("Enable DEV MODE (Developers ONLY)") { Value = false };
HardDriveCache = new BoolConfigurationItem("Enable HARD DRIVE CACHE (Developers ONLY)") { Value = false };
HardDriveCacheKeepTime = new StringConfigurationItem("Keep Cached files for (ms)") { Value = "300000" };
Vostfr = new BoolConfigurationItem("Replace VOSTFR or SUBFRENCH with ENGLISH") { Value = false };
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Jackett.Common.Models.IndexerConfig
{
public class ConfigurationDataAPIKeyAndRSSKey : ConfigurationData
{
public StringConfigurationItem ApiKey { get; private set; }
public StringConfigurationItem RSSKey { get; private set; }
public ConfigurationDataAPIKeyAndRSSKey()
{
ApiKey = new StringConfigurationItem("API Key");
RSSKey = new StringConfigurationItem("RSS Key");
}
}
}

View File

@@ -42,11 +42,13 @@ namespace Jackett.Common.Utils
}
// ex: "2 hours 1 day"
public static DateTime FromTimeAgo(string str)
public static DateTime FromTimeAgo(string str, DateTime? relativeFrom = null)
{
str = str.ToLowerInvariant();
var now = relativeFrom ?? DateTime.Now;
if (str.Contains("now"))
return DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local);
return DateTime.SpecifyKind(now, DateTimeKind.Local);
str = str.Replace(",", "");
str = str.Replace("ago", "");
@@ -80,7 +82,7 @@ namespace Jackett.Common.Utils
throw new Exception("TimeAgo parsing failed, unknown unit: " + unit);
}
return DateTime.SpecifyKind(DateTime.Now - timeAgo, DateTimeKind.Local);
return DateTime.SpecifyKind(now - timeAgo, DateTimeKind.Local);
}
// Uses the DateTimeRoutines library to parse the date
@@ -97,14 +99,23 @@ namespace Jackett.Common.Utils
throw new Exception("FromFuzzyTime parsing failed");
}
private static DateTime FromFuzzyPastTime(string str, string format, DateTime now)
{
var result = FromFuzzyTime(str, format);
if (result > now)
result = result.AddYears(-1);
return result;
}
public static DateTime FromUnknown(string str, string format = null)
public static DateTime FromUnknown(string str, string format = null, DateTime? relativeFrom = null)
{
try
{
str = ParseUtil.NormalizeSpace(str);
var now = relativeFrom ?? DateTime.Now;
if (str.ToLower().Contains("now"))
return DateTime.Now;
return now;
// ... ago
var match = _TimeAgoRegexp.Match(str);
@@ -119,7 +130,7 @@ namespace Jackett.Common.Utils
if (match.Success)
{
var time = str.Replace(match.Groups[0].Value, "");
var dt = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified);
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
dt += ParseTimeSpan(time);
return dt;
}
@@ -129,7 +140,7 @@ namespace Jackett.Common.Utils
if (match.Success)
{
var time = str.Replace(match.Groups[0].Value, "");
var dt = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified);
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
dt += ParseTimeSpan(time);
dt -= TimeSpan.FromDays(1);
return dt;
@@ -140,7 +151,7 @@ namespace Jackett.Common.Utils
if (match.Success)
{
var time = str.Replace(match.Groups[0].Value, "");
var dt = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified);
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
dt += ParseTimeSpan(time);
dt += TimeSpan.FromDays(1);
return dt;
@@ -151,7 +162,7 @@ namespace Jackett.Common.Utils
if (match.Success)
{
var time = str.Replace(match.Groups[0].Value, "");
var dt = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified);
var dt = DateTime.SpecifyKind(now.Date, DateTimeKind.Unspecified);
dt += ParseTimeSpan(time);
DayOfWeek dow;
@@ -176,24 +187,21 @@ namespace Jackett.Common.Utils
return dt;
}
try
// try parsing the str as an unix timestamp
if (long.TryParse(str, out var unixTimeStamp))
{
// try parsing the str as an unix timestamp
var unixTimeStamp = long.Parse(str);
return UnixTimestampToDateTime(unixTimeStamp);
}
catch (FormatException)
{
// it wasn't a timestamp, continue....
}
// it wasn't a timestamp, continue....
// add missing year
match = _MissingYearRegexp.Match(str);
if (match.Success)
{
var date = match.Groups[1].Value;
var newDate = DateTime.Now.Year + "-" + date;
var newDate = now.Year + "-" + date;
str = str.Replace(date, newDate);
return FromFuzzyPastTime(str, format, now);
}
// add missing year 2
@@ -202,7 +210,8 @@ namespace Jackett.Common.Utils
{
var date = match.Groups[1].Value;
var time = match.Groups[2].Value;
str = date + " " + DateTime.Now.Year + " " + time;
str = date + " " + now.Year + " " + time;
return FromFuzzyPastTime(str, format, now);
}
return FromFuzzyTime(str, format);
@@ -214,8 +223,10 @@ namespace Jackett.Common.Utils
}
// converts a date/time string to a DateTime object using a GoLang layout
public static DateTime ParseDateTimeGoLang(string date, string layout)
public static DateTime ParseDateTimeGoLang(string date, string layout, DateTime? relativeFrom = null)
{
var now = relativeFrom ?? DateTime.Now;
date = ParseUtil.NormalizeSpace(date);
var pattern = layout;
@@ -282,7 +293,10 @@ namespace Jackett.Common.Utils
try
{
return DateTime.ParseExact(date, pattern, CultureInfo.InvariantCulture);
var dateTime = DateTime.ParseExact(date, pattern, CultureInfo.InvariantCulture);
if (!pattern.Contains("yy") && dateTime > now)
dateTime = dateTime.AddYears(-1);
return dateTime;
}
catch (FormatException ex)
{

View File

@@ -88,7 +88,7 @@ namespace Jackett.Test.Common.Utils
public void FromUnknownTest()
{
var now = DateTime.Now;
var today = DateTime.UtcNow.Date;
var today = now.ToUniversalTime().Date;
var yesterday = today.AddDays(-1);
var tomorrow = today.AddDays(1);
var testCases = new Dictionary<string, DateTime>
@@ -105,27 +105,28 @@ namespace Jackett.Test.Common.Utils
};
foreach (var testCase in testCases)
Assert.AreEqual(testCase.Value, DateTimeUtil.FromUnknown(testCase.Key));
Assert.AreEqual(testCase.Value, DateTimeUtil.FromUnknown(testCase.Key, relativeFrom: now));
AssertSimilarDates(now, DateTimeUtil.FromUnknown("now"));
AssertSimilarDates(now.AddHours(-3), DateTimeUtil.FromUnknown("3 hours ago"));
Assert.AreEqual(now, DateTimeUtil.FromUnknown("now", relativeFrom: now));
AssertSimilarDates(now.AddHours(-3), DateTimeUtil.FromUnknown("3 hours ago", relativeFrom: now));
Assert.True((now - DateTimeUtil.FromUnknown("monday at 10:20 am")).TotalSeconds <= 3600 * 24 * 7); // 7 days
Assert.True((now - DateTimeUtil.FromUnknown("Tuesday at 22:20")).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("wednesday at \n 22:20")).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("\n thursday \n at 22:20")).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("friday at 22:20")).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("Saturday at 00:20")).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("sunday at 22:00")).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("monday at 10:20 am", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7); // 7 days
Assert.True((now - DateTimeUtil.FromUnknown("Tuesday at 22:20", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("wednesday at \n 22:20", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("\n thursday \n at 22:20", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("friday at 22:20", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("Saturday at 00:20", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7);
Assert.True((now - DateTimeUtil.FromUnknown("sunday at 22:00", relativeFrom: now)).TotalSeconds <= 3600 * 24 * 7);
Assert.AreEqual(new DateTime(2020, 10, 31, 3, 8, 27, DateTimeKind.Utc).ToLocalTime(),
DateTimeUtil.FromUnknown("1604113707"));
DateTimeUtil.FromUnknown("1604113707", relativeFrom: now));
Assert.AreEqual(new DateTime(now.Year, 2, 1), DateTimeUtil.FromUnknown("02-01"));
Assert.AreEqual(new DateTime(now.Year, 2, 1), DateTimeUtil.FromUnknown("2-1"));
Assert.AreEqual(new DateTime(now.Year, 1, 2, 10, 30, 0), DateTimeUtil.FromUnknown("2 Jan 10:30"));
var refDate = new DateTime(2021, 03, 12, 12, 00, 00, DateTimeKind.Local);
Assert.AreEqual(new DateTime(refDate.Year, 2, 1), DateTimeUtil.FromUnknown("02-01", relativeFrom: refDate));
Assert.AreEqual(new DateTime(refDate.Year, 2, 1), DateTimeUtil.FromUnknown("2-1", relativeFrom: refDate));
Assert.AreEqual(new DateTime(refDate.Year, 1, 2, 10, 30, 0), DateTimeUtil.FromUnknown("2 Jan 10:30", relativeFrom: refDate));
Assert.AreEqual(new DateTime(2005, 6, 10, 10, 30, 0),
Assert.AreEqual(new DateTime(2005, 6, 10, 10, 30, 0),
DateTimeUtil.FromUnknown("June 10, 2005 10:30AM"));
// bad cases
@@ -138,6 +139,13 @@ namespace Jackett.Test.Common.Utils
{
// ignored
}
Assert.AreEqual(new DateTime(refDate.Year - 1, 5, 2), DateTimeUtil.FromUnknown("05-02", relativeFrom: refDate));
Assert.AreEqual(new DateTime(refDate.Year - 1, 5, 2), DateTimeUtil.FromUnknown("5-2", relativeFrom: refDate));
Assert.AreEqual(new DateTime(refDate.Year - 1, 5, 2, 10, 30, 0), DateTimeUtil.FromUnknown("2 May 10:30", relativeFrom: refDate));
Assert.AreEqual(new DateTime(2020, 12, 31, 23, 59, 0), DateTimeUtil.FromUnknown("12-31 23:59", relativeFrom: new DateTime(2021, 12, 31, 23, 58, 59, DateTimeKind.Local)));
Assert.AreEqual(new DateTime(2020, 1, 1, 0, 1, 0), DateTimeUtil.FromUnknown("1-1 00:01", relativeFrom: new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Local)));
}
[Test]
@@ -149,8 +157,9 @@ namespace Jackett.Test.Common.Utils
DateTimeUtil.ParseDateTimeGoLang("21-06-2010 04:20:19 -04:00", "02-01-2006 15:04:05 -07:00"));
Assert.AreEqual(new DateTimeOffset(2010, 6, 21, 0, 0, 0, new TimeSpan(-5, -30, 0)).ToLocalTime().DateTime,
DateTimeUtil.ParseDateTimeGoLang("2010-06-21 -05:30", "2006-01-02 -07:00"));
Assert.AreEqual(new DateTime(now.Year, 9, 14, 7, 0, 0),
DateTimeUtil.ParseDateTimeGoLang("7am Sep. 14", "3pm Jan. 2"));
var refDate = new DateTime(2021, 03, 12, 12, 00, 00, DateTimeKind.Local);
Assert.AreEqual(new DateTime(refDate.Year - 1, 9, 14, 7, 0, 0),
DateTimeUtil.ParseDateTimeGoLang("7am Sep. 14", "3pm Jan. 2", relativeFrom:refDate));
// bad cases
try

View File

@@ -311,6 +311,7 @@ namespace Jackett.Updater
"Definitions/exoticaz.yml", // migrated to C#
"Definitions/extratorrent-ag.yml",
"Definitions/extratorrentclone.yml",
"Definitions/extremlymtorrents.yml",
"Definitions/filmsclub.yml",
"Definitions/freakstrackingsystem.yml",
"Definitions/freedomhd.yml",
@@ -420,6 +421,7 @@ namespace Jackett.Updater
"Definitions/thetorrents.yml",
"Definitions/theunknown.yml", // became 3evils #9678
"Definitions/tigers-dl.yml",
"Definitions/tjangto.yml",
"Definitions/tntvillage.yml",
"Definitions/topnow.yml",
"Definitions/torrentbomb.yml",
@@ -436,6 +438,7 @@ namespace Jackett.Updater
"Definitions/torrentwal.yml",
"Definitions/torrentwtf.yml",
"Definitions/torrentz2.yml",
"Definitions/torrentz2k.yml",
"Definitions/torrof.yml",
"Definitions/torviet.yml",
"Definitions/tspate.yml",