This commit is contained in:
2026-04-25 16:36:34 +08:00
commit db90e7579b
1876 changed files with 189777 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"label": "Message notification plugin",
"label.zh-CN": "消息通知插件",
"name": "com.msgbyte.notify",
"url": "/plugins/com.msgbyte.notify/index.js",
"version": "0.0.0",
"author": "msgbyte",
"description": "Ability to add notifications to apps",
"description.zh-CN": "为应用增加通知的能力",
"requireRestart": true
}

View File

@@ -0,0 +1,13 @@
{
"name": "@plugins/com.msgbyte.notify",
"main": "src/index.tsx",
"version": "0.0.0",
"private": true,
"dependencies": {
"lodash": "^4.17.21",
"tinycon": "^0.6.8"
},
"devDependencies": {
"@types/tinycon": "^0.6.3"
}
}

View File

@@ -0,0 +1,45 @@
import tinycon from 'tinycon';
/**
* 设置小红点
* @param num 小红点数
*/
let bubbleNum = 0;
export function setBubble(num: number) {
bubbleNum = num;
if (num < 0) {
num = 0;
}
tinycon.setBubble(num >= 100 ? 99 : num);
}
/**
* 增加小红点数量
*/
export function incBubble() {
setBubble(bubbleNum + 1);
}
const hiddenProperty =
'hidden' in document
? 'hidden'
: 'webkitHidden' in document
? 'webkitHidden'
: 'mozHidden' in document
? 'mozHidden'
: null;
const visibilityChangeEvent = hiddenProperty?.replace(
/hidden/i,
'visibilitychange'
);
const onVisibilityChange = function () {
if (!document[hiddenProperty ?? '']) {
// 显示标签页时清空
tinycon.setBubble(0);
} else {
// 隐藏标签页
}
};
if (typeof visibilityChangeEvent === 'string') {
document.addEventListener(visibilityChangeEvent, onVisibilityChange);
}

View File

@@ -0,0 +1,3 @@
export const PLUGIN_NAME = 'com.msgbyte.notify';
export const PLUGIN_SYSTEM_SETTINGS_DISABLED_SOUND = `${PLUGIN_NAME}.disabledSound`;

View File

@@ -0,0 +1,21 @@
import { regPluginSettings, showToasts } from '@capital/common';
import { initNotify } from './notify';
import { Translate } from './translate';
import { PLUGIN_SYSTEM_SETTINGS_DISABLED_SOUND } from './const';
if ('Notification' in window) {
initNotify();
} else {
if ((window as Window).innerWidth >= 768) {
// if not mobile
showToasts(Translate.nosupport, 'warning');
}
console.warn(Translate.nosupport);
}
regPluginSettings({
name: PLUGIN_SYSTEM_SETTINGS_DISABLED_SOUND,
label: Translate.disabledSound,
position: 'system',
type: 'boolean',
});

View File

@@ -0,0 +1,150 @@
import {
getGlobalState,
getCachedUserInfo,
getCachedBaseGroupInfo,
getServiceWorkerRegistration,
navigate,
sharedEvent,
getCachedUserSettings,
getMessageTextDecorators,
} from '@capital/common';
import { Translate } from './translate';
import { incBubble, setBubble } from './bubble';
import _get from 'lodash/get';
import { PLUGIN_SYSTEM_SETTINGS_DISABLED_SOUND } from './const';
const TAG = 'tailchat-message';
export function initNotify() {
if (Notification.permission === 'default') {
Notification.requestPermission();
}
const registration: ServiceWorkerRegistration | null =
getServiceWorkerRegistration();
if (registration) {
registration.addEventListener('notificationclick', (e: any) => {
const tag = e.notification.tag;
const data = e.notification.data;
handleMessageNotifyClick(tag, data);
});
}
let isBlur = false;
window.addEventListener('focus', () => {
setBubble(0); // 点击时清空
isBlur = false;
});
window.addEventListener('blur', () => (isBlur = true));
sharedEvent.on('receiveUnmutedMessage', (message) => {
const currentUserId = getGlobalState()?.user.info._id;
if (currentUserId === message.author) {
// 忽略本人消息
return;
}
const hidden = window.document.hidden ?? false;
if (hidden || isBlur) {
// 如果当前不是活跃窗口或处于隐藏状态,则创建通知
if (Notification.permission === 'granted') {
// TODO: 需要增加显示所在群组
Promise.all([
getCachedUserInfo(message.author),
message.groupId
? getCachedBaseGroupInfo(message.groupId).then((d) => d.name)
: Promise.resolve(Translate.dm),
]).then(([userInfo, scopeName]) => {
const nickname = userInfo?.nickname ?? '';
const icon = userInfo?.avatar ?? undefined;
const content = getMessageTextDecorators().serialize(message.content); // 只显示无富文本形式的消息
const title = `${Translate.from} [${scopeName}] ${nickname}`;
const options: NotificationOptions = {
body: content,
icon,
tag: TAG,
renotify: true,
data: message,
silent: true, // 因为有提示音了,所以禁音默认音
};
if (registration && registration.showNotification) {
registration.showNotification(title, options);
} else {
// fallback
const notification = new Notification(title, options);
notification.onclick = (e: any) => {
const tag = e.target.tag;
const data = e.target.data;
handleMessageNotifyClick(tag, data);
};
}
});
}
incBubble();
}
tryPlayNotificationSound(); // 不管当前是不是处于活跃状态,都发出提示音
});
}
/**
* 点击通知栏事件
*/
function handleMessageNotifyClick(tag, data) {
if (tag === 'tailchat-message') {
const message = data;
window.focus();
const { converseId, groupId } = message ?? {};
if (!converseId) {
console.warn('[notify] Not found converseId');
return;
}
if (groupId) {
// 群组消息
navigate(`/main/group/${groupId}/${converseId}`);
} else {
// 私信会话
navigate(`/main/personal/converse/${converseId}`);
}
}
}
let userSettings = null;
sharedEvent.on('loginSuccess', () => {
getCachedUserSettings().then((settings) => {
if (userSettings === null) {
userSettings = settings;
}
});
});
sharedEvent.on('userSettingsUpdate', (settings) => {
userSettings = settings;
});
/**
* 尝试播放通知声音
*/
async function tryPlayNotificationSound() {
if (_get(userSettings, PLUGIN_SYSTEM_SETTINGS_DISABLED_SOUND) === true) {
// 消息提示音被禁用
return;
}
try {
const audio = new Audio(
'/plugins/com.msgbyte.notify/assets/sounds_bing.mp3'
);
await audio.play();
} catch (err) {
console.error(err);
}
}

View File

@@ -0,0 +1,21 @@
import { localTrans } from '@capital/common';
export const Translate = {
nosupport: localTrans({
'zh-CN': '当前浏览器不支持 Notification',
'en-US': 'This browser not support Notification',
}),
slient: localTrans({ 'zh-CN': '免打扰', 'en-US': 'Slient' }),
from: localTrans({
'zh-CN': '来自',
'en-US': 'From',
}),
dm: localTrans({
'zh-CN': '私信',
'en-US': 'DM',
}),
disabledSound: localTrans({
'zh-CN': '禁用消息通知提示音',
'en-US': 'Disable message notification sound',
}),
};

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"esModuleInterop": true,
"jsx": "react",
"paths": {
"@capital/*": ["../../src/plugin/*"],
}
}
}