subhd.go 25 KB

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