subhd.go 20 KB

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