subhd.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. package subhd
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "image/jpeg"
  7. "math"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "regexp"
  12. "strings"
  13. "time"
  14. "github.com/allanpk716/ChineseSubFinder/pkg/types/common"
  15. "github.com/allanpk716/ChineseSubFinder/pkg/types/language"
  16. "github.com/allanpk716/ChineseSubFinder/pkg/types/series"
  17. "github.com/allanpk716/ChineseSubFinder/pkg/types/supplier"
  18. "github.com/allanpk716/ChineseSubFinder/pkg/logic/file_downloader"
  19. "github.com/allanpk716/ChineseSubFinder/pkg/rod_helper"
  20. "github.com/allanpk716/ChineseSubFinder/pkg/mix_media_info"
  21. "github.com/PuerkitoBio/goquery"
  22. "github.com/Tnze/go.num/v2/zh"
  23. "github.com/allanpk716/ChineseSubFinder/pkg/decode"
  24. "github.com/allanpk716/ChineseSubFinder/pkg/global_value"
  25. "github.com/allanpk716/ChineseSubFinder/pkg/my_util"
  26. "github.com/allanpk716/ChineseSubFinder/pkg/notify_center"
  27. "github.com/allanpk716/ChineseSubFinder/pkg/settings"
  28. "github.com/allanpk716/ChineseSubFinder/pkg/sub_parser_hub"
  29. "github.com/allanpk716/ChineseSubFinder/pkg/url_connectedness_helper"
  30. "github.com/go-rod/rod"
  31. "github.com/go-rod/rod/lib/proto"
  32. "github.com/nfnt/resize"
  33. "github.com/sirupsen/logrus"
  34. )
  35. type Supplier struct {
  36. settings *settings.Settings
  37. log *logrus.Logger
  38. fileDownloader *file_downloader.FileDownloader
  39. topic int
  40. tt time.Duration
  41. debugMode bool
  42. isAlive bool
  43. }
  44. func NewSupplier(fileDownloader *file_downloader.FileDownloader) *Supplier {
  45. sup := Supplier{}
  46. sup.log = fileDownloader.Log
  47. sup.fileDownloader = fileDownloader
  48. sup.topic = common.DownloadSubsPerSite
  49. sup.settings = fileDownloader.Settings
  50. if sup.settings.AdvancedSettings.Topic > 0 && sup.settings.AdvancedSettings.Topic != sup.topic {
  51. sup.topic = sup.settings.AdvancedSettings.Topic
  52. }
  53. sup.isAlive = true // 默认是可以使用的,如果 check 后,再调整状态
  54. // 默认超时是 2 * 60s,如果是调试模式则是 5 min
  55. sup.tt = common.BrowserTimeOut
  56. sup.debugMode = sup.settings.AdvancedSettings.DebugMode
  57. if sup.debugMode == true {
  58. sup.tt = common.OneMovieProcessTimeOut
  59. }
  60. return &sup
  61. }
  62. func (s *Supplier) CheckAlive() (bool, int64) {
  63. proxyStatus, proxySpeed, err := url_connectedness_helper.UrlConnectednessTest(s.settings.AdvancedSettings.SuppliersSettings.SubHD.RootUrl,
  64. s.settings.AdvancedSettings.ProxySettings.GetLocalHttpProxyUrl())
  65. if err != nil {
  66. s.log.Errorln(s.GetSupplierName(), "CheckAlive", "Error", err)
  67. s.isAlive = false
  68. return false, 0
  69. }
  70. if proxyStatus == false {
  71. s.log.Errorln(s.GetSupplierName(), "CheckAlive", "Status != 200")
  72. s.isAlive = false
  73. return false, proxySpeed
  74. }
  75. s.isAlive = true
  76. return true, proxySpeed
  77. }
  78. func (s *Supplier) IsAlive() bool {
  79. return s.isAlive
  80. }
  81. func (s *Supplier) OverDailyDownloadLimit() bool {
  82. if s.settings.AdvancedSettings.SuppliersSettings.SubHD.DailyDownloadLimit == 0 {
  83. s.log.Warningln(s.GetSupplierName(), "DailyDownloadLimit is 0, will Skip Download")
  84. return true
  85. }
  86. // 需要查询今天的限额
  87. count, err := s.fileDownloader.CacheCenter.DailyDownloadCountGet(s.GetSupplierName(),
  88. my_util.GetPublicIP(s.log, s.settings.AdvancedSettings.TaskQueue, s.settings.AdvancedSettings.ProxySettings))
  89. if err != nil {
  90. s.log.Errorln(s.GetSupplierName(), "DailyDownloadCountGet", err)
  91. return true
  92. }
  93. if count >= s.settings.AdvancedSettings.SuppliersSettings.Zimuku.DailyDownloadLimit {
  94. // 超限了
  95. s.log.Warningln(s.GetSupplierName(), "DailyDownloadLimit:", s.settings.AdvancedSettings.SuppliersSettings.SubHD.DailyDownloadLimit, "Now Is:", count)
  96. return true
  97. } else {
  98. // 没有超限
  99. s.log.Infoln(s.GetSupplierName(), "DailyDownloadLimit:", s.settings.AdvancedSettings.SuppliersSettings.Zimuku.DailyDownloadLimit, "Now Is:", count)
  100. return false
  101. }
  102. }
  103. func (s *Supplier) GetLogger() *logrus.Logger {
  104. return s.log
  105. }
  106. func (s *Supplier) GetSettings() *settings.Settings {
  107. return s.settings
  108. }
  109. func (s *Supplier) GetSupplierName() string {
  110. return common.SubSiteSubHd
  111. }
  112. func (s *Supplier) GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
  113. return s.getSubListFromFile4Movie(filePath)
  114. }
  115. func (s *Supplier) GetSubListFromFile4Series(seriesInfo *series.SeriesInfo) ([]supplier.SubInfo, error) {
  116. var browser *rod.Browser
  117. // TODO 是用本地的 Browser 还是远程的,推荐是远程的
  118. browser, err := rod_helper.NewBrowserEx(s.log, true, s.settings)
  119. if err != nil {
  120. return nil, err
  121. }
  122. defer func() {
  123. _ = browser.Close()
  124. }()
  125. mediaInfo, err := mix_media_info.GetMixMediaInfo(s.log, s.fileDownloader.SubtitleBestApi,
  126. seriesInfo.EpList[0].FileFullPath, false,
  127. s.settings.AdvancedSettings.ProxySettings)
  128. if err != nil {
  129. s.log.Errorln(s.GetSupplierName(), seriesInfo.EpList[0].FileFullPath, "GetMixMediaInfo", err)
  130. return nil, err
  131. }
  132. // 优先中文查询
  133. keyWord, err := mix_media_info.KeyWordSelect(mediaInfo, seriesInfo.EpList[0].FileFullPath, true, "cn")
  134. if err != nil {
  135. s.log.Errorln(s.GetSupplierName(), seriesInfo.EpList[0].FileFullPath, "keyWordSelect", err)
  136. return nil, err
  137. }
  138. if keyWord == "" {
  139. // 更换英文译名
  140. keyWord, err = mix_media_info.KeyWordSelect(mediaInfo, seriesInfo.EpList[0].FileFullPath, true, "en")
  141. if err != nil {
  142. s.log.Errorln(s.GetSupplierName(), seriesInfo.EpList[0].FileFullPath, "keyWordSelect", err)
  143. return nil, err
  144. }
  145. }
  146. var subInfos = make([]supplier.SubInfo, 0)
  147. var subList = make([]HdListItem, 0)
  148. for value := range seriesInfo.NeedDlSeasonDict {
  149. // 第一级界面,找到影片的详情界面
  150. //keyword := seriesInfo.Name + " 第" + zh.Uint64(value).String() + "季"
  151. keyword := keyWord + " 第" + zh.Uint64(value).String() + "季"
  152. s.log.Infoln("Search Keyword:", keyword)
  153. detailPageUrl, err := s.step0(browser, keyword)
  154. if err != nil {
  155. s.log.Errorln("subhd step0", keyword)
  156. return nil, err
  157. }
  158. if detailPageUrl == "" {
  159. // 如果只是搜索不到,则继续换关键词
  160. s.log.Warning("subhd first search keyword", keyword, "not found")
  161. keyword = seriesInfo.Name
  162. s.log.Warning("subhd Retry", keyword)
  163. s.log.Infoln("Search Keyword:", keyword)
  164. detailPageUrl, err = s.step0(browser, keyword)
  165. if err != nil {
  166. s.log.Errorln("subhd step0", keyword)
  167. return nil, err
  168. }
  169. }
  170. if detailPageUrl == "" {
  171. s.log.Warning("subhd search keyword", keyword, "not found")
  172. continue
  173. }
  174. // 列举字幕
  175. oneSubList, err := s.step1(browser, detailPageUrl, false)
  176. if err != nil {
  177. s.log.Errorln("subhd step1", keyword)
  178. return nil, err
  179. }
  180. subList = append(subList, oneSubList...)
  181. }
  182. // 与剧集需要下载的集 List 进行比较,找到需要下载的列表
  183. // 找到那些 Eps 需要下载字幕的
  184. subInfoNeedDownload := s.whichEpisodeNeedDownloadSub(seriesInfo, subList)
  185. // 下载字幕
  186. for i, item := range subInfoNeedDownload {
  187. subInfo, err := s.fileDownloader.GetEx(s.GetSupplierName(), browser, item.Url, int64(i), item.Season, item.Episode, s.DownFile)
  188. if err != nil {
  189. s.log.Errorln(s.GetSupplierName(), "GetEx", item.Title, item.Season, item.Episode, err)
  190. continue
  191. }
  192. subInfos = append(subInfos, *subInfo)
  193. }
  194. return subInfos, nil
  195. }
  196. func (s *Supplier) GetSubListFromFile4Anime(seriesInfo *series.SeriesInfo) ([]supplier.SubInfo, error) {
  197. panic("not implemented")
  198. }
  199. func (s *Supplier) getSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
  200. /*
  201. 虽然是传入视频文件路径,但是其实需要读取对应的视频文件目录下的
  202. movie.xml 以及 *.nfo,找到 IMDB id
  203. 优先通过 IMDB id 去查找字幕
  204. 如果找不到,再靠文件名提取影片名称去查找
  205. */
  206. // 找到这个视频文件,尝试得到 IMDB ID
  207. // 目前测试来看,加入 年 这个关键词去搜索,对 2020 年后的影片有利,因为网站有统一的详细页面了,而之前的,没有,会影响识别
  208. // 所以,year >= 2020 年,则可以多加一个关键词(年)去搜索影片
  209. imdbInfo, err := decode.GetVideoNfoInfo4Movie(filePath)
  210. if err != nil {
  211. // 允许的错误,跳过,继续进行文件名的搜索
  212. s.log.Errorln("model.GetImdbInfo", err)
  213. }
  214. var subInfoList []supplier.SubInfo
  215. if imdbInfo.ImdbId != "" {
  216. // 先用 imdb id 找
  217. subInfoList, err = s.getSubListFromKeyword4Movie(imdbInfo.ImdbId)
  218. if err != nil {
  219. // 允许的错误,跳过,继续进行文件名的搜索
  220. s.log.Errorln(s.GetSupplierName(), "keyword:", imdbInfo.ImdbId)
  221. s.log.Errorln("getSubListFromKeyword4Movie", "IMDBID can not found sub", filePath, err)
  222. }
  223. // 如果有就优先返回
  224. if len(subInfoList) > 0 {
  225. return subInfoList, nil
  226. }
  227. }
  228. s.log.Infoln(s.GetSupplierName(), filePath, "No subtitle found", "KeyWord:", imdbInfo.ImdbId)
  229. mediaInfo, err := mix_media_info.GetMixMediaInfo(s.log, s.fileDownloader.SubtitleBestApi,
  230. filePath, true,
  231. s.settings.AdvancedSettings.ProxySettings)
  232. if err != nil {
  233. s.log.Errorln(s.GetSupplierName(), filePath, "GetMixMediaInfo", err)
  234. return nil, err
  235. }
  236. // 优先中文查询
  237. keyWord, err := mix_media_info.KeyWordSelect(mediaInfo, filePath, true, "cn")
  238. if err != nil {
  239. s.log.Errorln(s.GetSupplierName(), filePath, "keyWordSelect", err)
  240. return nil, err
  241. }
  242. // 如果没有,那么就用文件名查找
  243. searchKeyword := my_util.VideoNameSearchKeywordMaker(s.log, keyWord, imdbInfo.Year)
  244. subInfoList, err = s.getSubListFromKeyword4Movie(searchKeyword)
  245. if err != nil {
  246. s.log.Errorln(s.GetSupplierName(), "keyword:", searchKeyword)
  247. return nil, err
  248. }
  249. if len(subInfoList) < 1 {
  250. // 切换到英文查询
  251. s.log.Infoln(s.GetSupplierName(), filePath, "No subtitle found", "KeyWord:", searchKeyword)
  252. keyWord, err = mix_media_info.KeyWordSelect(mediaInfo, filePath, true, "cn")
  253. if err != nil {
  254. s.log.Errorln(s.GetSupplierName(), filePath, "keyWordSelect", err)
  255. return nil, err
  256. }
  257. // 如果没有,那么就用文件名查找
  258. searchKeyword = my_util.VideoNameSearchKeywordMaker(s.log, keyWord, imdbInfo.Year)
  259. subInfoList, err = s.getSubListFromKeyword4Movie(searchKeyword)
  260. if err != nil {
  261. s.log.Errorln(s.GetSupplierName(), "keyword:", searchKeyword)
  262. return nil, err
  263. }
  264. if len(subInfoList) < 1 {
  265. s.log.Infoln(s.GetSupplierName(), filePath, "No subtitle found", "KeyWord:", searchKeyword)
  266. }
  267. }
  268. return subInfoList, nil
  269. }
  270. func (s *Supplier) getSubListFromKeyword4Movie(keyword string) ([]supplier.SubInfo, error) {
  271. s.log.Infoln("Search Keyword:", keyword)
  272. var browser *rod.Browser
  273. // TODO 是用本地的 Browser 还是远程的,推荐是远程的
  274. browser, err := rod_helper.NewBrowserEx(s.log, true, s.settings)
  275. if err != nil {
  276. return nil, err
  277. }
  278. defer func() {
  279. _ = browser.Close()
  280. }()
  281. var subInfos []supplier.SubInfo
  282. detailPageUrl, err := s.step0(browser, keyword)
  283. if err != nil {
  284. return nil, err
  285. }
  286. // 没有搜索到字幕
  287. if detailPageUrl == "" {
  288. return nil, nil
  289. }
  290. subList, err := s.step1(browser, detailPageUrl, true)
  291. if err != nil {
  292. return nil, err
  293. }
  294. for i, item := range subList {
  295. subInfo, err := s.fileDownloader.GetEx(s.GetSupplierName(), browser, item.Url, int64(i), 0, 0, s.DownFile)
  296. if err != nil {
  297. s.log.Errorln(s.GetSupplierName(), "GetEx", item.Title, item.Season, item.Episode, err)
  298. continue
  299. }
  300. subInfos = append(subInfos, *subInfo)
  301. }
  302. return subInfos, nil
  303. }
  304. func (s *Supplier) whichEpisodeNeedDownloadSub(seriesInfo *series.SeriesInfo, allSubList []HdListItem) []HdListItem {
  305. // 字幕很多,考虑效率,需要做成字典
  306. // key SxEx - SubInfos
  307. var allSubDict = make(map[string][]HdListItem)
  308. // 全季的字幕列表
  309. var oneSeasonSubDict = make(map[string][]HdListItem)
  310. for _, subInfo := range allSubList {
  311. _, season, episode, err := decode.GetSeasonAndEpisodeFromSubFileName(subInfo.Title)
  312. if err != nil {
  313. s.log.Errorln("whichEpisodeNeedDownloadSub.GetVideoInfoFromFileFullPath", subInfo.Title, err)
  314. continue
  315. }
  316. subInfo.Season = season
  317. subInfo.Episode = episode
  318. epsKey := my_util.GetEpisodeKeyName(season, episode)
  319. _, ok := allSubDict[epsKey]
  320. if ok == false {
  321. // 初始化
  322. allSubDict[epsKey] = make([]HdListItem, 0)
  323. if season != 0 && episode == 0 {
  324. oneSeasonSubDict[epsKey] = make([]HdListItem, 0)
  325. }
  326. }
  327. // 添加
  328. allSubDict[epsKey] = append(allSubDict[epsKey], subInfo)
  329. if season != 0 && episode == 0 {
  330. oneSeasonSubDict[epsKey] = append(oneSeasonSubDict[epsKey], subInfo)
  331. }
  332. }
  333. // 本地的视频列表,找到没有字幕的
  334. // 需要进行下载字幕的列表
  335. var subInfoNeedDownload = make([]HdListItem, 0)
  336. // 有那些 Eps 需要下载的,按 SxEx 反回 epsKey
  337. for epsKey, epsInfo := range seriesInfo.NeedDlEpsKeyList {
  338. // 从一堆字幕里面找合适的
  339. value, ok := allSubDict[epsKey]
  340. // 是否有
  341. if ok == true && len(value) > 0 {
  342. value[0].Season = epsInfo.Season
  343. value[0].Episode = epsInfo.Episode
  344. subInfoNeedDownload = append(subInfoNeedDownload, value[0])
  345. }
  346. }
  347. // 全季的字幕列表,也拼进去,后面进行下载
  348. for _, infos := range oneSeasonSubDict {
  349. if len(infos) < 1 {
  350. continue
  351. }
  352. subInfoNeedDownload = append(subInfoNeedDownload, infos[0])
  353. }
  354. // 返回前,需要把每一个 Eps 的 Season Episode 信息填充到每个 SubInfo 中
  355. return subInfoNeedDownload
  356. }
  357. // step0 找到这个影片的详情列表
  358. func (s *Supplier) step0(browser *rod.Browser, keyword string) (string, error) {
  359. var err error
  360. defer func() {
  361. if err != nil {
  362. notify_center.Notify.Add("subhd_step0", err.Error())
  363. }
  364. }()
  365. result, page, err := rod_helper.HttpGetFromBrowser(browser, fmt.Sprintf(s.settings.AdvancedSettings.SuppliersSettings.SubHD.RootUrl+common.SubSubHDSearchUrl, url.QueryEscape(keyword)), s.tt)
  366. if err != nil {
  367. return "", err
  368. }
  369. defer func() {
  370. _ = page.Close()
  371. }()
  372. // 是否有查找到的结果,至少要有结果。根据这里这样下面才能判断是分析失效了,还是就是没有结果而已
  373. re := regexp.MustCompile(`共\s*(\d+)\s*条`)
  374. matched := re.FindAllStringSubmatch(result, -1)
  375. if matched == nil || len(matched) < 1 {
  376. return "", common.SubHDStep0SubCountElementNotFound
  377. }
  378. subCount, err := decode.GetNumber2int(matched[0][0])
  379. if err != nil {
  380. return "", err
  381. }
  382. // 如果所搜没有找到字幕,就要返回
  383. if subCount < 1 {
  384. return "", nil
  385. }
  386. // 这里是确认能继续分析的详细连接
  387. doc, err := goquery.NewDocumentFromReader(strings.NewReader(result))
  388. if err != nil {
  389. return "", err
  390. }
  391. imgSelection := doc.Find("img.rounded-start")
  392. _, ok := imgSelection.Attr("src")
  393. if ok == true {
  394. if len(imgSelection.Nodes) < 1 {
  395. return "", common.SubHDStep0ImgParentLessThan1
  396. }
  397. step1Url := ""
  398. if imgSelection.Nodes[0].Parent.Data == "a" {
  399. // 第一个父级是不是超链接
  400. for _, attribute := range imgSelection.Nodes[0].Parent.Attr {
  401. if attribute.Key == "href" {
  402. step1Url = attribute.Val
  403. break
  404. }
  405. }
  406. } else if imgSelection.Nodes[0].Parent.Parent.Data == "a" {
  407. // 第二个父级是不是超链接
  408. for _, attribute := range imgSelection.Nodes[0].Parent.Parent.Attr {
  409. if attribute.Key == "href" {
  410. step1Url = attribute.Val
  411. break
  412. }
  413. }
  414. }
  415. if step1Url == "" {
  416. return "", common.SubHDStep0HrefIsNull
  417. }
  418. return step1Url, nil
  419. } else {
  420. return "", common.SubHDStep0HrefIsNull
  421. }
  422. }
  423. // step1 获取影片的详情字幕列表
  424. func (s *Supplier) step1(browser *rod.Browser, detailPageUrl string, isMovieOrSeries bool) ([]HdListItem, error) {
  425. var err error
  426. defer func() {
  427. if err != nil {
  428. notify_center.Notify.Add("subhd_step1", err.Error())
  429. }
  430. }()
  431. detailPageUrl = my_util.AddBaseUrl(s.settings.AdvancedSettings.SuppliersSettings.SubHD.RootUrl, detailPageUrl)
  432. result, page, err := rod_helper.HttpGetFromBrowser(browser, detailPageUrl, s.tt)
  433. if err != nil {
  434. return nil, err
  435. }
  436. defer func() {
  437. _ = page.Close()
  438. }()
  439. doc, err := goquery.NewDocumentFromReader(strings.NewReader(result))
  440. if err != nil {
  441. return nil, err
  442. }
  443. var lists []HdListItem
  444. const subTableKeyword = ".pt-2"
  445. const oneSubTrTitleKeyword = "a.link-dark"
  446. const oneSubTrDownloadCountKeyword = "div.px-3"
  447. const oneSubLangAndTypeKeyword = ".text-secondary"
  448. doc.Find(subTableKeyword).EachWithBreak(func(i int, tr *goquery.Selection) bool {
  449. if tr.Find(oneSubTrTitleKeyword).Size() == 0 {
  450. return true
  451. }
  452. // 文件的下载页面,还需要分析
  453. downUrl, exists := tr.Find(oneSubTrTitleKeyword).Eq(0).Attr("href")
  454. if !exists {
  455. return true
  456. }
  457. // 文件名
  458. title := strings.TrimSpace(tr.Find(oneSubTrTitleKeyword).Text())
  459. // 字幕类型
  460. insideSubType := tr.Find(oneSubLangAndTypeKeyword).Text()
  461. if sub_parser_hub.IsSubTypeWanted(insideSubType) == false {
  462. return true
  463. }
  464. // 下载的次数
  465. downCount, err := decode.GetNumber2int(tr.Find(oneSubTrDownloadCountKeyword).Eq(1).Text())
  466. if err != nil {
  467. return true
  468. }
  469. listItem := HdListItem{}
  470. listItem.Url = downUrl
  471. listItem.BaseUrl = s.settings.AdvancedSettings.SuppliersSettings.SubHD.RootUrl
  472. listItem.Title = title
  473. listItem.DownCount = downCount
  474. // 电影,就需要第一个
  475. // 连续剧,需要多个
  476. if isMovieOrSeries == true {
  477. if len(lists) >= s.topic {
  478. return false
  479. }
  480. }
  481. lists = append(lists, listItem)
  482. return true
  483. })
  484. return lists, nil
  485. }
  486. // DownFile 下载字幕 过防水墙
  487. func (s *Supplier) DownFile(browser *rod.Browser, subDownloadPageUrl string, TopN int64, Season, Episode int) (*supplier.SubInfo, error) {
  488. var err error
  489. defer func() {
  490. if err != nil {
  491. notify_center.Notify.Add("subhd_DownFile", err.Error())
  492. }
  493. }()
  494. subDownloadPageFullUrl := my_util.AddBaseUrl(s.settings.AdvancedSettings.SuppliersSettings.SubHD.RootUrl, subDownloadPageUrl)
  495. _, page, err := rod_helper.HttpGetFromBrowser(browser, subDownloadPageFullUrl, s.tt)
  496. if err != nil {
  497. return nil, err
  498. }
  499. defer func() {
  500. _ = page.Close()
  501. }()
  502. // 需要先判断是否先要输入验证码,然后才到下载界面
  503. // 下载字幕
  504. subInfo, err := s.downloadSubFile(browser, page, subDownloadPageUrl)
  505. if err != nil {
  506. return nil, err
  507. }
  508. subInfo.TopN = TopN
  509. subInfo.Season = Season
  510. subInfo.Episode = Episode
  511. return subInfo, nil
  512. }
  513. func (s *Supplier) downloadSubFile(browser *rod.Browser, page *rod.Page, subDownloadPageUrl string) (*supplier.SubInfo, error) {
  514. var err error
  515. var doc *goquery.Document
  516. downloadSuccess := false
  517. fileName := ""
  518. fileByte := []byte{0}
  519. err = rod.Try(func() {
  520. tmpDir := filepath.Join(global_value.DefTmpFolder(), "downloads")
  521. wait := browser.Timeout(30 * time.Second).WaitDownload(tmpDir)
  522. getDownloadFile := func() ([]byte, string, error) {
  523. info := wait()
  524. if info == nil {
  525. return nil, "", errors.New("download sub timeout")
  526. }
  527. downloadPath := filepath.Join(tmpDir, info.GUID)
  528. defer func() { _ = os.Remove(downloadPath) }()
  529. b, err := os.ReadFile(downloadPath)
  530. if err != nil {
  531. return nil, "", err
  532. }
  533. return b, info.SuggestedFilename, nil
  534. }
  535. // 初始化页面用于查询元素
  536. pString := page.MustHTML()
  537. doc, err = goquery.NewDocumentFromReader(strings.NewReader(pString))
  538. if err != nil {
  539. return
  540. }
  541. // 点击“验证获取下载地址”
  542. s.log.Debugln("click '验证获取下载地址'")
  543. clickCodeBtn := doc.Find(btnClickCodeBtn)
  544. if len(clickCodeBtn.Nodes) < 1 {
  545. return
  546. }
  547. element := page.MustElement(btnClickCodeBtn)
  548. findInputCode, err := page.Element(InputCode)
  549. if err != nil {
  550. return
  551. }
  552. if findInputCode != nil {
  553. s.log.Debugln("find '验证' 关键词")
  554. // 那么需要填写验证码
  555. element.MustClick()
  556. time.Sleep(time.Second * 2)
  557. // 填写“验证码”
  558. s.log.Debugln("填写验证码")
  559. el := page.MustElement(InputCode)
  560. el.MustInput(common.SubhdCode)
  561. //page.MustEval(`$("#gzhcode").attr("value","` + common2.SubhdCode + `");`)
  562. // 是否有“完成验证”按钮
  563. s.log.Debugln("查找是否有交验证码按钮1")
  564. downBtn := doc.Find(btnCommitCode)
  565. if len(downBtn.Nodes) < 1 {
  566. return
  567. }
  568. s.log.Debugln("查找是否有交验证码按钮2")
  569. element = page.MustElement(btnCommitCode)
  570. benCommit := element.MustText()
  571. if strings.Contains(benCommit, "验证") == false {
  572. s.log.Errorln("btn not found 完整验证")
  573. return
  574. }
  575. s.log.Debugln("点击提交验证码")
  576. element.MustClick()
  577. time.Sleep(time.Second * 2)
  578. s.log.Debugln("点击下载按钮")
  579. // 点击下载按钮
  580. page.MustElement(btnClickCodeBtn).MustClick()
  581. time.Sleep(time.Second * 2)
  582. } else {
  583. s.log.Debugln("点击下载按钮")
  584. // 直接可以下载
  585. element.MustClick()
  586. time.Sleep(time.Second * 2)
  587. }
  588. // 更新 page 的实例对应的 doc Content
  589. pString = page.MustHTML()
  590. doc, err = goquery.NewDocumentFromReader(strings.NewReader(pString))
  591. if err != nil {
  592. return
  593. }
  594. // 是否有腾讯的防水墙
  595. hasWaterWall := false
  596. waterWall := doc.Find(TCode)
  597. if len(waterWall.Nodes) >= 1 {
  598. hasWaterWall = true
  599. }
  600. s.log.Debugln("Need pass WaterWall", hasWaterWall)
  601. // 过墙
  602. if hasWaterWall == true {
  603. s.passWaterWall(page)
  604. }
  605. fileByte, fileName, err = getDownloadFile()
  606. if err != nil {
  607. return
  608. }
  609. downloadSuccess = true
  610. })
  611. if err != nil {
  612. return nil, err
  613. }
  614. if downloadSuccess == false {
  615. return nil, common.SubHDStep2ExCannotFindDownloadBtn
  616. }
  617. inSubInfo := supplier.NewSubInfo(s.GetSupplierName(), 1, fileName, language.ChineseSimple, subDownloadPageUrl, 0, 0, filepath.Ext(fileName), fileByte)
  618. return inSubInfo, nil
  619. }
  620. func (s *Supplier) passWaterWall(page *rod.Page) {
  621. const (
  622. waterIFrame = "#tcaptcha_iframe"
  623. dragBtn = "#tcaptcha_drag_button"
  624. slideBg = "#slideBg"
  625. )
  626. //等待驗證碼窗體載入
  627. page.MustElement(waterIFrame).MustWaitLoad()
  628. //進入到iframe
  629. iframe := page.MustElement(waterIFrame).MustFrame()
  630. // see iframe bug, see https://github.com/go-rod/rod/issues/548
  631. p := page.Browser().MustPageFromTargetID(proto.TargetTargetID(iframe.FrameID))
  632. //等待拖動條加載, 延遲500秒檢測變化, 以確認加載完畢
  633. p.MustElement(dragBtn).MustWaitStable()
  634. //等待缺口圖像載入
  635. slideBgEl := p.MustElement(slideBg).MustWaitLoad()
  636. slideBgEl = slideBgEl.MustWaitStable()
  637. //取得帶缺口圖像
  638. shadowbg := slideBgEl.MustResource()
  639. // 取得原始圖像
  640. src := slideBgEl.MustProperty("src")
  641. fullbg, _, err := my_util.DownFile(s.log, strings.Replace(src.String(), "img_index=1", "img_index=0", 1))
  642. if err != nil {
  643. s.log.Errorln("passWaterWall.DownFile", err)
  644. return
  645. }
  646. //取得img展示的真實尺寸
  647. shape, err := slideBgEl.Shape()
  648. if err != nil {
  649. s.log.Errorln("passWaterWall.Shape", err)
  650. return
  651. }
  652. bgbox := shape.Box()
  653. height, width := uint(math.Round(bgbox.Height)), uint(math.Round(bgbox.Width))
  654. //裁剪圖像
  655. shadowbgImg, _ := jpeg.Decode(bytes.NewReader(shadowbg))
  656. shadowbgImg = resize.Resize(width, height, shadowbgImg, resize.Lanczos3)
  657. fullbgImg, _ := jpeg.Decode(bytes.NewReader(fullbg))
  658. fullbgImg = resize.Resize(width, height, fullbgImg, resize.Lanczos3)
  659. //啓始left,排除干擾部份,所以右移10個像素
  660. left := fullbgImg.Bounds().Min.X + 10
  661. //啓始top, 排除干擾部份, 所以下移10個像素
  662. top := fullbgImg.Bounds().Min.Y + 10
  663. //最大left, 排除干擾部份, 所以左移10個像素
  664. maxleft := fullbgImg.Bounds().Max.X - 10
  665. //最大top, 排除干擾部份, 所以上移10個像素
  666. maxtop := fullbgImg.Bounds().Max.Y - 10
  667. //rgb比较阈值, 超出此阈值及代表找到缺口位置
  668. threshold := 20
  669. //缺口偏移, 拖動按鈕初始會偏移27.5
  670. distance := -27.5
  671. //取絕對值方法
  672. abs := func(n int) int {
  673. if n < 0 {
  674. return -n
  675. }
  676. return n
  677. }
  678. search:
  679. for i := left; i <= maxleft; i++ {
  680. for j := top; j <= maxtop; j++ {
  681. colorAR, colorAG, colorAB, _ := fullbgImg.At(i, j).RGBA()
  682. colorBR, colorBG, colorBB, _ := shadowbgImg.At(i, j).RGBA()
  683. colorAR, colorAG, colorAB = colorAR>>8, colorAG>>8, colorAB>>8
  684. colorBR, colorBG, colorBB = colorBR>>8, colorBG>>8, colorBB>>8
  685. if abs(int(colorAR)-int(colorBR)) > threshold ||
  686. abs(int(colorAG)-int(colorBG)) > threshold ||
  687. abs(int(colorAB)-int(colorBB)) > threshold {
  688. distance += float64(i)
  689. s.log.Debugln("對比完畢, 偏移量:", distance)
  690. break search
  691. }
  692. }
  693. }
  694. //獲取拖動按鈕形狀
  695. dragBtnBox := p.MustElement("#tcaptcha_drag_thumb").MustShape().Box()
  696. //启用滑鼠功能
  697. mouse := p.Mouse
  698. //模擬滑鼠移動至拖動按鈕處, 右移3的原因: 拖動按鈕比滑塊圖大3個像素
  699. mouse.MustMove(dragBtnBox.X+3, dragBtnBox.Y+(dragBtnBox.Height/2))
  700. //按下滑鼠左鍵
  701. mouse.MustDown("left")
  702. //開始拖動
  703. err = mouse.Move(dragBtnBox.X+distance, dragBtnBox.Y+(dragBtnBox.Height/2), 20)
  704. if err != nil {
  705. s.log.Errorln("mouse.Move", err)
  706. }
  707. //鬆開滑鼠左鍵, 拖动完毕
  708. mouse.MustUp("left")
  709. if s.debugMode == true {
  710. //截圖保存
  711. page.MustScreenshot(global_value.DefDebugFolder(), "result.png")
  712. }
  713. }
  714. type HdListItem struct {
  715. Url string `json:"url"`
  716. BaseUrl string `json:"baseUrl"`
  717. Title string `json:"title"`
  718. Ext string `json:"ext"`
  719. AuthorInfo string `json:"authorInfo"`
  720. Lang string `json:"lang"`
  721. Rate string `json:"rate"`
  722. DownCount int `json:"downCount"`
  723. Season int // 第几季,默认-1
  724. Episode int // 第几集,默认-1
  725. }
  726. //type HdContent struct {
  727. // Filename string `json:"filename"`
  728. // Ext string `json:"ext"`
  729. // Data []byte `json:"data"`
  730. //}
  731. const TCode = "#TencentCaptcha"
  732. const btnClickCodeBtn = "button.btn-danger"
  733. const btnCommitCode = "button.btn-primary"
  734. const InputCode = "#gzhcode" // id=gzhcode