zimuku.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. package zimuku
  2. import (
  3. "fmt"
  4. "github.com/PuerkitoBio/goquery"
  5. "github.com/Tnze/go.num/v2/zh"
  6. "github.com/allanpk716/ChineseSubFinder/internal/common"
  7. "github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
  8. "github.com/allanpk716/ChineseSubFinder/internal/pkg/language"
  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/sub_parser_hub"
  13. "github.com/allanpk716/ChineseSubFinder/internal/types"
  14. language2 "github.com/allanpk716/ChineseSubFinder/internal/types/language"
  15. "github.com/allanpk716/ChineseSubFinder/internal/types/series"
  16. "github.com/allanpk716/ChineseSubFinder/internal/types/supplier"
  17. "github.com/sirupsen/logrus"
  18. "path/filepath"
  19. "regexp"
  20. "sort"
  21. "strings"
  22. )
  23. type Supplier struct {
  24. reqParam types.ReqParam
  25. log *logrus.Logger
  26. topic int
  27. }
  28. func NewSupplier(_reqParam ...types.ReqParam) *Supplier {
  29. sup := Supplier{}
  30. sup.log = log_helper.GetLogger()
  31. sup.topic = common.DownloadSubsPerSite
  32. if len(_reqParam) > 0 {
  33. sup.reqParam = _reqParam[0]
  34. if sup.reqParam.Topic > 0 && sup.reqParam.Topic != sup.topic {
  35. sup.topic = sup.reqParam.Topic
  36. }
  37. }
  38. return &sup
  39. }
  40. func (s Supplier) GetSupplierName() string {
  41. return common.SubSiteZiMuKu
  42. }
  43. func (s Supplier) GetReqParam() types.ReqParam {
  44. return s.reqParam
  45. }
  46. func (s Supplier) GetSubListFromFile4Movie(filePath string) ([]supplier.SubInfo, error) {
  47. return s.getSubListFromMovie(filePath)
  48. }
  49. func (s Supplier) GetSubListFromFile4Series(seriesInfo *series.SeriesInfo) ([]supplier.SubInfo, error) {
  50. defer func() {
  51. s.log.Debugln(s.GetSupplierName(), seriesInfo.Name, "End...")
  52. }()
  53. s.log.Debugln(s.GetSupplierName(), seriesInfo.Name, "Start...")
  54. var err error
  55. /*
  56. 去网站搜索的时候,有个比较由意思的逻辑,有些剧集,哪怕只有一季,sonarr 也会给它命名为 Season 1
  57. 但是在 zimuku 搜索的时候,如果你加上 XXX 第一季 就搜索不出来,那么目前比较可行的办法是查询两次
  58. 第一次优先查询 XXX 第一季 ,如果返回的列表是空的,那么再查询 XXX
  59. */
  60. // 这里打算牺牲效率,提高代码的复用度,不然后续得维护一套电影的查询逻辑,一套剧集的查询逻辑
  61. // 比如,其实可以搜索剧集名称,应该可以得到多个季的列表,然后分析再继续
  62. // 现在粗暴点,直接一季搜索一次,跟电影的搜索一样,在首个影片就停止,然后继续往下
  63. AllSeasonSubResult := SubResult{}
  64. for value := range seriesInfo.SeasonDict {
  65. // 第一级界面,找到影片的详情界面
  66. keyword := seriesInfo.Name + " 第" + zh.Uint64(value).String() + "季"
  67. s.log.Debugln(s.GetSupplierName(), "step 0", "0 times", "keyword:", keyword)
  68. filmDetailPageUrl, err := s.step0(keyword)
  69. if err != nil {
  70. s.log.Errorln(s.GetSupplierName(), "step 0", "0 times", "keyword:", keyword, err)
  71. // 如果只是搜索不到,则继续换关键词
  72. if err != common.ZiMuKuSearchKeyWordStep0DetailPageUrlNotFound {
  73. s.log.Errorln(s.GetSupplierName(), "ZiMuKuSearchKeyWordStep0DetailPageUrlNotFound", keyword, err)
  74. continue
  75. }
  76. keyword = seriesInfo.Name
  77. s.log.Debugln(s.GetSupplierName(), "step 0", "1 times", "keyword:", keyword)
  78. filmDetailPageUrl, err = s.step0(keyword)
  79. if err != nil {
  80. s.log.Errorln(s.GetSupplierName(), "1 times", "keyword:", keyword, err)
  81. continue
  82. }
  83. }
  84. // 第二级界面,有多少个字幕
  85. s.log.Debugln(s.GetSupplierName(), "step 1", filmDetailPageUrl)
  86. subResult, err := s.step1(filmDetailPageUrl)
  87. if err != nil {
  88. s.log.Errorln(s.GetSupplierName(), "step 1", filmDetailPageUrl, err)
  89. continue
  90. }
  91. if AllSeasonSubResult.Title == "" {
  92. AllSeasonSubResult = subResult
  93. } else {
  94. AllSeasonSubResult.SubInfos = append(AllSeasonSubResult.SubInfos, subResult.SubInfos...)
  95. }
  96. }
  97. // 找到最大的优先级的字幕下载
  98. sort.Sort(SortByPriority{AllSeasonSubResult.SubInfos})
  99. // 找到那些 Eps 需要下载字幕的
  100. subInfoNeedDownload := s.whichEpisodeNeedDownloadSub(seriesInfo, AllSeasonSubResult)
  101. // 剩下的部分跟 GetSubListFroKeyword 一样,就是去下载了
  102. outSubInfoList := s.whichSubInfoNeedDownload(subInfoNeedDownload, err)
  103. // 返回前,需要把每一个 Eps 的 Season Episode 信息填充到每个 SubInfo 中
  104. return outSubInfoList, nil
  105. }
  106. func (s Supplier) GetSubListFromFile4Anime(seriesInfo *series.SeriesInfo) ([]supplier.SubInfo, error) {
  107. panic("not implemented")
  108. }
  109. func (s Supplier) getSubListFromMovie(fileFPath string) ([]supplier.SubInfo, error) {
  110. defer func() {
  111. s.log.Debugln(s.GetSupplierName(), fileFPath, "End...")
  112. }()
  113. s.log.Debugln(s.GetSupplierName(), fileFPath, "Start...")
  114. /*
  115. 虽然是传入视频文件路径,但是其实需要读取对应的视频文件目录下的
  116. movie.xml 以及 *.nfo,找到 IMDB id
  117. 优先通过 IMDB id 去查找字幕
  118. 如果找不到,再靠文件名提取影片名称去查找
  119. */
  120. // 得到这个视频文件名中的信息
  121. info, _, err := decode.GetVideoInfoFromFileFullPath(fileFPath)
  122. if err != nil {
  123. return nil, err
  124. }
  125. s.log.Debugln(s.GetSupplierName(), fileFPath, "GetVideoInfoFromFileFullPath -> Title:", info.Title)
  126. // 找到这个视频文件,尝试得到 IMDB ID
  127. // 目前测试来看,加入 年 这个关键词去搜索,对 2020 年后的影片有利,因为网站有统一的详细页面了,而之前的,没有,会影响识别
  128. // 所以,year >= 2020 年,则可以多加一个关键词(年)去搜索影片
  129. imdbInfo, err := decode.GetImdbInfo4Movie(fileFPath)
  130. if err != nil {
  131. // 允许的错误,跳过,继续进行文件名的搜索
  132. s.log.Errorln("model.GetImdbInfo", err)
  133. } else {
  134. s.log.Debugln(s.GetSupplierName(), fileFPath, "GetImdbInfo4Movie -> Title:", imdbInfo.Title)
  135. s.log.Debugln(s.GetSupplierName(), fileFPath, "GetImdbInfo4Movie -> OriginalTitle:", imdbInfo.OriginalTitle)
  136. s.log.Debugln(s.GetSupplierName(), fileFPath, "GetImdbInfo4Movie -> Year:", imdbInfo.Year)
  137. s.log.Debugln(s.GetSupplierName(), fileFPath, "GetImdbInfo4Movie -> ImdbId:", imdbInfo.ImdbId)
  138. }
  139. var subInfoList []supplier.SubInfo
  140. if imdbInfo.ImdbId != "" {
  141. // 先用 imdb id 找
  142. s.log.Debugln(s.GetSupplierName(), fileFPath, "getSubListFromKeyword -> Search By IMDB ID:", imdbInfo.ImdbId)
  143. subInfoList, err = s.getSubListFromKeyword(imdbInfo.ImdbId)
  144. if err != nil {
  145. // 允许的错误,跳过,继续进行文件名的搜索
  146. s.log.Errorln(s.GetSupplierName(), "keyword:", imdbInfo.ImdbId)
  147. s.log.Errorln("getSubListFromKeyword", "IMDBID can not found sub", fileFPath, err)
  148. }
  149. s.log.Debugln(s.GetSupplierName(), fileFPath, "getSubListFromKeyword -> Search By IMDB ID, subInfoList Count:", len(subInfoList))
  150. // 如果有就优先返回
  151. if len(subInfoList) > 0 {
  152. return subInfoList, nil
  153. }
  154. }
  155. // 如果没有,那么就用文件名查找
  156. searchKeyword := my_util.VideoNameSearchKeywordMaker(info.Title, imdbInfo.Year)
  157. s.log.Debugln(s.GetSupplierName(), fileFPath, "VideoNameSearchKeywordMaker Keyword:", searchKeyword)
  158. subInfoList, err = s.getSubListFromKeyword(searchKeyword)
  159. if err != nil {
  160. s.log.Errorln(s.GetSupplierName(), "keyword:", searchKeyword)
  161. return nil, err
  162. }
  163. s.log.Debugln(s.GetSupplierName(), fileFPath, "getSubListFromKeyword -> Search By Keyword, subInfoList Count:", len(subInfoList))
  164. return subInfoList, nil
  165. }
  166. func (s Supplier) getSubListFromKeyword(keyword string) ([]supplier.SubInfo, error) {
  167. var outSubInfoList []supplier.SubInfo
  168. // 第一级界面,找到影片的详情界面
  169. filmDetailPageUrl, err := s.step0(keyword)
  170. if err != nil {
  171. return nil, err
  172. }
  173. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step0 -> filmDetailPageUrl:", filmDetailPageUrl)
  174. // 第二级界面,有多少个字幕
  175. subResult, err := s.step1(filmDetailPageUrl)
  176. if err != nil {
  177. return nil, err
  178. }
  179. // 第三级界面,单个字幕详情
  180. // 找到最大的优先级的字幕下载
  181. sort.Sort(SortByPriority{subResult.SubInfos})
  182. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step1 -> subResult.Title:", subResult.Title)
  183. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step1 -> subResult.OtherName:", subResult.OtherName)
  184. for i, info := range subResult.SubInfos {
  185. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step1 -> info.Name", i, info.Name)
  186. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step1 -> info.DownloadUrl:", i, info.DownloadUrl)
  187. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step1 -> info.DetailUrl:", i, info.DetailUrl)
  188. s.log.Debugln(s.GetSupplierName(), "getSubListFromKeyword -> step1 -> info.DownloadTimes:", i, info.DownloadTimes)
  189. }
  190. outSubInfoList = s.whichSubInfoNeedDownload(subResult.SubInfos, err)
  191. return outSubInfoList, nil
  192. }
  193. func (s Supplier) whichEpisodeNeedDownloadSub(seriesInfo *series.SeriesInfo, AllSeasonSubResult SubResult) []SubInfo {
  194. // 字幕很多,考虑效率,需要做成字典
  195. // key SxEx - SubInfos
  196. var allSubDict = make(map[string]SubInfos)
  197. // 全季的字幕列表
  198. var oneSeasonSubDict = make(map[string]SubInfos)
  199. for _, subInfo := range AllSeasonSubResult.SubInfos {
  200. _, season, episode, err := decode.GetSeasonAndEpisodeFromSubFileName(subInfo.Name)
  201. if err != nil {
  202. s.log.Errorln("whichEpisodeNeedDownloadSub.GetVideoInfoFromFileFullPath", subInfo.Name, err)
  203. continue
  204. }
  205. subInfo.Season = season
  206. subInfo.Episode = episode
  207. epsKey := my_util.GetEpisodeKeyName(season, episode)
  208. _, ok := allSubDict[epsKey]
  209. if ok == false {
  210. // 初始化
  211. allSubDict[epsKey] = SubInfos{}
  212. if season != 0 && episode == 0 {
  213. oneSeasonSubDict[epsKey] = SubInfos{}
  214. }
  215. }
  216. // 添加
  217. allSubDict[epsKey] = append(allSubDict[epsKey], subInfo)
  218. if season != 0 && episode == 0 {
  219. oneSeasonSubDict[epsKey] = append(oneSeasonSubDict[epsKey], subInfo)
  220. }
  221. }
  222. // 本地的视频列表,找到没有字幕的
  223. // 需要进行下载字幕的列表
  224. var subInfoNeedDownload = make([]SubInfo, 0)
  225. // 有那些 Eps 需要下载的,按 SxEx 反回 epsKey
  226. for epsKey, epsInfo := range seriesInfo.NeedDlEpsKeyList {
  227. // 从一堆字幕里面找合适的
  228. value, ok := allSubDict[epsKey]
  229. // 是否有
  230. if ok == true && len(value) > 0 {
  231. value[0].Season = epsInfo.Season
  232. value[0].Episode = epsInfo.Episode
  233. subInfoNeedDownload = append(subInfoNeedDownload, value[0])
  234. } else {
  235. s.log.Infoln(s.GetSupplierName(), "Not Find Sub can be download",
  236. epsInfo.Title, epsInfo.Season, epsInfo.Episode)
  237. }
  238. }
  239. // 全季的字幕列表,也拼进去,后面进行下载
  240. for _, infos := range oneSeasonSubDict {
  241. subInfoNeedDownload = append(subInfoNeedDownload, infos[0])
  242. }
  243. // 返回前,需要把每一个 Eps 的 Season Episode 信息填充到每个 SubInfo 中
  244. return subInfoNeedDownload
  245. }
  246. func (s Supplier) whichSubInfoNeedDownload(subInfos SubInfos, err error) []supplier.SubInfo {
  247. var outSubInfoList = make([]supplier.SubInfo, 0)
  248. for i := range subInfos {
  249. err = s.step2(&subInfos[i])
  250. if err != nil {
  251. s.log.Error(s.GetSupplierName(), "step 2", subInfos[i].Name, err)
  252. continue
  253. }
  254. s.log.Debugln(s.GetSupplierName(), "whichSubInfoNeedDownload -> step2 -> info.SubDownloadPageUrl:", i, subInfos[i].SubDownloadPageUrl)
  255. }
  256. // TODO 这里需要考虑,可以设置为高级选项,不够就用 unknow 来补充
  257. // 首先过滤出中文的字幕,同时需要满足是支持的字幕
  258. var tmpSubInfo = make([]SubInfo, 0)
  259. for _, subInfo := range subInfos {
  260. tmpLang := language.LangConverter4Sub_Supplier(subInfo.Lang)
  261. if language.HasChineseLang(tmpLang) == true && sub_parser_hub.IsSubTypeWanted(subInfo.Ext) == true {
  262. tmpSubInfo = append(tmpSubInfo, subInfo)
  263. }
  264. }
  265. // 看字幕够不够
  266. if len(tmpSubInfo) < s.topic {
  267. for _, subInfo := range subInfos {
  268. if len(tmpSubInfo) >= s.topic {
  269. break
  270. }
  271. tmpLang := language.LangConverter4Sub_Supplier(subInfo.Lang)
  272. if language.HasChineseLang(tmpLang) == false {
  273. tmpSubInfo = append(tmpSubInfo, subInfo)
  274. }
  275. }
  276. }
  277. s.log.Debugln(s.GetSupplierName(), "step2 -> tmpSubInfo.Count", len(tmpSubInfo))
  278. for i, info := range tmpSubInfo {
  279. s.log.Debugln(s.GetSupplierName(), "ChineseSubs -> tmpSubInfo.Name:", i, info.Name)
  280. s.log.Debugln(s.GetSupplierName(), "ChineseSubs -> tmpSubInfo.DownloadUrl:", i, info.DownloadUrl)
  281. s.log.Debugln(s.GetSupplierName(), "ChineseSubs -> tmpSubInfo.DetailUrl:", i, info.DetailUrl)
  282. s.log.Debugln(s.GetSupplierName(), "ChineseSubs -> tmpSubInfo.DownloadTimes:", i, info.DownloadTimes)
  283. s.log.Debugln(s.GetSupplierName(), "ChineseSubs -> tmpSubInfo.SubDownloadPageUrl:", i, info.SubDownloadPageUrl)
  284. }
  285. // 第四级界面,具体字幕下载
  286. for i, subInfo := range tmpSubInfo {
  287. s.log.Debugln(s.GetSupplierName(), "step3 -> subInfo.SubDownloadPageUrl:", i, subInfo.SubDownloadPageUrl)
  288. fileName, data, err := s.step3(subInfo.SubDownloadPageUrl)
  289. if err != nil {
  290. s.log.Error(s.GetSupplierName(), "step 3", err)
  291. continue
  292. }
  293. // 默认都是包含中文字幕的,然后具体使用的时候再进行区分
  294. oneSubInfo := supplier.NewSubInfo(s.GetSupplierName(), int64(i), fileName, language2.ChineseSimple, my_util.AddBaseUrl(common.SubZiMuKuRootUrl, subInfo.SubDownloadPageUrl), 0,
  295. 0, filepath.Ext(fileName), data)
  296. oneSubInfo.Season = subInfo.Season
  297. oneSubInfo.Episode = subInfo.Episode
  298. outSubInfoList = append(outSubInfoList, *oneSubInfo)
  299. }
  300. for i, info := range outSubInfoList {
  301. s.log.Debugln(s.GetSupplierName(), "step3 -> Downloaded File Info", i, "FileName:", info.Name, "FileUrl:", info.FileUrl)
  302. }
  303. // 返回前,需要把每一个 Eps 的 Season Episode 信息填充到每个 SubInfo 中
  304. return outSubInfoList
  305. }
  306. // step0 先在查询界面找到字幕对应第一个影片的详情界面,需要解决自定义错误 ZiMuKuSearchKeyWordStep0DetailPageUrlNotFound
  307. func (s Supplier) step0(keyword string) (string, error) {
  308. var err error
  309. defer func() {
  310. if err != nil {
  311. notify_center.Notify.Add("zimuku_step0", err.Error())
  312. }
  313. }()
  314. httpClient := my_util.NewHttpClient(s.reqParam)
  315. // 第一级界面,有多少个字幕
  316. resp, err := httpClient.R().
  317. SetQueryParams(map[string]string{
  318. "q": keyword,
  319. }).
  320. Get(common.SubZiMuKuSearchUrl)
  321. if err != nil {
  322. return "", err
  323. }
  324. // 找到对应影片的详情界面
  325. re := regexp.MustCompile(`<p\s+class="tt\s+clearfix"><a\s+href="(/subs/[\w]+\.html)"\s+target="_blank"><b>(.*?)</b></a></p>`)
  326. matched := re.FindAllStringSubmatch(resp.String(), -1)
  327. if matched == nil || len(matched) < 1 {
  328. return "", common.ZiMuKuSearchKeyWordStep0DetailPageUrlNotFound
  329. }
  330. // 影片的详情界面 url
  331. filmDetailPageUrl := matched[0][1]
  332. return filmDetailPageUrl, nil
  333. }
  334. // step1 分析详情界面,找到有多少个字幕
  335. func (s Supplier) step1(filmDetailPageUrl string) (SubResult, error) {
  336. var err error
  337. defer func() {
  338. if err != nil {
  339. notify_center.Notify.Add("zimuku_step1", err.Error())
  340. }
  341. }()
  342. filmDetailPageUrl = my_util.AddBaseUrl(common.SubZiMuKuRootUrl, filmDetailPageUrl)
  343. httpClient := my_util.NewHttpClient(s.reqParam)
  344. resp, err := httpClient.R().
  345. Get(filmDetailPageUrl)
  346. if err != nil {
  347. return SubResult{}, err
  348. }
  349. doc, err := goquery.NewDocumentFromReader(strings.NewReader(resp.String()))
  350. if err != nil {
  351. return SubResult{}, err
  352. }
  353. var subResult SubResult
  354. subResult.SubInfos = SubInfos{}
  355. counterIndex := 3
  356. // 先找到页面”下载“关键词是第几列,然后下面的下载量才能正确的解析。否则,电影是[3],而在剧集中,因为多了字幕组的筛选,则为[4]
  357. doc.Find("#subtb thead tr th").Each(func(i int, th *goquery.Selection) {
  358. if th.Text() == "下载" {
  359. counterIndex = i
  360. }
  361. })
  362. doc.Find("#subtb tbody tr").Each(func(i int, tr *goquery.Selection) {
  363. // 字幕下载页面地址
  364. href, exists := tr.Find("a").Attr("href")
  365. if !exists {
  366. return
  367. }
  368. // 标题
  369. title, exists := tr.Find("a").Attr("title")
  370. if !exists {
  371. return
  372. }
  373. // 扩展名
  374. ext := tr.Find(".label-info").Text()
  375. // 作者信息
  376. authorInfos := tr.Find(".gray")
  377. authorInfo := ""
  378. authorInfos.Each(func(a_i int, a_lb *goquery.Selection) {
  379. authorInfo += a_lb.Text() + ","
  380. })
  381. authorInfoLen := len(authorInfo)
  382. if authorInfoLen > 0 {
  383. authorInfo = authorInfo[0 : authorInfoLen-3]
  384. }
  385. // 语言
  386. lang, exists := tr.Find("img").First().Attr("alt")
  387. if !exists {
  388. lang = ""
  389. }
  390. // 投票
  391. rate, exists := tr.Find(".rating-star").First().Attr("title")
  392. if !exists {
  393. rate = ""
  394. }
  395. vote, err := decode.GetNumber2Float(rate)
  396. if err != nil {
  397. return
  398. }
  399. // 下载次数统计
  400. downCountNub := 0
  401. downCount := tr.Find("td").Eq(counterIndex).Text()
  402. if strings.Contains(downCount, "万") {
  403. fNumb, err := decode.GetNumber2Float(downCount)
  404. if err != nil {
  405. return
  406. }
  407. downCountNub = int(fNumb * 10000)
  408. } else {
  409. downCountNub, err = decode.GetNumber2int(downCount)
  410. if err != nil {
  411. return
  412. }
  413. }
  414. var subInfo SubInfo
  415. subResult.Title = title
  416. subInfo.Name = title
  417. subInfo.DetailUrl = href
  418. subInfo.Ext = ext
  419. subInfo.AuthorInfo = authorInfo
  420. subInfo.Lang = lang
  421. subInfo.DownloadTimes = downCountNub
  422. subInfo.Score = vote
  423. // 计算优先级
  424. subInfo.Priority = subInfo.Score * float32(subInfo.DownloadTimes)
  425. subResult.SubInfos = append(subResult.SubInfos, subInfo)
  426. })
  427. return subResult, nil
  428. }
  429. // step2 第二级界面,单个字幕详情,需要判断 ZiMuKuDownloadUrlStep2NotFound 这个自定义错误
  430. func (s Supplier) step2(subInfo *SubInfo) error {
  431. var err error
  432. defer func() {
  433. if err != nil {
  434. notify_center.Notify.Add("zimuku_step2", err.Error())
  435. }
  436. }()
  437. detailUrl := my_util.AddBaseUrl(common.SubZiMuKuRootUrl, subInfo.DetailUrl)
  438. httpClient := my_util.NewHttpClient(s.reqParam)
  439. resp, err := httpClient.R().
  440. Get(detailUrl)
  441. if err != nil {
  442. return err
  443. }
  444. // 找到下载地址
  445. re := regexp.MustCompile(`<a\s+id="down1"\s+href="([^"]*/dld/[\w]+\.html)"`)
  446. matched := re.FindAllStringSubmatch(resp.String(), -1)
  447. if matched == nil || len(matched) == 0 || len(matched[0]) == 0 {
  448. s.log.Warnln("Step2,sub download url not found", detailUrl)
  449. return common.ZiMuKuDownloadUrlStep2NotFound
  450. }
  451. if strings.Contains(matched[0][1], "://") {
  452. subInfo.SubDownloadPageUrl = matched[0][1]
  453. } else {
  454. subInfo.SubDownloadPageUrl = fmt.Sprintf("%s%s", common.SubZiMuKuRootUrl, matched[0][1])
  455. }
  456. return nil
  457. }
  458. // step3 第三级界面,具体字幕下载 ZiMuKuDownloadUrlStep3NotFound ZiMuKuDownloadUrlStep3AllFailed
  459. func (s Supplier) step3(subDownloadPageUrl string) (string, []byte, error) {
  460. var err error
  461. defer func() {
  462. if err != nil {
  463. notify_center.Notify.Add("zimuku_step3", err.Error())
  464. }
  465. }()
  466. subDownloadPageUrl = my_util.AddBaseUrl(common.SubZiMuKuRootUrl, subDownloadPageUrl)
  467. httpClient := my_util.NewHttpClient(s.reqParam)
  468. resp, err := httpClient.R().
  469. Get(subDownloadPageUrl)
  470. if err != nil {
  471. return "", nil, err
  472. }
  473. re := regexp.MustCompile(`<li><a\s+rel="nofollow"\s+href="([^"]*/download/[^"]+)"`)
  474. matched := re.FindAllStringSubmatch(resp.String(), -1)
  475. if matched == nil || len(matched) == 0 || len(matched[0]) == 0 {
  476. s.log.Debugln("Step3,sub download url not found", subDownloadPageUrl)
  477. return "", nil, common.ZiMuKuDownloadUrlStep3NotFound
  478. }
  479. var filename string
  480. var data []byte
  481. s.reqParam.Referer = subDownloadPageUrl
  482. for i := 0; i < len(matched); i++ {
  483. data, filename, err = my_util.DownFile(my_util.AddBaseUrl(common.SubZiMuKuRootUrl, matched[i][1]), s.reqParam)
  484. if err != nil {
  485. s.log.Errorln("ZiMuKu step3 DownloadFile", err)
  486. continue
  487. }
  488. s.log.Debugln("Step3,DownFile, FileName:", filename, "DataLen:", len(data))
  489. return filename, data, nil
  490. }
  491. s.log.Debugln("Step3,sub download url not found", subDownloadPageUrl)
  492. return "", nil, common.ZiMuKuDownloadUrlStep3AllFailed
  493. }
  494. type SubResult struct {
  495. Title string // 字幕的标题
  496. OtherName string // 影片又名
  497. SubInfos SubInfos // 字幕的列表
  498. }
  499. type SubInfo struct {
  500. Name string // 字幕的名称
  501. Lang string // 语言
  502. AuthorInfo string // 作者
  503. Ext string // 后缀名
  504. Score float32 // 评分
  505. DownloadTimes int // 下载的次数
  506. Priority float32 // 优先级,使用评分和次数乘积而来,类似于 Score 投票
  507. DetailUrl string // 字幕的详情界面,需要再次分析具体的下载地址,地址需要拼接网站的根地址上去
  508. SubDownloadPageUrl string // 字幕的具体的下载页面,会有多个下载可用的链接
  509. DownloadUrl string // 字幕的下载地址
  510. Season int // 第几季,默认-1
  511. Episode int // 第几集,默认-1
  512. }
  513. // SubInfos 实现自定义排序
  514. type SubInfos []SubInfo
  515. func (s SubInfos) Len() int {
  516. return len(s)
  517. }
  518. func (s SubInfos) Less(i, j int) bool {
  519. return s[i].Priority > s[j].Priority
  520. }
  521. func (s SubInfos) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  522. type SortByPriority struct{ SubInfos }
  523. // Less 根据元素的优先级降序排序
  524. func (s SortByPriority) Less(i, j int) bool {
  525. return s.SubInfos[i].Priority > s.SubInfos[j].Priority
  526. }