提醒、推送设置与举报
本页介绍三件事:收到新消息时怎样决定是否提醒用户、由哪个标签页提醒;用 im.push 读取和修改用户的推送设置与会话免打扰;用 im.reports 举报消息、用户、群和聊天室。
收到消息时提醒
Web SDK 不会自己弹出浏览器通知或播放提示音,由你在收到 message.received 事件时决定。事件的载荷中已经带好了两项判断:
alert:本标签页是不是负责提醒的标签页(见由哪个标签页提醒);notify:按用户的推送设置和会话免打扰,这条消息是否应该提醒,以及通知的标题和正文(NotifyDecision)。
import type { NotifyDecision } from '@deeprespond/im-web';
im.on('message.received', ({ message, notify, alert }) => {
const decision = notify as NotifyDecision | null;
if (!alert || !decision?.notify) return;
if (document.visibilityState === 'visible') {
showToast(`${decision.title}:${decision.body}`); // 页面可见时用页面内的提示
return;
}
if (Notification.permission === 'granted') {
const n = new Notification(decision.title || '新消息', { body: decision.body, tag: message.conversation_id });
n.onclick = () => {
window.focus();
// 打开这个会话
};
}
});
// 在用户的操作中(如点击“开启通知”)请求权限
async function enableNotification(): Promise<void> {
await Notification.requestPermission();
}message.received只对通过长连接实时收到的、别人发来的消息发出;离线期间的消息在重新连接后由同步补齐,不发出这个事件,也就不会在上线时弹出一连串通知。notify的类型声明为unknown,请按NotifyDecision | null使用。- 用户正在看的会话通常不需要弹出通知。SDK 的判断不考虑这一点,请自行判断(如这个会话的消息列表打开着、页面可见时跳过)。
- 网页关闭后收不到任何提醒:Web SDK 没有浏览器推送(Web Push),新消息只能通过长连接得知。
由哪个标签页提醒
用户在同一浏览器中打开了多个页面时,每个标签页都会收到 message.received,如果都弹出通知,用户会收到重复的通知、听到重叠的提示音。SDK 选出一个“提醒标签页”:
- 有可见的标签页时,取最近获得焦点的那个;
- 都不可见时(如浏览器最小化),取同一浏览器中负责长连接的那个标签页。
只在事件载荷中 alert 为 true 的标签页提醒即可。im.connection.isAlertTab 是本标签页当前是否为提醒标签页,变化时发出 tabs.roleChanged,可以用于显示状态;弹出通知请以事件载荷中的 alert 为准,它是事件发出时的值。来电的提醒同样如此,见音视频的 call.incoming。
提醒的判断
notify 就是 im.push.shouldNotify(message) 的结果。也可以对任何一条消息自己调用,如在列表中补发提醒:
const decision = im.push.shouldNotify(message);
if (decision.notify) {
console.log(decision.title, decision.body);
} else {
console.log('不提醒', decision.reason);
}不提醒时 reason 说明原因:
reason | 说明 |
|---|---|
self | 本人发的消息 |
tip | 群提示。被移出群、群解散等由群组的事件得知,见群组 |
call | 通话记录。来电由 call.incoming 单独提醒 |
excluded | 发送时设置了 exclude_from_unread |
not_loaded | 推送设置还没有同步到(刚登录时),可以自行决定是否提醒 |
dnd | 用户开启了全局免打扰 |
quiet_hours | 当前在夜间免打扰时段内 |
muted | 这个会话设置了免打扰 |
not_mentioned | 群设置了“只提醒 @ 我”,这条消息没有 @ 本人或全体成员 |
提醒时 title 和 body 按用户的预览方式(effective_preview)生成,与手机上收到的推送一致:
| 预览方式 | 单聊 | 群聊 |
|---|---|---|
full | 标题为发送者,正文为消息摘要 | 标题为群名,正文为“发送者: 摘要”,@ 了本人的前面加“[有人@我]” |
sender_only | 标题为发送者,正文为“发来一条新消息” | 标题为群名,正文为“某某 发来一条新消息” |
none | 标题为空,正文为“你收到了一条新消息” | 同左 |
发送者取昵称(没有昵称为用户名),不使用好友备注和群昵称。消息摘要的规则与服务端的推送相同:文本取前 100 个字符,图片为“[图片]”,文件为“[文件] 文件名”,自定义消息为“[消息]”。
这个判断与服务端的离线推送规则近似,有以下差别:
- 发送消息时的
push.disabled、push.force不随消息下发,按都没有设置处理; - @ 全体成员按能突破“只提醒 @ 我”处理(服务端的默认值);
- 标题和正文只使用内置的中文模板,不使用你在控制台中覆盖的通知模板。
推送设置
用户的推送设置包括全局免打扰、夜间免打扰和预览方式。它们决定用户在手机 App 上收到的离线推送,也是上面网页提醒判断的依据。设置保存在服务端,在用户的全部设备间同步:用户在网页上修改后,手机上的推送随之变化。网页本身没有离线推送,见离线推送。
im.push.settings() 同步读取本地已同步到的设置,变化时发出 push.changed 事件:
function renderSettings(): void {
const s = im.push.settings();
if (!s) return; // 还没有同步到
render({
dnd: s.dnd ? (s.dnd.until ? `免打扰至 ${s.dnd.until}` : '一直免打扰') : '关闭',
quietHours: s.quiet_hours ? `${s.quiet_hours.start}~${s.quiet_hours.end}` : '关闭',
preview: s.effective_preview, // full / sender_only / none
});
}
im.on('push.changed', ({ settings }) => {
if (settings) renderSettings();
});
renderSettings();修改时只给出要改的项:
// 8 小时内免打扰
await im.push.updateSettings({ dnd: { duration_seconds: 8 * 3600 } });
// 一直免打扰;关闭免打扰
await im.push.updateSettings({ dnd: { duration_seconds: null } });
await im.push.updateSettings({ dnd: null });
// 每天 22:00 到次日 07:30 免打扰,按上海时间
await im.push.updateSettings({ quiet_hours: { start: '22:00', end: '07:30', timezone: 'Asia/Shanghai' } });
await im.push.updateSettings({ quiet_hours: null });
// 通知只显示发送者;恢复为应用的默认方式
await im.push.updateSettings({ preview: 'sender_only' });
await im.push.updateSettings({ preview: null });- 全局免打扰:一段时间内(1 秒到 10 年)或一直不推送。到期后 SDK 在本地自动恢复,发出
push.changed。 - 夜间免打扰:每天的一个时段,
end早于start表示跨过午夜,开始时刻算在时段内、结束时刻不算。时刻为HH:MM(24 小时制);时区为 IANA 时区名称,可以用Intl.DateTimeFormat().resolvedOptions().timeZone取得浏览器的时区。时区不随设备变化,用户换了时区需要重新设置。 - 预览方式:
full显示发送者和内容,sender_only只显示发送者,none都不显示。preview是用户自己的选择,为null时使用应用的默认方式;effective_preview是实际生效的方式。
会话免打扰
对一个单聊或群设置免打扰:
// 单聊:不提醒
await im.push.mute({ conversation_type: 'single', target: 'lisi' }, { mode: 'none' });
// 群:只在有人 @ 我时提醒,24 小时后自动恢复
await im.push.mute({ conversation_type: 'group', target: groupId }, { mode: 'mention_only', duration_seconds: 86400 });
// 取消
await im.push.unmute({ conversation_type: 'group', target: groupId });
// 读取(同步)
const mute = im.push.getMute({ conversation_type: 'group', target: groupId });
console.log(mute?.mode, mute?.until);target为单聊对方的用户名或群 ID。mode为none(不提醒)或mention_only(只在 @ 本人或全体成员时提醒,只用于群)。- 单聊只能设置
none;不能对自己设置。这两种情况 SDK 在本地以local_validation拒绝(reason为invalid_mode、self_conversation)。 - 省略
duration_seconds一直有效;给出的(1 秒到 10 年)到期后自动恢复,SDK 在本地到期时更新会话列表并发出push.changed。 - 设置后会话列表中这个会话的
muted随之变化(见会话),im.conversations.getUnreadTotal({ excludeMuted: true })不再计入它。 - 免打扰只影响推送和提醒,不影响消息的接收和未读数。退出或被移出群、群解散后,对这个群的免打扰随之删除。每个用户最多 10000 个会话免打扰。
push.changed 事件的载荷为 { settings, mutes }:settings 为 true 表示推送设置变了;mutes 列出免打扰变化了的会话({ conversation_type, target })。其他设备上的修改同样实时同步过来。
举报
用户可以举报一条消息、一个用户、一个群、一个聊天室或一条聊天室消息。举报进入你在控制台中的审核记录,由审核人员处理,见内容安全。
import type { ReportReason } from '@deeprespond/im-web';
if (im.config?.report_enabled !== false) {
// 举报一条消息
const { duplicate } = await im.reports.submit({ target_type: 'message', message_id: '99584445836689408', reason: 'fraud' });
showToast(duplicate ? '你已举报过' : '已举报,我们会尽快处理');
}
// 举报一个用户,附上他发来的消息作为证据(最多 5 条)
const reason: ReportReason = 'abuse';
await im.reports.submit({
target_type: 'user',
username: 'lisi',
reason,
description: '多次发送辱骂信息',
evidence_message_ids: ['99584445836689408', '99584445836689409'],
});
// 本人 90 天内的举报
const page = await im.reports.mine({ limit: 20 });
for (const r of page.items) console.log(r.target_type, r.reason, r.status === 'pending' ? '处理中' : '已处理');- 入口:运行配置的
report_enabled为false时应用关闭了客户端举报,请隐藏举报入口;这时调用以permission_denied(report_disabled)拒绝。 - 原因:只能从固定的 10 种中选择,见 ReportReason。说明最多 500 个字符。
- 证据:举报用户和群时可以附上最多 5 条消息的
message_id。举报用户的证据必须是被举报的人发的,举报群的必须是这个群里的,都不能是已撤回的;SDK 先用本地保存的消息检查,不符合的以local_validation(reason为invalid_evidence)拒绝。请在界面上只让用户勾选符合条件的消息。 - 只能举报看得到的内容:入群之前、离开群之后的群消息,本人已删除的、已撤回的消息不能举报,服务端返回
not_found。聊天室消息只能举报最近 24 小时内的,读不到时可以改为举报聊天室或发送者。不能举报自己(local_validation,reason为self_report)。 - 重复举报:同一个人对同一个对象只算一次,再次举报返回原来的那一次,
duplicate为true,同样提示“已举报”。 - 频率:每个用户每分钟 5 次、每天 30 次,超出时以
rate_limited拒绝,不会自动重试。 - 结果:举报人只能看到“处理中”(
pending)和“已处理”(closed),看不到处理结论。
接口参考
方法返回 Promise 的,失败时以 DRError 拒绝;没有登录时以 not_signed_in 拒绝,网络错误为 network_error、timeout。错误码见事件与错误和服务端的错误码。
im.push.settings()
同步读取本地的推送设置。全局免打扰已到期的,返回的 dnd 为 null。变化时发出 push.changed(settings 为 true)。
返回值:PushSettings | null,还没有同步到时为 null。见 PushSettings。
im.push.updateSettings()
修改推送设置,只修改给出的项。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
patch.dnd | { duration_seconds: number | null } | null | 否 | 全局免打扰:duration_seconds 为 1 到 315360000 秒,为 null 一直开启;dnd 为 null 关闭 |
patch.quiet_hours | { start: string; end: string; timezone: string } | null | 否 | 夜间免打扰:start、end 为 HH:MM,timezone 为 IANA 时区名称;null 关闭 |
patch.preview | PushPreview | null | 否 | 预览方式:'full'、'sender_only'、'none';null 使用应用的默认方式 |
至少给出一项。
返回值:Promise<PushSettings>,修改后的完整设置。
可能的错误:local_validation(reason 为 required,一项也没有给出或缺少时区;out_of_range,时长超出范围;invalid_format,时刻的格式不对;invalid_value,预览方式不对)、invalid_argument(invalid_quiet_hours,开始等于结束;invalid_timezone,时区不是 IANA 时区名称)。
im.push.mute()
设置会话免打扰。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
target.conversation_type | 'single' | 'group' | 是 | 单聊或群 |
target.target | string | 是 | 单聊对方的用户名或群 ID |
p.mode | 'none' | 'mention_only' | 是 | none 不提醒;mention_only 只在 @ 本人时提醒,只用于群 |
p.duration_seconds | number | 否 | 时长,1 到 315360000 秒;省略一直有效 |
返回值:Promise<void>。
可能的错误:local_validation(reason 为 invalid_mode,单聊设置了 mention_only;self_conversation,对自己设置;out_of_range,时长超出范围;required、invalid_value,参数缺失或不对)、limit_exceeded(会话免打扰已达 10000 个)。
im.push.unmute()
取消会话免打扰。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
target | MuteTarget | 是 | 见 MuteTarget |
返回值:Promise<void>。
可能的错误:local_validation(参数缺失或不对)。
im.push.getMute()
同步读取一个会话的免打扰。变化时发出 push.changed(mutes 中列出这个会话)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
target | MuteTarget | 是 | 见 MuteTarget |
返回值:MuteInfo | null,没有设置或已到期时为 null。MuteInfo 为 { mode, until },见会话。
im.push.shouldNotify()
按本地的推送设置和会话免打扰,判断一条新消息是否应该提醒,并给出通知的标题和正文。同步执行。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
message | MessageView | 是 | 消息,见消息 |
返回值:NotifyDecision,见 NotifyDecision。
im.reports.submit()
提交举报。不会自动重试。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
p | ReportInput | 是 | 举报的对象、原因和说明,见 ReportInput |
返回值:Promise<{ report: Report; duplicate: boolean }>。duplicate 为 true 表示重复举报,report 为原来的那一次。
可能的错误:local_validation(reason 为 invalid_value,对象类型或原因不对;required,缺少对象的 ID;too_long,说明超过 500 个字符;control_characters,说明中有控制字符;self_report,举报自己;invalid_evidence,证据不符合要求;too_many_items,证据超过 5 条)、permission_denied(report_disabled)、not_found(对象不存在或本人看不到)、invalid_argument(invalid_evidence)、rate_limited。
im.reports.mine()
查询本人 90 天内的举报,翻页。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page.cursor | string | null | 否 | 上一页返回的 next_cursor |
page.limit | number | 否 | 每页条数 |
返回值:Promise<Page<Report>>,即 { items: Report[]; next_cursor: string | null },没有下一页时 next_cursor 为 null。
数据结构
NotifyDecision
| 字段 | 类型 | 说明 |
|---|---|---|
notify | boolean | 是否应该提醒 |
title | string | 可选,提醒时给出:通知的标题,预览方式为 none 时为空字符串 |
body | string | 可选,提醒时给出:通知的正文 |
reason | string | 可选,不提醒时给出原因:self、tip、call、excluded、not_loaded、dnd、quiet_hours、muted、not_mentioned |
PushSettings
与服务端的推送设置相同。
| 字段 | 类型 | 说明 |
|---|---|---|
dnd | { until: string | null } | null | 全局免打扰,没有开启为 null;until 为到期时间,null 表示一直开启 |
quiet_hours | { start: string; end: string; timezone: string } | null | 夜间免打扰,没有设置为 null |
preview | PushPreview | null | 用户选择的预览方式,没有选择为 null |
effective_preview | PushPreview | 实际生效的预览方式 |
settings_version | number | 设置的版本号,SDK 同步用 |
PushPreview
'full' | 'sender_only' | 'none':显示发送者和内容、只显示发送者、都不显示。
MuteTarget
| 字段 | 类型 | 说明 |
|---|---|---|
conversation_type | 'single' | 'group' | 单聊或群 |
target | string | 单聊对方的用户名或群 ID |
ReportInput
按 target_type 分为五种,都有 reason(必填)和 description(可选,最多 500 个字符,可以有换行):
target_type | 其他字段 | 说明 |
|---|---|---|
message | message_id | 举报一条单聊或群消息 |
user | username;evidence_message_ids? | 举报一个用户,可以附上最多 5 条他发的消息 |
group | group_id;evidence_message_ids? | 举报一个群,可以附上最多 5 条这个群里的消息 |
chatroom | room_id | 举报一个聊天室 |
chatroom_message | room_id;message_id | 举报一条聊天室消息 |
ReportReason
| 取值 | 含义 |
|---|---|
ad | 广告、骚扰 |
fraud | 诈骗 |
porn | 色情低俗 |
politics | 涉政 |
terrorism | 暴恐 |
abuse | 辱骂攻击 |
illegal | 违法违禁 |
infringement | 侵权 |
minor | 涉及未成年人 |
other | 其他 |
Report
| 字段 | 类型 | 说明 |
|---|---|---|
report_id | string | 举报 ID |
target_type | string | 举报的对象类型,同 ReportInput |
username | string | null | 被举报的用户(举报用户时) |
message_id | string | null | 被举报的消息 |
group_id | string | null | 被举报的群 |
room_id | string | null | 被举报的聊天室 |
reason | ReportReason | 原因 |
status | 'pending' | 'closed' | 处理中或已处理 |
created_at | string | 举报时间 |
