Mudu.Room.Comment 评论组件

获取评论页数

// 返回评论页数,类型为int
var commentPage = Mudu.Room.Comment.GetPage()

发送评论

Mudu.Room.Comment.Send(
  // 要发送的评论文本,类型为string
  '活动很赞很给力',

  // 发送完成的回调函数,参数为response对象
  function (response) {
    response = JSON.parse(response)
    if (response.status === 'y') {
      console.log('发送成功')
    }
    if (response.status === 'n') {
      console.log('发送失败,错误码为:' + response.flag)
    }
  }
)
  • response对象说明
{
  // 成功状态,类型为string,成功时为'y',失败时为'n'
  status: 'y',

  // 状态码码,类型为int,成功时为0,失败不为0
  flag: 100,

  // 信息,类型为string,成功时为'发送消息成功!',失败时为'发送消息失败!'
  info: "发送消息成功!/失败!"
}
  • flag状态码码对照表
flag info status
100 发送消息成功 y
101 管理员禁止了聊天 n
102 观众被禁言 n
104 禁止匿名聊天 n
105 发送聊天内容过长 n
106 频繁发送 n
107 发送聊天内容中包含敏感词 n
135 系统不允许观众使用匿名身份 n
136 昵称中包含敏感词 n
103 其他错误,发送失败 n

获取评论

Mudu.Room.Comment.Get(
  // 要获取评论的页码,类型为int
  2,

  // 评论获取成功的回调函数,参数为response对象
  function (response) {
    response = JSON.parse(response)
    if (response.status === 'y') {
      console.log('获取评论成功,数据为:', response.data)
    } 
    if (response.status === 'n'){
      console.log('获取评论失败')
    }
  }
)
  • response返回结果说明
{
    // 状态
    "status": 'y',

    // 返回的数据
    "data": {
      // 还剩下多少页
      page: 70,

      // 获取到的评论
      comments: [
        {
          "id": 3438056,
          "visitor_id": "1493401897797743654_api",
          "username": "小白白",
          "message": "这个真不错",
          "avatar": "http://cdn12.mudu.tv/thumbnails/uploads/9/b5ed4ba472bbaa27e092be7594a2e31d.jpg",
          "dateline": "2020-04-16T15:56:00+08:00",
          "pushed": 1,
          "priority": 10,
          "top": 0,
          "msg_type": 10,
          "is_admin": 0
        },
        ......
      ]
    },
}
  • comments说明
名称 说明 类型
id 评论id int
visitor_id 观众标志 string
username 评论者的观众名 string
message 评论的文本 string
avatar 评论这个的头像 string
dateline 评论时间 字符串
pushed 是否已经发送了弹幕,0未发送,1已发送 int
priority 优先级 ,0 未审核,10已审核,时间戳(置顶时间) int
top 是否置顶 ,0 不置顶,1置顶 int
msg_type 消息来源类型, 10为普通文本消息...,详情见下表 int
is_admin 是否为管理员发送, 1是, 0不是 int
  • msg_type 和 message之间的映射关系
类型 标志位(msg_type) 存储类型 示例(message) 备注
普通评论 10 字符串 "真香" 表情替换
发送普通红包 20 字符串json '{"id":"nm4dk9jm", "type":1, "name":"恭喜发财!大吉大利!", }' type 2:钉钉
发送口令红包 21 字符串json '{"id":"nm4dk9jm", "type":1, "name":"恭喜发财!大吉大利!"}' type 2:钉钉
发送竞答红包 22 字符串json '{"id":"nm4dk9jm", "name":"参与竞答领取红包!"}'
发送竞答红包(自定义) 23 字符串json '{"id":"nm4dk9jm", "name":"参与竞答领取红包!"}'
抢口令红包评论 31 字符串 "大吉大利,今晚吃鸡"
免费道具打赏 40 字符串json '{"src":"https://xxx.com/a.jpg". "name":"免费道具", "number":1 }'
付费道具打赏 41 字符串json '{"src":"https://xxx.com/c.jpg". "name":"付费道具", "number":88 }'
现金打赏 42 字符串json '{"name":"赏赐", "amount":88.99 }'
图片评论 50 字符串 "https://xxx.com/abc.jpg"
系统自定义通知消息 60 字符串 "欢迎大家参与讨论"
观众匿名消息 11 字符串 "猜猜我是谁"

若控制台中配置了聊天文字颜色或者是回复消息,SDK 使用方需要在收到消息后自行解析消息内容,参考此页底部的说明。


获取当前用户使用匿名身份聊天相关配置

var anonymousCommentConfig = Mudu.Room.Comment.GetAnonymousCommentConfig()
  • anonymousCommentConfig 说明
属性 描述 类型
allowed 系统是否开启了允许观众使用匿名身份聊天 boolean
inUse 用户是否使用了匿名身份 boolean
nickname 观众匿名名称 string
avatar 观众匿名头像地址 string

设置用户匿名身份

说明:

  1. 如果设置开启用户匿名身份成功,用户之后发送聊天时将会使用匿名身份发送;
  2. 当次设置仅在本次sdk生命周期内有效,下次初始化sdk时需要重新设置聊天匿名身份;
  3. 为避免与默认的“匿名观众”身份混淆,建议在对观众Assign实名身份之后再允许其使用匿名身份聊天
const param = {
  // 设置当前用户是否使用匿名身份聊天
  inUse: true,
  // 设置当前用户匿名身份的昵称,最大长度 60
  nickname: '匿名用户',
  // 设置当前用户匿名身份的头像
  avatar: 'https://static.mudu.tv/assets/anonymized-user-avatar.png'
}
Mudu.Room.Comment.SetAnonymousCommentConfig(
  // 要设置的匿名配置
  param,
  // 设置结果
  function (response) {
    response = JSON.parse(response)
    if (response.status === 'y') {
      console.log('设置用户匿名聊天身份成功')
    }
    if (response.status === 'n') {
      console.log('设置用户匿名聊天身份失败:', response)
    }
  }
)
  • response 返回结果说明
{
  // 是否设置成功标志
  status: 'y',
  errcode: 1000
}
  • errcode 说明
errcode 说明 对应status
1000 成功 'y'
1406 设置昵称过长(可设置长度不超过60的字符串) 'n'
4935 系统不允许观众使用匿名身份 'n'
4936 昵称中包含敏感词 'n'

Comment.New事件

Comment.New事件会在评论新增的时候被触发

Mudu.MsgBus.On(
  // 事件名,值为Comment.New
  'Comment.New', 

  // 事件处理函数,参数为新的评论,类型为object
  function (newComment) {
    newComment = JSON.parse(newComment) // newComment 结构见页面底部
    console.log(newComment.username + '发送了一条新评论: ' + newComment.message)
  }
)

Comment.Top事件

Comment.Top事件会在评论置顶/取消置顶时触发

Mudu.MsgBus.On(
  // 事件名,值为Comment.Top
  'Comment.Top', 

  // 事件处理函数,参数为新的评论,类型为object
  function (newComment) {
    newComment = JSON.parse(newComment) // newComment 结构见页面底部
    console.log('一条评论被' + (newComment.top === 1 ? '置顶' : '取消置顶') + '了');
  }
)

Comment.Delete事件

Comment.Delete事件会在评论被管理员删除时触发

Mudu.MsgBus.On(
  // 事件名,值为Comment.Delete
  'Comment.Delete', 

  // 事件处理函数,参数为被删除的评论,类型为object
  function (newComment) {
    newComment = JSON.parse(newComment) // newComment 结构见页面底部
    console.log('一条评论被删除了');
  }
)
  • 事件处理函数参数newComment对象说明
名称 说明 类型
id 评论id int
visitor_id 观众标志 string
username 评论者的观众名 string
message 评论的文本 string
avatar 评论这个的头像 string
dateline 评论的时间戳 int
pushed 是否已经发送了弹幕,0未发送,1已发送 int
priority 优先级 ,0 未审核,10已审核,时间戳(置顶时间) int
top 是否置顶 ,0 不置顶,1置顶 int
msg_type 消息来源类型, 10为普通文本消息...,详情见上表 int
is_admin 是否为管理员发送, 1是, 0不是 int

解析结构化聊天消息

⚠️ 为了安全起见,你应该仅解析管理员发送的消息,而不是所有用户消息。

// 假设观众张三发送了一条 `hello`,管理员将字体颜色设为 `#0ea5e9` 并回复他 `你好`
// 消息经控制台发送后,被组装为一个特殊结构的字符串,如下所示:
const message = "[MSG]c=你好;m.col=#0ea5e9;m.q.id=123;m.q.s=张三;m.q.c=hello"
const parsed = isStructuredMessage(message) ? decodeStructuredMessage(message) : message
console.log(parsed) // { t: 'm', c: '你好', m: { col: '#0ea5e9', q: { id: '123', s: '张三', c: 'hello' } } }

下面为解析结构化聊天消息的工具代码,可以直接复制到项目中使用

// 消息解析工具函数

// 🔹 通用元信息接口
export interface StructuredMeta {
  col?: string // color
  q?: StructuredQuote // quote
}

// 🔹 回复信息(递归类型)
export interface StructuredQuote {
  id: string
  s: string // sender
  c: string // content
  m?: StructuredMeta
}

// 🔹 主消息结构
export interface StructuredMessage {
  t?: 'm' // type
  c: string // content
  m?: StructuredMeta // meta
}

const PREFIX = '[MSG]'
const cache = new Map<string, StructuredMessage>()

/** 转义 = ; . */
function escapeStructuredValue(value: string | number): string {
  return String(value).replace(/[=;.]/g, '\\$&');
}

/** 反转义 = ; . */
function unescapeStructuredValue(value: string): string {
  return value.replace(/\\([=;.])/g, '$1');
}

// ----------------------
// 是否为结构化消息
// ----------------------
export function isStructuredMessage(text: string) {
  return typeof text === 'string' && text.startsWith(PREFIX)
}

// ----------------------
// 编码结构化消息
// ----------------------
export function encodeStructuredMessage(obj: StructuredMessage): string {
  const kvPairs: string[] = [];

  function traverse(prefix: string, value: any): void {
    if (typeof value === 'object' && value !== null) {
      for (const key in value) {
        traverse(prefix ? `${prefix}.${key}` : key, value[key]);
      }
    } else {
      kvPairs.push(`${prefix}=${escapeStructuredValue(value)}`);
    }
  }

  traverse('', obj);

  return PREFIX + kvPairs.join(';');
}

// ----------------------
// 解码结构化消息
// ----------------------
export function decodeStructuredMessage(str: string): StructuredMessage | null {
  if (cache.has(str)) return cache.get(str)!;

  if (!isStructuredMessage(str)) return null;
  const body = str.slice(PREFIX.length);
  if (!body) return null;

  const result: Record<string, any> = {};

  // 拆分 key=value 对,支持转义和 HTML 实体
  function splitPairs(input: string): string[] {
    const res: string[] = [];
    let buf = '';
    let esc = false;
    let inEntity = false;
    for (const ch of input) {
      if (esc) { buf += ch; esc = false; continue; }
      if (ch === '\\') { buf += ch; esc = true; continue; }
      if (!inEntity && ch === '&') { buf += ch; inEntity = true; continue; }
      if (inEntity) { buf += ch; if (ch === ';') inEntity = false; continue; }
      if (ch === ';') { if (buf) res.push(buf); buf = ''; continue; }
      buf += ch;
    }
    if (buf) res.push(buf);
    return res;
  }

  // 查找未转义的等号
  function findUnescapedEqual(s: string): number {
    let esc = false;
    for (let i = 0; i < s.length; i++) {
      const ch = s[i];
      if (esc) { esc = false; continue; }
      if (ch === '\\') { esc = true; continue; }
      if (ch === '=') return i;
    }
    return -1;
  }

  // 拆分 key 路径,支持转义的点
  function splitPath(k: string): string[] {
    return k.split(/(?<!\\)\./).map(p => p.replace(/\\\./g, '.'));
  }

  try {
    const pairs = splitPairs(body);
    let hasValid = false;

    for (const p of pairs) {
      if (!p) continue;
      const eqIdx = findUnescapedEqual(p);
      if (eqIdx < 0) continue;

      const path = splitPath(p.slice(0, eqIdx));
      const value = unescapeStructuredValue(p.slice(eqIdx + 1));

      let target = result;
      path.forEach((key, i) => {
        if (i === path.length - 1) {
          target[key] = value;
        } else {
          if (!target[key] || typeof target[key] !== 'object') target[key] = {};
          target = target[key];
        }
      });
      hasValid = true;
    }


    if (hasValid) {
      cache.set(str, result as StructuredMessage);
      return result as StructuredMessage;
    }
  } catch (error) {
    console.error('decodeStructuredMessage error:', error);
  }

  return null
}

results matching ""

    No results matching ""