Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var AGSVPTDefinition = &v2.SiteDefinition{ ID: "agsvpt", Name: "AGSVPT", Aka: []string{"末日种子库", "AGSV"}, Description: "Arctic Global Seed Vault", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.agsvpt.com/"}, FaviconURL: "https://www.agsvpt.com/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 72, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/index.php", ResponseType: "document", }, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{ URL: "/userdetails.php", ResponseType: "document", }, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "messageCount", }, }, { RequestConfig: v2.RequestConfig{ URL: "/mybonus.php", ResponseType: "document", }, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上[传傳]量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下[载載]量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio)</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('冰晶') + td", "td.rowhead:contains('Bonus') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{ "#outer td[rowspan]", "div:contains('你当前每小时能获取')", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "messageCount": { Text: "0", Selector: []string{"td[style*='background: red'] a[href*='messages.php']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: agsvptLevelRequirements, }
var AudiencesDefinition = &v2.SiteDefinition{ ID: "audiences", Name: "Audiences", Aka: []string{"audiences.me", "AD"}, Description: "Audiences 私人影视站", Schema: v2.SchemaNexusPHP, URLs: []string{"https://audiences.me/"}, FaviconURL: "https://audiences.me/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "h1 .nowrap b"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('Transfer') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('Transfer') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio)(?:</strong>)?[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:爆米花|爆米花系统)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span[style*='line-height']", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", HRIcon: "img.hitandrun, img[alt*='H&R'], img[title*='H&R']", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font.twoupfree, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var BTSchoolDefinition = &v2.SiteDefinition{ ID: "btschool", Name: "BTSCHOOL", Aka: []string{"学校", "BTS"}, Description: "汇聚每一个人的影响力", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.btschool.club/"}, LegacyURLs: []string{"https://pt.btschool.net/"}, FaviconURL: "https://pt.btschool.club/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 20, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: btschoolLevelRequirements, }
var BYRPTDefinition = &v2.SiteDefinition{ ID: "byrpt", Name: "BYRPT", Aka: []string{"BYR", "北邮人"}, Description: "BYRPT 教育网综合资源站", Schema: v2.SchemaNexusPHP, URLs: []string{"https://byr.pt/"}, FaviconURL: "https://byr.pt/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"td.navbar-user-data a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"td.navbar-user-data a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", ".navbar-user-data", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", ".navbar-user-data", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", ".navbar-user-data", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"td.rowhead:contains('魔力值') + td", "td.navbar-user-data", ".navbar-user-data", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)(?:魔力值|Bonus).*?:\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{".navbar-user-data", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`arrowup[^>]*></div>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{".navbar-user-data", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`arrowdown[^>]*></div>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type", Size: "td.rowfollow:nth-child(6)", Seeders: "td.rowfollow:nth-child(7)", Leechers: "td.rowfollow:nth-child(8)", Snatched: "td.rowfollow:nth-child(9)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(2) a.cat-link", UploadTime: "td.rowfollow:nth-child(5)", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, "pro_free": v2.DiscountFree, "pro_50pctdown": v2.DiscountPercent50, }, DiscountSelector: "h1 font[class], h1 img[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var BaoziDefinition = &v2.SiteDefinition{ ID: "baozi", Name: "T-Baozi", Aka: []string{"包子", "Baozi", "T-包子"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://p.t-baozi.cc/"}, FaviconURL: "https://p.t-baozi.cc/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"uploaded", "downloaded", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*[::]\s*</span>\s*</a>\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var CarPTDefinition = &v2.SiteDefinition{ ID: "carpt", Name: "CarPT", Aka: []string{"车PT"}, Description: "以车载音乐与影视资源为主的 NexusPHP PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://carpt.net/"}, FaviconURL: "https://carpt.net/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var CrabPTDefinition = &v2.SiteDefinition{ ID: "crabpt", Name: "CrabPT", Aka: []string{"蟹黄堡", "蟹堡", "蟹PT"}, Description: "蟹黄堡 — 综合性 NexusPHP PT 站点(crabpt.vip)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://crabpt.vip/"}, FaviconURL: "https://crabpt.vip/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var CsptDefinition = &v2.SiteDefinition{ ID: "cspt", Name: "财神", Description: "综合性 PT 站点(NexusPHP + Tailwind 皮肤)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://cspt.top/"}, FaviconURL: "https://cspt.top/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "trueDownloaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "span.user-id a[href*='userdetails.php'][class*='Name']", "#user-info-pannl a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "span.user-id a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php'][class*='Name']", }, }, "seeding": { Selector: []string{"div.menu-base-info"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)当前做种</title>.*?<a[^>]*>\s*([\d,]+)\s*</a>`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"div.menu-base-info"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)正在下载</title>.*?<a[^>]*>\s*([\d,]+)\s*</a>`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{ "div.menu-base-info a[href*='mybonus.php']", "td.rowhead:contains('金元宝') + td", "td.rowhead:contains('魔力值') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "div.torrent-table-sub-info:has(a[href*='details.php'])", Title: "a.torrent-info-text-name", TitleLink: "a.torrent-info-text-name", Subtitle: "div.torrent-info-text-small_name", Size: "div.torrent-info-text-size", Seeders: "div.torrent-info-text-seeders", Leechers: "div.torrent-info-text-leechers", Snatched: "div.torrent-info-text-finished", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DiscountEndTime: "div.torrent-title font[color] span[title]", DownloadLink: "div.torrent-manage a[href*='download.php']", Category: "div.torrent-cat img[alt]", UploadTime: "div.torrent-info-text-added span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php'], td.rowhead:contains('行为') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 font[color] span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(TiB|GiB|MiB|KiB|TB|GB|MB|KB)`, }, }
CsptDefinition 描述财神(cspt.top)站点。
站点内核为标准 NexusPHP,但套用了 Tailwind 皮肤,因此呈现出「列表页 div、详情页 table」 的混合结构,是目前唯一需要 div 版行选择器的站点:
- torrents.php 的种子列表完全由 div 组成:外层 div.torrents 内每行是 div.torrent-table-sub-info,其下并列 div.torrent-cat(分类)、div.torrent-title (标题/副标题/评分/标签)、包裹 div.torrent-info 的统计列以及 div.torrent-manage (收藏/下载)。表头行是另一个 div(bg-[main_tittle]),同样带 torrent-cat / torrent-title / torrent-info,但**不带** torrent-table-sub-info,因此行选择器 必须锚定 torrent-table-sub-info,并额外用 :has(a[href*='details.php']) 兜底。
- 统计列有稳定的语义类名(torrent-info-text-size / -seeders / -leechers / -finished / -added),优先用类名而非 nth-child,避免站点调整列序后失配。
- details.php 仍是常规 table 布局,且保留 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域,因此详情解析与 btschool/eastgame 一致。
- 站点没有 #info_block(源码里被注释掉了),顶部个人信息改为 div.menu-base-info, 做种/下载数藏在 svg <title> 之后的 <a> 里,只能用 html + 正则取值。
- 魔力值在本站叫「金元宝」,userdetails.php 的行标签同名。
var CyanBugDefinition = &v2.SiteDefinition{ ID: "cyanbug", Name: "CyanBug", Aka: []string{"大青虫", "青虫"}, Description: "大青虫 — 综合性 NexusPHP PT 站点(cyanbug.net)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://cyanbug.net/"}, FaviconURL: "https://cyanbug.net/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var DiscFanDefinition = &v2.SiteDefinition{ ID: "discfan", Name: "DiscFan", Aka: []string{"碟粉"}, Description: "以华语影视为主的综合 PT 站点(繁体界面)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://discfan.net/"}, FaviconURL: "https://discfan.net/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "trueDownloaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上傳量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下載量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "trueUploaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>實際上傳量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>實際下載量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{ "table.torrentname td.embedded:has(a[href*='details.php'])", "table.torrentname td.embedded", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)<br\s*/?>(?:\s*<span[^>]*>[^<]*</span>)*\s*([^<]+)`}}, {Name: "replace", Args: []any{"'", "'"}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{"<", "<"}}, {Name: "replace", Args: []any{">", ">"}}, {Name: "replace", Args: []any{" ", " "}}, {Name: "replace", Args: []any{"&", "&"}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下載') + td a[href*='download.php'], td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副標題') + td, td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1#top font[class]", EndTimeSelector: "h1#top span[title]", SizeSelector: "td.rowhead:contains('基本資訊'), td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
DiscFanDefinition 描述 DiscFan(碟粉)站点。
该站点为标准 NexusPHP 架构,但整站使用**繁体中文**界面,所有标签与常见简体站点不同, 选择器需要专门适配:
- 用户详情页的传输行标签是「傳送」,既不是简体「传输」也不是繁体「傳輸」; 行内为嵌套 table 的合并单元格(分享率/上傳量/下載量/實際上傳量/實際下載量),需以 html + regex 拆分。
- 详情页体积行标签是「基本資訊」(非「基本信息」),且 ParseSizeMB 取的是标签单元格的 下一个兄弟节点,因此 SizeSelector 必须指向标签列本身。
- 搜索页副标题是 <br /> 之后的裸文本节点,前面可能还有若干语言标签 <span>, 无法用普通 CSS 选择器命中,需要走 SubtitleSelector(html + regex 跳过标签块)。
- 促销结束时间没有独立可选中的稳定元素,仅存在于促销图标的 onmouseover 提示中 (HTML 实体编码),交由驱动的 parseDiscountEndTimeFromOnmouseover 兜底解析。
var DubheDefinition = &v2.SiteDefinition{ ID: "dubhe", Name: "Dubhe", Aka: []string{"天枢", "北斗第一星"}, Description: "天枢 — 综合性 NexusPHP PT 站点(dubhe.site)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://dubhe.site/"}, FaviconURL: "https://dubhe.site/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var DuckBoobeeDefinition = &v2.SiteDefinition{ ID: "duckboobee", Name: "DuckBoobee", Aka: []string{"鸭鸭", "duckboobee.org"}, Description: "DuckBoobee — NexusPHP PT 站点(duckboobee.org)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://duckboobee.org/"}, FaviconURL: "https://duckboobee.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var EastgameDefinition = &v2.SiteDefinition{ ID: "eastgame", Name: "TLFBits", Aka: []string{"TLF", "The Last Fantasy"}, Description: "简单的分享,简单的快乐(综合性 PT 站点)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.eastgame.org/"}, FaviconURL: "https://pt.eastgame.org/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>(?:总上传量/奖励上传量/纯上传量|上传量)</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>总上传量/奖励上传量/纯上传量</strong>[::\s]*[\d.,]+\s*[KMGTP]?i?B\s*/\s*[\d.,]+\s*[KMGTP]?i?B\s*/\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{"table.torrentname td.embedded"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)<br\s*/?>(?:\s*<span[^>]*>)?\s*([^<]+)`}}, {Name: "replace", Args: []any{"'", "'"}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{"<", "<"}}, {Name: "replace", Args: []any{">", ">"}}, {Name: "replace", Args: []any{" ", " "}}, {Name: "replace", Args: []any{"&", "&"}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DiscountEndTime: "table.torrentname span[title]", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载直链') + td a[href*='download.php'], td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
EastgameDefinition 描述 TLFBits(pt.eastgame.org)站点。
站点为标准 NexusPHP 架构(BlueGene 皮肤 + tlfbits2020 分类图标),详情页保留了 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域,体积为十进制单位, 因此绝大多数选择器与 btschool 一致。仅两处需要单独适配:
- 搜索页副标题是 <br /> 之后的裸文本节点(无包裹元素),必须走 SubtitleSelector。
- userdetails.php 的「传输」行把上传量写成合并标签 <strong>总上传量/奖励上传量/纯上传量</strong>: 5.405 TB/120.00 GB/5.288 TB, btschool 的 <strong>上传量</strong> 正则无法命中,需要专用正则。
var Et8Definition = &v2.SiteDefinition{ ID: "et8", Name: "TCCF", Aka: []string{"TorrentCCF"}, Description: "综合性 PT 站点,影视 / 纪录片 / 电子学习资源较多", Schema: v2.SchemaNexusPHP, URLs: []string{"https://et8.org/"}, FaviconURL: "https://et8.org/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{"table.torrentname td.embedded"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<br\s*/?>\s*([^<]+)`}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::][^\d]*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
Et8Definition 站点 et8.org(页面 <title> 自称 TCCF,种子标题常见 [TorrentCCF首发] 前缀)。
与标准 NexusPHP 的差异:
- 种子列表多出一列「进度」,位于「完成数」之后、「发布者」之前(共 10 列); 由于该列排在 size/seeders/leechers/snatched 之后,nth-child(5~8) 不受影响。
- 副标题是 <br /> 之后的裸文本节点,无标签包裹,纯 CSS 选不中, 故改用 SubtitleSelector 取 td.embedded 的 innerHTML 后正则截取。
- 优惠剩余时间同时存在于优惠图标的 onmouseover tooltip 与单元格可见文本中, 这里不设置 DiscountEndTime,交由驱动的 onmouseover 兜底解析, 避免 CSS 选择器误命中同行的发布时间 span。
var FiveTwoPTDefinition = &v2.SiteDefinition{ ID: "52pt", Name: "52PT", Aka: []string{"我爱PT"}, Description: "低调地在这个PT校园快乐成长 快乐分享", Schema: v2.SchemaNexusPHP, URLs: []string{"https://52pt.site/"}, FaviconURL: "https://52pt.site/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 24, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上[传傳][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下[载載][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('BT时间') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "#info_block a[href*='mybonus.php']", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(?:做种数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(?:下载数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: fiveTwoPTLevelRequirements, }
var GTKPWDefinition = &v2.SiteDefinition{ ID: "gtkpw", Name: "GTKPW", Aka: []string{"PT.GTK", "pt.gtkpw.xyz", "myaltbox"}, Description: "GTKPW — NexusPHP PT 站点(pt.gtkpw.xyz / pt.gtk.pw / pt.gtk.xyz / t.myaltbox.com)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.gtkpw.xyz/", "https://pt.gtk.pw/", "https://pt.gtk.xyz/", "https://t.myaltbox.com/"}, FaviconURL: "https://pt.gtkpw.xyz/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "trueUploaded", "trueDownloaded", }, }, { RequestConfig: v2.RequestConfig{URL: "/mybonus.php", ResponseType: "document"}, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "trueUploaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('傳送') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:实际上传量|實際上傳量|實際上傳|实际上传)</strong>[\s::]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('傳送') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:实际下载量|實際下載量|實際下載|实际下载)</strong>[\s::]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "bonusPerHour": { Selector: []string{ "#outer", "body", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:你當前每小時能獲取|你当前每小时能获取|您當前每小時能獲取|您当前每小时能获取)[^\d-]{0,20}([\d.,]+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var GameGamePTDefinition = &v2.SiteDefinition{ ID: "gamegamept", Name: "GameGamePT", Aka: []string{"GGPT", "游戏PT"}, Description: "GGPT — 以游戏资源为主的 NexusPHP PT 站点(gamegamept.com)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.gamegamept.com/"}, FaviconURL: "https://www.gamegamept.com/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt", "trueUploaded", "trueDownloaded", }, }, { RequestConfig: v2.RequestConfig{URL: "/mybonus.php", ResponseType: "document"}, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "trueUploaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('傳送') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:实际上传量|實際上傳量|實際上傳|实际上传)</strong>[\s::]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('傳送') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:实际下载量|實際下載量|實際下載|实际下载)</strong>[\s::]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "bonusPerHour": { Selector: []string{ "#outer", "body", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:你當前每小時能獲取|你当前每小时能获取|您當前每小時能獲取|您当前每小时能获取)[^\d-]{0,20}([\d.,]+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var HDDolbyDefinition = &v2.SiteDefinition{ ID: "hddolby", Name: "HD Dolby", Aka: []string{"高清杜比"}, Description: "高清杜比 - 需要同时配置 Cookie 和 RSS Key。RSS Key 用于搜索和下载,Cookie 用于获取时魔等信息", Schema: v2.SchemaHDDolby, AuthMethod: v2.AuthMethodCookieAndAPIKey, URLs: []string{"https://www.hddolby.com/"}, LegacyURLs: []string{"https://hddolby.com/"}, FaviconURL: "https://www.hddolby.com/favicon.ico", Unavailable: false, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 3, RateWindow: time.Hour, RateWindowLimit: 50, LevelRequirements: []v2.SiteLevelRequirement{ { ID: 1, Name: "User", Privilege: "新用户的默认级别。", }, { ID: 2, Name: "Power User", Interval: "P2W", Downloaded: "120GB", Ratio: 2.0, SeedingBonus: 60000, Privilege: "得到0个邀请名额;可以直接发布种子;可以查看NFO文档;可以查看用户列表;可以请求续种;可以查看排行榜;可以查看其它用户的种子历史;可以删除自己上传的字幕。", }, { ID: 3, Name: "(连长)Elite User", NameAka: []string{"Elite User"}, Interval: "P4W", Downloaded: "256GB", Ratio: 2.5, SeedingBonus: 120000, Privilege: "Elite User及以上用户封存账号后不会被删除。", }, { ID: 4, Name: "Crazy User", Interval: "P8W", Downloaded: "512GB", Ratio: 3.0, SeedingBonus: 240000, Privilege: "得到0个邀请名额;可以在做种/下载/发布的时候选择匿名模式。", }, { ID: 5, Name: "Insane User", Interval: "P12W", Downloaded: "768GB", Ratio: 3.5, SeedingBonus: 360000, Privilege: "无", }, { ID: 6, Name: "Veteran User", Interval: "P20W", Downloaded: "1TB", Ratio: 4.0, SeedingBonus: 600000, Privilege: "可以查看其它用户的评论、帖子历史。", }, { ID: 7, Name: "Extreme User", Interval: "P28W", Downloaded: "2TB", Ratio: 4.5, SeedingBonus: 720000, Privilege: "Extreme User及以上用户会永远保留账号。", }, { ID: 8, Name: "Ultimate User", Interval: "P40W", Downloaded: "4TB", Ratio: 5.0, SeedingBonus: 1200000, Privilege: "无", }, { ID: 9, Name: "Nexus Master", Interval: "P56W", Downloaded: "8TB", Ratio: 5.5, SeedingBonus: 1680000, Privilege: "无", }, }, }
var HDFansDefinition = &v2.SiteDefinition{ ID: "hdfans", Name: "HDFans", Aka: []string{"红豆饭"}, Description: "与志同道合之人前行 分享更多值得珍藏的资源", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hdfans.org/"}, FaviconURL: "https://hdfans.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 72, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/index.php", ResponseType: "document", }, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{ URL: "/userdetails.php", ResponseType: "document", }, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "messageCount", "lastAccessAt"}, }, { RequestConfig: v2.RequestConfig{ URL: "/mybonus.php", ResponseType: "document", }, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "#info_block", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "#info_block", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "#info_block", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('魔力') + td", "td.rowhead:contains('Bonus') + td", "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "bonusPerHour": { Selector: []string{ "#outer td[rowspan]", "div:contains('你当前每小时能获取')", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "messageCount": { Text: "0", Selector: []string{"td[style*='background: red'] a[href*='messages.php']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(?:做种数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(?:下载数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: hdfansLevelRequirements, }
var HDHomeDefinition = &v2.SiteDefinition{ ID: "hdhome", Name: "HDHome", Aka: []string{"HDHome", "家园"}, Description: "高清影视 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hdhome.org/"}, FaviconURL: "https://hdhome.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"#info_block", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|分享率:|Ratio)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(?:做种数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(?:下载数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span[style*='float:left']", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var HDSkyDefinition = &v2.SiteDefinition{ ID: "hdsky", Name: "HDSky", Aka: []string{"HDS", "天空"}, Description: "高清发烧友后花园PT", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hdsky.me/"}, FaviconURL: "https://hdsky.me/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/index.php", ResponseType: "document", }, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{ URL: "/userdetails.php", ResponseType: "document", }, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "seedingBonus", "joinTime", "lastAccessAt", "hnrUnsatisfied", "hnrPreWarning", "messageCount", }, }, { RequestConfig: v2.RequestConfig{ URL: "/mybonus.php", ResponseType: "document", }, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上[传傳][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下[载載][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td > font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率</strong>[::\s]*\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('Class') + td img", }, Attr: "title", }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('魔力') + td", "td.rowhead:contains('Bonus') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "seedingBonus": { Selector: []string{ "td.rowhead:contains('做种积分') + td", "td.rowhead:contains('做種積分') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{ "#outer td[rowspan]", "div:contains('你当前每小时能获取')", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "messageCount": { Text: "0", Selector: []string{"td[style*='background: red'] a[href*='messages.php']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{ "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{ "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, "hnrUnsatisfied": { Text: "0", Selector: []string{"#info_block a[href*='myhr.php']"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`\d+\s*/\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "hnrPreWarning": { Text: "0", Selector: []string{"#info_block a[href*='myhr.php']"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载链接') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, LevelRequirements: hdSkyNewLevelRequirements, }
HDSkyDefinition is the site definition for HDSky
var HDSkyNewRequirementsDate = time.Date(2025, 3, 1, 0, 0, 0, 0, time.FixedZone("CST", 8*3600))
HDSkyNewRequirementsDate is the date after which new level requirements apply
var HDTimeDefinition = &v2.SiteDefinition{ ID: "hdtime", Name: "HDTime", Aka: []string{"HDTIME"}, Description: "HDTime 综合影视 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hdtime.org/"}, FaviconURL: "https://hdtime.org/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "span.nowrap a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "span.nowrap a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('Transfer') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('Transfer') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('Ratio') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"td.rowhead:contains('魔力值') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var HDVideoDefinition = &v2.SiteDefinition{ ID: "hdvideo", Name: "HDVideo", Aka: []string{"HD视频", "hdvideo.top"}, Description: "HDVideo — NexusPHP PT 站点(hdvideo.top)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hdvideo.top/"}, FaviconURL: "https://hdvideo.top/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "h1 span.nowrap > b"}, }, "uploaded": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞无]*([\d.,]+|∞|无限|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var HXPTDefinition = &v2.SiteDefinition{ ID: "hxpt", Name: "HXPT", Aka: []string{"好学"}, Description: "以教育类资源为主的 NexusPHP PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.hxpt.org/"}, FaviconURL: "https://www.hxpt.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('火花') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:火花|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname div.torrent-subtitle, table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up, font.free", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var HdareaDefinition = &v2.SiteDefinition{ ID: "hdarea", Name: "HDArea", Aka: []string{"高清视界", "High Definition Area"}, Description: "HDArea 高清设备交流分享平台", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hdarea.club/"}, FaviconURL: "https://hdarea.club/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents:has(td.colhead) > tbody > tr:has(table.torrentname), " + "table.torrents:has(td.colhead) > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > div[title]", Size: "td.rowfollow:nth-child(6)", Seeders: "td.rowfollow:nth-child(7)", Leechers: "td.rowfollow:nth-child(8)", Snatched: "td.rowfollow:nth-child(9)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DiscountEndTime: "table.torrentname font[color] span[title]", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(5) span[title]", DetailDownloadLink: "td.rowhead:contains('下载链接') + td a[href*='download.php'], td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::][^\d]*([\d.]+)\s*([KMGTP]i?B)`, }, }
HdareaDefinition 描述 HDArea(hdarea.club)站点。
站点为 NexusPHP 1.5 魔改(页面自称 "Powered by HDAREA(基于NexusPHP1.5修改)"),详情页保留 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域,userdetails.php 的 「传输」行是标准的 <strong>上传量</strong> 合并单元格,因此多数选择器与 btschool 一致。 需要单独适配的三处:
- torrents.php 上存在 **两个** table.torrents: 第一个是「预告区(前 5 最新预告)」面板,表头为 td.colhead_notice(colspan=10), 内容藏在 tbody#kokk 里;第二个才是真正的种子列表,表头为 10 个 td.colhead。 若按常规写 `table.torrents > tbody > tr:has(table.torrentname)`,预告区一旦有内容 就会被一起解析进来,因此这里用 :has(td.colhead) 把范围锁死在真实列表表格。
- 副标题是名称单元格内的第二个 <div>,带 title 属性(<div style="color: #666" title="...">), 全页仅此一种 div[title],可直接用普通字符串选择器命中。
- 促销结束时间以 (<font color='...'>剩余时间:<span title="..."></font>) 真实渲染, 但同一子表里的豆瓣评分 <span title="豆瓣评分"> 也是 span[title],必须用 font[color] 限定范围;不同促销等级的 font color 取值不同(免费 #0000FF、2X 免费 #00CC66), 故只判断属性存在而不判断取值。
var HhanclubDefinition = &v2.SiteDefinition{ ID: "hhanclub", Name: "HHCLUB", Description: "综合性 PT 站点(NexusPHP 内核 + Tailwind 皮肤)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://hhanclub.net/"}, FaviconURL: "https://hhanclub.net/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "seedingBonus", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ `span.font-bold:matchesOwn(^\s*上传量:\s*$) + span`, "span.font-bold:contains('上传量:') + span", }, Filters: []v2.Filter{{Name: "parseSize"}}, }, "downloaded": { Selector: []string{ `span.font-bold:matchesOwn(^\s*下载量:\s*$) + span`, "span.font-bold:contains('下载量:') + span", }, Filters: []v2.Filter{{Name: "parseSize"}}, }, "ratio": { Selector: []string{ `span.font-bold:matchesOwn(^\s*分享率:\s*$) + span font`, "span.font-bold:contains('分享率:') + span font", `span.font-bold:matchesOwn(^\s*分享率:\s*$) + span`, }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "levelName": { Selector: []string{ `span.font-bold:matchesOwn(^\s*等级:\s*$) + span b`, `span.font-bold:matchesOwn(^\s*等级:\s*$) + span img`, "span.font-bold:contains('等级:') + span b", }, }, "bonus": { Selector: []string{ `span.font-bold:matchesOwn(^\s*憨豆:\s*$) + div`, "span.font-bold:contains('憨豆:') + div", "a[href*='mybonus.php'] div", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "seedingBonus": { Selector: []string{ `span.font-bold:matchesOwn(^\s*做种积分:\s*$) + span`, "span.font-bold:contains('做种积分:') + span", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "seeding": { Selector: []string{ "div:haschild(img[alt='做种数'])", "a[href*='userdetails.php'][href*='action=2'] div", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "leeching": { Selector: []string{ "div:haschild(img[alt='下载数'])", "a[href*='userdetails.php'][href*='action=3'] div", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ `span.font-bold:matchesOwn(^\s*加入日期:\s*$) + span`, "span.font-bold:contains('加入日期:') + span", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ `span.font-bold:matchesOwn(^\s*最近动向:\s*$) + span`, "span.font-bold:contains('最近动向:') + span", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "div.torrent-table-sub-info", Title: "a.torrent-info-text-name", TitleLink: "a.torrent-info-text-name", Subtitle: "div.torrent-info-text-small_name", Size: "div.torrent-info-text-size", Seeders: "div.torrent-info-text-seeders", Leechers: "div.torrent-info-text-leechers", Snatched: "div.torrent-info-text-finished", DiscountIcon: "span.promotion-tag", DiscountMapping: map[string]v2.DiscountLevel{ "promotion-tag-free": v2.DiscountFree, "promotion-tag-2xfree": v2.Discount2xFree, "promotion-tag-50": v2.DiscountPercent50, "promotion-tag-30": v2.DiscountPercent30, }, DiscountEndTime: "span:contains('剩余时间') > span[title]", DownloadLink: "a[href*='download.php']", UploadTime: "div.torrent-info-text-added span[title]", DetailDownloadLink: "a.index[href*='download.php'], a[href*='download.php?id=']", DetailSubtitle: `div.font-bold:matchesOwn(^\s*副标题\s*$) + div`, }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "promotion-tag-free": v2.DiscountFree, "promotion-tag-2xfree": v2.Discount2xFree, "promotion-tag-50": v2.DiscountPercent50, "promotion-tag-30": v2.DiscountPercent30, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "span.promotion-tag", EndTimeSelector: "span:contains('剩余时间') > span[title]", SizeSelector: `span.font-bold:contains('大小:')`, SizeRegex: `([\d.]+)\s*([KMGT]i?B)`, }, }
HhanclubDefinition 描述 HHCLUB(hhanclub.net)站点。
站点内核仍是 NexusPHP(<title>HHCLUB :: 种子 - Powered by NexusPHP</title>), 但皮肤(styles/HHan)用 Tailwind 全量重写了 DOM:**列表页与详情页都没有任何 table/tr/td**,且促销标记不是 NexusPHP 通用的 img.pro_* 图标。三处关键适配:
列表行是纯 div。页面内 `class="torrents"` 出现 0 次,`<table>`/`<tr>` 出现 0 次。 真实行的公共父元素是 `div.torrent-table-sub-info`(100 个,与 download.php?id= 的 100 次出现一一对应);表头行是同级的另一个 div(携带 torrent-cat / torrent-title 但**没有** torrent-table-sub-info),因此按该 class 选行即天然 排除表头,无需 :has() 判别。列内统计值都带语义 class (torrent-info-text-size / -added / -seeders / -leechers / -finished), 一律用 class 选择器而非 nth-child。
促销标记是站点自定义 span:<span class="promotion-tag promotion-tag-free">免费</span>。 页面内嵌的 tailwindcss 声明只定义了 4 种:-free / -50 / -30 / -2xfree。 搜索侧 parseDiscountFromElement 用 class+src+alt 的**子串**匹配,映射 key 采用 完整 class 名后互不为子串("promotion-tag-free" 不是 "promotion-tag promotion-tag-2xfree" 的子串,反之亦然),因此 map 遍历顺序 随机也不会出现 free 覆盖 2xfree;详情侧 ParseDiscount 按空白拆分 class 后做 **精确**查表,同一份映射同时满足两条路径。
详情页体积在嵌套 div 里: <span class="font-bold"><b>大小:</b></span><span class="">17.33 GB</span> ParseSizeMB 取 SizeSelector 元素的 .Next() 兄弟节点文本,所以 SizeSelector 必须 指向**外层 span**(其下一个兄弟正是数值 span),且正则里不能再带「大小:」前缀, 否则匹配不到(兄弟节点文本只有 "17.33 GB")。
已知限制(无法由选择器解决,非本站定义可控):列表页标题锚点内嵌新种角标 <a class='... torrent-info-text-name'>标题<span class='new'>[新]</span></a>(实测 100 行中 91 行携带), 而 ParseSearch 取标题走 strings.TrimSpace(titleElem.Text()),没有 Attr/Filters 钩子 (SiteSelectors 只为副标题提供了 SubtitleSelector *FieldSelector),CSS 也无法排除子元素, 因此搜索结果标题会带 "[新]" 后缀。行内不存在任何携带干净标题的备选元素(锚点无 title 属性)。 影响面有限:详情页走 input[name='torrent_name'] 的 value,标题干净, 因此 RSS 下载、免费到期监控与 .torrent 文件名均不受影响,仅搜索列表展示带角标。
已核对:三个页面均无 hitandrun / hit_run / Hit and Run 痕迹,故不声明 H&R; 采集数据未包含等级门槛页面,故不声明 LevelRequirements。
var KamePTDefinition = &v2.SiteDefinition{ ID: "kamept", Name: "KamePT", Aka: []string{"龟龟", "Kame"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://kamept.com/"}, FaviconURL: "https://kamept.com/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus", "seedingBonus", "uploaded", "downloaded", "ratio"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上传量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下载量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seedingBonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`做种积分\s*</font>\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td", "td.rowhead:contains('等級') + td", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", HRIcon: "img.hitandrun, img[alt*='H&R'], img[title*='H&R']", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
KamePTDefinition is the site definition for KamePT (kamept.com). Standard NexusPHP site; user stats (上传量/下载量/分享率/魔力值/做种积分/做种/下载) live in the #info_block div present on both index.php and userdetails.php, while 等级/加入日期/最近动向 stay in the standard userdetails.php rows.
var KeepFRDSDefinition = &v2.SiteDefinition{ ID: "keepfrds", Name: "PT@KEEPFRDS", Aka: []string{"FRDS", "朋友"}, Description: "综合性 PT 站点,以影视压制资源为主", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.keepfrds.com/"}, FaviconURL: "https://pt.keepfrds.com/static/favicon-64x64.png", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "bonusPerHour", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "trueDownloaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "bonus": { Selector: []string{"#info_block #totalBonus", "#totalBonus"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{"#info_block #perBonus", "#perBonus"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "seeding": { Selector: []string{"#info_block a[href*='option-torrents=3']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block a[href*='option-torrents=5']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowfollow.join_date", "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowfollow.last_seen", "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{"td.browse_td_name_cell", "table.torrentname td.embedded"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)<br\s*/?>(?:\s*<span[^>]*>)?\s*([^<]+)`}}, {Name: "replace", Args: []any{"'", "'"}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{"<", "<"}}, {Name: "replace", Args: []any{">", ">"}}, {Name: "replace", Args: []any{" ", " "}}, {Name: "replace", Args: []any{"&", "&"}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "h1#top", IDSelector: "input[name='pid']", DiscountSelector: "h1#top font[class]", EndTimeSelector: "h1#top span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*([KMGTP]i?B)`, }, }
KeepFRDSDefinition 描述 PT@KEEPFRDS 站点。
该站点为标准 NexusPHP 架构,但有三处与常规站点不同,选择器需要专门适配:
- 详情页没有 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域, 标题只存在于 h1#top 文本中,并在尾部附带 [免费] 促销标记(由共享解析器剥离)。
- 体积单位使用二进制形式(GiB / TiB / MiB),默认 SizeRegex 不包含 i 形式, 因此必须自定义 SizeRegex。
- 搜索页副标题是 <br /> 之后的裸文本节点,没有任何包裹元素, 无法用普通 CSS 选择器命中,需要走 SubtitleSelector(html + regex)。
var LajiduiDefinition = &v2.SiteDefinition{ ID: "lajidui", Name: "lajidui", Aka: []string{"垃圾堆"}, Description: "极致文件共享体验", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.lajidui.top/"}, FaviconURL: "https://pt.lajidui.top/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上[传傳][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下[载載][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('Transfer') + td", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "#info_block a[href*='mybonus.php']", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(?:做种数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(?:下载数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: lajiduiLevelRequirements, }
var LemonHDDefinition = &v2.SiteDefinition{ ID: "lemonhd", Name: "LemonHD", Aka: []string{"Lemon"}, Description: "LemonHD 综合影视 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://lemonhd.net/"}, FaviconURL: "https://lemonhd.net/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "span.nowrap a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "span.nowrap a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"td.rowhead:contains('魔力值') + td", ".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{".bottom .medium", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var LongPTDefinition = &v2.SiteDefinition{ ID: "longpt", Name: "LongPT", Aka: []string{"龙PT", "longpt.org"}, Description: "LongPT — NexusPHP PT 站点(longpt.org)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://longpt.org/"}, FaviconURL: "https://longpt.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var LuckptDefinition = &v2.SiteDefinition{ ID: "luckpt", Name: "LuckPT", Description: "综合性 PT 站点(NexusPHP)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.luckpt.de/"}, FaviconURL: "https://pt.luckpt.de/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:幸运星|魔力值)\s*</font>\s*(?:\[[^\]]*\]\s*)+[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{"table.torrentname td.embedded:has(a[href*='details.php'])"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)<br\s*/?>(?:\s*<span[^>]*>[^<]*</span>)*\s*([^<]+)`}}, {Name: "replace", Args: []any{"'", "'"}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{"<", "<"}}, {Name: "replace", Args: []any{">", ">"}}, {Name: "replace", Args: []any{" ", " "}}, {Name: "replace", Args: []any{"&", "&"}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DiscountEndTime: "table.torrentname font:contains('剩余时间') span[title]", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font[class]", EndTimeSelector: "h1 font:contains('剩余时间') span[title], h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::][^\d]*([\d.]+)[\s\x{00a0}]*([KMGT]i?B)`, }, }
LuckptDefinition 描述 LuckPT(pt.luckpt.de)站点。
站点为标准 NexusPHP 架构(页面标题 "LuckPT :: ... - Powered by NexusPHP"), 详情页保留了 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域, 因此标题/ID 直接走隐藏域取值。需要单独适配的点:
- 搜索页名称单元格前有封面缩略图单元格(data-has-cover=1,20/20 行都有), td.embedded 的第一个是封面而不是名称,必须用 :has(a[href*='details.php']) 定位名称单元格。
- 副标题是 <br /> 之后的裸文本节点,且前面还有 1~3 个彩色标签 <span>(官方/中字/完结…), eastgame 只跳过一个 span 的正则无法命中,需要跳过任意个完整 span。
- 名称单元格内除促销的「剩余时间」<span title> 外,还有一个审核状态 <span title="通过">, 促销结束时间选择器必须限定在 font:contains('剩余时间') 内,否则非促销行会误取 title="通过"。
- 「基本信息」行体积同时出现十进制(46.11 GB)与二进制(GiB)单位,SizeRegex 的单位组放宽为 [KMGT]i?B;共享的 ParseSizeMB 已能换算 KiB/MiB/GiB/TiB。
- userdetails.php 的「传输」行为合并单元格,且魔力值在本站叫「幸运星」, 标签后面跟了两个 [链接](详情/商城)才是冒号,需要专用正则。
var MTeamDefinition = &v2.SiteDefinition{ ID: "mteam", Name: "M-Team - TP", Aka: []string{"MTeam", "馒头"}, Description: "综合性网站,有分享率要求", Schema: v2.SchemaMTorrent, RateLimit: 1.0, RateBurst: 3, URLs: []string{ "https://api.m-team.cc", "https://kp.m-team.cc", "https://zp.m-team.io", "https://xp.m-team.cc", "https://ap.m-team.cc", "https://next.m-team.cc", "https://ob.m-team.cc", }, WebURL: "https://kp.m-team.cc", LegacyURLs: []string{"https://xp.m-team.io/", "https://pt.m-team.cc/", "https://tp.m-team.cc/"}, FaviconURL: "https://kp.m-team.cc/favicon.ico", TimezoneOffset: "+0800", UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id", "name", "joinTime"}, RequestDelay: 300, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/api/member/profile", Method: "POST", ResponseType: "json", }, Fields: []string{ "id", "name", "joinTime", "uploaded", "downloaded", "trueUploaded", "trueDownloaded", "levelName", "levelId", "bonus", "lastAccessAt", }, }, { RequestConfig: v2.RequestConfig{ URL: "/api/tracker/myPeerStatistics", Method: "POST", ResponseType: "json", }, Fields: []string{"seeding", "seedingSize", "uploads"}, }, { RequestConfig: v2.RequestConfig{ URL: "/api/tracker/mybonus", Method: "POST", ResponseType: "json", }, Fields: []string{"bonusPerHour"}, }, { RequestConfig: v2.RequestConfig{ URL: "/api/msg/notify/statistic", Method: "POST", ResponseType: "json", }, Fields: []string{"messageCount"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"data.id"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "name": { Selector: []string{"data.username"}, }, "joinTime": { Selector: []string{"data.createdDate"}, Filters: []v2.Filter{{Name: "parseTime"}}, }, "uploaded": { Selector: []string{"data.memberCount.uploaded"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "downloaded": { Selector: []string{"data.memberCount.downloaded"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "trueUploaded": { Selector: []string{"data.memberCount.trueUploaded"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "trueDownloaded": { Selector: []string{"data.memberCount.trueDownloaded"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "levelName": { Selector: []string{"data.role"}, }, "levelId": { Selector: []string{"data.role"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonus": { Selector: []string{"data.memberCount.bonus"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "lastAccessAt": { Selector: []string{"data.memberStatus.lastBrowse"}, Filters: []v2.Filter{{Name: "parseTime"}}, }, "seeding": { Selector: []string{"data.seederCount"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "seedingSize": { Selector: []string{"data.seederSize"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "uploads": { Selector: []string{"data.uploadCount"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{"data.formulaParams.finalBs"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "messageCount": { Selector: []string{"data.unMake"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, }, }, LevelRequirements: []v2.SiteLevelRequirement{ { ID: 1, Name: "User", }, { ID: 2, Name: "Power User", Interval: "P4W", Downloaded: "200GB", Ratio: 2, Privilege: "魔力值加成:+1%;可以使用匿名發表候選種子;可以上傳字幕", }, { ID: 3, Name: "Elite User", Interval: "P8W", Downloaded: "400GB", Ratio: 3, Privilege: "魔力值加成:+2%;可以發送邀請;可以管理自己上傳的字幕;可以檢視別人的下載紀錄;可以使用個性條", }, { ID: 4, Name: "Crazy User", Interval: "P12W", Downloaded: "500GB", Ratio: 4, Privilege: "魔力值加成:+3%", }, { ID: 5, Name: "Insane User", Interval: "P16W", Downloaded: "800GB", Ratio: 5, Privilege: "魔力值加成:+4%;可以檢視排行榜", }, { ID: 6, Name: "Veteran User", Interval: "P20W", Downloaded: "1000GB", Ratio: 6, Privilege: "魔力值加成:+5%;封存帳號後不會被刪除帳號", }, { ID: 7, Name: "Extreme User", Interval: "P24W", Downloaded: "2000GB", Ratio: 7, Privilege: "魔力值加成:+6%;永遠保留", }, { ID: 8, Name: "Ultimate User", Interval: "P28W", Downloaded: "2500GB", Ratio: 8, Privilege: "魔力值加成:+7%", }, { ID: 9, Name: "Nexus Master", Interval: "P32W", Downloaded: "3000GB", Ratio: 9, Privilege: "魔力值加成:+8%", }, { ID: 100, Name: "VIP", GroupType: v2.LevelGroupVIP, }, }, }
MTeamDefinition is the site definition for M-Team
var MTeamLevelIDMap = map[string]string{
"1": "User",
"2": "Power User",
"3": "Elite User",
"4": "Crazy User",
"5": "Insane User",
"6": "Veteran User",
"7": "Extreme User",
"8": "Ultimate User",
"9": "Nexus Master",
"10": "VIP",
}
MTeamLevelIDMap maps API role ID to level name
var MooKoDefinition = &v2.SiteDefinition{ ID: "mooko", Name: "MooKo", Aka: []string{"MK"}, Description: "MooKo is a CHINESE Private Torrent Tracker for MOVIES / TV / GENERAL", Schema: v2.SchemaGazelle, URLs: []string{"https://mooko.org/"}, FaviconURL: "https://mooko.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRCalcSeedTime: v2.NewSizeTieredHRCalc( []v2.HRSeedTimeRule{ {MinSizeGB: 0, MaxSizeGB: 10, SeedTimeH: 36}, {MinSizeGB: 10, MaxSizeGB: 20, SeedTimeH: 72}, {MinSizeGB: 20, MaxSizeGB: 50, SeedTimeH: 120}, {MinSizeGB: 50, MaxSizeGB: 200, SeedTimeH: 168}, {MinSizeGB: 200, MaxSizeGB: 0, SeedTimeH: 336}, }, 168, ), }
var MuaDefinition = &v2.SiteDefinition{ ID: "mua", Name: "Mua", Aka: []string{"xloli", "mua.xloli.cc"}, Description: "Mua (xloli) — NexusPHP 二次元/动画 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://mua.xloli.cc/"}, FaviconURL: "https://mua.xloli.cc/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", }, }, { RequestConfig: v2.RequestConfig{URL: "/mybonus.php", ResponseType: "document"}, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "td.rowhead:contains('用户ID/UID') + td", "td.rowhead:contains('用户ID') + td", "#info_block a.User_Name", }, Filters: []v2.Filter{{Name: "regex", Args: []any{`(\d+)`}}, {Name: "parseNumber"}}, }, "name": { Selector: []string{"#info_block a.User_Name", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[\s::]*([\d.,]+|无限|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*?\[<a[^>]*>使用</a>\][::]?\s*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/?>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/?>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{"#outer", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:你当前每小时能获取|您当前每小时能获取)[^\d-]{0,20}([\d.,]+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag):not(.tag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", }, }
var MyPTCCDefinition = &v2.SiteDefinition{ ID: "myptcc", Name: "MyPTCC", Aka: []string{"我的PT", "cc.mypt.cc"}, Description: "我的PT(CC) 人类资源库", Schema: v2.SchemaNexusPHP, URLs: []string{"https://cc.mypt.cc/"}, FaviconURL: "https://cc.mypt.cc/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 72, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "h1 .nowrap b", "h1 b"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)(?:</strong>)?[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", HRIcon: "img.hitandrun, img[alt*='H&R'], img[title*='H&R']", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font.twoupfree, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var NicePTDefinition = &v2.SiteDefinition{ ID: "nicept", Name: "NicePT", Aka: []string{"NicePT", "好趣", "www.nicept.net"}, Description: "NicePT — NexusPHP PT 站点(繁体界面,www.nicept.net)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.nicept.net/"}, FaviconURL: "https://www.nicept.net/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt", "trueUploaded", "trueDownloaded", }, }, { RequestConfig: v2.RequestConfig{URL: "/mybonus.php", ResponseType: "document"}, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('傳送') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上傳量|上传量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('傳送') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下載量|下载量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('傳送') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等級') + td > img", "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "trueUploaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:實際上傳量|实际上传量|實際上傳|实际上传)</strong>[\s::]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:實際下載量|实际下载量|實際下載|实际下载)</strong>[\s::]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "bonusPerHour": { Selector: []string{ "#outer", "body", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:你當前每小時能獲取|你当前每小时能获取|您當前每小時能獲取|您当前每小时能获取)[^\d-]{0,20}([\d.,]+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下載連結') + td a[href*='download.php'], td.rowhead:contains('下载链接') + td a[href*='download.php'], td.rowhead:contains('下載') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副標題') + td, td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本資訊'), td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var NovaHDDefinition = &v2.SiteDefinition{ ID: "novahd", Name: "NovaHD", Aka: []string{"Nova"}, Description: "NovaHD PT站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.novahd.top/"}, FaviconURL: "https://pt.novahd.top/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/index.php", ResponseType: "document", }, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{ URL: "/userdetails.php", ResponseType: "document", }, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "messageCount", }, }, { RequestConfig: v2.RequestConfig{ URL: "/mybonus.php", ResponseType: "document", }, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", }, Filters: []v2.Filter{ {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", }, Filters: []v2.Filter{ {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td > font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", }, Filters: []v2.Filter{ {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('魔力') + td", "td.rowhead:contains('Bonus') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{ "#outer td[rowspan]", "div:contains('你当前每小时能获取')", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "messageCount": { Text: "0", Selector: []string{"td[style*='background: red'] a[href*='messages.php']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{ "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{ "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: novaHDLevelRequirements, }
NovaHDDefinition is the site definition for NovaHD
var OnePTBADefinition = &v2.SiteDefinition{ ID: "1ptba", Name: "1PTBar", Aka: []string{"壹PT", "1PTBA"}, Description: "分享互联,收获快乐", Schema: v2.SchemaNexusPHP, URLs: []string{"https://1ptba.com/"}, FaviconURL: "https://1ptba.com/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 24, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: onePTBALevelRequirements, }
var OpenCDDefinition = &v2.SiteDefinition{ ID: "opencd", Name: "OpenCD", Aka: []string{"OCD", "皇后"}, Description: "OpenCD 音樂 PT 站點", Schema: v2.SchemaNexusPHP, URLs: []string{"https://open.cd/"}, FaviconURL: "https://open.cd/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name'] b", "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('传送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('上传量') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上[傳传][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('传送') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('下载量') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下[載载][量]?</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('傳送') + td", "td.rowhead:contains('传送') + td", "td.rowhead:contains('分享率') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率</strong>[::\s]*\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('魔力') + td", "td.rowhead:contains('Bonus') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入时间') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='plugin_details.php'], table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='plugin_details.php'], table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded font[color='#888888']", Category: "td:nth-child(1) img[alt]", UploadTime: "td:nth-child(6) span[title]", Size: "td:nth-child(7)", Seeders: "td:nth-child(8)", Leechers: "td:nth-child(9)", Snatched: "td:nth-child(10)", DiscountIcon: "table.torrentname img.pro_free, table.torrentname img.pro_free2up, table.torrentname img.pro_50pctdown, table.torrentname img.pro_50pctdown2up, table.torrentname img.pro_30pctdown, table.torrentname img.pro_2up", DiscountEndTime: "table.torrentname span[title]", DetailDownloadLink: "td.rowtitle:contains('下載鏈接') + td a[href*='download.php'], td.rowhead:contains('下载链接') + td a[href*='download.php'], td.rowtitle:contains('連結') + td a[href*='download.php']", DetailSubtitle: "div.smalltitle, td.rowtitle:contains('副標題') + td, td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, "pro_free": v2.DiscountFree, "pro_free2up": v2.Discount2xFree, "pro_2up": v2.Discount2xUp, "pro_50pctdown": v2.DiscountPercent50, "pro_50pctdown2up": v2.Discount2x50, "pro_30pctdown": v2.DiscountPercent30, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "div.title, h1", DiscountSelector: "div.title img[class*='pro_'], div.title font[class], h1 font[class]", EndTimeSelector: "div.title span[title], h1 span[title]", SizeSelector: "td.rowtitle:contains('大小') + td, td.rowhead:contains('大小') + td, td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
OpenCDDefinition is the site definition for OpenCD (open.cd) OpenCD uses Traditional Chinese NexusPHP with several template customizations:
- Detail page uses <div class="title"> + td.rowtitle (not <h1> + td.rowhead)
- Detail links use plugin_details.php (not details.php)
- Discount end time is embedded in the onmouseover tooltip of the free icon
var OurBitsDefinition = &v2.SiteDefinition{ ID: "ourbits", Name: "OurBits", Aka: []string{"OB", "我们的部落"}, Description: "OurBits — NexusPHP 综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://ourbits.club/"}, FaviconURL: "https://ourbits.club/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt", "trueUploaded", "trueDownloaded", }, }, { RequestConfig: v2.RequestConfig{URL: "/mybonus.php", ResponseType: "document"}, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"#info_block a.User_Name", "#info_block a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"#info_block a.User_Name", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*?\[<a[^>]*>使用</a>\][::]?\s*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:真实上传量|实际上传量)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:真实下载量|实际下载量)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "bonusPerHour": { Selector: []string{"#outer", "body"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:你当前每小时能获取|您当前每小时能获取)[^\d-]{0,20}([\d.,]+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table#torrenttable > tbody > tr:has(table.torrentname), table#torrenttable > tr:has(table.torrentname), table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", }, }
var PT0FFCCDefinition = &v2.SiteDefinition{ ID: "pt0ffcc", Name: "Farmm", Aka: []string{"0ff", "pt.0ff.cc", "种子农场"}, Description: "Farmm 种子农场(pt.0ff.cc)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.0ff.cc/"}, FaviconURL: "https://pt.0ff.cc/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "h1 > span.nowrap > b", "h1 span.nowrap b", "a[href*='userdetails.php'][class*='Name'] b", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('上传量') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:^|[^实])上传量</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('下载量') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:^|[^实])下载量</strong>[::\s]*\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td", "td.rowhead:contains('分享率') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率</strong>[::\s]*\s*(?:<font[^>]*>)?([\d.,]+|∞|无限|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "table.main td.rowhead:contains('等级') + td img", "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('Class') + td > img", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td span[title]", "td.rowhead:contains('加入日期') + td", }, Attr: "title", Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值[^::]*[::]\s*(?:</?[^>]+>)*\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/?>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/?>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded", Category: "td:nth-child(1) a[href*='?cat=']", UploadTime: "td:nth-child(4) span[title]", Size: "td:nth-child(5)", Seeders: "td:nth-child(6)", Leechers: "td:nth-child(7)", Snatched: "td:nth-child(8)", DiscountIcon: "table.torrentname img.pro_free, table.torrentname img.pro_free2up, table.torrentname img.pro_2up, table.torrentname img.pro_50pctdown, table.torrentname img.pro_50pctdown2up, table.torrentname img.pro_30pctdown, table.torrentname img.pro_twoupfree, table.torrentname img.pro_twouphalfdown", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php'], a.index[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "h1#top, h1", DiscountSelector: "h1#top b font[class], h1 b font[class], h1#top font[class], h1 font[class]", EndTimeSelector: "h1#top span[title], h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*(?:</?b>)*\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PT0FFCCDefinition is the site definition for Farmm (pt.0ff.cc). The site is a standard NexusPHP template derived from CHD/scenetorrents with two significant deviations:
- Detail page: size is inline inside the `基本信息` row (not its own rowhead row). The SizeRegex pattern extracts from the value cell's HTML.
- Userinfo page: HDSky-style combined `传输` row packs ratio + uploaded + downloaded in a single cell; selectors use regex to parse the combined HTML.
var PTCHDBitsDefinition = &v2.SiteDefinition{ ID: "ptchdbits", Name: "PTCHDBits", Aka: []string{"CHD", "彩虹岛"}, Description: "CHDBits 综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://ptchdbits.co/"}, FaviconURL: "https://ptchdbits.co/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus", "seedingBonus", "uploaded", "downloaded", "ratio"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上传量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下载量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seedingBonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`做种积分\s*[::]\s*</font>\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td", "td.rowhead:contains('等級') + td", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最近活动') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "pro_free": v2.DiscountFree, "pro_free2up": v2.Discount2xFree, "pro_2up": v2.Discount2xUp, "pro_50pctdown": v2.DiscountPercent50, "pro_50pctdown2up": v2.Discount2x50, "pro_30pctdown": v2.DiscountPercent30, "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 img.pro_free, h1 img[class*='pro_'], h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PTCHDBitsDefinition is the site definition for PTCHDBits (ptchdbits.co, CHDBits mirror). Standard NexusPHP site; user stats (上传量/下载量/分享率/魔力值/做种/下载) live in the #info_block div present on both index.php and userdetails.php. Detail-page discount is an img.pro_free icon (no <font class='free'>), so DiscountSelector/DiscountMapping handle img classes.
var PTCafeDefinition = &v2.SiteDefinition{ ID: "ptcafe", Name: "PTCafe", Aka: []string{"咖啡", "咖啡PT"}, Description: "咖啡 — 综合性 NexusPHP PT 站点(ptcafe.club)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://ptcafe.club/"}, FaviconURL: "https://ptcafe.club/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var PTFansDefinition = &v2.SiteDefinition{ ID: "ptfans", Name: "PTFans", Aka: []string{"PTF", "PT粉丝"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://ptfans.cc/"}, FaviconURL: "https://ptfans.cc/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 168, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus", "uploaded", "downloaded", "ratio"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上传量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下载量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td", "td.rowhead:contains('等級') + td", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DiscountEndTime: "table.torrentname td.embedded font[color] span[title], table.torrentname td.embedded span[title]", HRIcon: "img.hitandrun, img[alt*='H&R'], img[title*='H&R']", Category: "td.rowfollow:nth-child(1) span[title], td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PTFansDefinition is the site definition for PTFans (ptfans.cc). Standard NexusPHP site with H&R; user stats (上传量/下载量/分享率/魔力值/做种/下载) live in the #info_block div present on both index.php and userdetails.php, while 等级/加入日期/最近动向 stay in the standard userdetails.php rows. Username link uses the PowerUser_Name class (matched via [class*='Name']).
var PTHomeDefinition = &v2.SiteDefinition{ ID: "pthome", Name: "PTHome", Aka: []string{"铂金家", "PT之家"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pthome.net/"}, FaviconURL: "https://pthome.net/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus", "seedingBonus", "uploaded", "downloaded", "ratio"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上传量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下载量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seedingBonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`做种积分\s*[::]\s*</font>\s*(?:</a>)?\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td", "td.rowhead:contains('等級') + td", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最近活动') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PTHomeDefinition is the site definition for PTHome (pthome.net, 铂金家). Standard NexusPHP site; user stats live in the #info_block div present on both index.php and userdetails.php. Uses full-width colons (:) in info_block, and the torrents table carries the "torrents torrents-table" class (matched by table.torrents).
var PTLoverDefinition = &v2.SiteDefinition{ ID: "ptlover", Name: "PTLover", Aka: []string{"AFUN", "PT Lover"}, Description: "PTLover 综合资源站", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.ptlover.cc/"}, FaviconURL: "https://www.ptlover.cc/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "h1 .nowrap b", "h1 b"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞I-]*(?:</font>)?\s*([\d.,]+|∞|Inf)`}}, { Name: "parseNumber", }}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": {Selector: []string{"td.rowhead:contains('喵饼') + td"}, Filters: []v2.Filter{{Name: "parseNumber"}}}, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", HRIcon: "img.hitandrun, img[alt*='H&R'], img[title*='H&R']", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font.twoupfree, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var PTSKITDefinition = &v2.SiteDefinition{ ID: "ptskit", Name: "PTSKIT", Aka: []string{"拾刻"}, Description: "以短剧与影视资源为主的 NexusPHP PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.ptskit.org/"}, FaviconURL: "https://www.ptskit.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td", "#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等级') + td img", "td.rowhead:contains('Class') + td > img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var PTTimeDefinition = &v2.SiteDefinition{ ID: "pttime", Name: "PTT", Aka: []string{"PTTime", "PT時間"}, Description: "PTT (PTT-NP fork of NexusPHP)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.pttime.org/"}, FaviconURL: "https://www.pttime.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonus", "levelName"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"joinTime", "bonusPerHour", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传|上傳|Uploaded)[::]?\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载|下載|Downloaded)[::]?\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio)[::]?\s*</font>\s*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`title="当前做种"[^>]*>[^<]*</font>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`title="当前下载"[^>]*>[^<]*</font>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值[^::]*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`\(([^)]+)\)\s*[A-Za-z][A-Za-z ]+User`}}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入时间') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "bonusPerHour": { Selector: []string{ "#info_block", "body", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`\(([\d.,]+)\s*魔力/小[時时]\)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a.torrentname_title, table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a.torrentname_title, table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > font[title]", Category: "td.rowfollow:nth-child(1) span.category[title], td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(5) span[title]", Size: "td.rowfollow:nth-child(6)", Seeders: "td.rowfollow:nth-child(7)", Leechers: "td.rowfollow:nth-child(8)", Snatched: "td.rowfollow:nth-child(9)", DiscountIcon: "table.torrentname font.promotion, td.embedded font.promotion, table.torrentname img.pro_free, table.torrentname img.pro_free2up, table.torrentname img.pro_50pctdown, table.torrentname img.pro_30pctdown, table.torrentname img.pro_2up, table.torrentname img.pro_50pctdown2up", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, "thirtypercent": v2.DiscountPercent30, "zeroupzerodown": v2.DiscountFree, }, DetailDownloadLink: "tr:has(td.rowhead:contains('下载')) a[href*='download.php'], td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td.rowfollow, td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, "thirtypercent": v2.DiscountPercent30, "zeroupzerodown": v2.DiscountFree, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1#top, h1", DiscountSelector: "h1#top font[class], h1 font[class]", EndTimeSelector: "h1#top span[title], h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*(?:</?b>)*\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PTTimeDefinition is the site definition for PTT (pttime.org) PTT uses a custom "PTT-NP" NexusPHP fork with several deviations:
- Discount in search: <font class="promotion free"> etc. (NOT img.pro_*)
- Category cell uses <span class="category" title="..."> (NOT img[alt])
- Userinfo labels use "上传:" / "下载:" / "分享率:" (no 量 suffix)
- Info block emoji arrows for seeding/leeching (NOT class="arrowup/arrowdown")
- Hidden columns (PTR, comments) use style="display:none" but still consume nth-child indices
var PTerClubDefinition = &v2.SiteDefinition{ ID: "pterclub", Name: "PTerClub", Aka: []string{"猫站", "PTer", "PT之友"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pterclub.net/"}, FaviconURL: "https://pterclub.net/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus", "seedingBonus", "uploaded", "downloaded", "ratio"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上传量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下载量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`猫粮\s*</span>\s*</a>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seedingBonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`做种积分\s*[::]\s*</span>\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td", "td.rowhead:contains('等級') + td", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最近活动') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PTerClubDefinition is the site definition for PTerClub (pterclub.net, 猫站). Standard NexusPHP site; user stats live in the #info_block div. Notable quirks:
- bonus label is "猫粮" (not 魔力值), inside a <span class="color_bonus"> wrapped in <a>
- 上传量/下载量/分享率 use full-width colons (:)
- detail-page free is encoded in the H1 <font class='free'> onmouseover tooltip
var PandaPTDefinition = &v2.SiteDefinition{ ID: "pandapt", Name: "PandaPT", Aka: []string{"熊猫", "Panda"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pandapt.net/"}, FaviconURL: "https://pandapt.net/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus", "uploaded", "downloaded", "ratio"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name'] b", "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`上传量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`下载量[::]\s*</font>\s*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('等级') + td", "td.rowhead:contains('等級') + td", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
PandaPTDefinition is the site definition for PandaPT (pandapt.net). Standard NexusPHP site; user stats (上传量/下载量/分享率/魔力值/做种/下载) live in the #info_block div present on both index.php and userdetails.php, while 等级/加入日期/最近动向 stay in the standard userdetails.php rows.
var PtlgsDefinition = &v2.SiteDefinition{ ID: "ptlgs", Name: "PTLGS", Aka: []string{"LGS"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://ptlgs.org/"}, FaviconURL: "https://ptlgs.org/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "h1 span.nowrap b", "h1 b", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`分享率[::]\s*</font>\s*([\d.,]+|无限|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"td.rowhead:contains('做种积分') + td", "td.rowhead:contains('做種積分') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小:[^\d]*([\d.]+)\s*(GB|MB|KB|TB)`, }, HREnabled: true, HRSeedTimeHours: 0, LevelRequirements: []v2.SiteLevelRequirement{ {ID: 1, Name: "User", NameAka: []string{"新人"}}, }, }
PtlgsDefinition is the site definition for PTLGS (ptlgs.org), a classic NexusPHP site. See GitHub issue #377 for the source HTML fixtures.
var QingwaptDefinition = &v2.SiteDefinition{ ID: "qingwapt", Name: "青蛙", Description: "综合性 PT 站点(NexusPHP)", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.qingwapt.com/"}, FaviconURL: "https://www.qingwapt.com/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "trueDownloaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:蝌蚪|魔力值)\s*</font>(?:\s*\[[^\]]*\])*\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{"table.torrentname td.embedded:has(a[href*='details.php'])"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)>\s*([^<>]+?)\s*$`}}, {Name: "replace", Args: []any{"'", "'"}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{"<", "<"}}, {Name: "replace", Args: []any{">", ">"}}, {Name: "replace", Args: []any{" ", " "}}, {Name: "replace", Args: []any{"&", "&"}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "table.torrentname span[alt], img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DiscountEndTime: "table.torrentname font[color] span[title]", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(TiB|GiB|MiB|KiB|TB|GB|MB|KB)`, }, }
QingwaptDefinition 描述青蛙(www.qingwapt.com)站点。
站点为标准 NexusPHP 架构(qw 皮肤),详情页保留了 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域,userdetails.php 的「传输」行是经典的 <strong>上传量</strong> 合并单元格,因此绝大多数选择器与 btschool 一致。 需要单独适配的三处:
- 搜索页促销标记不是 img.pro_free,而是自绘的文字徽章 <span style="..." alt="Free" onmouseover="...">Free</span>, 必须用 span[alt] 命中(parseDiscountFromElement 会读 class+src+alt)。
- 促销剩余时间渲染为 <font color='#0000FF'>剩余时间:<span title="...">…</span></font>, 名称单元格里还存在 title="置顶促销" / title="自动审核通过" 等非时间 span, 因此必须限定到 font[color] 之内,否则会取到非时间值。
- 搜索页副标题是名称单元格末尾的裸文本节点,且前面穿插若干自绘标签 span (官方 / 中字 / 分集),只能取单元格 html 后用正则截取尾部文本。
魔力值在本站叫「蝌蚪」,#info_block 中的标签文本据此适配。
var RailgunptDefinition = &v2.SiteDefinition{ ID: "railgunpt", Name: "RailgunPT", Aka: []string{"bilibili.download"}, Description: "综合性 PT 站点,影视 / 剧集 / 音乐 / 动漫资源较多", Schema: v2.SchemaNexusPHP, URLs: []string{"https://bilibili.download/"}, FaviconURL: "https://bilibili.download/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "trueDownloaded", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php'][class*='Name']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", SubtitleSelector: &v2.FieldSelector{ Selector: []string{"table.torrentname td.embedded:has(a[href*='details.php'])"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?s)<br\s*/?>(?:\s*<span[^>]*>[^<]*</span>)*\s*([^<]+)`}}, {Name: "replace", Args: []any{"'", "'"}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{""", `"`}}, {Name: "replace", Args: []any{"<", "<"}}, {Name: "replace", Args: []any{">", ">"}}, {Name: "replace", Args: []any{" ", " "}}, {Name: "replace", Args: []any{"&", "&"}}, {Name: "trim"}, }, }, Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_50pctdown2up, img.pro_30pctdown", DownloadLink: "a[href*='download.php']", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php'], td.rowhead:contains('种子链接') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::][^\d]*([\d.]+)[\s\x{00a0}]*(TiB|TB|GiB|GB|MiB|MB|KiB|KB)`, }, }
RailgunptDefinition 描述 RailgunPT(bilibili.download)站点。
域名与站点自称不一致:域名是 bilibili.download,但页面 <title>、页脚版权链接 (<a href="https://bilibili.download">RailgunPT</a>)与官方 TG 群(t.me/RailgunPT) 均自称 RailgunPT,因此站点 ID / Name 取 RailgunPT,URL 取真实域名。
架构为标准 NexusPHP(CHD/scenetorrents 分类图标皮肤),详情页保留 input[name='torrent_name'] / input[name='detail_torrent_id'] 隐藏域, 因此大部分选择器与 btschool / eastgame 一致。需要单独适配的地方:
- 种子列表的名称子表在标题单元格之前多出一个 46x46 海报缩略图 td.embedded, 所以副标题不能直接取 table.torrentname 下第一个 td.embedded, 必须用 :has(a[href*='details.php']) 锁定真正的名称单元格。
- 副标题是 <br /> 之后的裸文本节点,且前面可能有 1~3 个 <span title="">标签</span> 徽章(官方 / 国语 / 中字 / HDR),纯 CSS 选不中,故走 SubtitleSelector 的 innerHTML + 正则,先吞掉徽章 span 再截取裸文本。
- 促销剩余时间同时存在于促销图标的 onmouseover 提示与名称单元格的可见 「剩余时间:<span title>」中;但徽章 span 带的是 title=""(空值), 用 span[title] 选择器有误命中风险,因此不设置 DiscountEndTime, 交由驱动的 parseDiscountEndTimeFromOnmouseover() 兜底解析。 实测抓取样本 30 个促销种子全部带 onmouseover 提示,无长期促销。
- 详情页体积单位混用十进制(基本信息行 2.17 GB)与 IEC(MediaInfo 里的 2.17 GiB), SizeRegex 的单位组同时接受两种写法(ParseSizeMB 已支持 KiB/MiB/GiB/TiB)。
- 站点无 H&R(三张抓取页面 hitandrun 出现 0 次),故不设置 HREnabled。
var RaingfhDefinition = &v2.SiteDefinition{ ID: "raingfh", Name: "RainGFH", Aka: []string{"雨", "raingfh.top"}, Description: "雨 PT 站", Schema: v2.SchemaNexusPHP, URLs: []string{"https://raingfh.top/"}, FaviconURL: "https://raingfh.top/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "bonus", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "h1 .nowrap b", "h1 b"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞I-]*(?:</font>)?\s*([\d.,]+|∞|Inf)`}}, { Name: "parseNumber", }}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('雨滴') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`雨滴\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td"}, Filters: []v2.Filter{{Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt], td.rowfollow:nth-child(1) img[title]", UploadTime: "td.rowfollow:nth-child(4) span[title]", HRIcon: "img.hitandrun, img[alt*='H&R'], img[title*='H&R']", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font.twoupfree, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var RousiProDefinition = &v2.SiteDefinition{ ID: "rousipro", Name: "Rousi Pro", Aka: []string{"Rousi", "肉丝"}, Description: "综合性PT站点,使用Bearer Token认证", Schema: v2.SchemaRousi, AuthMethod: v2.AuthMethodPasskey, RateLimit: 2.0, RateBurst: 5, URLs: []string{ "https://rousi.pro", }, FaviconURL: "https://rousi.pro/favicon.ico", TimezoneOffset: "+0800", CreateDriver: createRousiDriver, }
var SoulVoiceDefinition = &v2.SiteDefinition{ ID: "soulvoice", Name: "SoulVoice", Aka: []string{"聆音"}, Description: "致力于建设一个有声资源,电子书为主,学习资料,影视资源为辅的PT分享站", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.soulvoice.club/"}, FaviconURL: "https://pt.soulvoice.club/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, HREnabled: true, HRSeedTimeHours: 72, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"uploaded", "downloaded", "ratio", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}}, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}}, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}}, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{{Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}}, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{{Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}}, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: soulvoiceLevelRequirements, }
var SpringSundayDefinition = &v2.SiteDefinition{ ID: "springsunday", Name: "SpringSunday", Aka: []string{"SSD"}, Description: "Classic Movie Compression Team", Schema: v2.SchemaNexusPHP, RateLimit: 0.5, RateBurst: 2, URLs: []string{"https://springsunday.net/"}, LegacyURLs: []string{"https://hdcmct.org/"}, FaviconURL: "https://springsunday.net/favicon.ico", TimezoneOffset: "+0800", Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "div.torrent-title > a[href*='details.php']", TitleLink: "div.torrent-title > a[href*='details.php']", Subtitle: "div.torrent-smalldescr > span[title]:last-of-type", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "span.torrent-pro-free, span.torrent-pro-2up, span.torrent-pro-50pctdown, span.torrent-pro-30pctdown, span.torrent-pro-2xfree", DiscountEndTime: "div.torrent-title span[style*='DimGray'] span[title]", DownloadLink: "a[href*='download.php']", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow.nowrap span[title]", DetailSubtitle: "td.rowhead:contains('副标题') + td.rowfollow", }, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/index.php", ResponseType: "document", }, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{ URL: "/userdetails.php", ResponseType: "document", }, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "seedingBonus", "joinTime", "lastAccessAt", "messageCount", }, }, { RequestConfig: v2.RequestConfig{ URL: "/mybonus.php", ResponseType: "document", }, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'] span", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "td.rowhead:contains('传输') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "bonus": { Selector: []string{ "#info_block a[href*='mybonus.php']", }, Attr: "title", Filters: []v2.Filter{ {Name: "regex", Args: []any{`茉莉[::\s]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "bonusPerHour": { Selector: []string{ "h3:contains('当前每小时') + table tbody", "table:has(th:contains('每小时茉莉')) tbody", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`我的数据</b></td>(?:<td[^>]*>[\d.,]*</td>){9}<td[^>]*>([\d.,]+)</td>`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{ "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{ "#info_block", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "seedingBonus": { Selector: []string{ "td.rowhead:contains('积分') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`做种积分[::</b>\s]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "messageCount": { Text: "0", Selector: []string{ "body", }, Attr: "html", Filters: []v2.Filter{ {Name: "sumRegexMatches", Args: []any{`你有(\d+)条新`}}, }, }, }, }, LevelRequirements: []v2.SiteLevelRequirement{ { ID: 1, Name: "新人(User)", Privilege: "新用户的默认级别。可以发种,可以请求续种;可以在做种/下载/发布的时候选择匿名模式;可以上传字幕或删除自己上传的字幕;可以更新过期的外部信息。", }, { ID: 2, Name: "精英(Elite)", NameAka: []string{"Elite", "精英"}, Downloaded: "500GB", Ratio: 1.2, Alternative: []v2.AlternativeRequirement{ {SeedingBonus: 100000, Uploads: 1}, {SeedingBonus: 150000}, }, Privilege: "可以在做种/下载/发布的时候选择匿名模式;可以查看用户列表;可以查看排行榜;可以浏览论坛邀请区;自助申请保种员;等级加成 0.05。", }, { ID: 3, Name: "大师(Master)", NameAka: []string{"Master", "大师"}, Downloaded: "1TB", Ratio: 1.2, Alternative: []v2.AlternativeRequirement{ {SeedingBonus: 500000, Uploads: 100}, {SeedingBonus: 1000000}, }, Privilege: "可以访问高级用户论坛,等级加成 0.15。", }, { ID: 4, Name: "神仙(God)", NameAka: []string{"God", "神仙"}, Downloaded: "3TB", Ratio: 2, Alternative: []v2.AlternativeRequirement{ {SeedingBonus: 1200000, Uploads: 300}, {SeedingBonus: 2400000}, }, Privilege: "彩色 ID 特权;可以查看普通日志;等级加成 0.25。", }, { ID: 5, Name: "神王(Immortal)", NameAka: []string{"Immortal", "神王"}, Privilege: "成为当月神王时奖励当时邀请茉莉价格的一半茉莉,最酷炫的动态彩色 ID 特权;常规时期可以购买及发送邀请;等级加成0.35。", }, { ID: 100, Name: "贵宾(VIP)", NameAka: []string{"VIP", "贵宾"}, GroupType: v2.LevelGroupVIP, Privilege: "贵宾(VIP)的权限和神王(Immortal)相同。贵宾(VIP)及其以上等级免除自动降级。", }, }, }
SpringSundayDefinition is the site definition for SpringSunday
var TMPTDefinition = &v2.SiteDefinition{ ID: "tmpt", Name: "TMPT", Aka: []string{"TMPT", "唐门"}, Description: "唐门影视 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://tmpt.top/"}, FaviconURL: "https://tmpt.top/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"#info_block", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(?:做种数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(?:下载数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span.subtitle", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var TangPTDefinition = &v2.SiteDefinition{ ID: "tangpt", Name: "TangPT", Aka: []string{"糖果", "TPT"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://www.tangpt.top/"}, FaviconURL: "https://www.tangpt.top/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"uploaded", "downloaded", "levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`魔力值\s*</font>\s*\[[^\]]*\]\s*[::]\s*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
TangPTDefinition 适配 www.tangpt.top(NexusPHP,CHD scenetorrents 模板,Cookie 认证)。
var U2DMHYDefinition = &v2.SiteDefinition{ ID: "u2dmhy", Name: "U2分享園@動漫花園", Aka: []string{"U2", "動漫花園"}, Description: "以动漫资源为主的 PT 站点,使用 UCoin 经济体系", Schema: v2.SchemaNexusPHP, URLs: []string{"https://u2.dmhy.org/"}, FaviconURL: "https://u2.dmhy.org/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "seeding", "leeching"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "uploaded", "downloaded", "ratio", "trueUploaded", "trueDownloaded", "bonus", "levelName", "joinTime", "lastAccessAt", }, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='_Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "#info_block a[href*='userdetails.php'][class*='_Name']", "#info_block a[href*='userdetails.php']", }, }, "seeding": { Selector: []string{"#info_block a[href*='ullist=1']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "uploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>上传量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>下载量</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>分享率</strong>[::\s]*(?:<font[^>]*>)?([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "trueUploaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际上传</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "trueDownloaded": { Selector: []string{"td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`<strong>实际下载</strong>[::\s]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "bonus": { Selector: []string{"td.rowhead:contains('UCoin') + td"}, Filters: []v2.Filter{ {Name: "regex", Args: []any{`\(([\d.,]+)\)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img"}, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a.tooltip[href*='details.php']", TitleLink: "table.torrentname a.tooltip[href*='details.php']", Subtitle: "table.torrentname span.tooltip", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_30pctdown", DiscountEndTime: "table.torrentname time[title]", Category: "td.rowfollow:nth-child(1) a[href*='cat=']", UploadTime: "td.rowfollow:nth-child(4) time[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "pro_free2up": v2.Discount2xFree, "pro_free": v2.DiscountFree, "pro_2up": v2.Discount2xUp, "pro_50pctdown": v2.DiscountPercent50, "pro_30pctdown": v2.DiscountPercent30, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "h1#top", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "td.rowhead:contains('流量优惠') + td img[class*='pro_'], td.rowhead:contains('流量優惠') + td img[class*='pro_']", EndTimeSelector: "td.rowhead:contains('流量优惠') + td time[title], td.rowhead:contains('流量優惠') + td time[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小[::][^\d]*([\d.]+)\s*([KMGTP]i?B)`, }, }
U2DMHYDefinition 描述 U2分享園@動漫花園(u2.dmhy.org)站点。
站点名称取自页面 <title>「U2分享園@動漫花園 :: 种子」,保留繁体字「園」。 搜索页是标准 NexusPHP <table class="torrents"> 布局,但详情页与用户页有四处需要专门适配:
- 详情页没有 input[name='torrent_name'] 隐藏域(只有 detail_torrent_id), 标题只存在于 h1#top 文本中,须走共享解析器的文本兜底路径。
- 「基本信息」行的第一个字段是「发布时间:」而非「大小:」,且冒号为半角、 体积单位为二进制形式(GiB / TiB),因此 SizeRegex 必须同时放宽冒号与单位。
- 促销状态在详情页由 img.pro_* 图标表示(而非 <font class="free">), 搜索页的促销剩余时间使用 <time title="..."> 而非 <span title="...">。
- 用户页标签为简体(加入日期 / 最近动向 / 传输 / 等级), 站点货币是 UCoin 而非魔力值,「原积分」行只有历史链接、没有数值。
var UBitsDefinition = &v2.SiteDefinition{ ID: "ubits", Name: "UBits", Aka: []string{"UBits", "优比特"}, Description: "综合影视 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://ubits.club/"}, FaviconURL: "https://ubits.club/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']"}, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{"a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']"}, }, "uploaded": { Selector: []string{"#info_block", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", "td.rowhead:contains('传输') + td", "td.rowhead:contains('傳輸') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio|做种/下载时间比率)[^\d∞]*([\d.,]+|∞|Inf)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{"td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img"}, Attr: "title", }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('Bonus') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "joinTime": { Selector: []string{"td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td"}, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('上次訪問') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(?:做种数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(?:下载数:)?(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span.subtitle", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run", "H&R"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, }
var XingYunGeDefinition = &v2.SiteDefinition{ ID: "xingyunge", Name: "XingYunGe", Aka: []string{"星陨阁"}, Description: "三十年河东,三十年河西!莫欺少年穷!", Schema: v2.SchemaNexusPHP, URLs: []string{"https://pt.xingyungept.org/"}, LegacyURLs: []string{"https://xingyunge.top/"}, FaviconURL: "https://pt.xingyungept.org/favicon.ico", TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{ URL: "/index.php", ResponseType: "document", }, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonusIndex"}, }, { RequestConfig: v2.RequestConfig{ URL: "/userdetails.php", ResponseType: "document", }, Assertion: map[string]string{"id": "params.id"}, Fields: []string{ "name", "uploaded", "downloaded", "ratio", "levelName", "bonus", "joinTime", "messageCount", }, }, { RequestConfig: v2.RequestConfig{ URL: "/mybonus.php", ResponseType: "document", }, Fields: []string{"bonusPerHour"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "a.User_Name[href*='userdetails.php']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{ "#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td", "td.rowhead:contains('上傳量') + td", "td.rowhead:contains('Uploaded') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{ "#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td", "td.rowhead:contains('下載量') + td", "td.rowhead:contains('Downloaded') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{ "#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td font", "td.rowhead:contains('分享率') + td", "td.rowhead:contains('Ratio') + td font", "td.rowhead:contains('Ratio') + td", }, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio)[^0-9]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td > img", "td.rowhead:contains('等級') + td > img", "td.rowhead:contains('Class') + td > img", "td.rowhead:contains('等级') + td img", }, Attr: "title", }, "bonusIndex": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`星[焰焱]\s*</font>\s*\[<a[^>]*>使用</a>\][::\s]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{ "td.rowhead:contains('魔力值') + td", "td.rowhead:contains('星焱') + td", "td.rowhead:contains('Bonus') + td", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "bonusPerHour": { Selector: []string{ "#outer td[rowspan]", "div:contains('你当前每小时能获取')", }, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "messageCount": { Text: "0", Selector: []string{"td[style*='background: red'] a[href*='messages.php']"}, Filters: []v2.Filter{{Name: "parseNumber"}}, }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "split", Args: []any{" (", 0}}, {Name: "parseTime"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>(\d+)`}}, {Name: "parseNumber"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:not(.optiontag)", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_2up, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "h1", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowfollow:contains('大小')", SizeRegex: `大小[::]\s*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: xingYunGeLevelRequirements, }
var ZmptDefinition = &v2.SiteDefinition{ ID: "zmpt", Name: "ZmPT", Aka: []string{"织梦", "ZM"}, Description: "综合性 PT 站点", Schema: v2.SchemaNexusPHP, URLs: []string{"https://zmpt.cc/"}, FaviconURL: "https://zmpt.cc/favicon.ico", AuthMethod: v2.AuthMethodCookie, TimezoneOffset: "+0800", RateLimit: 0.5, RateBurst: 2, UserInfo: &v2.UserInfoConfig{ PickLast: []string{"id"}, RequestDelay: 500, Process: []v2.UserInfoProcess{ { RequestConfig: v2.RequestConfig{URL: "/index.php", ResponseType: "document"}, Fields: []string{"id", "name", "uploaded", "downloaded", "ratio", "seeding", "leeching", "bonus"}, }, { RequestConfig: v2.RequestConfig{URL: "/userdetails.php", ResponseType: "document"}, Assertion: map[string]string{"id": "params.id"}, Fields: []string{"levelName", "joinTime", "lastAccessAt"}, }, }, Selectors: map[string]v2.FieldSelector{ "id": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", "a[href*='userdetails.php']", }, Attr: "href", Filters: []v2.Filter{{Name: "querystring", Args: []any{"id"}}}, }, "name": { Selector: []string{ "a[href*='userdetails.php'][class*='Name']", "#info_block a[href*='userdetails.php']", }, }, "uploaded": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('上传量') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:上传量|上傳量|Uploaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "downloaded": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('下载量') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:下载量|下載量|Downloaded)[^\d]*([\d.,]+\s*[KMGTP]?i?B)`}}, {Name: "parseSize"}, }, }, "ratio": { Selector: []string{"#info_block", "td.rowhead:contains('传输') + td", "td.rowhead:contains('分享率') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:分享率|Ratio)[^\d∞]*([\d.,]+|∞|Inf|---)`}}, {Name: "parseNumber"}, }, }, "bonus": { Selector: []string{"#info_block", "td.rowhead:contains('电力值') + td", "td.rowhead:contains('魔力值') + td"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`(?:电力值|電力值|魔力值|Bonus)[^\d]*([\d.,]+)`}}, {Name: "parseNumber"}, }, }, "seeding": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowup"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "leeching": { Selector: []string{"#info_block"}, Attr: "html", Filters: []v2.Filter{ {Name: "regex", Args: []any{`class="arrowdown"[^>]*/>\s*(\d+)`}}, {Name: "parseNumber"}, }, }, "levelName": { Selector: []string{ "td.rowhead:contains('等级') + td img", "td.rowhead:contains('等級') + td img", "td.rowhead:contains('Class') + td img", }, Attr: "title", }, "joinTime": { Selector: []string{ "td.rowhead:contains('加入日期') + td", "td.rowhead:contains('加入時間') + td", "td.rowhead:contains('Join') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, "lastAccessAt": { Selector: []string{ "td.rowhead:contains('最近动向') + td", "td.rowhead:contains('最近動向') + td", "td.rowhead:contains('最后活动') + td", "td.rowhead:contains('最後活動') + td", "td.rowhead:contains('上次访问') + td", "td.rowhead:contains('Last access') + td", }, Filters: []v2.Filter{ {Name: "regex", Args: []any{`(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})`}}, {Name: "parseTime"}, }, }, }, }, Selectors: &v2.SiteSelectors{ TableRows: "table.torrents > tbody > tr:has(table.torrentname), table.torrents > tr:has(table.torrentname)", Title: "table.torrentname a[href*='details.php']", TitleLink: "table.torrentname a[href*='details.php']", Subtitle: "table.torrentname td.embedded > span:last-of-type, table.torrentname td.embedded > span", Size: "td.rowfollow:nth-child(5)", Seeders: "td.rowfollow:nth-child(6)", Leechers: "td.rowfollow:nth-child(7)", Snatched: "td.rowfollow:nth-child(8)", DiscountIcon: "img.pro_free, img.pro_free2up, img.pro_2up, img.pro_50pctdown, img.pro_30pctdown, img.pro_50pctdown2up", Category: "td.rowfollow:nth-child(1) img[alt]", UploadTime: "td.rowfollow:nth-child(4) span[title]", DetailDownloadLink: "td.rowhead:contains('下载') + td a[href*='download.php']", DetailSubtitle: "td.rowhead:contains('副标题') + td", }, DetailParser: &v2.DetailParserConfig{ TimeLayout: "2006-01-02 15:04:05", DiscountMapping: map[string]v2.DiscountLevel{ "free": v2.DiscountFree, "twoup": v2.Discount2xUp, "twoupfree": v2.Discount2xFree, "thirtypercent": v2.DiscountPercent30, "halfdown": v2.DiscountPercent50, "twouphalfdown": v2.Discount2x50, }, HRKeywords: []string{"hitandrun", "hit_run.gif", "Hit and Run", "Hit & Run"}, TitleSelector: "input[name='torrent_name']", IDSelector: "input[name='detail_torrent_id']", DiscountSelector: "h1 font.free, h1 font[class]", EndTimeSelector: "h1 span[title]", SizeSelector: "td.rowhead:contains('基本信息')", SizeRegex: `大小:[^\d]*([\d.]+)\s*(GB|MB|KB|TB)`, }, LevelRequirements: []v2.SiteLevelRequirement{ {ID: 1, Name: "User", NameAka: []string{"新人"}}, }, }
ZmptDefinition is the site definition for ZmPT (zmpt.cc, 织梦), a vanilla NexusPHP site. UserInfo stats are read from the top-of-page #info_block (color-class fonts); note ZmPT labels bonus as「电力值」instead of「魔力值」.
Functions ¶
func GetHDSkyLevelRequirements ¶
func GetHDSkyLevelRequirements(joinTime int64) []v2.SiteLevelRequirement
GetHDSkyLevelRequirements returns appropriate level requirements based on join date
func GetMTeamLevelName ¶
GetMTeamLevelName returns level name from role ID
Types ¶
This section is empty.
Source Files
¶
- agsvpt.go
- audiences.go
- baozi.go
- btschool.go
- byrpt.go
- carpt.go
- crabpt.go
- cspt.go
- cyanbug.go
- discfan.go
- dubhe.go
- duckboobee.go
- eastgame.go
- et8.go
- fiveTwoPT.go
- gamegamept.go
- gtkpw.go
- hdarea.go
- hddolby.go
- hdfans.go
- hdhome.go
- hdsky.go
- hdtime.go
- hdvideo.go
- hhanclub.go
- hxpt.go
- kamept.go
- keepfrds.go
- lajidui.go
- lemonhd.go
- longpt.go
- luckpt.go
- mooko.go
- mteam.go
- mua.go
- myptcc.go
- nicept.go
- novahd.go
- onepatba.go
- opencd.go
- ourbits.go
- pandapt.go
- pt0ffcc.go
- ptcafe.go
- ptchdbits.go
- pterclub.go
- ptfans.go
- pthome.go
- ptlgs.go
- ptlover.go
- ptskit.go
- pttime.go
- qingwapt.go
- railgunpt.go
- raingfh.go
- rousipro.go
- soulvoice.go
- springsunday.go
- tangpt.go
- tmpt.go
- u2dmhy.go
- ubits.go
- xingyunge.go
- zmpt.go