player.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. package service
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "encoding/xml"
  7. "fmt"
  8. "hash/crc32"
  9. "html/template"
  10. "strconv"
  11. "strings"
  12. "time"
  13. dm2 "go-common/app/interface/main/dm2/model"
  14. history "go-common/app/interface/main/history/model"
  15. "go-common/app/interface/main/player/dao"
  16. "go-common/app/interface/main/player/model"
  17. tagmdl "go-common/app/interface/main/tag/model"
  18. accmdl "go-common/app/service/main/account/api"
  19. arcmdl "go-common/app/service/main/archive/api"
  20. "go-common/app/service/main/archive/model/archive"
  21. "go-common/app/service/main/assist/model/assist"
  22. locmdl "go-common/app/service/main/location/model"
  23. resmdl "go-common/app/service/main/resource/model"
  24. "go-common/library/log"
  25. "go-common/library/net/metadata"
  26. "go-common/library/sync/errgroup"
  27. )
  28. const (
  29. _content = `<a href="%s" target="_blank"><font color="#FFFFFF">%s</font></a>`
  30. _china = "中国"
  31. _local = "局域网"
  32. _accBanNor = 0 // no block
  33. _accBanSta = 1 // block MSpacesta
  34. _accBlockSta = 1
  35. _dmMaskPlatWeb = 0
  36. _mockBlockTime = 100
  37. )
  38. var (
  39. _copyRightMap = map[int32]string{
  40. 0: "Nnknown",
  41. 1: "Original",
  42. 2: "Copy",
  43. }
  44. // if typeid in this xml add bottom = 1
  45. _bottomMap = map[int32]struct{}{
  46. // 番剧
  47. 33: {},
  48. 32: {},
  49. 153: {},
  50. // 电影
  51. 82: {},
  52. 85: {},
  53. 145: {},
  54. 146: {},
  55. 147: {},
  56. 83: {},
  57. // note 电视剧存在三级分区
  58. 15: {},
  59. 34: {},
  60. 86: {},
  61. 128: {},
  62. // 三级分区
  63. 110: {},
  64. 111: {},
  65. 112: {},
  66. 113: {},
  67. 87: {},
  68. 88: {},
  69. 89: {},
  70. 90: {},
  71. 91: {},
  72. 92: {},
  73. 73: {},
  74. }
  75. iconTagIDs = map[int64]struct{}{
  76. 516: {},
  77. 374306: {},
  78. 16054: {},
  79. 18612: {},
  80. 2611047: {},
  81. 1008087: {},
  82. 50: {},
  83. 2513658: {},
  84. 56: {},
  85. 2512304: {},
  86. 6977: {},
  87. 8035683: {},
  88. 1060128: {},
  89. }
  90. )
  91. // Carousel return carousel items.
  92. func (s *Service) Carousel(c context.Context) (items []*model.Item, err error) {
  93. items = s.caItems
  94. return
  95. }
  96. // Player return player info.
  97. func (s *Service) Player(c context.Context, mid, aid, cid int64, cdnIP, refer string, now time.Time) (res []byte, err error) {
  98. var (
  99. ip = metadata.String(c, metadata.RemoteIP)
  100. vi *arcmdl.ViewReply
  101. cuPage *arcmdl.Page
  102. pi = &model.Player{
  103. IP: ip,
  104. Login: mid > 0,
  105. Time: now.Unix(),
  106. ZoneIP: cdnIP,
  107. Upermission: "1000,1001",
  108. }
  109. withU bool
  110. )
  111. if vi, err = s.view(c, aid); err != nil {
  112. dao.PromError("View接口错误", "s.arcClientView3(%d) error(%v)", aid, err)
  113. return
  114. } else if vi == nil || vi.Arc == nil {
  115. log.Error("vi(%v) is nill || vi.Archive is nil", vi)
  116. return
  117. } else if len(vi.Pages) == 0 {
  118. log.Error("len(vi.Pages) == 0 aid(%d)", aid)
  119. return
  120. }
  121. for _, page := range vi.Pages {
  122. if cid == page.Cid {
  123. cuPage = page
  124. break
  125. }
  126. }
  127. if cuPage == nil {
  128. log.Warn("cuPage is nil aid(%d) cid(%d) refer(%s)", aid, cid, refer)
  129. }
  130. s.fillArc(c, cid, pi, vi, cuPage, ip, now)
  131. withU = s.fillAcc(c, pi, vi, mid, cid, ip, now)
  132. // template
  133. var doc = bytes.NewBuffer(nil)
  134. if withU {
  135. s.tWithU.Execute(doc, pi)
  136. } else {
  137. s.tNoU.Execute(doc, pi)
  138. }
  139. if s.params != "" {
  140. doc.WriteString(s.params)
  141. }
  142. res = doc.Bytes()
  143. return
  144. }
  145. func (s *Service) fillAcc(c context.Context, pi *model.Player, vi *arcmdl.ViewReply, mid, cid int64, ip string, now time.Time) (withU bool) {
  146. if mid == 0 {
  147. return
  148. }
  149. var (
  150. proReply *accmdl.ProfileStatReply
  151. pro map[int64]*history.History
  152. err error
  153. )
  154. if proReply, err = s.accClient.ProfileWithStat3(c, &accmdl.MidReq{Mid: mid}); err != nil {
  155. dao.PromError("UserInfo接口错误", "s.acc.UserInfo(%v) error(%v)", mid, err)
  156. return
  157. }
  158. if proReply != nil {
  159. withU = true
  160. var nameBu = bytes.NewBuffer(nil)
  161. if err = xml.EscapeText(nameBu, []byte(proReply.Profile.Name)); err != nil {
  162. log.Error("xml.EscapeText(%s) error(%v)", proReply.Profile.Name, err)
  163. } else {
  164. pi.Name = nameBu.String()
  165. }
  166. pi.User = proReply.Profile.Mid
  167. pi.UserHash = midCrc(proReply.Profile.Mid)
  168. pi.Money = fmt.Sprintf("%.2f", proReply.Coins)
  169. pi.Face = strings.Replace(proReply.Profile.Face, "http://", "//", 1)
  170. var bs []byte
  171. if bs, err = json.Marshal(proReply.LevelInfo); err != nil {
  172. log.Error("json.Marshal(%v) error(%v)", proReply.LevelInfo, err)
  173. } else {
  174. pi.LevelInfo = template.HTML(bs)
  175. }
  176. vip := model.VIPInfo{Type: proReply.Profile.Vip.Type, DueDate: proReply.Profile.Vip.DueDate, VipStatus: proReply.Profile.Vip.Status}
  177. if bs, err = json.Marshal(vip); err != nil {
  178. log.Error("json.Marshal(%v) error(%v)", vip, err)
  179. } else {
  180. pi.Vip = template.HTML(bs)
  181. }
  182. off := &model.Official{Type: -1}
  183. if proReply.Profile.Official.Role != 0 {
  184. if proReply.Profile.Official.Role <= 2 {
  185. off.Type = 0
  186. } else {
  187. off.Type = 1
  188. }
  189. off.Desc = proReply.Profile.Official.Title
  190. }
  191. if bs, err = json.Marshal(off); err != nil {
  192. log.Error("json.Marshal(%v) error(%v)", off, err)
  193. } else {
  194. pi.OfficialVerify = template.HTML(bs)
  195. }
  196. group, errCtx := errgroup.WithContext(c)
  197. if vi.Arc != nil {
  198. pi.Upermission = userPermission(vi.Arc, proReply)
  199. // NOTE: if vInfo==nil, no admin
  200. if mid == vi.Arc.Author.Mid {
  201. pi.IsAdmin = true
  202. }
  203. group.Go(func() error {
  204. arg := &history.ArgPro{Mid: mid, RealIP: ip, Aids: []int64{vi.Arc.Aid}}
  205. if pro, err = s.his.Progress(errCtx, arg); err != nil {
  206. dao.PromError("Progress接口错误", "s.his.Progress(%d,%d) error(%v)", mid, vi.Arc.Aid, err)
  207. } else if progress, ok := pro[vi.Arc.Aid]; ok && progress != nil && progress.Cid > 0 && progress.Cid == cid {
  208. if progress.Pro >= 0 {
  209. pi.LastPlayTime = 1000 * progress.Pro
  210. pi.LastCid = progress.Cid
  211. } else if len(vi.Pages) != 0 {
  212. for _, page := range vi.Pages {
  213. if page.Cid == progress.Cid {
  214. pi.LastPlayTime = 1000 * page.Duration
  215. pi.LastCid = progress.Cid
  216. break
  217. }
  218. }
  219. }
  220. }
  221. return nil
  222. })
  223. }
  224. if s.c.Rule.NoAssistMid != vi.Arc.Author.Mid {
  225. group.Go(func() error {
  226. if assist, err := s.ass.Assist(errCtx, &assist.ArgAssist{Mid: vi.Arc.Author.Mid, AssistMid: proReply.Profile.Mid, Type: assist.TypeDm, RealIP: ip}); err != nil {
  227. dao.PromError("Assist接口错误", "s.ass.Assist(%d,%d) error(%v)", vi.Arc.Author.Mid, proReply.Profile.Mid, err)
  228. } else {
  229. pi.Role = strconv.FormatInt(assist.Assist, 10)
  230. }
  231. return nil
  232. })
  233. }
  234. if proReply.Profile.Silence == _accBanSta {
  235. group.Go(func() error {
  236. if blockTime, err := s.dao.BlockTime(errCtx, mid); err != nil {
  237. dao.PromError("BlockTime接口错误", "s.dao.BlockTime(%d) error(%v)", mid, err)
  238. } else if blockTime != nil {
  239. if blockTime.BlockStatus == _accBlockSta {
  240. pi.BlockTime = blockTime.BlockedEnd - now.Unix()
  241. if blockTime.BlockedForever || blockTime.BlockedEnd == 0 {
  242. pi.BlockTime = _mockBlockTime
  243. }
  244. }
  245. }
  246. return nil
  247. })
  248. }
  249. group.Wait()
  250. }
  251. return
  252. }
  253. func (s *Service) fillArc(c context.Context, cid int64, pi *model.Player, vi *arcmdl.ViewReply, page *arcmdl.Page, ip string, now time.Time) {
  254. // 稿件和其弹幕信息
  255. pi.Aid = vi.Arc.Aid
  256. pi.Typeid = vi.Arc.TypeID
  257. if page != nil {
  258. if page.From != "sina" {
  259. pi.Vtype = page.From
  260. } else {
  261. pi.Vtype = ""
  262. }
  263. pi.Maxlimit = dmLimit(page.Duration)
  264. pi.Chatid = page.Cid
  265. pi.Oriurl = oriURL(page.From, page.Vid)
  266. pi.Pid = int64(page.Page)
  267. } else {
  268. pi.Chatid = cid
  269. pi.Maxlimit = 1500
  270. pi.Pid = 1
  271. }
  272. pi.Arctype = _copyRightMap[vi.Arc.Copyright]
  273. pi.SuggestComment = false
  274. pi.Click = int(vi.Arc.Stat.View)
  275. group, errCtx := errgroup.WithContext(c)
  276. group.Go(func() error {
  277. if click, err := s.arc.Click3(errCtx, &archive.ArgAid2{Aid: vi.Arc.Aid}); err != nil {
  278. dao.PromError("Click接口错误", "s.arc.Click2(%d) error(%v)", vi.Arc.Aid, err)
  279. } else if click != nil {
  280. pi.FwClick = click.H5 + click.Outter
  281. }
  282. return nil
  283. })
  284. pi.OnlineCount = 1
  285. group.Go(func() error {
  286. if onlineCount, err := s.dao.OnlineCount(errCtx, pi.Aid, cid); err == nil && onlineCount > 1 {
  287. pi.OnlineCount = onlineCount
  288. }
  289. return nil
  290. })
  291. group.Go(func() error {
  292. pi.MaskNew = s.dmMask(errCtx, cid)
  293. return nil
  294. })
  295. group.Go(func() error {
  296. pi.Subtitle = s.dmSubtitle(errCtx, pi.Aid, cid)
  297. return nil
  298. })
  299. group.Go(func() error {
  300. if ipInfo, e := s.loc.Info(errCtx, &locmdl.ArgIP{IP: ip}); e != nil {
  301. log.Error("fillArc s.loc.Info(%s) error(%v)", ip, e)
  302. } else if ipInfo != nil {
  303. pi.Zoneid = ipInfo.ZoneID
  304. pi.Country = ipInfo.Country
  305. pi.Acceptaccel = ipInfo.Country != _china && ipInfo.Country != _local
  306. pi.Cache = ipInfo.Country != _china && ipInfo.Country != _local
  307. }
  308. return nil
  309. })
  310. if vi.Arc.AttrVal(archive.AttrBitHasViewpoint) == archive.AttrYes {
  311. group.Go(func() error {
  312. pi.ViewPoints = s.viewPoints(errCtx, pi.Aid, cid)
  313. return nil
  314. })
  315. }
  316. group.Go(func() error {
  317. pi.PlayerIcon = s.tagPlayerIcon(errCtx, pi.Aid, ip)
  318. return nil
  319. })
  320. group.Wait()
  321. pi.Duration = formatDuration(vi.Arc.Duration)
  322. pi.AllowBp = vi.Arc.AttrVal(archive.AttrBitAllowBp) == 1
  323. if _, ok := _bottomMap[vi.Arc.TypeID]; ok {
  324. pi.Bottom = 1
  325. }
  326. pi.Acceptguest = false
  327. if s.BrBegin.Unix() <= now.Unix() && now.Unix() <= s.BrEnd.Unix() {
  328. pi.BrTCP = s.c.Broadcast.TCPAddr
  329. pi.BrWs = s.c.Broadcast.WsAddr
  330. pi.BrWss = s.c.Broadcast.WssAddr
  331. }
  332. for index, pa := range vi.Pages {
  333. if pa != nil && cid == pa.Cid && index+1 < len(vi.Pages) {
  334. pi.HasNext = 1
  335. }
  336. }
  337. }
  338. func isAdmin(uRank int32) (b bool) {
  339. // 32000 -> admin
  340. // 31300 -> 评论管理员
  341. if uRank == 31300 || uRank == 32000 {
  342. b = true
  343. return
  344. }
  345. return
  346. }
  347. func userPermission(a *arcmdl.Arc, u *accmdl.ProfileStatReply) (permission string) {
  348. if u.Profile.Silence == _accBanNor || isAdmin(u.Profile.Rank) {
  349. permission = strings.Join(append([]string{strconv.FormatInt(int64(u.Profile.Rank), 10), "1001"}), ",")
  350. } else {
  351. permission = "0"
  352. }
  353. // if a.AttrVal(archive.AttrBitNoMission) == 0 && a.Author.Mid == u.Mid {
  354. // permission = strings.Join([]string{permission, "20000"}, ",")
  355. // }
  356. return
  357. }
  358. func oriURL(dmType, dmIndex string) (url string) {
  359. switch dmType {
  360. case "sina":
  361. url = "http://p.you.video.sina.com.cn/swf/bokePlayer20131203_V4_1_42_33.swf?vid=" + dmIndex
  362. case "youku":
  363. url = "http://v.youku.com/v_show/id_" + dmIndex + ".html"
  364. case "qq":
  365. if len(dmIndex) >= 3 {
  366. url = "http://v.qq.com/page/" + dmIndex[0:1] + "/" + dmIndex[1:2] + "/" + dmIndex[2:3] + "/" + dmIndex + ".html"
  367. }
  368. default:
  369. url = ""
  370. }
  371. return
  372. }
  373. func formatDuration(duration int64) (du string) {
  374. if duration == 0 {
  375. du = "00:00"
  376. } else {
  377. var duFen, duMiao string
  378. duFen = strconv.Itoa(int(duration / 60))
  379. if int(duration%60) < 10 {
  380. duMiao = "0" + strconv.Itoa(int(duration%60))
  381. } else {
  382. duMiao = strconv.Itoa(int(duration % 60))
  383. }
  384. du = duFen + ":" + duMiao
  385. }
  386. return
  387. }
  388. func midCrc(mid int64) string {
  389. midStr := strconv.FormatInt(mid, 10)
  390. return fmt.Sprintf("%08x", crc32.ChecksumIEEE([]byte(midStr)))
  391. }
  392. func dmLimit(duration int64) (limit int) {
  393. switch {
  394. case duration > 3600:
  395. limit = 8000
  396. case duration > 2400:
  397. limit = 6000
  398. case duration > 900:
  399. limit = 3000
  400. case duration > 600:
  401. limit = 1500
  402. case duration > 150:
  403. limit = 1000
  404. case duration > 60:
  405. limit = 500
  406. case duration > 30:
  407. limit = 300
  408. case duration <= 30:
  409. limit = 100
  410. default:
  411. limit = 1500
  412. }
  413. return
  414. }
  415. func (s *Service) dmMask(c context.Context, cid int64) (mask template.HTML) {
  416. if dmMask, err := s.dm2.Mask(c, &dm2.ArgMask{Cid: cid, Plat: _dmMaskPlatWeb}); err != nil {
  417. dao.PromError("MaskList 错误", "s.dm2.MaskList cid(%d) error(%v)", cid, err)
  418. } else if dmMask != nil && dmMask.MaskURL != "" {
  419. dmMask.MaskURL = strings.Replace(dmMask.MaskURL, "http://", "//", 1)
  420. if bs, err := json.Marshal(dmMask); err != nil {
  421. log.Error("dmMask json.Marshal(%+v) error(%v)", dmMask, err)
  422. } else {
  423. mask = template.HTML(bs)
  424. }
  425. }
  426. return
  427. }
  428. func (s *Service) dmSubtitle(c context.Context, aid, cid int64) (subtitle template.HTML) {
  429. if dmSub, err := s.dm2.SubtitleGet(c, &dm2.ArgSubtitleGet{Aid: aid, Oid: cid, Type: dm2.SubTypeVideo}); err != nil {
  430. log.Error("s.dm2.SubtitleGet aid(%d) cid(%d) error(%v)", aid, cid, err)
  431. } else {
  432. if dmSub != nil {
  433. if len(dmSub.Subtitles) == 0 {
  434. dmSub.Subtitles = make([]*dm2.VideoSubtitle, 0)
  435. }
  436. for _, v := range dmSub.Subtitles {
  437. v.SubtitleURL = strings.Replace(v.SubtitleURL, "http://", "//", 1)
  438. }
  439. if bs, err := json.Marshal(dmSub); err != nil {
  440. log.Error("dmSubject json.Marshal(%v) error(%v)", dmSub, err)
  441. } else {
  442. subtitle = template.HTML(bs)
  443. }
  444. }
  445. }
  446. return
  447. }
  448. func (s *Service) tagPlayerIcon(c context.Context, aid int64, ip string) (icon template.HTML) {
  449. icon = s.icon
  450. now := time.Now()
  451. tags, err := s.tag.ArcTags(c, &tagmdl.ArgAid{Aid: aid, RealIP: ip})
  452. if err != nil {
  453. log.Error("tagPlayerIcon s.tag.ArcTags aid(%d) error(%v)", aid, err)
  454. return
  455. }
  456. // TODO delete tmp logic
  457. if now.Unix() >= s.c.Icon.Start.Unix() && now.Unix() <= s.c.Icon.End.Unix() {
  458. for _, vt := range tags {
  459. if _, ok := iconTagIDs[vt.ID]; ok {
  460. playerIcon := &resmdl.PlayerIcon{
  461. URL1: s.c.Icon.URL1,
  462. Hash1: s.c.Icon.Hash1,
  463. URL2: s.c.Icon.URL2,
  464. Hash2: s.c.Icon.Hash2,
  465. }
  466. bs, err := json.Marshal(playerIcon)
  467. if err != nil {
  468. log.Error("tagPlayerIcon json.Marshal(%v) error(%v)", playerIcon, err)
  469. continue
  470. }
  471. icon = template.HTML(bs)
  472. break
  473. }
  474. }
  475. }
  476. return
  477. }
  478. func (s *Service) viewPoints(c context.Context, aid, cid int64) (points template.HTML) {
  479. if data, err := s.dao.ViewPoints(c, aid, cid); err != nil {
  480. log.Error("s.dao.ViewPoints aid(%d) cid(%d) error(%v)", aid, cid, err)
  481. } else if len(data) > 0 {
  482. if bs, err := json.Marshal(data); err != nil {
  483. log.Error("viewPoints json.Marshal(%v) error(%v)", data, err)
  484. } else {
  485. points = template.HTML(bs)
  486. }
  487. }
  488. return
  489. }
  490. func (s *Service) view(c context.Context, aid int64) (data *arcmdl.ViewReply, err error) {
  491. if view, ok := s.bnj2019ViewMap[aid]; ok && view != nil {
  492. data = view
  493. return
  494. }
  495. return s.arcClient.View(c, &arcmdl.ViewRequest{Aid: aid})
  496. }