subhd.go 21 KB

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