提醒、免打擾與舉報
本頁介紹三件事:App 在前臺收到新訊息時怎樣決定是否提醒使用者;用 im.push 讀取和修改使用者的推送設定(全域性免打擾、夜間免打擾、預覽方式)與會話免打擾;用 im.reports 舉報訊息、使用者、群和聊天室。
App 在後臺或被殺時的提醒由離線推送完成,見離線推送。這裡的推送設定和會話免打擾同時決定離線推送和 App 內的提醒。
| 要做什麼 | 看哪裡 |
|---|---|
| App 在前臺時提示新訊息 | 收到訊息時提醒 |
| 全域性免打擾、夜間免打擾、通知顯示的內容 | 推送設定 |
| 某個單聊或群免打擾 | 會話免打擾 |
| 舉報訊息、使用者、群或聊天室 | 舉報 |
收到訊息時提醒
Flutter SDK 不會自己在 App 內彈出提示或播放提示音,由你在收到 MessageReceived 事件時決定。事件的 notify 已經按使用者的推送設定和會話免打擾判斷好這條訊息是否應該提醒,並給出了提醒的標題和正文(NotifyDecision):
im.on<MessageReceived>().listen((e) {
final decision = e.notify;
if (!decision.notify) return;
// 用户正在看这个会话时不需要提醒
if (e.message.conversationId == currentConversationId) return;
showBanner(decision.title ?? '新消息', decision.body ?? '', conversationId: e.message.conversationId ?? '');
});MessageReceived只對透過長連線實時收到的、別人發來的訊息發出;離線期間的訊息在重新連線後由同步補齊,不發出這個事件,也就不會在上線時彈出一連串提示。- 使用者正在看的會話通常不需要提醒,SDK 的判斷不考慮這一點,請自行判斷。
- 手機上只做 App 內的提醒:App 在後臺時,服務端認為這臺裝置不在前臺,會推送這條訊息,由系統顯示通知。不要再用本地通知外掛顯示一次,否則使用者會收到兩條。
- 桌面端(macOS 等):桌面 App 保持長連線時一直算在前臺,服務端不推送,需要你在收到
MessageReceived時用本地通知外掛顯示系統通知。
提醒的判斷
notify 就是 im.push.shouldNotify(message) 的結果。也可以對任何一條訊息自己呼叫:
final decision = im.push.shouldNotify(message);
if (decision.notify) {
print('${decision.title}:${decision.body}');
} else {
print('不提醒:${decision.reason}');
}不提醒時 reason 說明原因:
reason | 說明 |
|---|---|
self | 本人發的訊息 |
tip | 群提示。被移出群、群解散等由群組的事件得知,見群組 |
call | 通話記錄。來電由 CallIncoming 單獨提醒,見音視訊通話 |
excluded | 傳送時設定了 exclude_from_unread |
not_loaded | 推送設定還沒有同步到(剛登入時),可以自行決定是否提醒 |
dnd | 使用者開啟了全域性免打擾 |
quiet_hours | 當前在夜間免打擾時段內 |
muted | 這個會話設定了免打擾 |
not_mentioned | 群設定了“只提醒 @ 我”,這條訊息沒有 @ 本人或全體成員 |
提醒時 title 和 body 按使用者的預覽方式(effectivePreview)生成,與手機上收到的推送一致:
| 預覽方式 | 單聊 | 群聊 |
|---|---|---|
full | 標題為傳送者,正文為訊息摘要 | 標題為群名,正文為“傳送者: 摘要”,@ 了本人的前面加“[有人@我]” |
senderOnly | 標題為傳送者,正文為“發來一條新訊息” | 標題為群名,正文為“某某 發來一條新訊息” |
none | 標題為空,正文為“你收到了一條新訊息” | 同左 |
傳送者取暱稱(沒有暱稱為使用者名稱),不使用好友備註和群暱稱。訊息摘要的規則與服務端的推送相同:文字取前 100 個字元,圖片為“[圖片]”,檔案為“[檔案] 檔名”,自定義訊息為“[訊息]”。
這個判斷與服務端的離線推送規則近似,有以下差別:
- 傳送訊息時的
push.disabled、push.force不隨訊息下發,按都沒有設定處理; - @ 全體成員按能突破“只提醒 @ 我”處理(服務端的預設值);
- 標題和正文只使用內建的中文模板,不使用你在控制檯中覆蓋的通知模板。
推送設定
使用者的推送設定包括全域性免打擾、夜間免打擾和預覽方式。它們決定使用者收到的離線推送,也是上面 App 內提醒判斷的依據。設定儲存在服務端,在使用者的全部裝置間同步:使用者在一臺手機上修改後,他其他裝置上的推送隨之變化。
im.push.settings() 同步讀取本地已同步到的設定,變化時發出 PushChanged 事件:
void renderSettings() {
final s = im.push.settings();
if (s == null) return; // 还没有同步到
final until = s.dndUntil;
final quiet = s.quietHours;
render({
'dnd': !s.dndEnabled ? '关闭' : (until == null ? '一直免打扰' : '免打扰至 ${until.toLocal()}'),
'quietHours': quiet == null ? '关闭' : '${quiet.start}~${quiet.end}',
'preview': s.effectivePreview.wire, // full / sender_only / none
});
}
im.on<PushChanged>().where((e) => e.settings).listen((_) => renderSettings());
renderSettings();修改時只給出要改的項:
// 8 小时内免打扰
await im.push.updateSettings(dnd: const DndChange.on(durationSeconds: 8 * 3600));
// 一直免打扰;关闭免打扰
await im.push.updateSettings(dnd: const DndChange.on());
await im.push.updateSettings(dnd: const DndChange.off());
// 每天 22:00 到次日 07:30 免打扰,按上海时间
await im.push.updateSettings(
quietHours: const QuietHoursChange.set(QuietHours(start: '22:00', end: '07:30', timezone: 'Asia/Shanghai')),
);
await im.push.updateSettings(quietHours: const QuietHoursChange.off());
// 通知只显示发送者;恢复为应用的默认方式
await im.push.updateSettings(preview: const PreviewChange.set(PushPreview.senderOnly));
await im.push.updateSettings(preview: const PreviewChange.useDefault());- 全域性免打擾:一段時間內(1 秒到 10 年)或一直不推送。到期後 SDK 在本地自動恢復,發出
PushChanged。 - 夜間免打擾:每天的一個時段,
end早於start表示跨過午夜,開始時刻算在時段內、結束時刻不算。時刻為HH:MM(24 小時制);時區為 IANA 時區名稱(如Asia/Shanghai)。Dart 的DateTime.now().timeZoneName是時區的縮寫(如CST),不能直接使用,可以用flutter_timezone等外掛取得裝置的 IANA 時區。時區不隨裝置變化,使用者換了時區需要重新設定。 - 預覽方式:
full顯示傳送者和內容,senderOnly只顯示傳送者,none都不顯示。preview是使用者自己的選擇,為null時使用應用的預設方式;effectivePreview是實際生效的方式。
會話免打擾
對一個單聊或群設定免打擾:
// 单聊:不提醒
await im.push.mute(const MuteTarget.single('lisi'), mode: MuteMode.none);
// 群:只在有人 @ 我时提醒,24 小时后自动恢复
await im.push.mute(MuteTarget.group(groupId), mode: MuteMode.mentionOnly, durationSeconds: 86400);
// 取消
await im.push.unmute(MuteTarget.group(groupId));
// 读取(同步)
final mute = im.push.getMute(MuteTarget.group(groupId));
print('${mute?.muteMode} ${mute?.until}');MuteMode.none不提醒;MuteMode.mentionOnly只在 @ 本人或全體成員時提醒,只用於群。- 單聊只能設定
MuteMode.none;不能對自己設定。這兩種情況 SDK 在本地以local_validation拒絕(reason分別為invalid_mode、self_conversation)。 - 省略
durationSeconds一直有效;給出的(1 秒到 10 年)到期後自動恢復,SDK 在本地到期時更新會話列表併發出PushChanged。 - 設定後會話列表中這個會話的
muted隨之變化(見會話),im.conversations.getUnreadTotal(excludeMuted: true)不再計入它,切到後臺時上報的角標預設也不計入它。 - 免打擾隻影響推送和提醒,不影響訊息的接收和未讀數。退出或被移出群、群解散後,對這個群的免打擾隨之刪除。每個使用者最多 10000 個會話免打擾。
PushChanged 事件的 settings 為 true 表示推送設定變了;mutes 列出免打擾變化了的會話(MuteTarget)。其他裝置上的修改同樣實時同步過來:
im.on<PushChanged>().listen((e) {
for (final target in e.mutes) {
final mute = im.push.getMute(target);
print('${target.conversationType} ${target.target}:${mute == null ? '已取消免打扰' : '免打扰'}');
}
});舉報
使用者可以舉報一條訊息、一個使用者、一個群、一個聊天室或一條聊天室訊息。舉報進入你在控制檯中的稽核記錄,由稽核人員處理,見內容安全。
// 应用关闭了客户端举报时,隐藏举报入口
final canReport = im.config?.reportEnabled ?? false;
if (canReport) {
// 举报一条消息
final result = await im.reports.submit(const ReportTarget.message('99584445836689408'), reason: ReportReason.fraud);
showToast(result.duplicate ? '你已举报过' : '已举报,我们会尽快处理');
}
// 举报一个用户,附上他发来的消息作为证据(最多 5 条)
await im.reports.submit(
const ReportTarget.user('lisi', evidenceMessageIds: ['99584445836689408', '99584445836689409']),
reason: ReportReason.abuse,
description: '多次发送辱骂信息',
);
// 本人 90 天内的举报
final page = await im.reports.mine(limit: 20);
for (final r in page.items) {
print('${r.targetType} ${r.reason} ${r.status == 'pending' ? '处理中' : '已处理'}');
}- 入口:執行配置的
reportEnabled為false時應用關閉了客戶端舉報,請隱藏舉報入口;這時呼叫以permission_denied(reason為report_disabled)拒絕。 - 原因:只能從固定的 10 種中選擇,見 ReportReason。說明最多 500 個字元,可以有換行。
- 證據:舉報使用者和群時可以附上最多 5 條訊息的
messageId。舉報使用者的證據必須是被舉報的人發的,舉報群的必須是這個群裡的,都不能是已撤回的;SDK 先用本地儲存的訊息檢查,不符合的以local_validation(reason為invalid_evidence)拒絕。請在介面上只讓使用者勾選符合條件的訊息。 - 只能舉報看得到的內容:入群之前、離開群之後的群訊息,本人已刪除的、已撤回的訊息不能舉報,服務端返回
not_found。聊天室訊息只能舉報最近 24 小時內的,讀不到時可以改為舉報聊天室或傳送者。不能舉報自己(local_validation,reason為self_report)。 - 重複舉報:同一個人對同一個物件只算一次,再次舉報返回原來的那一次,
duplicate為true,同樣提示“已舉報”。 - 頻率:每個使用者每分鐘 5 次、每天 30 次,超出時以
rate_limited拒絕,不會自動重試。 - 結果:舉報人只能看到“處理中”(
pending)和“已處理”(closed),看不到處理結論。
介面參考
返回 Future 的方法失敗時丟擲 DRException;沒有登入時為 not_signed_in,網路錯誤為 network_error、timeout。錯誤碼見事件與錯誤處理和服務端的錯誤碼。
im.push.settings()
同步讀取本地的推送設定。全域性免打擾已到期的視為關閉。變化時發出 PushChanged(settings 為 true)。
返回值:PushSettings?,還沒有同步到時為 null。見 PushSettings。
im.push.updateSettings()
修改推送設定,只修改給出的項。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
dnd | DndChange? | 否(命名引數) | 全域性免打擾:DndChange.on(durationSeconds: ...) 開啟,durationSeconds 為 1 到 315360000 秒,省略為一直開啟;DndChange.off() 關閉 |
quietHours | QuietHoursChange? | 否(命名引數) | 夜間免打擾:QuietHoursChange.set(QuietHours(...)) 設定,QuietHoursChange.off() 關閉 |
preview | PreviewChange? | 否(命名引數) | 預覽方式:PreviewChange.set(PushPreview.xxx) 設定,PreviewChange.useDefault() 使用應用的預設方式 |
至少給出一項。
返回值:Future<PushSettings>,修改後的完整設定。
可能的錯誤:local_validation(reason 為 required,一項也沒有給出或缺少時區;out_of_range,時長超出範圍;invalid_format,時刻的格式不對)、invalid_argument(invalid_quiet_hours,開始等於結束;invalid_timezone,時區不是 IANA 時區名稱)。
im.push.mute()
設定會話免打擾。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
target | MuteTarget | 是 | MuteTarget.single(username) 或 MuteTarget.group(groupId) |
mode | MuteMode | 是(命名引數) | MuteMode.none 不提醒;MuteMode.mentionOnly 只在 @ 本人時提醒,只用於群 |
durationSeconds | int? | 否(命名引數) | 時長,1 到 315360000 秒;省略一直有效 |
返回值:Future<void>。
可能的錯誤:local_validation(reason 為 invalid_mode,單聊設定了 mentionOnly;self_conversation,對自己設定;out_of_range,時長超出範圍;required,目標為空)、limit_exceeded(會話免打擾已達 10000 個)。
im.push.unmute()
取消會話免打擾。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
target | MuteTarget | 是 | 見 MuteTarget |
返回值:Future<void>。
可能的錯誤:local_validation(目標為空、對自己設定)。
im.push.getMute()
同步讀取一個會話的免打擾。變化時發出 PushChanged(mutes 中列出這個會話)。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
target | MuteTarget | 是 | 見 MuteTarget |
返回值:MuteInfo?,沒有設定或已到期時為 null。見 MuteInfo。
im.push.shouldNotify()
按本地的推送設定和會話免打擾,判斷一條新訊息是否應該提醒,並給出提醒的標題和正文。同步執行。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | MessageView | 是 | 訊息,見訊息 |
返回值:NotifyDecision,見 NotifyDecision。
im.reports.submit()
提交舉報。不會自動重試。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
target | ReportTarget | 是 | 舉報的物件,見 ReportTarget |
reason | String | 是(命名引數) | 原因,取 ReportReason 中的常量 |
description | String? | 否(命名引數) | 說明,最多 500 個字元,可以有換行 |
返回值:Future<ReportSubmitResult>。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 天內的舉報,翻頁。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
cursor | String? | 否(命名引數) | 上一頁返回的 nextCursor |
limit | int? | 否(命名引數) | 每頁條數 |
返回值:Future<DRPage<Report>>:items 為本頁的舉報,nextCursor 為下一頁的遊標,沒有下一頁時為 null。
資料結構
NotifyDecision
| 欄位 | 型別 | 說明 |
|---|---|---|
notify | bool | 是否應該提醒 |
title | String? | 提醒時給出:標題,預覽方式為 none 時為空字串 |
body | String? | 提醒時給出:正文 |
reason | String? | 不提醒時給出原因:self、tip、call、excluded、not_loaded、dnd、quiet_hours、muted、not_mentioned |
PushSettings
推送設定,原始資料在 raw 中,欄位與服務端的推送設定相同。
| 欄位 | 型別 | 說明 |
|---|---|---|
dndEnabled | bool | 是否開啟了全域性免打擾(已到期的為 false) |
dndUntil | DateTime? | 全域性免打擾的到期時間(UTC);一直開啟或沒有開啟為 null,用 dndEnabled 區分 |
quietHours | QuietHours? | 夜間免打擾,沒有設定為 null |
preview | PushPreview? | 使用者選擇的預覽方式,沒有選擇為 null |
effectivePreview | PushPreview | 實際生效的預覽方式 |
settingsVersion | int | 設定的版本號,SDK 同步用 |
QuietHours
| 欄位 | 型別 | 說明 |
|---|---|---|
start | String | 開始時刻,HH:MM |
end | String | 結束時刻,HH:MM;早於 start 表示跨過午夜 |
timezone | String | IANA 時區名稱,如 Asia/Shanghai |
PushPreview
列舉:full 顯示傳送者和內容、senderOnly 只顯示傳送者、none 都不顯示。wire 為服務端的取值(full、sender_only、none)。
DndChange、QuietHoursChange、PreviewChange
updateSettings() 的引數:
| 建構函式 | 說明 |
|---|---|
DndChange.on({int? durationSeconds}) | 開啟全域性免打擾,durationSeconds 秒後到期;省略為一直開啟 |
DndChange.off() | 關閉全域性免打擾 |
QuietHoursChange.set(QuietHours value) | 設定夜間免打擾 |
QuietHoursChange.off() | 關閉夜間免打擾 |
PreviewChange.set(PushPreview value) | 設定預覽方式 |
PreviewChange.useDefault() | 使用應用的預設方式 |
MuteTarget
| 建構函式 | 說明 |
|---|---|
MuteTarget.single(String username) | 與這個使用者的單聊 |
MuteTarget.group(String groupId) | 這個群 |
欄位 conversationType(single 或 group)和 target(使用者名稱或群 ID)。兩個 MuteTarget 的型別和目標相同即相等,可以用作 Map 的鍵。
MuteMode
列舉:none 不提醒、mentionOnly 只提醒 @ 我(只用於群)。wire 為服務端的取值(none、mention_only)。
MuteInfo
| 欄位 | 型別 | 說明 |
|---|---|---|
mode | String | none 或 mention_only |
muteMode | MuteMode? | mode 的列舉形式,不認識的取值為 null |
until | DateTime? | 到期時間;一直有效為 null |
PushChanged
| 欄位 | 型別 | 說明 |
|---|---|---|
settings | bool | 推送設定是否變了 |
mutes | List<MuteTarget> | 免打擾變化了的會話 |
ReportTarget
舉報的物件(sealed class):
| 建構函式 | targetType | 說明 |
|---|---|---|
ReportTarget.message(String messageId) | message | 舉報一條單聊或群訊息 |
ReportTarget.user(String username, {List<String>? evidenceMessageIds}) | user | 舉報一個使用者,可以附上最多 5 條他發的訊息 |
ReportTarget.group(String groupId, {List<String>? evidenceMessageIds}) | group | 舉報一個群,可以附上最多 5 條這個群裡的訊息 |
ReportTarget.chatroom(String roomId) | chatroom | 舉報一個聊天室 |
ReportTarget.chatroomMessage(String roomId, String messageId) | chatroom_message | 舉報一條聊天室訊息 |
ReportReason
舉報原因的常量(String),ReportReason.all 為全部取值:
| 常量 | 取值 | 含義 |
|---|---|---|
ReportReason.ad | ad | 廣告、騷擾 |
ReportReason.fraud | fraud | 詐騙 |
ReportReason.porn | porn | 色情低俗 |
ReportReason.politics | politics | 涉政 |
ReportReason.terrorism | terrorism | 暴恐 |
ReportReason.abuse | abuse | 辱罵攻擊 |
ReportReason.illegal | illegal | 違法違禁 |
ReportReason.infringement | infringement | 侵權 |
ReportReason.minor | minor | 涉及未成年人 |
ReportReason.other | other | 其他 |
ReportSubmitResult
| 欄位 | 型別 | 說明 |
|---|---|---|
report | Report | 這次舉報;重複舉報時為原來的那一次 |
duplicate | bool | 是否為重複舉報 |
Report
原始資料在 raw 中。
| 欄位 | 型別 | 說明 |
|---|---|---|
reportId | String | 舉報 ID |
targetType | String | 舉報的物件型別,同 ReportTarget |
username | String? | 被舉報的使用者(舉報使用者時) |
messageId | String? | 被舉報的訊息 |
groupId | String? | 被舉報的群 |
roomId | String? | 被舉報的聊天室 |
reason | String | 原因,見 ReportReason |
status | String | pending 處理中或 closed 已處理 |
createdAt | DateTime? | 舉報時間 |
