mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-13 07:24:08 +02:00
Compare commits
52 Commits
v0.17.934
...
v0.17.1036
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c2797e132e | ||
![]() |
d34dbcb626 | ||
![]() |
6740c7c40f | ||
![]() |
17fc2d50cf | ||
![]() |
77af202e2c | ||
![]() |
cdbe24dfdf | ||
![]() |
7983bc9a57 | ||
![]() |
381e674ac4 | ||
![]() |
921093934f | ||
![]() |
ca3466050c | ||
![]() |
993116c96f | ||
![]() |
98dad4c169 | ||
![]() |
7789a72ffb | ||
![]() |
4de2124b98 | ||
![]() |
00c1ffa8c6 | ||
![]() |
c1cbede92f | ||
![]() |
52be410655 | ||
![]() |
0b630cee7c | ||
![]() |
86b369ef1e | ||
![]() |
1310206d9e | ||
![]() |
de6c365865 | ||
![]() |
cbd14c2b2d | ||
![]() |
324abe94a3 | ||
![]() |
0126e20984 | ||
![]() |
22ef17fe5b | ||
![]() |
be64500580 | ||
![]() |
47a4f0f422 | ||
![]() |
db6a8d89a8 | ||
![]() |
483a72babd | ||
![]() |
7a94b8809e | ||
![]() |
dd894ed267 | ||
![]() |
f808a071da | ||
![]() |
2d207a482d | ||
![]() |
38849f57f8 | ||
![]() |
ce2e7d8d1a | ||
![]() |
72585f9761 | ||
![]() |
c862fabeb4 | ||
![]() |
5894372c49 | ||
![]() |
d9ed8b981d | ||
![]() |
86f185d345 | ||
![]() |
9e9c56e4c9 | ||
![]() |
853179fd72 | ||
![]() |
0dd5026cb1 | ||
![]() |
35232b1a5c | ||
![]() |
49a507f6a0 | ||
![]() |
ce527439f2 | ||
![]() |
1e07a196df | ||
![]() |
d8d88962c6 | ||
![]() |
5ac8095741 | ||
![]() |
b2c0cb6ca9 | ||
![]() |
62de0458e5 | ||
![]() |
e1aa849315 |
5
.github/ISSUE_TEMPLATE.md
vendored
5
.github/ISSUE_TEMPLATE.md
vendored
@@ -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.
|
||||
|
||||
|
16
README.md
16
README.md
@@ -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
|
||||
|
@@ -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
@@ -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>
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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 }} "
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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"}
|
||||
|
@@ -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: ""
|
||||
|
@@ -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:
|
||||
|
@@ -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]
|
||||
|
@@ -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
|
||||
|
@@ -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]
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
@@ -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:
|
||||
|
@@ -7,6 +7,7 @@ type: public
|
||||
encoding: UTF-8
|
||||
links:
|
||||
- https://www.toros.co/
|
||||
- https://toros.nocensor.space/
|
||||
|
||||
caps:
|
||||
categorymappings:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
@@ -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:
|
||||
|
@@ -8,6 +8,7 @@ encoding: UTF-8
|
||||
links:
|
||||
- https://yourbittorrent.com/
|
||||
- https://yourbittorrent2.com/
|
||||
- https://yourbittorrent.nocensor.space/
|
||||
legacylinks:
|
||||
- https://yourbittorrent.host/
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
276
src/Jackett.Common/Indexers/BeyondHDAPI.cs
Normal file
276
src/Jackett.Common/Indexers/BeyondHDAPI.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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/"
|
||||
};
|
||||
|
||||
|
@@ -51,7 +51,7 @@ namespace Jackett.Common.Indexers
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
|
@@ -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, @"<[^>]+>| ", "").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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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/"
|
||||
};
|
||||
|
||||
|
@@ -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" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
{
|
||||
|
@@ -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
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user