thelounge-plugin-ntfy 1.2.0 → 1.4.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/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # The Lounge ntfy Plugin
2
2
 
3
+ ![NPM Version](https://img.shields.io/npm/v/thelounge-plugin-ntfy?style=for-the-badge)
4
+ ![NPM Downloads](https://img.shields.io/npm/dy/thelounge-plugin-ntfy?style=for-the-badge)
5
+
3
6
  A plugin for [The Lounge](https://thelounge.chat/) that sends a message to an [ntfy](https://ntfy.sh/) server whenever you are mentioned in a chat.
4
7
 
5
8
  ## Installation
@@ -21,8 +24,10 @@ This plugin introduces the `/ntfy` command, subcommands are:
21
24
  - `/ntfy status`: Show the ntfy listener status for this network
22
25
  - `/ntfy test`: Send a test notification
23
26
  - `/ntfy config`: Config commands
24
- - `/ntfy config set <setting_key> <setting_value>`: Set a configuration setting
25
- - `/ntfy config remove <setting_key>`: Set a configuration setting to null
27
+ - `/ntfy config set <setting_key> <setting_value>`: Set a global configuration setting
28
+ - `/ntfy config remove <setting_key>`: Set a global configuration setting to null
29
+ - `/ntfy config network set <setting_key> <setting_value>`: Set a per-network setting for this network
30
+ - `/ntfy config network remove <setting_key>`: Remove per-network setting for this network
26
31
  - `/ntfy config print`: Print the current configuration with warnings if any
27
32
 
28
33
  ## Setup
@@ -43,12 +48,14 @@ To start/stop sending push notifications in the desired network, enter:
43
48
 
44
49
  ## Private Messages
45
50
 
46
- By default, you will only be notified when you are mentioned, **this includes messages sent privately to you**. If you want to be notified of all private messages, enter this command and start the notifier like usual:
51
+ By default, you will only be notified when you are mentioned, **this includes messages sent privately to you**. If you want to be notified of all private messages on a specific network, enter this command while connected to that network and start the notifier like usual:
47
52
 
48
53
  ```
49
- /ntfy config set config.notify_on_private_messages true
54
+ /ntfy config network set config.notify_on_private_messages true
50
55
  ```
51
56
 
57
+ This setting is per-network, so you can enable it for some networks and disable it for others.
58
+
52
59
  ## License
53
60
 
54
61
  This plugin is licensed under [MIT](https://opensource.org/license/mit)
@@ -12,3 +12,6 @@ services:
12
12
  - $PWD/index.js:/var/opt/thelounge/packages/thelounge-plugin-ntfy/index.js
13
13
  - $PWD/package.json:/var/opt/thelounge/packages/thelounge-plugin-ntfy/package.json
14
14
  - $PWD/package-lock.json:/var/opt/thelounge/packages/thelounge-plugin-ntfy/package-lock.json
15
+
16
+ # Installation: su node -c "thelounge install file:/var/opt/thelounge/packages/thelounge-plugin-ntfy"
17
+ # Removal: su node -c "thelounge uninstall thelounge-plugin-ntfy"
package/index.js CHANGED
@@ -6,6 +6,8 @@ const {
6
6
  setRootDir,
7
7
  loadUserConfig,
8
8
  saveUserSetting,
9
+ saveNetworkSetting,
10
+ PER_NETWORK_KEYS,
9
11
  } = require("./src/config.js");
10
12
 
11
13
  // user -> Map<network.uuid -> handler and client>
@@ -22,17 +24,23 @@ const ntfyCommand = {
22
24
  say(`/${command} start - Start the ntfy listener for this network`);
23
25
  say(`/${command} stop - Stop the ntfy listener for this network`);
24
26
  say(
25
- `/${command} status - Show the ntfy listener status for this network`
27
+ `/${command} status - Show the ntfy listener status for this network`,
26
28
  );
27
29
  say(`/${command} test - Send a test notification`);
28
30
  say(
29
- `/${command} config set <setting_key> <setting_value> - Set a configuration setting`
31
+ `/${command} config set <setting_key> <setting_value> - Set a global configuration setting`,
30
32
  );
31
33
  say(
32
- `/${command} config remove <setting_key> - Set configuration setting to null`
34
+ `/${command} config remove <setting_key> - Set global configuration setting to null`,
33
35
  );
34
36
  say(
35
- `/${command} config print - Print the current configuration with warnings if any`
37
+ `/${command} config network set <setting_key> <setting_value> - Set a per-network setting for this network`,
38
+ );
39
+ say(
40
+ `/${command} config network remove <setting_key> - Remove per-network setting for this network`,
41
+ );
42
+ say(
43
+ `/${command} config print - Print the current configuration with warnings if any`,
36
44
  );
37
45
  };
38
46
 
@@ -197,7 +205,7 @@ const ntfyCommand = {
197
205
  const response = saveUserSetting(
198
206
  client.client.name,
199
207
  settingKey,
200
- settingValue
208
+ settingValue,
201
209
  );
202
210
 
203
211
  say(response);
@@ -217,7 +225,7 @@ const ntfyCommand = {
217
225
  const response = saveUserSetting(
218
226
  client.client.name,
219
227
  settingKey,
220
- null
228
+ null,
221
229
  );
222
230
 
223
231
  say(response);
@@ -228,13 +236,30 @@ const ntfyCommand = {
228
236
  case "print": {
229
237
  const [userConfig, errors] = loadUserConfig(client.client.name);
230
238
 
239
+ const sensitiveKeys = new Set(["ntfy.password", "ntfy.token"]);
240
+ const perNetworkKeys = new Set([
241
+ "config.notify_on_private_messages",
242
+ ]);
243
+
231
244
  const printConfig = (obj, parentKey = "") => {
232
245
  for (const key in obj) {
233
246
  const value = obj[key];
234
247
  const fullKey = parentKey ? `${parentKey}.${key}` : key;
235
248
 
236
- if (typeof value === "object" && value !== null) {
249
+ if (perNetworkKeys.has(fullKey)) {
250
+ // Special handling for per-network settings
251
+ if (typeof value === "object" && value !== null) {
252
+ const networkValue = value[network.uuid];
253
+ say(
254
+ `${fullKey}=${networkValue !== undefined ? networkValue : "(not set for this network)"}`,
255
+ );
256
+ } else {
257
+ say(`${fullKey}=(not set for this network)`);
258
+ }
259
+ } else if (typeof value === "object" && value !== null) {
237
260
  printConfig(value, fullKey);
261
+ } else if (sensitiveKeys.has(fullKey) && value) {
262
+ say(`${fullKey}=********`);
238
263
  } else {
239
264
  say(`${fullKey}=${value}`);
240
265
  }
@@ -257,13 +282,85 @@ const ntfyCommand = {
257
282
  userConfig.ntfy.password
258
283
  ) {
259
284
  say(
260
- "Warning: Both ntfy.token and ntfy.username/password are set, ntfy.token will be used for authentication"
285
+ "Warning: Both ntfy.token and ntfy.username/password are set, ntfy.token will be used for authentication",
261
286
  );
262
287
  }
263
288
 
264
289
  break;
265
290
  }
266
291
 
292
+ case "network": {
293
+ const networkArgs = subsubcommand.slice(1);
294
+
295
+ if (networkArgs.length === 0) {
296
+ helpMessage();
297
+ return;
298
+ }
299
+
300
+ if (typeof networkArgs[0] !== "string") {
301
+ helpMessage();
302
+ return;
303
+ }
304
+
305
+ switch (networkArgs[0].toLowerCase()) {
306
+ case "set": {
307
+ const setArgs = networkArgs.slice(1);
308
+
309
+ if (setArgs.length < 2) {
310
+ say("Usage: /ntfy config network set <setting_key> <value>");
311
+ say(
312
+ `Available per-network settings: ${Array.from(PER_NETWORK_KEYS).join(", ")}`,
313
+ );
314
+ return;
315
+ }
316
+
317
+ const settingKey = setArgs[0];
318
+ const settingValue = setArgs.slice(1).join(" ");
319
+ const response = saveNetworkSetting(
320
+ client.client.name,
321
+ settingKey,
322
+ network.uuid,
323
+ settingValue,
324
+ );
325
+
326
+ say(response);
327
+
328
+ break;
329
+ }
330
+
331
+ case "remove": {
332
+ const removeArgs = networkArgs.slice(1);
333
+
334
+ if (removeArgs.length < 1) {
335
+ say("Usage: /ntfy config network remove <setting_key>");
336
+ say(
337
+ `Available per-network settings: ${Array.from(PER_NETWORK_KEYS).join(", ")}`,
338
+ );
339
+ return;
340
+ }
341
+
342
+ const settingKey = removeArgs[0];
343
+ const response = saveNetworkSetting(
344
+ client.client.name,
345
+ settingKey,
346
+ network.uuid,
347
+ null,
348
+ );
349
+
350
+ say(response);
351
+
352
+ break;
353
+ }
354
+
355
+ default: {
356
+ helpMessage();
357
+ break;
358
+ }
359
+ }
360
+
361
+ break;
362
+ }
363
+
267
364
  default: {
268
365
  helpMessage();
269
366
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thelounge-plugin-ntfy",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "A plugin for The Lounge that sends push notifications via ntfy when highlighted",
5
5
  "keywords": [
6
6
  "thelounge",
@@ -29,7 +29,7 @@
29
29
  "ajv": "^8.17.1",
30
30
  "ajv-errors": "^3.0.0",
31
31
  "ajv-formats": "^3.0.1",
32
- "ntfy": "^1.11.8",
32
+ "ntfy": "~1.13.3",
33
33
  "thelounge": "^4.4.3"
34
34
  }
35
35
  }
package/src/config.js CHANGED
@@ -5,6 +5,12 @@ const path = require("path");
5
5
  const Ajv2019 = require("ajv/dist/2019").default;
6
6
  const addFormats = require("ajv-formats");
7
7
  const addErrors = require("ajv-errors");
8
+ const {
9
+ initEncryptionKey,
10
+ encrypt,
11
+ decrypt,
12
+ isEncrypted,
13
+ } = require("./crypto.js");
8
14
 
9
15
  const DEFAULT_CONFIG = {
10
16
  ntfy: {
@@ -15,20 +21,23 @@ const DEFAULT_CONFIG = {
15
21
  token: null,
16
22
  },
17
23
  config: {
18
- notify_on_private_messages: false,
24
+ notify_on_private_messages: {}, // Per-network: { "network-uuid": true/false }
19
25
  },
20
26
  };
21
27
 
22
- const ALLOWED_KEYS = new Set([
28
+ const GLOBAL_KEYS = new Set([
23
29
  "ntfy.server",
24
30
  "ntfy.topic",
25
31
  "ntfy.username",
26
32
  "ntfy.password",
27
33
  "ntfy.token",
28
- "config.notify_on_private_messages",
29
34
  ]);
35
+ const GLOBAL_BOOLEAN_KEYS = new Set([]);
30
36
 
31
- const BOOLEAN_KEYS = new Set(["config.notify_on_private_messages"]);
37
+ const PER_NETWORK_KEYS = new Set(["config.notify_on_private_messages"]);
38
+ const PER_NETWORK_BOOLEAN_KEYS = new Set(["config.notify_on_private_messages"]);
39
+
40
+ const SENSITIVE_KEYS = new Set(["ntfy.password", "ntfy.token"]);
32
41
 
33
42
  const userConfigSchema = {
34
43
  type: "object",
@@ -102,8 +111,9 @@ const userConfigSchema = {
102
111
  required: ["notify_on_private_messages"],
103
112
  properties: {
104
113
  notify_on_private_messages: {
105
- type: "boolean",
106
- default: false,
114
+ type: "object",
115
+ additionalProperties: { type: ["boolean", "string"] },
116
+ default: {},
107
117
  },
108
118
  },
109
119
  },
@@ -121,6 +131,38 @@ let rootDir = null;
121
131
 
122
132
  function setRootDir(dir) {
123
133
  rootDir = dir;
134
+ initEncryptionKey(dir);
135
+ }
136
+
137
+ function decryptSensitiveFields(config) {
138
+ const decrypted = JSON.parse(JSON.stringify(config)); // Deep clone
139
+
140
+ for (const key of SENSITIVE_KEYS) {
141
+ const keys = key.split(".");
142
+ let curr = decrypted;
143
+
144
+ for (let i = 0; i < keys.length - 1; i++) {
145
+ if (curr[keys[i]]) {
146
+ curr = curr[keys[i]];
147
+ } else {
148
+ curr = null;
149
+ break;
150
+ }
151
+ }
152
+
153
+ if (curr) {
154
+ const finalKey = keys[keys.length - 1];
155
+ if (curr[finalKey] && isEncrypted(curr[finalKey])) {
156
+ try {
157
+ curr[finalKey] = decrypt(curr[finalKey]);
158
+ } catch (e) {
159
+ // Leave as-is
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ return decrypted;
124
166
  }
125
167
 
126
168
  function loadUserConfig(username) {
@@ -142,10 +184,12 @@ function loadUserConfig(username) {
142
184
  userConfig = JSON.parse(fs.readFileSync(userConfigPath, "utf-8"));
143
185
  } catch (e) {
144
186
  throw new Error(
145
- `Invalid JSON in user config for ${username}: ${e.message}`
187
+ `Invalid JSON in user config for ${username}: ${e.message}`,
146
188
  );
147
189
  }
148
190
 
191
+ userConfig = decryptSensitiveFields(userConfig);
192
+
149
193
  const validate = ajv.compile(userConfigSchema);
150
194
  const valid = validate(userConfig);
151
195
 
@@ -153,12 +197,39 @@ function loadUserConfig(username) {
153
197
  }
154
198
  }
155
199
 
200
+ function encryptSensitiveFields(config) {
201
+ const encrypted = JSON.parse(JSON.stringify(config)); // Deep clone
202
+
203
+ for (const key of SENSITIVE_KEYS) {
204
+ const keys = key.split(".");
205
+ let curr = encrypted;
206
+
207
+ for (let i = 0; i < keys.length - 1; i++) {
208
+ if (curr[keys[i]]) {
209
+ curr = curr[keys[i]];
210
+ } else {
211
+ curr = null;
212
+ break;
213
+ }
214
+ }
215
+
216
+ if (curr) {
217
+ const finalKey = keys[keys.length - 1];
218
+ if (curr[finalKey] && !isEncrypted(curr[finalKey])) {
219
+ curr[finalKey] = encrypt(curr[finalKey]);
220
+ }
221
+ }
222
+ }
223
+
224
+ return encrypted;
225
+ }
226
+
156
227
  function saveUserSetting(username, settingKey, settingValue) {
157
228
  if (!rootDir) {
158
229
  throw new Error("Root directory is not set");
159
230
  }
160
231
 
161
- if (ALLOWED_KEYS.has(settingKey)) {
232
+ if (GLOBAL_KEYS.has(settingKey)) {
162
233
  let userConfig = loadUserConfig(username)[0];
163
234
 
164
235
  const keys = settingKey.split(".");
@@ -176,7 +247,7 @@ function saveUserSetting(username, settingKey, settingValue) {
176
247
  return `Error: expected value to be a string`;
177
248
  }
178
249
 
179
- if (BOOLEAN_KEYS.has(settingKey)) {
250
+ if (GLOBAL_BOOLEAN_KEYS.has(settingKey)) {
180
251
  try {
181
252
  settingValue = settingValue
182
253
  ? JSON.parse(settingValue.toLowerCase())
@@ -192,25 +263,118 @@ function saveUserSetting(username, settingKey, settingValue) {
192
263
 
193
264
  curr[keys[keys.length - 1]] = settingValue;
194
265
 
266
+ const configToSave = encryptSensitiveFields(userConfig);
267
+
195
268
  const userConfigPath = path.join(rootDir, "config", `${username}.json`);
196
269
 
197
270
  fs.mkdirSync(path.dirname(userConfigPath), { recursive: true });
198
271
  fs.writeFileSync(
199
272
  userConfigPath,
200
- JSON.stringify(userConfig, null, 2),
201
- "utf-8"
273
+ JSON.stringify(configToSave, null, 2),
274
+ "utf-8",
202
275
  );
203
276
 
204
277
  return "Success";
205
278
  }
206
279
 
207
280
  return `Invalid setting ${settingKey}, allowed settings are: ${Array.from(
208
- ALLOWED_KEYS
281
+ GLOBAL_KEYS,
209
282
  ).join(", ")}`;
210
283
  }
211
284
 
285
+ function saveNetworkSetting(username, settingKey, networkUuid, settingValue) {
286
+ if (!rootDir) {
287
+ throw new Error("Root directory is not set");
288
+ }
289
+
290
+ if (!PER_NETWORK_KEYS.has(settingKey)) {
291
+ return `Invalid per-network setting ${settingKey}, allowed settings are: ${Array.from(
292
+ PER_NETWORK_KEYS,
293
+ ).join(", ")}`;
294
+ }
295
+
296
+ let userConfig = loadUserConfig(username)[0];
297
+
298
+ const keys = settingKey.split(".");
299
+
300
+ let curr = userConfig;
301
+
302
+ for (let i = 0; i < keys.length - 1; i++) {
303
+ const key = keys[i];
304
+ if (key in curr) {
305
+ curr = curr[key];
306
+ }
307
+ }
308
+
309
+ const finalKey = keys[keys.length - 1];
310
+
311
+ if (!curr[finalKey] || typeof curr[finalKey] !== "object") {
312
+ curr[finalKey] = {};
313
+ }
314
+
315
+ if (settingValue === null) {
316
+ delete curr[finalKey][networkUuid];
317
+ } else if (PER_NETWORK_BOOLEAN_KEYS.has(settingKey)) {
318
+ try {
319
+ const boolValue = JSON.parse(settingValue.toLowerCase());
320
+
321
+ if (typeof boolValue !== "boolean") {
322
+ return `Invalid value for ${settingKey}, expected a boolean`;
323
+ }
324
+
325
+ curr[finalKey][networkUuid] = boolValue;
326
+ } catch {
327
+ return `Invalid value for ${settingKey}, expected a boolean (true/false)`;
328
+ }
329
+ } else {
330
+ curr[finalKey][networkUuid] = settingValue;
331
+ }
332
+
333
+ const configToSave = encryptSensitiveFields(userConfig);
334
+
335
+ const userConfigPath = path.join(rootDir, "config", `${username}.json`);
336
+
337
+ fs.mkdirSync(path.dirname(userConfigPath), { recursive: true });
338
+ fs.writeFileSync(
339
+ userConfigPath,
340
+ JSON.stringify(configToSave, null, 2),
341
+ "utf-8",
342
+ );
343
+
344
+ return "Success";
345
+ }
346
+
347
+ function getNetworkSetting(
348
+ userConfig,
349
+ settingKey,
350
+ networkUuid,
351
+ defaultValue = false,
352
+ ) {
353
+ const keys = settingKey.split(".");
354
+
355
+ let curr = userConfig;
356
+
357
+ for (const key of keys) {
358
+ if (curr && typeof curr === "object" && key in curr) {
359
+ curr = curr[key];
360
+ } else {
361
+ return defaultValue;
362
+ }
363
+ }
364
+
365
+ if (curr && typeof curr === "object" && networkUuid in curr) {
366
+ return curr[networkUuid];
367
+ }
368
+
369
+ return defaultValue;
370
+ }
371
+
212
372
  module.exports = {
213
373
  setRootDir,
214
374
  loadUserConfig,
215
375
  saveUserSetting,
376
+ saveNetworkSetting,
377
+ getNetworkSetting,
378
+ PER_NETWORK_KEYS,
379
+ PER_NETWORK_BOOLEAN_KEYS,
216
380
  };
package/src/crypto.js ADDED
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const ALGORITHM = "aes-256-gcm";
8
+ const KEY_LENGTH = 32; // 256 bits
9
+ const IV_LENGTH = 16; // 128 bits
10
+ const AUTH_TAG_LENGTH = 16; // 128 bits
11
+ const ENCRYPTED_PREFIX = "enc:";
12
+
13
+ let encryptionKey = null;
14
+
15
+ function initEncryptionKey(configDir) {
16
+ const keyPath = path.join(configDir, ".ntfy_key");
17
+
18
+ if (fs.existsSync(keyPath)) {
19
+ const keyData = fs.readFileSync(keyPath, "utf-8").trim();
20
+ encryptionKey = Buffer.from(keyData, "hex");
21
+
22
+ if (encryptionKey.length !== KEY_LENGTH) {
23
+ throw new Error("Invalid encryption key length");
24
+ }
25
+ } else {
26
+ encryptionKey = crypto.randomBytes(KEY_LENGTH);
27
+ fs.mkdirSync(configDir, { recursive: true });
28
+ fs.writeFileSync(keyPath, encryptionKey.toString("hex"), {
29
+ encoding: "utf-8",
30
+ mode: 0o600, // R/W for owner only
31
+ });
32
+ }
33
+ }
34
+
35
+ function isInitialized() {
36
+ return encryptionKey !== null;
37
+ }
38
+
39
+ function encrypt(plaintext) {
40
+ if (!encryptionKey) {
41
+ throw new Error("Encryption key not initialized");
42
+ }
43
+
44
+ if (!plaintext || typeof plaintext !== "string") {
45
+ return plaintext;
46
+ }
47
+
48
+ if (plaintext.startsWith(ENCRYPTED_PREFIX)) {
49
+ return plaintext;
50
+ }
51
+
52
+ const iv = crypto.randomBytes(IV_LENGTH);
53
+ const cipher = crypto.createCipheriv(ALGORITHM, encryptionKey, iv, {
54
+ authTagLength: AUTH_TAG_LENGTH,
55
+ });
56
+
57
+ let encrypted = cipher.update(plaintext, "utf8", "hex");
58
+ encrypted += cipher.final("hex");
59
+
60
+ const authTag = cipher.getAuthTag();
61
+
62
+ return `${ENCRYPTED_PREFIX}${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
63
+ }
64
+
65
+ function decrypt(encryptedText) {
66
+ if (!encryptionKey) {
67
+ throw new Error("Encryption key not initialized");
68
+ }
69
+
70
+ if (!encryptedText || typeof encryptedText !== "string") {
71
+ return encryptedText;
72
+ }
73
+
74
+ if (!encryptedText.startsWith(ENCRYPTED_PREFIX)) {
75
+ return encryptedText;
76
+ }
77
+
78
+ const data = encryptedText.slice(ENCRYPTED_PREFIX.length);
79
+ const parts = data.split(":");
80
+
81
+ if (parts.length !== 3) {
82
+ throw new Error("Invalid encrypted data format");
83
+ }
84
+
85
+ const [ivHex, authTagHex, encrypted] = parts;
86
+ const iv = Buffer.from(ivHex, "hex");
87
+ const authTag = Buffer.from(authTagHex, "hex");
88
+
89
+ const decipher = crypto.createDecipheriv(ALGORITHM, encryptionKey, iv, {
90
+ authTagLength: AUTH_TAG_LENGTH,
91
+ });
92
+ decipher.setAuthTag(authTag);
93
+
94
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
95
+ decrypted += decipher.final("utf8");
96
+
97
+ return decrypted;
98
+ }
99
+
100
+ function isEncrypted(value) {
101
+ return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
102
+ }
103
+
104
+ module.exports = {
105
+ initEncryptionKey,
106
+ isInitialized,
107
+ encrypt,
108
+ decrypt,
109
+ isEncrypted,
110
+ ENCRYPTED_PREFIX,
111
+ };
package/src/handler.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { loadUserConfig } = require("./config.js");
3
+ const { loadUserConfig, getNetworkSetting } = require("./config.js");
4
4
  const { PluginLogger } = require("./logger.js");
5
5
 
6
6
  function createHandler(client, network) {
@@ -23,7 +23,7 @@ function createHandler(client, network) {
23
23
  // Mentions always notify
24
24
  notify = true;
25
25
  } else if (isPM) {
26
- // PMs notify only if enabled in config
26
+ // PMs notify only if enabled in config for this network
27
27
  const [uc, errors] = loadUserConfig(client.client.name);
28
28
 
29
29
  if (errors.length > 0) {
@@ -32,7 +32,14 @@ function createHandler(client, network) {
32
32
 
33
33
  userConfig = uc;
34
34
 
35
- if (userConfig.config.notify_on_private_messages) {
35
+ const notifyOnPMs = getNetworkSetting(
36
+ userConfig,
37
+ "config.notify_on_private_messages",
38
+ network.uuid,
39
+ false,
40
+ );
41
+
42
+ if (notifyOnPMs) {
36
43
  notify = true;
37
44
  }
38
45
  }