chat-lines.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. const MAX_LINES = 80
  2. /** 左下角弹幕展示的类型(不含 follow:关注成功不在此展示) */
  3. const CHAT_TYPES = new Set(['msg', 'seek', 'like', 'review'])
  4. const NAME_COLORS = [
  5. '#ff9f43', '#ffffff', '#ff9f43', '#ffd93d', '#6bcb77',
  6. '#8fbc8f', '#6bcb77', '#c084fc', '#ff6b8a', '#ff6b8a', '#c084fc'
  7. ]
  8. export function resolveRoomGroupId(session, roomKey = '') {
  9. if (!session || typeof session !== 'object') return String(roomKey || '')
  10. // 发送/订阅群组与老工程一致:优先 liveid,其次 group
  11. const group = session.liveid || session.group || session.live_id
  12. if (group != null && group !== '') return String(group)
  13. return String(roomKey || '')
  14. }
  15. /**
  16. * 收集当前房间可用于匹配 WS group 的候选(参考 live-room.nvue)。
  17. * 后端可能把 group 发成 liveid(hash) / 路由 id / 数字 id。
  18. */
  19. export function collectRoomGroupCandidates(room, roomKey = '') {
  20. return [
  21. roomKey,
  22. room?.liveid,
  23. room?.group,
  24. room?.id,
  25. room?.live_id,
  26. room?.liveId
  27. ]
  28. .filter((v) => v !== undefined && v !== null && v !== '')
  29. .map(String)
  30. }
  31. /**
  32. * 判断 WS 消息是否属于当前直播间(参考 live-room.nvue 的 groupCandidates)。
  33. * @param {string} [roomKey] 路由 id(liveId),后端有时把 group 发成 id 而非 liveid
  34. */
  35. export function groupMatchesRoom(msg, room, roomKey = '') {
  36. const msgGroup =
  37. msg?.group != null && msg.group !== ''
  38. ? String(msg.group)
  39. : (msg?.liveid != null && msg.liveid !== '' ? String(msg.liveid) : '')
  40. if (!msgGroup) return true
  41. const keys = collectRoomGroupCandidates(room, roomKey)
  42. return !keys.length || keys.includes(msgGroup)
  43. }
  44. function readInnerMessage(msg) {
  45. if (!msg) return null
  46. if (msg.message && typeof msg.message === 'object') return msg.message
  47. if (typeof msg.message === 'string') return { type: 'msg', text: msg.message }
  48. return null
  49. }
  50. export function isChatDisplayType(innerType) {
  51. return CHAT_TYPES.has(innerType)
  52. }
  53. export function extractEnterNotice(msg) {
  54. const inner = readInnerMessage(msg)
  55. if (!inner || inner.type !== 'coming') return null
  56. const form = msg.form || {}
  57. return {
  58. nickname: form.nickname || form.name || msg.username || '观众'
  59. }
  60. }
  61. export function extractChatLine(msg) {
  62. if (!msg) return null
  63. const inner = readInnerMessage(msg)
  64. if (inner) {
  65. if (inner.type === 'coming') return null
  66. if (isChatDisplayType(inner.type)) {
  67. const text = inner.text || inner.msg || inner.message || inner.content
  68. if (!text) return null
  69. const form = msg.form || {}
  70. return buildLine({
  71. userId: form.id,
  72. user: form.nickname || form.name || msg.username || msg.nickname || '观众',
  73. avatar: form.avatar || msg.avatar || '',
  74. text: String(text)
  75. })
  76. }
  77. }
  78. if (msg.type === 'msg' || msg.type === 'message') {
  79. const text = msg.message || msg.content || msg.text
  80. if (!text || typeof text === 'object') return null
  81. return buildLine({
  82. userId: msg.form?.id ?? msg.user_id,
  83. user: msg.form?.nickname || msg.username || msg.nickname || '观众',
  84. avatar: msg.form?.avatar || msg.avatar || '',
  85. text: String(text)
  86. })
  87. }
  88. return null
  89. }
  90. function buildLine({ userId, user, avatar, text }) {
  91. return {
  92. id: `${Date.now()}_${Math.random()}`,
  93. userId: userId != null ? String(userId) : '',
  94. user: user || '观众',
  95. avatar: avatar || '',
  96. text: text || ''
  97. }
  98. }
  99. export function chatLineFingerprint(line) {
  100. if (!line) return ''
  101. return `${line.userId}|${(line.text || '').slice(0, 200)}`
  102. }
  103. export function appendChatLine(list, line) {
  104. if (!line) return list
  105. const next = list.concat(line)
  106. return next.length > MAX_LINES ? next.slice(-MAX_LINES) : next
  107. }
  108. export function nicknameColor(userId) {
  109. const id = Number.parseInt(String(userId || '0'), 10)
  110. const idx = Number.isFinite(id) ? Math.abs(id) % NAME_COLORS.length : 0
  111. return NAME_COLORS[idx]
  112. }
  113. /**
  114. * 后端在主播下播时会推送 `{ message: { type: 'end' } }` 或 `{ type: 'end' }`。
  115. */
  116. export function isBroadcastEndedSignal(msg) {
  117. if (!msg) return false
  118. if (msg.type === 'end') return true
  119. if (msg.message && typeof msg.message === 'object' && msg.message.type === 'end') return true
  120. return false
  121. }