fix: support WPUSH notifications over gRPC

This commit is contained in:
whyour
2026-09-13 01:08:14 +08:00
parent 841740f19a
commit 041a437453
5 changed files with 184 additions and 3 deletions
+1
View File
@@ -51,6 +51,7 @@ export const NotificationModeStringMap = {
19: 'ntfy',
20: 'wxPusherBot',
21: 'wxPusherSpt',
22: 'wpush',
} as const;
export const LINUX_DEPENDENCE_COMMAND: Record<
+6 -1
View File
@@ -152,6 +152,7 @@ enum NotificationMode {
ntfy = 19;
wxPusherBot = 20;
wxPusherSpt = 21;
wpush = 22;
}
message NotificationInfo {
@@ -247,6 +248,10 @@ message NotificationInfo {
optional string wxPusherBotUids = 68;
optional string wxPusherSptList = 70;
optional string wpushApiKey = 71;
optional string wpushChannel = 72;
optional string wpushTopicCode = 73;
}
message SystemNotifyRequest {
@@ -275,4 +280,4 @@ service Api {
rpc EnableCrons(EnableCronsRequest) returns (Response) {}
rpc DisableCrons(DisableCronsRequest) returns (Response) {}
rpc RunCrons(RunCronsRequest) returns (Response) {}
}
}
+61 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.6.1
// protoc v3.21.12
// protoc v3.17.3
// source: back/protos/api.proto
/* eslint-disable */
@@ -44,6 +44,7 @@ export enum NotificationMode {
ntfy = 19,
wxPusherBot = 20,
wxPusherSpt = 21,
wpush = 22,
UNRECOGNIZED = -1,
}
@@ -115,6 +116,9 @@ export function notificationModeFromJSON(object: any): NotificationMode {
case 21:
case "wxPusherSpt":
return NotificationMode.wxPusherSpt;
case 22:
case "wpush":
return NotificationMode.wpush;
case -1:
case "UNRECOGNIZED":
default:
@@ -168,6 +172,8 @@ export function notificationModeToJSON(object: NotificationMode): string {
return "wxPusherBot";
case NotificationMode.wxPusherSpt:
return "wxPusherSpt";
case NotificationMode.wpush:
return "wpush";
case NotificationMode.UNRECOGNIZED:
default:
return "UNRECOGNIZED";
@@ -400,6 +406,9 @@ export interface NotificationInfo {
wxPusherBotTopicIds?: string | undefined;
wxPusherBotUids?: string | undefined;
wxPusherSptList?: string | undefined;
wpushApiKey?: string | undefined;
wpushChannel?: string | undefined;
wpushTopicCode?: string | undefined;
}
export interface SystemNotifyRequest {
@@ -2967,6 +2976,9 @@ function createBaseNotificationInfo(): NotificationInfo {
wxPusherBotTopicIds: undefined,
wxPusherBotUids: undefined,
wxPusherSptList: undefined,
wpushApiKey: undefined,
wpushChannel: undefined,
wpushTopicCode: undefined,
};
}
@@ -3182,6 +3194,15 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
if (message.wxPusherSptList !== undefined) {
writer.uint32(562).string(message.wxPusherSptList);
}
if (message.wpushApiKey !== undefined) {
writer.uint32(570).string(message.wpushApiKey);
}
if (message.wpushChannel !== undefined) {
writer.uint32(578).string(message.wpushChannel);
}
if (message.wpushTopicCode !== undefined) {
writer.uint32(586).string(message.wpushTopicCode);
}
return writer;
},
@@ -3752,6 +3773,30 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
message.wxPusherSptList = reader.string();
continue;
}
case 71: {
if (tag !== 570) {
break;
}
message.wpushApiKey = reader.string();
continue;
}
case 72: {
if (tag !== 578) {
break;
}
message.wpushChannel = reader.string();
continue;
}
case 73: {
if (tag !== 586) {
break;
}
message.wpushTopicCode = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@@ -3845,6 +3890,9 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
: undefined,
wxPusherBotUids: isSet(object.wxPusherBotUids) ? globalThis.String(object.wxPusherBotUids) : undefined,
wxPusherSptList: isSet(object.wxPusherSptList) ? globalThis.String(object.wxPusherSptList) : undefined,
wpushApiKey: isSet(object.wpushApiKey) ? globalThis.String(object.wpushApiKey) : undefined,
wpushChannel: isSet(object.wpushChannel) ? globalThis.String(object.wpushChannel) : undefined,
wpushTopicCode: isSet(object.wpushTopicCode) ? globalThis.String(object.wpushTopicCode) : undefined,
};
},
@@ -4060,6 +4108,15 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
if (message.wxPusherSptList !== undefined) {
obj.wxPusherSptList = message.wxPusherSptList;
}
if (message.wpushApiKey !== undefined) {
obj.wpushApiKey = message.wpushApiKey;
}
if (message.wpushChannel !== undefined) {
obj.wpushChannel = message.wpushChannel;
}
if (message.wpushTopicCode !== undefined) {
obj.wpushTopicCode = message.wpushTopicCode;
}
return obj;
},
@@ -4138,6 +4195,9 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
message.wxPusherBotTopicIds = object.wxPusherBotTopicIds ?? undefined;
message.wxPusherBotUids = object.wxPusherBotUids ?? undefined;
message.wxPusherSptList = object.wxPusherSptList ?? undefined;
message.wpushApiKey = object.wpushApiKey ?? undefined;
message.wpushChannel = object.wpushChannel ?? undefined;
message.wpushTopicCode = object.wpushTopicCode ?? undefined;
return message;
},
};
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.6.1
// protoc v3.21.12
// protoc v3.17.3
// source: back/protos/health.proto
/* eslint-disable */
+115
View File
@@ -0,0 +1,115 @@
const assert = require('node:assert/strict');
const path = require('node:path');
const test = require('node:test');
const load = require('../helpers/load-security-module.cjs');
const back = (file) => path.join(__dirname, '../../back', file);
const protocol = load(back('protos/api.ts'));
const constants = load(back('config/const.ts'), {
'./container': { maybeSudo: (x) => x },
});
test('WPUSH survives protobuf and JSON round trips without changing existing modes', () => {
assert.equal(protocol.NotificationMode.wpush, 22);
for (let value = 0; value <= 22; value++) {
const name = protocol.notificationModeToJSON(value);
assert.equal(protocol.notificationModeFromJSON(name), value);
assert.equal(constants.NotificationModeStringMap[value], name);
}
const request = protocol.SystemNotifyRequest.create({
title: 'title',
content: 'content',
notificationInfo: {
type: 22,
wpushApiKey: 'test-key',
wpushChannel: 'feishu',
wpushTopicCode: 'test-topic',
},
});
const decoded = protocol.SystemNotifyRequest.decode(
protocol.SystemNotifyRequest.encode(request).finish(),
);
assert.equal(decoded.notificationInfo.wpushApiKey, 'test-key');
assert.equal(decoded.notificationInfo.wpushChannel, 'feishu');
assert.equal(decoded.notificationInfo.wpushTopicCode, 'test-topic');
assert.deepEqual(decoded, request);
assert.deepEqual(
protocol.SystemNotifyRequest.fromJSON(
protocol.SystemNotifyRequest.toJSON(request),
),
request,
);
});
test('decoded SystemNotify requests reach WPUSH with explicit or saved configuration', async () => {
const calls = [];
const decorators = { Service: () => (x) => x, Inject: () => () => {} };
const NotificationService = load(back('services/notify.ts'), {
typedi: decorators,
'./user': {},
'../config/util': {},
'../shared/i18n': { t: (x) => x },
'../config/http': {
httpClient: {
post: async (url, options) => {
calls.push({ url, body: options.json });
return { code: 0 };
},
},
},
}).default;
const SystemService = load(back('services/system.ts'), {
typedi: decorators,
'../config': {},
'../config/const': constants,
'../config/util': {},
'../data/dependence': {},
'../data/system': {},
'../shared/pLimit': {},
'../shared/i18n': { t: (x) => x },
'./notify': {},
'./schedule': {},
'./sock': {},
}).default;
const notification = new NotificationService();
notification.userService = {
getNotificationMode: async () => ({
type: 'wpush',
wpushApiKey: 'saved-key',
}),
};
const system = new SystemService();
system.notificationService = notification;
for (const info of [
{
type: 22,
wpushApiKey: 'explicit-key',
wpushChannel: 'feishu',
wpushTopicCode: 'topic',
},
{ type: 22, wpushApiKey: 'explicit-key' },
undefined,
]) {
const decoded = protocol.SystemNotifyRequest.decode(
protocol.SystemNotifyRequest.encode(
protocol.SystemNotifyRequest.create({
title: 'title',
content: 'content',
notificationInfo: info,
}),
).finish(),
);
assert.equal((await system.notify(decoded)).code, 200);
assert.deepEqual(calls.at(-1), {
url: 'https://api.wpush.cn/api/v1/send',
body: {
apikey: info ? 'explicit-key' : 'saved-key',
title: 'title',
content: 'content',
channel: info?.wpushChannel || 'wechat',
...(info?.wpushTopicCode ? { topic_code: 'topic' } : {}),
},
});
}
});