iobroker.gotify-ws 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/main.js ADDED
@@ -0,0 +1,411 @@
1
+ /* eslint-disable prefer-const */
2
+ 'use strict';
3
+
4
+ const WebSocket = require('ws');
5
+ const utils = require('@iobroker/adapter-core');
6
+ const adapterName = require('./package.json').name.split('.').pop();
7
+
8
+ let systemLang = 'de'; // system language
9
+ let ws = null;
10
+ let timer = null;
11
+ let stop = false;
12
+
13
+ class GotifyWs extends utils.Adapter {
14
+ /**
15
+ * @param {Partial<utils.AdapterOptions>} [options={}]
16
+ */
17
+ constructor(options) {
18
+ super({
19
+ ...options,
20
+ // @ts-ignore
21
+ name: adapterName,
22
+ });
23
+ this.on('ready', this.onReady.bind(this));
24
+ this.on('message', this.onMessage.bind(this));
25
+ this.on('unload', this.onUnload.bind(this));
26
+ }
27
+
28
+ async onReady() {
29
+ const sysLang = await this.getForeignObjectAsync('system.config');
30
+
31
+ if (sysLang && sysLang.common && sysLang.common.language) {
32
+ systemLang = sysLang.common.language;
33
+ }
34
+
35
+ this.setState('info.connection', false, true);
36
+ this.connectWebSocket();
37
+ }
38
+
39
+ /**
40
+ * @param {() => void} callback
41
+ */
42
+ onUnload(callback) {
43
+ this.setState('info.connection', false, true);
44
+
45
+ try {
46
+ if (ws) {
47
+ stop = true;
48
+ ws.close();
49
+ ws.terminate();
50
+ clearTimeout(timer);
51
+ timer = null;
52
+ }
53
+ callback();
54
+ } catch (e) {
55
+ callback();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * @param {{ command: string; from: string; callback: ioBroker.MessageCallback | ioBroker.MessageCallbackInfo | undefined; }} obj
61
+ */
62
+ async onMessage(obj) {
63
+ switch (obj.command) {
64
+ case 'sendToInstance':
65
+ // @ts-ignore
66
+ if (obj && obj.command === 'sendToInstance' && obj.message && obj.message.type) {
67
+ // eslint-disable-next-line prefer-const
68
+ let resultInstances = [];
69
+
70
+ // @ts-ignore
71
+ const instances = await this.getObjectViewAsync('system', 'instance', {
72
+ // @ts-ignore
73
+ startkey: `system.adapter.${obj.message.type}.`,
74
+ // @ts-ignore
75
+ endkey: `system.adapter.${obj.message.type}.\u9999`,
76
+ }).catch((err) => this.log.error(err));
77
+
78
+ if (instances && instances.rows && instances.rows.length != 0) {
79
+ instances.rows.forEach(async (row) => {
80
+ resultInstances.push({
81
+ label: row.id.replace('system.adapter.', ''),
82
+ value: row.id.replace('system.adapter.', ''),
83
+ });
84
+ });
85
+ } else {
86
+ resultInstances.push({
87
+ label: this._('none', systemLang),
88
+ value: 'none',
89
+ });
90
+ }
91
+ this.log.debug(`sendToInstance - ${JSON.stringify(obj)}`);
92
+ this.sendTo(obj.from, obj.command, resultInstances, obj.callback);
93
+ }
94
+ break;
95
+
96
+ case 'sendToTelegramUser':
97
+ // eslint-disable-next-line no-case-declarations
98
+ let useUsername = false;
99
+
100
+ // @ts-ignore
101
+ if (obj && obj.command === 'sendToTelegramUser' && obj.message && obj.message.instance) {
102
+ // @ts-ignore
103
+ const inst = obj.message.instance ? obj.message.instance : this.config.telegramInstance;
104
+ const userList = await this.getForeignStateAsync(`${inst}.communicate.users`);
105
+ const configTelegram = await this.getForeignObjectAsync(`system.adapter.${inst}`);
106
+
107
+ if (configTelegram && configTelegram.native) {
108
+ const native = configTelegram.native;
109
+ useUsername = native.useUsername;
110
+ }
111
+
112
+ // eslint-disable-next-line prefer-const
113
+ let resultUser = [{ label: this._('All Receiver', systemLang), value: 'allTelegramUsers' }];
114
+
115
+ if (userList && userList.val) {
116
+ // @ts-ignore
117
+ const users = JSON.parse(userList.val);
118
+
119
+ for (const i in users) {
120
+ resultUser.push({
121
+ label: useUsername === true ? users[i].userName : users[i].firstName,
122
+ value: useUsername === true ? users[i].userName : users[i].firstName,
123
+ });
124
+ }
125
+
126
+ try {
127
+ this.sendTo(obj.from, obj.command, resultUser, obj.callback);
128
+ } catch (err) {
129
+ this.log.error(`Cannot parse stored user IDs from Telegram: ${err}`);
130
+ }
131
+ }
132
+ }
133
+ break;
134
+ case 'sendToDiscordTarget':
135
+ // @ts-ignore
136
+ if (obj && obj.command === 'sendToDiscordTarget' && obj.message && obj.message.instance) {
137
+ let resultTarget = [{ label: this._('none', systemLang), value: 'none' }];
138
+ // @ts-ignore
139
+ const targetList = await this.sendToAsync(obj.message.instance, 'getNotificationTargets', {});
140
+
141
+ if (Array.isArray(targetList)) {
142
+ for (const i in targetList) {
143
+ resultTarget.push({
144
+ label: targetList[i].value,
145
+ value: targetList[i].value,
146
+ });
147
+ }
148
+ }
149
+
150
+ try {
151
+ this.sendTo(obj.from, obj.command, resultTarget, obj.callback);
152
+ } catch (err) {
153
+ this.log.error(`Cannot parse stored user IDs from Discord: ${err}`);
154
+ }
155
+ } else if (obj && obj.command === 'sendToDiscordTarget') {
156
+ let resultTarget = [{ label: this._('none', systemLang), value: 'none' }];
157
+
158
+ try {
159
+ this.sendTo(obj.from, obj.command, resultTarget, obj.callback);
160
+ } catch (err) {
161
+ this.log.error(`Cannot parse stored user IDs from Discord: ${err}`);
162
+ }
163
+ }
164
+ break;
165
+ }
166
+ }
167
+
168
+ async connectWebSocket() {
169
+ if (this.config.ip && this.config.port) {
170
+ const uri = `ws://${this.config.ip}:${this.config.port}/stream`;
171
+
172
+ ws = new WebSocket(uri, {
173
+ headers: {
174
+ 'X-Gotify-Key': this.config.token,
175
+ },
176
+ });
177
+
178
+ ws.on('open', () => {
179
+ this.setState('info.connection', true, true);
180
+ this.log.info('WebSocket connected');
181
+ });
182
+
183
+ ws.on('message', async (data) => {
184
+ const line = JSON.parse(data);
185
+ this.pushMessage(line);
186
+ });
187
+
188
+ ws.on('close', () => {
189
+ this.setState('info.connection', false, true);
190
+ this.log.info('WebSocket closed');
191
+ if (stop === false) {
192
+ timer = setTimeout(this.connectWebSocket, 5000);
193
+ }
194
+ });
195
+
196
+ ws.on('error', (err) => {
197
+ this.setState('info.connection', false, true);
198
+ // @ts-ignore
199
+ this.log.error('WebSocket error:', err);
200
+ });
201
+ } else {
202
+ this.log.error('WebSocket error: Please check your Configuration');
203
+ }
204
+ }
205
+
206
+ async pushMessage(line) {
207
+ if (this.config.notificationType) {
208
+ switch (this.config.notificationType) {
209
+ case 'telegram':
210
+ if (
211
+ this.config.telegramUser &&
212
+ this.config.telegramUser === 'allTelegramUsers' &&
213
+ this.config.telegramInstance
214
+ ) {
215
+ const message = line.message.replace(/[`]/g, '');
216
+ const formatMessage = message.replace(/[']/g, '"');
217
+ const title =
218
+ line.title != '' ? `<b>${line.title.replace(/[`]/g, '')}</b>` : '<b>Gotifi WS Message</b>';
219
+
220
+ try {
221
+ this.sendTo(this.config.telegramInstance, 'send', {
222
+ parse_mode: 'HTML',
223
+ text: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
224
+ });
225
+ } catch (err) {
226
+ this.log.warn(`Error sending Telegram message: ${err}`);
227
+ }
228
+ } else if (
229
+ this.config.telegramUser &&
230
+ this.config.telegramUser != 'allTelegramUsers' &&
231
+ this.config.telegramInstance
232
+ ) {
233
+ const message = line.message.replace(/[`]/g, '');
234
+ const formatMessage = message.replace(/[']/g, '"');
235
+ const title =
236
+ line.title != '' ? `<b>${line.title.replace(/[`]/g, '')}</b>` : '<b>Gotifi WS Message</b>';
237
+
238
+ try {
239
+ this.sendTo(this.config.telegramInstance, 'send', {
240
+ user: this.config.telegramUser,
241
+ parse_mode: 'HTML',
242
+ text: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
243
+ });
244
+ } catch (err) {
245
+ this.log.warn(`Error sending Telegram message: ${err}`);
246
+ }
247
+ }
248
+ break;
249
+ case 'email':
250
+ if (this.config.emailInstance && this.config.emailReceiver && this.config.emailSender) {
251
+ const message = line.message.replace(/[`]/g, '');
252
+ const formatMessage = message.replace(/[']/g, '"');
253
+ const title = line.title != '' ? line.title.replace(/[`]/g, '') : 'Gotifi WS Message';
254
+
255
+ try {
256
+ this.sendTo(this.config.emailInstance, 'send', {
257
+ text: formatMessage,
258
+ to: this.config.emailReceiver,
259
+ subject: title,
260
+ from: this.config.emailSender,
261
+ });
262
+ } catch (err) {
263
+ this.log.warn(`Error sending E-Mail message: ${err}`);
264
+ }
265
+ }
266
+ break;
267
+ case 'pushover':
268
+ if (
269
+ this.config.pushoverSilentNotice === true &&
270
+ this.config.pushoverInstance &&
271
+ this.config.pushoverDeviceID
272
+ ) {
273
+ const message = line.message.replace(/[`]/g, '');
274
+ const formatMessage = message.replace(/[']/g, '"');
275
+ const title =
276
+ line.title != '' ? `<b>${line.title.replace(/[`]/g, '')}</b>` : '<b>Gotifi WS Message</b>';
277
+
278
+ try {
279
+ this.sendTo(this.config.pushoverInstance, 'send', {
280
+ message: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
281
+ sound: '',
282
+ priority: -1,
283
+ title: title,
284
+ device: this.config.pushoverDeviceID,
285
+ html: 1,
286
+ });
287
+ } catch (err) {
288
+ this.log.warn(`Error sending Pushover message: ${err}`);
289
+ }
290
+ } else if (this.config.pushoverInstance && this.config.pushoverDeviceID) {
291
+ const message = line.message.replace(/[`]/g, '');
292
+ const formatMessage = message.replace(/[']/g, '"');
293
+ const title =
294
+ line.title != '' ? `<b>${line.title.replace(/[`]/g, '')}</b>` : '<b>Gotifi WS Message</b>';
295
+
296
+ try {
297
+ this.sendTo(this.config.pushoverInstance, 'send', {
298
+ message: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
299
+ sound: '',
300
+ title: title,
301
+ device: this.config.pushoverDeviceID,
302
+ html: 1,
303
+ });
304
+ } catch (err) {
305
+ this.log.warn(`Error sending Pushover message: ${err}`);
306
+ }
307
+ }
308
+ break;
309
+ case 'whatsapp-cmb':
310
+ if (this.config.whatsappInstance) {
311
+ const message = line.message.replace(/[`]/g, '');
312
+ const formatMessage = message.replace(/[']/g, '"');
313
+ const title = line.title != '' ? `*${line.title.replace(/[`]/g, '')}*` : '*Gotifi WS Message*';
314
+
315
+ try {
316
+ this.sendTo(this.config.whatsappInstance, 'send', {
317
+ text: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
318
+ });
319
+ } catch (err) {
320
+ this.log.warn(`Error sending WhatsApp message: ${err}`);
321
+ }
322
+ }
323
+ break;
324
+ case 'signal-cmb':
325
+ if (this.config.signalInstance) {
326
+ const message = line.message.replace(/[`]/g, '');
327
+ const formatMessage = message.replace(/[']/g, '"');
328
+ const title = line.title != '' ? line.title.replace(/[`]/g, '') : 'Gotifi WS Message';
329
+
330
+ try {
331
+ this.sendTo(this.config.signalInstance, 'send', {
332
+ text: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
333
+ });
334
+ } catch (err) {
335
+ this.log.warn(`Error sending Signal message: ${err}`);
336
+ }
337
+ }
338
+ break;
339
+ case 'matrix':
340
+ if (this.config.matrixInstance) {
341
+ const message = line.message.replace(/[`]/g, '');
342
+ const formatMessage = message.replace(/[']/g, '"');
343
+ const title =
344
+ line.title != '' ? `<b>${line.title.replace(/[`]/g, '')}</b>` : '<b>Gotifi WS Message</b>';
345
+
346
+ try {
347
+ this.sendTo(this.config.matrixInstance, {
348
+ html: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
349
+ });
350
+ } catch (err) {
351
+ this.log.warn(`Error sending Matrix message: ${err}`);
352
+ }
353
+ }
354
+ break;
355
+ case 'discord':
356
+ if (this.config.discordInstance && this.config.discordTarget) {
357
+ const message = line.message.replace(/[`]/g, '');
358
+ const formatMessage = message.replace(/[']/g, '"');
359
+ const title =
360
+ line.title != '' ? `<b>${line.title.replace(/[`]/g, '')}</b>` : 'Gotifi WS Message';
361
+
362
+ if (this.config.discordTarget.match(/^\d+$/)) {
363
+ // send to a single user
364
+ try {
365
+ this.sendTo(this.config.discordInstance, 'sendMessage', {
366
+ userId: this.config.discordTarget,
367
+ content: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
368
+ });
369
+ } catch (err) {
370
+ this.log.warn(`Error sending Discord message: ${err}`);
371
+ }
372
+ } else if (this.config.discordTarget.match(/^\d+\/\d+$/)) {
373
+ // send to a server channel
374
+ const [serverId, channelId] = this.config.discordTarget.split('/');
375
+ try {
376
+ this.sendTo(this.config.discordInstance, 'sendMessage', {
377
+ serverId,
378
+ channelId,
379
+ content: `${title != '' ? `${title}\n` : ''}${formatMessage}`,
380
+ });
381
+ } catch (err) {
382
+ this.log.warn(`Error sending Discord message: ${err}`);
383
+ }
384
+ }
385
+ }
386
+ break;
387
+ }
388
+ } else {
389
+ this.log.error('Push-Message error: Please check your Configuration');
390
+ }
391
+ }
392
+ _(word, systemLang) {
393
+ const translations = require(`./admin/i18n/${systemLang ? systemLang : 'en'}/translations.json`);
394
+
395
+ if (translations[word]) {
396
+ return translations[word];
397
+ } else {
398
+ this.log.debug('Please translate in translations.json: ' + word);
399
+ return word;
400
+ }
401
+ }
402
+ }
403
+
404
+ if (require.main !== module) {
405
+ /**
406
+ * @param {Partial<utils.AdapterOptions>} [options={}]
407
+ */
408
+ module.exports = (options) => new GotifyWs(options);
409
+ } else {
410
+ new GotifyWs();
411
+ }
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "iobroker.gotify-ws",
3
+ "version": "0.1.0",
4
+ "description": "Gotify web socket for connection to various notification systems",
5
+ "author": {
6
+ "name": "simatec",
7
+ "email": "simatec@simateccloud.de"
8
+ },
9
+ "homepage": "https://github.com/simatec/ioBroker.gotify-ws",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "Gotify",
13
+ "Websocket",
14
+ "Telegram",
15
+ "Whatsapp",
16
+ "Discord",
17
+ "Mail",
18
+ "Pushover"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/simatec/ioBroker.gotify-ws.git"
23
+ },
24
+ "engines": {
25
+ "node": ">= 18"
26
+ },
27
+ "dependencies": {
28
+ "@iobroker/adapter-core": "^3.1.6",
29
+ "ws": "^8.17.1",
30
+ "axios": "^1.7.2"
31
+ },
32
+ "devDependencies": {
33
+ "@alcalzone/release-script": "^3.7.0",
34
+ "@alcalzone/release-script-plugin-iobroker": "^3.7.1",
35
+ "@alcalzone/release-script-plugin-license": "^3.7.0",
36
+ "@alcalzone/release-script-plugin-manual-review": "^3.7.0",
37
+ "@iobroker/adapter-dev": "^1.3.0",
38
+ "@iobroker/testing": "^4.1.3",
39
+ "@tsconfig/node20": "^20.1.4",
40
+ "@types/chai": "^4.3.16",
41
+ "@types/chai-as-promised": "^7.1.8",
42
+ "@types/mocha": "^10.0.6",
43
+ "@types/node": "^20.14.5",
44
+ "@types/proxyquire": "^1.3.31",
45
+ "@types/sinon": "^17.0.3",
46
+ "@types/sinon-chai": "^3.2.12",
47
+ "chai": "^4.3.10",
48
+ "chai-as-promised": "^7.1.2",
49
+ "eslint": "^9.5.0",
50
+ "mocha": "^10.4.0",
51
+ "proxyquire": "^2.1.3",
52
+ "sinon": "^18.0.0",
53
+ "sinon-chai": "^3.7.0",
54
+ "typescript": "~5.4.5"
55
+ },
56
+ "main": "main.js",
57
+ "files": [
58
+ "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).json",
59
+ "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
60
+ "lib/",
61
+ "www/",
62
+ "io-package.json",
63
+ "LICENSE",
64
+ "main.js"
65
+ ],
66
+ "scripts": {
67
+ "test": "npm run test:package && npm run test:unit",
68
+ "test:package": "mocha test/package --exit",
69
+ "test:unit": "mocha test/unit --exit",
70
+ "test:integration": "mocha test/integration --exit",
71
+ "release": "release-script",
72
+ "release-patch": "release-script patch --yes",
73
+ "release-minor": "release-script minor --yes",
74
+ "release-major": "release-script major --yes",
75
+ "translate": "translate-adapter"
76
+ },
77
+ "bugs": {
78
+ "url": "https://github.com/simatec/ioBroker.gotify-ws/issues"
79
+ },
80
+ "readmeFilename": "README.md"
81
+ }