diff --git a/src/Jackett.Common/Definitions/mteamtp.yml b/src/Jackett.Common/Definitions/mteamtp.yml
deleted file mode 100644
index 7960aa2dc..000000000
--- a/src/Jackett.Common/Definitions/mteamtp.yml
+++ /dev/null
@@ -1,222 +0,0 @@
----
-id: mteamtp
-name: M-Team - TP
-description: "M-Team TP (MTTP) is a CHINESE Private Torrent Tracker for HD MOVIES / TV / 3X"
-language: zh-CN
-type: private
-encoding: UTF-8
-requestDelay: 5
-links:
- - https://kp.m-team.cc/
- - https://tp.m-team.cc/
- - https://pt.m-team.cc/
-
-caps:
- categorymappings:
- - {id: 401, cat: Movies/SD, desc: "Movie(電影)/SD", default: true}
- - {id: 419, cat: Movies/HD, desc: "Movie(電影)/HD", default: true}
- - {id: 420, cat: Movies/DVD, desc: "Movie(電影)/DVDiSo", default: true}
- - {id: 421, cat: Movies/BluRay, desc: "Movie(電影)/Blu-Ray", default: true}
- - {id: 439, cat: Movies/Other, desc: "Movie(電影)/Remux", default: true}
- - {id: 403, cat: TV/SD, desc: "TV Series(影劇/綜藝)/SD", default: true}
- - {id: 402, cat: TV/HD, desc: "TV Series(影劇/綜藝)/HD", default: true}
- - {id: 435, cat: TV/SD, desc: "TV Series(影劇/綜藝)/DVDiSo", default: true}
- - {id: 438, cat: TV/HD, desc: "TV Series(影劇/綜藝)/BD", default: true}
- - {id: 404, cat: TV/Documentary, desc: "紀錄教育", default: true}
- - {id: 405, cat: TV/Anime, desc: "Anime(動畫)", default: true}
- - {id: 407, cat: TV/Sport, desc: "Sports(運動)", default: true}
- - {id: 422, cat: PC/0day, desc: "Software(軟體)", default: true}
- - {id: 423, cat: PC/Games, desc: "PCGame(PC遊戲)", default: true}
- - {id: 427, cat: Books, desc: "eBook(電子書)", default: true}
- - {id: 409, cat: Other, desc: "Misc(其他)", default: true}
- # music
- - {id: 406, cat: Audio/Video, desc: "MV(演唱)", default: true}
- - {id: 408, cat: Audio/Other, desc: "Music(AAC/ALAC)", default: true}
- - {id: 434, cat: Audio, desc: "Music(無損)", default: true}
- # adult
- - {id: 410, cat: XXX, desc: "AV(有碼)/HD Censored", default: false}
- - {id: 429, cat: XXX, desc: "AV(無碼)/HD Uncensored", default: false}
- - {id: 424, cat: XXX, desc: "AV(有碼)/SD Censored", default: false}
- - {id: 430, cat: XXX, desc: "AV(無碼)/SD Uncensored", default: false}
- - {id: 426, cat: XXX, desc: "AV(無碼)/DVDiSo Uncensored", default: false}
- - {id: 437, cat: XXX, desc: "AV(有碼)/DVDiSo Censored", default: false}
- - {id: 431, cat: XXX, desc: "AV(有碼)/Blu-Ray Censored", default: false}
- - {id: 432, cat: XXX, desc: "AV(無碼)/Blu-Ray Uncensored", default: false}
- - {id: 436, cat: XXX, desc: "AV(網站)/0Day", default: false}
- - {id: 425, cat: XXX, desc: "IV(寫真影集)/Video Collection", default: false}
- - {id: 433, cat: XXX, desc: "IV(寫真圖集)/Picture Collection", default: false}
- - {id: 411, cat: XXX, desc: "H-Game(遊戲)", default: false}
- - {id: 412, cat: XXX, desc: "H-Anime(動畫)", default: false}
- - {id: 413, cat: XXX, desc: "H-Comic(漫畫)", default: false}
-
- modes:
- search: [q]
- tv-search: [q, season, ep, imdbid]
- movie-search: [q, imdbid]
- music-search: [q]
- book-search: [q]
-
-settings:
- - name: username
- type: text
- label: Username
- - name: password
- type: password
- label: Password
- - name: freeleech
- type: checkbox
- label: Search freeleech only
- default: false
- - name: sort
- type: select
- label: Sort requested from site
- default: 4
- options:
- 4: created
- 7: seeders
- 5: size
- 1: title
- - name: type
- type: select
- label: Order requested from site
- default: desc
- options:
- desc: desc
- asc: asc
- - name: info_tpp
- type: info
- label: Results Per Page
- default: For best results, change the Torrents per page: setting to 100 on your account profile.
- - name: info_title
- type: info
- label: About Titles
- default: For best results, disable the torrent name tooltip in User CP/Tracker Settings/Torrents Page. Otherwise long release names will be cut off.
- - name: info_download_link
- type: info
- label: About Download Links
- default: For best results, you must enable the Download icon in User CP/Tracker Settings/Torrents Page.
-
-login:
- path: takelogin.php
- method: post
- inputs:
- username: "{{ .Config.username }}"
- password: "{{ .Config.password }}"
- error:
- - selector: td.embedded:has(h2:contains("登录失败"))
- - selector: td.embedded:has(h2:contains("failed"))
- - selector: td.toolbox:contains("錯誤")
- - selector: td.toolbox:contains("Error")
- - selector: td.toolbox:contains("限制登")
- test:
- path: index.php
- selector: a[href="logout.php"]
-
-search:
- paths:
- - path: torrents.php
- categories: [401, 419, 420, 421, 439, 403, 402, 435, 438, 404, 405, 407, 422, 423, 427, 409]
- - path: adult.php
- categories: [410, 429, 424, 430, 426, 437, 431, 432, 436, 425, 433, 411, 412, 413]
- - path: music.php
- categories: [406, 408, 434]
- allowEmptyInputs: true
- inputs:
- $raw: "{{ range .Categories }}cat{{.}}=1&{{end}}"
- search: "{{ if .Query.IMDBID }}{{ .Query.IMDBID }}{{ else }}{{ .Keywords }}{{ end }}"
- # 0 incldead, 1 active, 2 dead
- incldead: 0
- # 0 all, 1 normal, 2 free, 3 2x, 4 2xfree, 5 50%, 6 2x50%, 7 30%
- spstate: "{{ if .Config.freeleech }}2{{ else }}0{{ end }}"
- # 0 title, 3 uploader, 4 imdb url
- search_area: "{{ if .Query.IMDBID }}4{{ else }}0{{ end }}"
- # 0 AND, 1 OR, 2 exact
- search_mode: 0
- sort: "{{ .Config.sort }}"
- type: "{{ .Config.type }}"
- notnewword: 1
-
- rows:
- selector: table.torrents > tbody > tr:has(table.torrentname)
-
- fields:
- title_default:
- # shortened for long release names
- selector: a[href^="details.php?id="] > b
- title:
- # not available if IMDB tooltips are turned on
- selector: a[title][href^="details.php?id="]
- attribute: title
- optional: true
- default: "{{ .Result.title_default }}"
- category:
- selector: a[href^="?cat="]
- attribute: href
- filters:
- - name: querystring
- args: cat
- details:
- selector: a[href^="details.php?id="]
- attribute: href
- download:
- selector: a[href^="download.php?id="]
- attribute: href
- poster:
- selector: img[alt="torrent thumbnail"][src]
- attribute: src
- filters:
- - name: replace
- args: ["pic/nopic.jpg", ""]
- imdbid:
- selector: a[href*="imdb.com/title/tt"]
- attribute: href
- size:
- selector: td.rowfollow:nth-last-child(6)
- grabs:
- selector: td.rowfollow:nth-last-child(3)
- seeders:
- selector: td.rowfollow:nth-last-child(5)
- leechers:
- selector: td.rowfollow:nth-last-child(4)
- date_added:
- selector: td.rowfollow:nth-last-child(7) > span[title]
- optional: true
- attribute: title
- filters:
- - name: append
- args: " +08:00" # CST
- - name: dateparse
- args: "yyyy-MM-dd HH:mm:ss zzz"
- date_elapsed:
- selector: td.rowfollow:nth-last-child(7):not(:has(span))
- optional: true
- filters:
- - name: append
- args: " +08:00" # CST
- - name: dateparse
- args: "yyyy-MM-ddHH:mm:ss zzz"
- date:
- text: "{{ if or .Result.date_elapsed .Result.date_added }}{{ or .Result.date_elapsed .Result.date_added }}{{ else }}now{{ end }}"
- downloadvolumefactor:
- case:
- img.pro_free: 0
- img.pro_free2up: 0
- img.pro_50pctdown: 0.5
- img.pro_50pctdown2up: 0.5
- img.pro_30pctdown: 0.3
- "*": 1
- uploadvolumefactor:
- case:
- img.pro_50pctdown2up: 2
- img.pro_free2up: 2
- img.pro_2up: 2
- "*": 1
- minimumratio:
- text: 1
- minimumseedtime:
- # 2 days (as seconds = 2 x 24 x 60 x 60)
- text: 172800
- description:
- selector: td:nth-child(2)
- remove: a, b, font, img, span
-# NexusPHP Standard v1.5 Beta 4
diff --git a/src/Jackett.Common/Definitions/mteamtp2fa.yml b/src/Jackett.Common/Definitions/mteamtp2fa.yml
deleted file mode 100644
index 10605468b..000000000
--- a/src/Jackett.Common/Definitions/mteamtp2fa.yml
+++ /dev/null
@@ -1,225 +0,0 @@
----
-id: mteamtp2fa
-name: M-Team - TP (2FA)
-description: "This indexer uses a cookie login for M-Team TP (MTTP) for those that want to use 2FA"
-language: zh-CN
-type: private
-encoding: UTF-8
-requestDelay: 5
-links:
- - https://kp.m-team.cc/
- - https://tp.m-team.cc/
- - https://pt.m-team.cc/
-
-caps:
- categorymappings:
- - {id: 401, cat: Movies/SD, desc: "Movie(電影)/SD", default: true}
- - {id: 419, cat: Movies/HD, desc: "Movie(電影)/HD", default: true}
- - {id: 420, cat: Movies/DVD, desc: "Movie(電影)/DVDiSo", default: true}
- - {id: 421, cat: Movies/BluRay, desc: "Movie(電影)/Blu-Ray", default: true}
- - {id: 439, cat: Movies/Other, desc: "Movie(電影)/Remux", default: true}
- - {id: 403, cat: TV/SD, desc: "TV Series(影劇/綜藝)/SD", default: true}
- - {id: 402, cat: TV/HD, desc: "TV Series(影劇/綜藝)/HD", default: true}
- - {id: 435, cat: TV/SD, desc: "TV Series(影劇/綜藝)/DVDiSo", default: true}
- - {id: 438, cat: TV/HD, desc: "TV Series(影劇/綜藝)/BD", default: true}
- - {id: 404, cat: TV/Documentary, desc: "紀錄教育", default: true}
- - {id: 405, cat: TV/Anime, desc: "Anime(動畫)", default: true}
- - {id: 407, cat: TV/Sport, desc: "Sports(運動)", default: true}
- - {id: 422, cat: PC/0day, desc: "Software(軟體)", default: true}
- - {id: 423, cat: PC/Games, desc: "PCGame(PC遊戲)", default: true}
- - {id: 427, cat: Books, desc: "eBook(電子書)", default: true}
- - {id: 409, cat: Other, desc: "Misc(其他)", default: true}
- # music
- - {id: 406, cat: Audio/Video, desc: "MV(演唱)", default: true}
- - {id: 408, cat: Audio/Other, desc: "Music(AAC/ALAC)", default: true}
- - {id: 434, cat: Audio, desc: "Music(無損)", default: true}
- # adult
- - {id: 410, cat: XXX, desc: "AV(有碼)/HD Censored", default: false}
- - {id: 429, cat: XXX, desc: "AV(無碼)/HD Uncensored", default: false}
- - {id: 424, cat: XXX, desc: "AV(有碼)/SD Censored", default: false}
- - {id: 430, cat: XXX, desc: "AV(無碼)/SD Uncensored", default: false}
- - {id: 426, cat: XXX, desc: "AV(無碼)/DVDiSo Uncensored", default: false}
- - {id: 437, cat: XXX, desc: "AV(有碼)/DVDiSo Censored", default: false}
- - {id: 431, cat: XXX, desc: "AV(有碼)/Blu-Ray Censored", default: false}
- - {id: 432, cat: XXX, desc: "AV(無碼)/Blu-Ray Uncensored", default: false}
- - {id: 436, cat: XXX, desc: "AV(網站)/0Day", default: false}
- - {id: 425, cat: XXX, desc: "IV(寫真影集)/Video Collection", default: false}
- - {id: 433, cat: XXX, desc: "IV(寫真圖集)/Picture Collection", default: false}
- - {id: 411, cat: XXX, desc: "H-Game(遊戲)", default: false}
- - {id: 412, cat: XXX, desc: "H-Anime(動畫)", default: false}
- - {id: 413, cat: XXX, desc: "H-Comic(漫畫)", default: false}
-
- modes:
- search: [q]
- tv-search: [q, season, ep, imdbid]
- movie-search: [q, imdbid]
- music-search: [q]
- book-search: [q]
-
-settings:
- - name: cookie
- type: text
- label: Cookie
- - name: infocookie
- type: info
- label: How to get the Cookie
- default: "
- Login to this tracker with your browser
- Open the DevTools panel by pressing F12
- Select the Network tab
- Click on the Doc button (Chrome Browser) or HTML button (FireFox)
- Refresh the page by pressing F5
- Click on the first row entry
- Select the Headers tab on the Right panel
- Find 'cookie:' in the Request Headers section
- Select and Copy the whole cookie string (everything after 'cookie: ') and Paste here.
"
- - name: useragent
- type: text
- label: User-Agent
- - name: info_useragent
- type: info
- label: How to get the User-Agent
- default: "- From the same place you fetched the cookie,
- Find 'user-agent:' in the Request Headers section
- Select and Copy the whole user-agent string (everything after 'user-agent: ') and Paste here.
"
- - name: freeleech
- type: checkbox
- label: Search freeleech only
- default: false
- - name: sort
- type: select
- label: Sort requested from site
- default: 4
- options:
- 4: created
- 7: seeders
- 5: size
- 1: title
- - name: type
- type: select
- label: Order requested from site
- default: desc
- options:
- desc: desc
- asc: asc
- - name: info_tpp
- type: info
- label: Results Per Page
- default: For best results, change the Torrents per page: setting to 100 on your account profile.
- - name: info_title
- type: info
- label: About Titles
- default: For best results, disable the torrent name tooltip in User CP/Tracker Settings/Torrents Page. Otherwise long release names will be cut off.
- - name: info_download_link
- type: info
- label: About Download Links
- default: For best results, you must enable the Download icon in User CP/Tracker Settings/Torrents Page.
-
-login:
- method: cookie
- inputs:
- cookie: "{{ .Config.cookie }}"
- test:
- path: index.php
- selector: a[href="logout.php"]
-
-search:
- paths:
- - path: torrents.php
- categories: [401, 419, 420, 421, 439, 403, 402, 435, 438, 404, 405, 407, 422, 423, 427, 409]
- - path: adult.php
- categories: [410, 429, 424, 430, 426, 437, 431, 432, 436, 425, 433, 411, 412, 413]
- - path: music.php
- categories: [406, 408, 434]
- allowEmptyInputs: true
- inputs:
- $raw: "{{ range .Categories }}cat{{.}}=1&{{end}}"
- search: "{{ if .Query.IMDBID }}{{ .Query.IMDBID }}{{ else }}{{ .Keywords }}{{ end }}"
- # 0 incldead, 1 active, 2 dead
- incldead: 0
- # 0 all, 1 normal, 2 free, 3 2x, 4 2xfree, 5 50%, 6 2x50%, 7 30%
- spstate: "{{ if .Config.freeleech }}2{{ else }}0{{ end }}"
- # 0 title, 3 uploader, 4 imdb url
- search_area: "{{ if .Query.IMDBID }}4{{ else }}0{{ end }}"
- # 0 AND, 1 OR, 2 exact
- search_mode: 0
- sort: "{{ .Config.sort }}"
- type: "{{ .Config.type }}"
- notnewword: 1
-
- headers:
- User-Agent: ["{{ .Config.useragent }}"]
-
- rows:
- selector: table.torrents > tbody > tr:has(table.torrentname)
-
- fields:
- title_default:
- # shortened for long release names
- selector: a[href^="details.php?id="] > b
- title:
- # not available if IMDB tooltips are turned on
- selector: a[title][href^="details.php?id="]
- attribute: title
- optional: true
- default: "{{ .Result.title_default }}"
- category:
- selector: a[href^="?cat="]
- attribute: href
- filters:
- - name: querystring
- args: cat
- details:
- selector: a[href^="details.php?id="]
- attribute: href
- download:
- selector: a[href^="download.php?id="]
- attribute: href
- poster:
- selector: img[alt="torrent thumbnail"][src]
- attribute: src
- filters:
- - name: replace
- args: ["pic/nopic.jpg", ""]
- imdbid:
- selector: a[href*="imdb.com/title/tt"]
- attribute: href
- size:
- selector: td.rowfollow:nth-last-child(6)
- grabs:
- selector: td.rowfollow:nth-last-child(3)
- seeders:
- selector: td.rowfollow:nth-last-child(5)
- leechers:
- selector: td.rowfollow:nth-last-child(4)
- date_added:
- selector: td.rowfollow:nth-last-child(7) > span[title]
- optional: true
- attribute: title
- filters:
- - name: append
- args: " +08:00" # CST
- - name: dateparse
- args: "yyyy-MM-dd HH:mm:ss zzz"
- date_elapsed:
- selector: td.rowfollow:nth-last-child(7):not(:has(span))
- optional: true
- filters:
- - name: append
- args: " +08:00" # CST
- - name: dateparse
- args: "yyyy-MM-ddHH:mm:ss zzz"
- date:
- text: "{{ if or .Result.date_elapsed .Result.date_added }}{{ or .Result.date_elapsed .Result.date_added }}{{ else }}now{{ end }}"
- downloadvolumefactor:
- case:
- img.pro_free: 0
- img.pro_free2up: 0
- img.pro_50pctdown: 0.5
- img.pro_50pctdown2up: 0.5
- img.pro_30pctdown: 0.3
- "*": 1
- uploadvolumefactor:
- case:
- img.pro_50pctdown2up: 2
- img.pro_free2up: 2
- img.pro_2up: 2
- "*": 1
- minimumratio:
- text: 1
- minimumseedtime:
- # 2 days (as seconds = 2 x 24 x 60 x 60)
- text: 172800
- description:
- selector: td:nth-child(2)
- remove: a, b, font, img, span
-# NexusPHP Standard v1.5 Beta 4
diff --git a/src/Jackett.Common/Indexers/MTeamTp.cs b/src/Jackett.Common/Indexers/MTeamTp.cs
new file mode 100644
index 000000000..45a7ecc1e
--- /dev/null
+++ b/src/Jackett.Common/Indexers/MTeamTp.cs
@@ -0,0 +1,367 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Jackett.Common.Extensions;
+using Jackett.Common.Models;
+using Jackett.Common.Models.IndexerConfig.Bespoke;
+using Jackett.Common.Serializer;
+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;
+
+namespace Jackett.Common.Indexers
+{
+ [ExcludeFromCodeCoverage]
+ public class MTeamTp : IndexerBase
+ {
+ public override string Id => "mteamtp";
+ public override string Name => "M-Team - TP";
+ public override string Description => "M-Team TP (MTTP) is a CHINESE Private Torrent Tracker for HD MOVIES / TV / 3X";
+ public override string SiteLink { get; protected set; } = "https://kp.m-team.cc/";
+ public override string[] AlternativeSiteLinks => new[]
+ {
+ "https://kp.m-team.cc/",
+ "https://tp.m-team.cc/",
+ "https://pt.m-team.cc/"
+ };
+ public override string Language => "zh-CN";
+ public override string Type => "private";
+
+ public override TorznabCapabilities TorznabCaps => SetCapabilities();
+
+ private readonly int[] _trackerAdultCategories = { 410, 429, 424, 430, 426, 437, 431, 432, 436, 425, 433, 411, 412, 413, 440 };
+
+ private new ConfigurationDataMTeamTp configData => (ConfigurationDataMTeamTp)base.configData;
+
+ public MTeamTp(IIndexerConfigurationService configService, WebClient client, Logger logger, IProtectionService p, ICacheService cs)
+ : base(configService: configService,
+ client: client,
+ logger: logger,
+ p: p,
+ cacheService: cs,
+ configData: new ConfigurationDataMTeamTp())
+ {
+ webclient.requestDelay = 5;
+ }
+
+ private static TorznabCapabilities SetCapabilities()
+ {
+ var caps = new TorznabCapabilities
+ {
+ TvSearchParams = new List
+ {
+ TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
+ },
+ MovieSearchParams = new List
+ {
+ MovieSearchParam.Q, MovieSearchParam.ImdbId
+ },
+ MusicSearchParams = new List
+ {
+ MusicSearchParam.Q
+ },
+ BookSearchParams = new List
+ {
+ BookSearchParam.Q
+ }
+ };
+
+ caps.Categories.AddCategoryMapping(401, TorznabCatType.MoviesSD, "Movie(電影)/SD");
+ caps.Categories.AddCategoryMapping(419, TorznabCatType.MoviesHD, "Movie(電影)/HD");
+ caps.Categories.AddCategoryMapping(420, TorznabCatType.MoviesDVD, "Movie(電影)/DVDiSo");
+ caps.Categories.AddCategoryMapping(421, TorznabCatType.MoviesBluRay, "Movie(電影)/Blu-Ray");
+ caps.Categories.AddCategoryMapping(439, TorznabCatType.MoviesHD, "Movie(電影)/Remux");
+ caps.Categories.AddCategoryMapping(403, TorznabCatType.TVSD, "TV Series(影劇/綜藝)/SD");
+ caps.Categories.AddCategoryMapping(402, TorznabCatType.TVHD, "TV Series(影劇/綜藝)/HD");
+ caps.Categories.AddCategoryMapping(435, TorznabCatType.TVSD, "TV Series(影劇/綜藝)/DVDiSo");
+ caps.Categories.AddCategoryMapping(438, TorznabCatType.TVHD, "TV Series(影劇/綜藝)/BD");
+ caps.Categories.AddCategoryMapping(404, TorznabCatType.TVDocumentary, "紀錄教育");
+ caps.Categories.AddCategoryMapping(405, TorznabCatType.TVAnime, "Anime(動畫)");
+ caps.Categories.AddCategoryMapping(407, TorznabCatType.TVSport, "Sports(運動)");
+ caps.Categories.AddCategoryMapping(422, TorznabCatType.PC0day, "Software(軟體)");
+ caps.Categories.AddCategoryMapping(423, TorznabCatType.PCGames, "PCGame(PC遊戲)");
+ caps.Categories.AddCategoryMapping(427, TorznabCatType.Books, "eBook(電子書)");
+ caps.Categories.AddCategoryMapping(409, TorznabCatType.Other, "Misc(其他)");
+
+ // music
+ caps.Categories.AddCategoryMapping(406, TorznabCatType.AudioVideo, "MV(演唱)");
+ caps.Categories.AddCategoryMapping(408, TorznabCatType.AudioOther, "Music(AAC/ALAC)");
+ caps.Categories.AddCategoryMapping(434, TorznabCatType.Audio, "Music(無損)");
+
+ // adult
+ caps.Categories.AddCategoryMapping(410, TorznabCatType.XXX, "AV(有碼)/HD Censored");
+ caps.Categories.AddCategoryMapping(429, TorznabCatType.XXX, "AV(無碼)/HD Uncensored");
+ caps.Categories.AddCategoryMapping(424, TorznabCatType.XXXSD, "AV(有碼)/SD Censored");
+ caps.Categories.AddCategoryMapping(430, TorznabCatType.XXXSD, "AV(無碼)/SD Uncensored");
+ caps.Categories.AddCategoryMapping(426, TorznabCatType.XXXDVD, "AV(無碼)/DVDiSo Uncensored");
+ caps.Categories.AddCategoryMapping(437, TorznabCatType.XXXDVD, "AV(有碼)/DVDiSo Censored");
+ caps.Categories.AddCategoryMapping(431, TorznabCatType.XXX, "AV(有碼)/Blu-Ray Censored");
+ caps.Categories.AddCategoryMapping(432, TorznabCatType.XXX, "AV(無碼)/Blu-Ray Uncensored");
+ caps.Categories.AddCategoryMapping(436, TorznabCatType.XXX, "AV(網站)/0Day");
+ caps.Categories.AddCategoryMapping(425, TorznabCatType.XXX, "IV(寫真影集)/Video Collection");
+ caps.Categories.AddCategoryMapping(433, TorznabCatType.XXXImageSet, "IV(寫真圖集)/Picture Collection");
+ caps.Categories.AddCategoryMapping(411, TorznabCatType.XXX, "H-Game(遊戲)");
+ caps.Categories.AddCategoryMapping(412, TorznabCatType.XXX, "H-Anime(動畫)");
+ caps.Categories.AddCategoryMapping(413, TorznabCatType.XXX, "H-Comic(漫畫)");
+ caps.Categories.AddCategoryMapping(440, TorznabCatType.XXX, "AV(Gay)/HD");
+
+ return caps;
+ }
+
+ public override async Task ApplyConfiguration(JToken configJson)
+ {
+ LoadValuesFromJson(configJson);
+
+ if (configData.ApiKey.Value.IsNullOrWhiteSpace())
+ {
+ throw new Exception("Missing API Key.");
+ }
+
+ var releases = await PerformQuery(new TorznabQuery());
+
+ await ConfigureIfOK(string.Empty, releases.Any(),
+ () => throw new Exception("Could not find releases."));
+
+ return IndexerConfigurationStatus.Completed;
+ }
+
+ public override async Task Download(Uri link)
+ {
+ var response = await RequestWithCookiesAsync(
+ link.ToString(),
+ method: RequestType.POST,
+ headers: new Dictionary
+ {
+ { "Accept", "application/json" },
+ { "x-api-key", configData.ApiKey.Value }
+ });
+
+ if (!STJson.TryDeserialize(response.ContentString, out var jsonResponse))
+ {
+ throw new Exception("Invalid response received from M-Team, not a valid JSON");
+ }
+
+ if (jsonResponse.Data.IsNullOrWhiteSpace())
+ {
+ throw new Exception($"Unable to find download link for: {link}");
+ }
+
+ return await base.Download(new Uri(jsonResponse.Data));
+ }
+
+ protected override async Task> PerformQuery(TorznabQuery query)
+ {
+ var releases = new List();
+
+ var categoryMapping = MapTorznabCapsToTrackers(query).Select(int.Parse).Distinct().ToList();
+
+ var adultCategories = categoryMapping.Where(c => _trackerAdultCategories.Contains(c)).ToList();
+ var normalCategories = categoryMapping.Except(adultCategories).ToList();
+
+ if (!categoryMapping.Any() || normalCategories.Any())
+ {
+ releases.AddRange(await FetchTrackerReleasesAsync(MTeamTpRequestType.Normal, query, normalCategories));
+ }
+
+ if (adultCategories.Any())
+ {
+ releases.AddRange(await FetchTrackerReleasesAsync(MTeamTpRequestType.Adult, query, adultCategories));
+ }
+
+ return releases
+ .OrderByDescending(o => o.PublishDate)
+ .ToArray();
+ }
+
+ private async Task> FetchTrackerReleasesAsync(MTeamTpRequestType requestType, TorznabQuery query, IEnumerable categories)
+ {
+ var releases = new List();
+
+ var searchQuery = new MTeamTpApiSearchQuery
+ {
+ Mode = requestType,
+ Categories = categories?.Select(x => x.ToString()).ToArray() ?? Array.Empty(),
+ PageNumber = 1,
+ PageSize = 100
+ };
+
+ if (query.ImdbID.IsNotNullOrWhiteSpace())
+ {
+ searchQuery.Imdb = query.ImdbID.Trim();
+ }
+
+ var searchTerm = query.GetQueryString();
+
+ if (searchTerm.IsNotNullOrWhiteSpace())
+ {
+ searchQuery.Keyword = searchTerm;
+ }
+
+ if (configData.FreeleechOnly.Value)
+ {
+ searchQuery.Discount = "FREE";
+ }
+
+ var response = await RequestWithCookiesAndRetryAsync(
+ $"{SiteLink.TrimEnd('/')}/api/torrent/search",
+ method: RequestType.POST,
+ rawbody: STJson.ToJson(searchQuery),
+ headers: new Dictionary
+ {
+ { "Accept", "application/json" },
+ { "Content-Type", "application/json" },
+ { "x-api-key", configData.ApiKey.Value }
+ });
+
+ if (response.Status != HttpStatusCode.OK)
+ {
+ throw new Exception($"Unknown status code: {(int)response.Status} ({response.Status})");
+ }
+
+ if (!STJson.TryDeserialize(response.ContentString, out var jsonResponse))
+ {
+ throw new Exception("Invalid response received from M-Team, not a valid JSON");
+ }
+
+ if (jsonResponse?.Data?.Torrents == null)
+ {
+ return releases;
+ }
+
+ foreach (var torrent in jsonResponse.Data.Torrents)
+ {
+ var torrentId = int.Parse(torrent.Id);
+ var infoUrl = new Uri($"{SiteLink.TrimEnd('/')}/detail/{torrentId}");
+ var downloadUrl = new Uri($"{SiteLink.TrimEnd('/')}/api/torrent/genDlToken?id={torrentId}");
+
+ var release = new ReleaseInfo
+ {
+ Guid = infoUrl,
+ Title = CleanTitle(torrent.Name),
+ Details = infoUrl,
+ Link = downloadUrl,
+ Category = MapTrackerCatToNewznab(torrent.Category),
+ Description = torrent.Description,
+ Files = int.Parse(torrent.NumFiles),
+ Size = long.Parse(torrent.Size),
+ Grabs = int.Parse(torrent.Status.TimesCompleted),
+ Seeders = int.Parse(torrent.Status.Seeders),
+ Peers = int.Parse(torrent.Status.Seeders) + int.Parse(torrent.Status.Leechers),
+ DownloadVolumeFactor = torrent.Status.Discount.ToUpperInvariant() switch
+ {
+ "FREE" => 0,
+ "_2X_FREE" => 0,
+ "PERCENT_50" => 0.5,
+ "_2X_PERCENT_50" => 0.5,
+ "PERCENT_70" => 0.3,
+ _ => 1
+ },
+ UploadVolumeFactor = torrent.Status.Discount.ToUpperInvariant() switch
+ {
+ "_2X_FREE" => 2,
+ "_2X_PERCENT_50" => 2,
+ _ => 1
+ },
+ MinimumRatio = 1,
+ MinimumSeedTime = 172800 // 2 days
+ };
+
+ if (torrent.Imdb.IsNotNullOrWhiteSpace())
+ {
+ release.Imdb = ParseUtil.GetImdbId(torrent.Imdb.Split('/').LastOrDefault()).GetValueOrDefault();
+ }
+
+ if (torrent.Status?.CreatedDate != null &&
+ DateTime.TryParseExact($"{torrent.Status.CreatedDate} +08:00", "yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var publishDate))
+ {
+ release.PublishDate = publishDate;
+ }
+
+ releases.Add(release);
+ }
+
+ return releases;
+ }
+
+ private static string CleanTitle(string title)
+ {
+ title = Regex.Replace(title, @"\s+", " ", RegexOptions.Compiled);
+
+ return title.Trim();
+ }
+ }
+
+ internal enum MTeamTpRequestType
+ {
+ Normal,
+ Adult
+ }
+
+ internal class MTeamTpApiSearchQuery
+ {
+ [JsonProperty(Required = Required.Always)]
+ public MTeamTpRequestType Mode { get; set; }
+
+ [JsonProperty(Required = Required.Always)]
+ public IEnumerable Categories { get; set; }
+
+ public string Discount { get; set; }
+ public string Imdb { get; set; }
+ public string Keyword { get; set; }
+ public int? PageNumber { get; set; }
+ public int? PageSize { get; set; }
+ }
+
+ internal class MTeamTpApiResponse
+ {
+ public MTeamTpApiData Data { get; set; }
+ }
+
+ internal class MTeamTpApiData
+ {
+ [JsonPropertyName("data")]
+ public IReadOnlyCollection Torrents { get; set; }
+ }
+
+ internal class MTeamTpApiTorrent
+ {
+ public string Id { get; set; }
+ public string Name { get; set; }
+
+ [JsonPropertyName("smallDescr")]
+ public string Description { get; set; }
+
+ public string Category { get; set; }
+
+ [JsonPropertyName("numfiles")]
+ public string NumFiles { get; set; }
+
+ public string Imdb { get; set; }
+ public string Size { get; set; }
+ public MTeamTpApiReleaseStatus Status { get; set; }
+ }
+
+ internal class MTeamTpApiReleaseStatus
+ {
+ public string CreatedDate { get; set; }
+ public string Discount { get; set; }
+ public string TimesCompleted { get; set; }
+ public string Seeders { get; set; }
+ public string Leechers { get; set; }
+ }
+
+ internal class MTeamTpApiDownloadTokenResponse
+ {
+ public string Data { get; set; }
+ }
+}
diff --git a/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataMTeamTp.cs b/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataMTeamTp.cs
new file mode 100644
index 000000000..a14fda2df
--- /dev/null
+++ b/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataMTeamTp.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Jackett.Common.Models.IndexerConfig.Bespoke
+{
+ [ExcludeFromCodeCoverage]
+ internal class ConfigurationDataMTeamTp : ConfigurationData
+ {
+ public StringConfigurationItem ApiKey { get; private set; }
+ public BoolConfigurationItem FreeleechOnly { get; private set; }
+
+ public ConfigurationDataMTeamTp()
+ {
+ ApiKey = new StringConfigurationItem("API Key");
+ FreeleechOnly = new BoolConfigurationItem("Search freeleech only") { Value = false };
+ }
+ }
+}
diff --git a/src/Jackett.Common/Serializer/STJson.cs b/src/Jackett.Common/Serializer/STJson.cs
index e87acaf9f..b087264d6 100644
--- a/src/Jackett.Common/Serializer/STJson.cs
+++ b/src/Jackett.Common/Serializer/STJson.cs
@@ -47,5 +47,10 @@ namespace Jackett.Common.Serializer
return false;
}
}
+
+ public static string ToJson(object obj)
+ {
+ return JsonSerializer.Serialize(obj, _SerializerSettings);
+ }
}
}
diff --git a/src/Jackett.Updater/Program.cs b/src/Jackett.Updater/Program.cs
index 7707604ef..d4325c8dd 100644
--- a/src/Jackett.Updater/Program.cs
+++ b/src/Jackett.Updater/Program.cs
@@ -499,6 +499,8 @@ namespace Jackett.Updater
"Definitions/moviesite.yml",
"Definitions/movietorrent.yml", // will need c# #11284
"Definitions/moviezone.yml", // migrated to teracod #9743
+ "Definitions/mteamtp.yml", // migrated to C# (API)
+ "Definitions/mteamtp2fa.yml", // migrated to C# (API)
"Definitions/music-master.yml",
"Definitions/muziekfabriek.yml",
"Definitions/nachtwerk.yml",