subhd.go 21 KB

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