feat: add apprise notification #10

This commit is contained in:
moonrailgun 2024-01-13 18:16:09 +08:00
parent 699fe6c967
commit 09c3cfa0dc
12 changed files with 96 additions and 15 deletions

View File

@ -8,6 +8,9 @@ COPY . .
RUN apk add --update --no-cache python3 g++ make py3-pip RUN apk add --update --no-cache python3 g++ make py3-pip
# Push client(only support pure text message)
RUN pip install apprise
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
RUN pnpm build RUN pnpm build

View File

@ -50,6 +50,7 @@
"detect-browser": "^5.3.0", "detect-browser": "^5.3.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eventemitter-strict": "^1.0.1", "eventemitter-strict": "^1.0.1",
"execa": "^5.1.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"express-validator": "^7.0.1", "express-validator": "^7.0.1",

View File

@ -82,6 +82,9 @@ dependencies:
eventemitter-strict: eventemitter-strict:
specifier: ^1.0.1 specifier: ^1.0.1
version: 1.0.1 version: 1.0.1
execa:
specifier: ^5.1.1
version: 5.1.1
express: express:
specifier: ^4.18.2 specifier: ^4.18.2
version: 4.18.2 version: 4.18.2
@ -5183,7 +5186,6 @@ packages:
onetime: 5.1.2 onetime: 5.1.2
signal-exit: 3.0.7 signal-exit: 3.0.7
strip-final-newline: 2.0.0 strip-final-newline: 2.0.0
dev: true
/execa@7.2.0: /execa@7.2.0:
resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
@ -5983,7 +5985,6 @@ packages:
/human-signals@2.1.0: /human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
dev: true
/human-signals@4.3.1: /human-signals@4.3.1:
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
@ -6911,7 +6912,6 @@ packages:
/merge-stream@2.0.0: /merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
/merge2@1.4.1: /merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
@ -6961,7 +6961,6 @@ packages:
/mimic-fn@2.1.0: /mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/mimic-fn@4.0.0: /mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
@ -7346,7 +7345,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1
dev: true
/npm-run-path@5.2.0: /npm-run-path@5.2.0:
resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==}
@ -7423,7 +7421,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dependencies: dependencies:
mimic-fn: 2.1.0 mimic-fn: 2.1.0
dev: true
/onetime@6.0.0: /onetime@6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
@ -9484,7 +9481,6 @@ packages:
/signal-exit@3.0.7: /signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: true
/signal-exit@4.1.0: /signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
@ -9839,7 +9835,6 @@ packages:
/strip-final-newline@2.0.0: /strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/strip-final-newline@3.0.0: /strip-final-newline@3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}

View File

@ -0,0 +1,26 @@
import { Form, Input } from 'antd';
import React from 'react';
export const NotificationApprise: React.FC = React.memo(() => {
return (
<>
<Form.Item
label="Apprise URL"
name={['payload', 'appriseUrl']}
rules={[{ required: true }]}
>
<Input placeholder="For example: pushdeer://pushKey" />
</Form.Item>
<div className="text-sm opacity-80">
Read more:{' '}
<a
href="https://github.com/caronc/apprise/wiki#notification-services"
target="_blank"
>
https://github.com/caronc/apprise/wiki#notification-services
</a>
</div>
</>
);
});
NotificationApprise.displayName = 'NotificationApprise';

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { NotificationSMTP } from './smtp'; import { NotificationSMTP } from './smtp';
import { NotificationTelegram } from './telegram'; import { NotificationTelegram } from './telegram';
import { NotificationApprise } from './apprise';
interface NotificationStrategy { interface NotificationStrategy {
label: string; label: string;
@ -14,6 +15,11 @@ export const notificationStrategies: NotificationStrategy[] = [
name: 'smtp', name: 'smtp',
form: NotificationSMTP, form: NotificationSMTP,
}, },
{
label: 'Apprise(Support 90+ services)',
name: 'apprise',
form: NotificationApprise,
},
{ {
label: 'Telegram', label: 'Telegram',
name: 'telegram', name: 'telegram',

View File

@ -19,7 +19,6 @@ import { initUdpServer } from './udp/server';
import { createServer } from 'http'; import { createServer } from 'http';
import { initSocketio } from './ws'; import { initSocketio } from './ws';
import { monitorManager } from './model/monitor'; import { monitorManager } from './model/monitor';
import { settings } from './utils/settings';
import { env } from './utils/env'; import { env } from './utils/env';
import cors from 'cors'; import cors from 'cors';
import { serverStatusRouter } from './router/serverStatus'; import { serverStatusRouter } from './router/serverStatus';
@ -28,7 +27,7 @@ import { logger } from './utils/logger';
import { monitorRouter } from './router/monitor'; import { monitorRouter } from './router/monitor';
import { healthRouter } from './router/health'; import { healthRouter } from './router/health';
const port = settings.port; const port = env.port;
const app = express(); const app = express();
const httpServer = createServer(app); const httpServer = createServer(app);

View File

@ -1,10 +1,10 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import { getBillingCreditGrants } from '../openai'; import { getBillingCreditGrants } from '../openai';
describe.runIf(!!process.env.OPENAI_SESS_KEY)('openai', () => { describe.runIf(!!process.env.TEST_OPENAI_SESS_KEY)('openai', () => {
test('getBillingCreditGrants should be ok', async () => { test('getBillingCreditGrants should be ok', async () => {
const res = await getBillingCreditGrants( const res = await getBillingCreditGrants(
String(process.env.OPENAI_SESS_KEY) String(process.env.TEST_OPENAI_SESS_KEY)
); );
expect(typeof res.allUSD).toBe('number'); expect(typeof res.allUSD).toBe('number');

View File

@ -0,0 +1,17 @@
import { describe, test } from 'vitest';
import { apprise } from '../apprise';
import { token } from '../../token';
describe.runIf(!!process.env.TEST_APPRISE_URL)('apprise', () => {
test('apprise should be work', async () => {
await apprise.send(
{
payload: {
appriseUrl: process.env.TEST_APPRISE_URL,
},
} as any,
'test title',
[token.text('Foooo'), token.newline(), token.text('Baaaar')]
);
});
});

View File

@ -0,0 +1,34 @@
import { NotificationProvider } from './type';
import { baseContentTokenizer } from '../token';
import execa from 'execa';
interface ApprisePayload {
appriseUrl: string;
}
// Fork from https://github.com/louislam/uptime-kuma/blob/HEAD/server/notification-providers/smtp.js
export const apprise: NotificationProvider = {
send: async (notification, title, message) => {
const payload = notification.payload as unknown as ApprisePayload;
const content = baseContentTokenizer.parse(message);
const args = ['-vv', '-b', content, payload.appriseUrl];
if (title) {
args.push('-t');
args.push(title);
}
const { stdout } = await execa('apprise', args);
const output = stdout
? stdout.toString()
: 'ERROR: maybe apprise not found';
if (output) {
if (output.includes('ERROR')) {
throw new Error(output);
}
}
},
};

View File

@ -1,8 +1,10 @@
import { apprise } from './apprise';
import { smtp } from './smtp'; import { smtp } from './smtp';
import { telegram } from './telegram'; import { telegram } from './telegram';
import type { NotificationProvider } from './type'; import type { NotificationProvider } from './type';
export const notificationProviders: Record<string, NotificationProvider> = { export const notificationProviders: Record<string, NotificationProvider> = {
smtp, smtp,
apprise,
telegram, telegram,
}; };

View File

@ -1,4 +1,5 @@
export const env = { export const env = {
port: Number(process.env.PORT || 12345),
allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER), allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER),
allowOpenapi: checkEnvTrusty(process.env.ALLOW_OPENAPI), allowOpenapi: checkEnvTrusty(process.env.ALLOW_OPENAPI),
websiteId: process.env.WEBSITE_ID, websiteId: process.env.WEBSITE_ID,

View File

@ -1,3 +0,0 @@
export const settings = {
port: Number(process.env.PORT || 12345),
};