omdnotificationforwarder 4.1__tar.gz → 4.2__tar.gz
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.
- omdnotificationforwarder-4.1/README.md → omdnotificationforwarder-4.2/PKG-INFO +52 -1
- omdnotificationforwarder-4.1/PKG-INFO → omdnotificationforwarder-4.2/README.md +30 -21
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/bin/notificationforwarder +1 -1
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/pyproject.toml +4 -1
- omdnotificationforwarder-4.2/src/notificationforwarder/nostr/__init__.py +1 -0
- omdnotificationforwarder-4.2/src/notificationforwarder/nostr/formatter.py +66 -0
- omdnotificationforwarder-4.2/src/notificationforwarder/nostr/forwarder.py +141 -0
- omdnotificationforwarder-4.2/src/notificationforwarder/oauth2webhook/__init__.py +0 -0
- omdnotificationforwarder-4.2/src/notificationforwarder/oauth2webhook/forwarder.py +98 -0
- omdnotificationforwarder-4.2/tests/test_builtin_plugins_focus.py +279 -0
- omdnotificationforwarder-4.2/tests/test_nostr.py +261 -0
- omdnotificationforwarder-4.2/tests/test_oauth2webhook.py +206 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_reporter.py +8 -3
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/.gitignore +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/baseclass.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/component_loader.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/email/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/email/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/example/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/example/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/json/__init__.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/json/logger.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/naemonlog/reporter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/rabbitmq/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/rabbitmq/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/runtime_config.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/runtime_flow.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/spool.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/syslog/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/syslog/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/telegram/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/text/__init__.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/text/logger.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/src/notificationforwarder/webhook/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/lib/python/notificationforwarder/split1/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/lib/python/notificationforwarder/split2/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/lib/python/notificationforwarder/split2/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/lib/python/notificationforwarder/split3/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/lib/python/notificationforwarder/split3/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/alertmanager_servicenow/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/bayern/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/datadup/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/datapost/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/discard/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/split1/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/split4/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/split4/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/ticketsystem/forwarder.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/ticketsystem/reporter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/pythonpath/local/lib/python/notificationforwarder/vong/formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_alertmanager.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_classes.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_delivery_resilience.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_discard.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_formatter.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_logger.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_package.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_paths.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_runtime_foundation.py +0 -0
- {omdnotificationforwarder-4.1 → omdnotificationforwarder-4.2}/tests/test_webhook.py +0 -0
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: omdnotificationforwarder
|
|
3
|
+
Version: 4.2
|
|
4
|
+
Summary: A framework for notification scripts for OMD
|
|
5
|
+
Project-URL: Homepage, https://github.com/lausser/noteventificationforhandlerwarder
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/lausser/noteventificationforhandlerwarder/issues
|
|
7
|
+
Author-email: Gerhard Lausser <lausser@yahoo.com>
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.6.1
|
|
12
|
+
Requires-Dist: coshsh
|
|
13
|
+
Requires-Dist: jinja2
|
|
14
|
+
Provides-Extra: nostr
|
|
15
|
+
Requires-Dist: pynostr; extra == 'nostr'
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: flake8; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest; extra == 'test'
|
|
19
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
20
|
+
Requires-Dist: requests; extra == 'test'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
1
23
|
# notificationforwarder
|
|
2
24
|
|
|
3
25
|
In this framework, two aspects are in the focus. How to transport a notification to the recipient system and in which format.
|
|
@@ -18,6 +40,8 @@ Let me list some of the formatter/forwarder combinations which are usually found
|
|
|
18
40
|
|json |Splunk HEC |
|
|
19
41
|
|json |RabbitMQ |
|
|
20
42
|
|
|
43
|
+
|markdown-like Nostr note|Nostr relays |
|
|
44
|
+
|
|
21
45
|
Of course json is not json, the attributes and values are different depending on the recipient.
|
|
22
46
|
|
|
23
47
|
|
|
@@ -240,6 +264,34 @@ There is also a SyslogFormatter, which creates the log line as:
|
|
|
240
264
|
|
|
241
265
|
If you want a different format, then copy *lib/python/notificationforwarder/syslog/formatter.py* to *local/lib/python/notificationforwarder/syslog/formatter.py* and modify it like you want. Or, with *--formatter*, you can use whatever formatter is suitable, as long as it's payload attribute consists of a line of text.
|
|
242
266
|
|
|
267
|
+
### Nostr
|
|
268
|
+
|
|
269
|
+
The Nostr plugin pair publishes an encrypted direct message with labeled lines and default monitoring tags so the alert appears in chat-oriented clients.
|
|
270
|
+
|
|
271
|
+
Install the optional Nostr support with `pip install notificationforwarder[nostr]`.
|
|
272
|
+
|
|
273
|
+
|parameter|description|default|
|
|
274
|
+
|---------|-----------|-------|
|
|
275
|
+
|relays|comma-separated relay URLs|`wss://relay.damus.io`|
|
|
276
|
+
|nsec|bech32 private key for signing|required|
|
|
277
|
+
|tags|extra Nostr tags to include|`monitoring` plus host/service/state tags|
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
$USER1$/notificationforwarder \
|
|
283
|
+
--forwarder nostr \
|
|
284
|
+
--forwarderopt relays='wss://relay.damus.io,wss://nostr-pub.wellorder.net' \
|
|
285
|
+
--forwarderopt nsec='$NSEC$' \
|
|
286
|
+
--formatter nostr \
|
|
287
|
+
--eventopt HOSTNAME='$HOSTNAME$' \
|
|
288
|
+
--eventopt SERVICEDESC='$SERVICEDESC$' \
|
|
289
|
+
--eventopt SERVICESTATE='$SERVICESTATE$' \
|
|
290
|
+
--eventopt SERVICEOUTPUT='$SERVICEOUTPUT$'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The recipient `p` tag can be provided through the `tags` forwarder option or custom formatter output. The message content is encrypted before relay publication, so it shows up as a DM in clients that support NIP-04 direct messages.
|
|
294
|
+
|
|
243
295
|
## Loggers
|
|
244
296
|
|
|
245
297
|
The framework uses a modular logging architecture similar to formatters, forwarders, and reporters. By default, notificationforwarder uses **text format logging** - you don't need to do anything, logging works exactly as it did before. The traditional text format is backward compatible with all existing installations.
|
|
@@ -411,4 +463,3 @@ define command{
|
|
|
411
463
|
>> $USER4$/var/log/notificationforwarder_errors.log 2>&1
|
|
412
464
|
}
|
|
413
465
|
```
|
|
414
|
-
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: omdnotificationforwarder
|
|
3
|
-
Version: 4.1
|
|
4
|
-
Summary: A framework for notification scripts for OMD
|
|
5
|
-
Project-URL: Homepage, https://github.com/lausser/noteventificationforhandlerwarder
|
|
6
|
-
Project-URL: Bug Tracker, https://github.com/lausser/noteventificationforhandlerwarder/issues
|
|
7
|
-
Author-email: Gerhard Lausser <lausser@yahoo.com>
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Requires-Python: >=3.6.1
|
|
12
|
-
Requires-Dist: coshsh
|
|
13
|
-
Requires-Dist: jinja2
|
|
14
|
-
Provides-Extra: test
|
|
15
|
-
Requires-Dist: flake8; extra == 'test'
|
|
16
|
-
Requires-Dist: pytest; extra == 'test'
|
|
17
|
-
Requires-Dist: pytest-cov; extra == 'test'
|
|
18
|
-
Requires-Dist: requests; extra == 'test'
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
1
|
# notificationforwarder
|
|
22
2
|
|
|
23
3
|
In this framework, two aspects are in the focus. How to transport a notification to the recipient system and in which format.
|
|
@@ -38,6 +18,8 @@ Let me list some of the formatter/forwarder combinations which are usually found
|
|
|
38
18
|
|json |Splunk HEC |
|
|
39
19
|
|json |RabbitMQ |
|
|
40
20
|
|
|
21
|
+
|markdown-like Nostr note|Nostr relays |
|
|
22
|
+
|
|
41
23
|
Of course json is not json, the attributes and values are different depending on the recipient.
|
|
42
24
|
|
|
43
25
|
|
|
@@ -260,6 +242,34 @@ There is also a SyslogFormatter, which creates the log line as:
|
|
|
260
242
|
|
|
261
243
|
If you want a different format, then copy *lib/python/notificationforwarder/syslog/formatter.py* to *local/lib/python/notificationforwarder/syslog/formatter.py* and modify it like you want. Or, with *--formatter*, you can use whatever formatter is suitable, as long as it's payload attribute consists of a line of text.
|
|
262
244
|
|
|
245
|
+
### Nostr
|
|
246
|
+
|
|
247
|
+
The Nostr plugin pair publishes an encrypted direct message with labeled lines and default monitoring tags so the alert appears in chat-oriented clients.
|
|
248
|
+
|
|
249
|
+
Install the optional Nostr support with `pip install notificationforwarder[nostr]`.
|
|
250
|
+
|
|
251
|
+
|parameter|description|default|
|
|
252
|
+
|---------|-----------|-------|
|
|
253
|
+
|relays|comma-separated relay URLs|`wss://relay.damus.io`|
|
|
254
|
+
|nsec|bech32 private key for signing|required|
|
|
255
|
+
|tags|extra Nostr tags to include|`monitoring` plus host/service/state tags|
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
$USER1$/notificationforwarder \
|
|
261
|
+
--forwarder nostr \
|
|
262
|
+
--forwarderopt relays='wss://relay.damus.io,wss://nostr-pub.wellorder.net' \
|
|
263
|
+
--forwarderopt nsec='$NSEC$' \
|
|
264
|
+
--formatter nostr \
|
|
265
|
+
--eventopt HOSTNAME='$HOSTNAME$' \
|
|
266
|
+
--eventopt SERVICEDESC='$SERVICEDESC$' \
|
|
267
|
+
--eventopt SERVICESTATE='$SERVICESTATE$' \
|
|
268
|
+
--eventopt SERVICEOUTPUT='$SERVICEOUTPUT$'
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The recipient `p` tag can be provided through the `tags` forwarder option or custom formatter output. The message content is encrypted before relay publication, so it shows up as a DM in clients that support NIP-04 direct messages.
|
|
272
|
+
|
|
263
273
|
## Loggers
|
|
264
274
|
|
|
265
275
|
The framework uses a modular logging architecture similar to formatters, forwarders, and reporters. By default, notificationforwarder uses **text format logging** - you don't need to do anything, logging works exactly as it did before. The traditional text format is backward compatible with all existing installations.
|
|
@@ -431,4 +441,3 @@ define command{
|
|
|
431
441
|
>> $USER4$/var/log/notificationforwarder_errors.log 2>&1
|
|
432
442
|
}
|
|
433
443
|
```
|
|
434
|
-
|
|
@@ -78,7 +78,7 @@ Example for an HTTP-based reporter:
|
|
|
78
78
|
help='Logger type: text (default) or json, or custom logger module',
|
|
79
79
|
default='text')
|
|
80
80
|
parser.add_argument('--version', action='version',
|
|
81
|
-
version=f'%(prog)s 4.
|
|
81
|
+
version=f'%(prog)s 4.2')
|
|
82
82
|
|
|
83
83
|
args = parser.parse_args()
|
|
84
84
|
if not hasattr(args, 'formatter'):
|
|
@@ -21,7 +21,7 @@ packages = ["src/notificationforwarder"]
|
|
|
21
21
|
|
|
22
22
|
[project]
|
|
23
23
|
name = "omdnotificationforwarder"
|
|
24
|
-
version = "4.
|
|
24
|
+
version = "4.2"
|
|
25
25
|
authors = [
|
|
26
26
|
{ name="Gerhard Lausser", email="lausser@yahoo.com" },
|
|
27
27
|
]
|
|
@@ -39,6 +39,9 @@ dependencies = [
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
[project.optional-dependencies]
|
|
42
|
+
nostr = [
|
|
43
|
+
"pynostr",
|
|
44
|
+
]
|
|
42
45
|
test = [
|
|
43
46
|
"requests",
|
|
44
47
|
"flake8",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Nostr formatter and forwarder for notificationforwarder."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Generate a Nostr keypair and derive bech32 values.
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
from pynostr.key import PrivateKey
|
|
5
|
+
|
|
6
|
+
private_key = PrivateKey()
|
|
7
|
+
print(private_key.bech32()) # nsec1...
|
|
8
|
+
print(private_key.public_key.bech32()) # npub1...
|
|
9
|
+
```
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from notificationforwarder.baseclass import NotificationFormatter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NostrFormatter(NotificationFormatter):
|
|
18
|
+
def _pick(self, event, *names, default=""):
|
|
19
|
+
for name in names:
|
|
20
|
+
value = event.eventopts.get(name)
|
|
21
|
+
if value not in (None, ""):
|
|
22
|
+
return value
|
|
23
|
+
return default
|
|
24
|
+
|
|
25
|
+
def _build_tags(self, host, service, state, extra_tags=None):
|
|
26
|
+
tags = [["t", "monitoring"]]
|
|
27
|
+
if host:
|
|
28
|
+
tags.append(["host", host])
|
|
29
|
+
if service:
|
|
30
|
+
tags.append(["service", service])
|
|
31
|
+
if state:
|
|
32
|
+
tags.append(["state", state])
|
|
33
|
+
if isinstance(extra_tags, str):
|
|
34
|
+
try:
|
|
35
|
+
extra_tags = json.loads(extra_tags)
|
|
36
|
+
except Exception:
|
|
37
|
+
extra_tags = []
|
|
38
|
+
for tag in extra_tags or []:
|
|
39
|
+
if isinstance(tag, (list, tuple)) and tag:
|
|
40
|
+
tags.append(list(tag))
|
|
41
|
+
return tags
|
|
42
|
+
|
|
43
|
+
def format_event(self, event):
|
|
44
|
+
host = self._pick(event, "HOSTNAME", "HOST", "HOSTNAME_SHORT", "HOSTNAME_FULL")
|
|
45
|
+
service = self._pick(event, "SERVICEDESC", "SERVICE", default="")
|
|
46
|
+
state = self._pick(event, "SERVICESTATE", "HOSTSTATE", "STATE", default="")
|
|
47
|
+
output = self._pick(event, "SERVICEOUTPUT", "HOSTOUTPUT", "OUTPUT", default="")
|
|
48
|
+
|
|
49
|
+
lines = [
|
|
50
|
+
f"Host: {host}",
|
|
51
|
+
f"Service: {service or '-'}",
|
|
52
|
+
f"State: {state or '-'}",
|
|
53
|
+
f"Output: {output or '-'}",
|
|
54
|
+
]
|
|
55
|
+
content = "\n".join(lines)
|
|
56
|
+
event.payload = {
|
|
57
|
+
"kind": 4,
|
|
58
|
+
"content": content,
|
|
59
|
+
"tags": self._build_tags(
|
|
60
|
+
host,
|
|
61
|
+
service,
|
|
62
|
+
state,
|
|
63
|
+
event.forwarderopts.get("tags"),
|
|
64
|
+
),
|
|
65
|
+
}
|
|
66
|
+
event.summary = f"{host or 'unknown'}/{service or '-'}: {state or '-'}"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from pynostr.encrypted_dm import EncryptedDirectMessage
|
|
4
|
+
from pynostr.event import Event
|
|
5
|
+
from pynostr.key import PrivateKey, PublicKey
|
|
6
|
+
import pynostr.relay as relay_mod
|
|
7
|
+
from pynostr.relay_manager import RelayManager
|
|
8
|
+
|
|
9
|
+
from notificationforwarder.baseclass import NotificationForwarder, timeout
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NostrForwarder(NotificationForwarder):
|
|
13
|
+
def __init__(self, opts):
|
|
14
|
+
super(self.__class__, self).__init__(opts)
|
|
15
|
+
setattr(self, "relays", getattr(self, "relays", "wss://relay.damus.io"))
|
|
16
|
+
setattr(self, "relay_urls", getattr(self, "relay_urls", self.relays))
|
|
17
|
+
setattr(self, "nsec", getattr(self, "nsec", None))
|
|
18
|
+
setattr(self, "timeout_seconds", int(getattr(self, "timeout_seconds", 6)))
|
|
19
|
+
setattr(self, "kind", int(getattr(self, "kind", 1)))
|
|
20
|
+
|
|
21
|
+
def _relay_list(self):
|
|
22
|
+
relays = self.relay_urls if self.relay_urls is not None else self.relays
|
|
23
|
+
if isinstance(relays, str):
|
|
24
|
+
return [relay.strip() for relay in relays.split(",") if relay.strip()]
|
|
25
|
+
if isinstance(relays, (list, tuple)):
|
|
26
|
+
return [str(relay).strip() for relay in relays if str(relay).strip()]
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
def _private_key(self):
|
|
30
|
+
if not self.nsec:
|
|
31
|
+
raise ValueError("nsec is required for Nostr publishing")
|
|
32
|
+
if hasattr(PrivateKey, "from_nsec"):
|
|
33
|
+
return PrivateKey.from_nsec(self.nsec)
|
|
34
|
+
return PrivateKey(self.nsec)
|
|
35
|
+
|
|
36
|
+
def _build_event(self, formatted_event):
|
|
37
|
+
payload = formatted_event.payload or {}
|
|
38
|
+
if not isinstance(payload, dict):
|
|
39
|
+
raise ValueError("Nostr formatter must produce a dictionary payload")
|
|
40
|
+
tags = self._normalize_tags(payload.get("tags", []))
|
|
41
|
+
extra_tags = []
|
|
42
|
+
event_forwarder_tags = formatted_event.forwarderopts.get("tags", [])
|
|
43
|
+
if isinstance(event_forwarder_tags, str):
|
|
44
|
+
try:
|
|
45
|
+
event_forwarder_tags = json.loads(event_forwarder_tags)
|
|
46
|
+
except Exception:
|
|
47
|
+
event_forwarder_tags = []
|
|
48
|
+
if isinstance(event_forwarder_tags, (list, tuple)):
|
|
49
|
+
extra_tags.extend(event_forwarder_tags)
|
|
50
|
+
forwarder_tags = getattr(self, "tags", [])
|
|
51
|
+
if isinstance(forwarder_tags, str):
|
|
52
|
+
try:
|
|
53
|
+
forwarder_tags = json.loads(forwarder_tags)
|
|
54
|
+
except Exception:
|
|
55
|
+
forwarder_tags = []
|
|
56
|
+
if isinstance(forwarder_tags, (list, tuple)):
|
|
57
|
+
extra_tags.extend(forwarder_tags)
|
|
58
|
+
if extra_tags:
|
|
59
|
+
tags.extend(self._normalize_tags(extra_tags))
|
|
60
|
+
event = Event(payload.get("content", ""), tags=tags)
|
|
61
|
+
if hasattr(event, "kind"):
|
|
62
|
+
event.kind = payload.get("kind", self.kind)
|
|
63
|
+
return event
|
|
64
|
+
|
|
65
|
+
def _recipient_pubkey(self, event):
|
|
66
|
+
for tag in getattr(event, "tags", []) or []:
|
|
67
|
+
if isinstance(tag, (list, tuple)) and len(tag) > 1 and tag[0] == "p":
|
|
68
|
+
return tag[1]
|
|
69
|
+
raise ValueError("Nostr DM requires a recipient p tag")
|
|
70
|
+
|
|
71
|
+
def _normalize_pubkey(self, value):
|
|
72
|
+
# Accept recipient public keys as either npub1... or raw hex.
|
|
73
|
+
if not isinstance(value, str):
|
|
74
|
+
return value
|
|
75
|
+
value = value.strip()
|
|
76
|
+
if value.startswith("npub1"):
|
|
77
|
+
try:
|
|
78
|
+
return PublicKey.from_npub(value).hex()
|
|
79
|
+
except Exception:
|
|
80
|
+
return value
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
def _normalize_tags(self, tags):
|
|
84
|
+
normalized = []
|
|
85
|
+
for tag in tags or []:
|
|
86
|
+
if isinstance(tag, (list, tuple)) and tag:
|
|
87
|
+
item = list(tag)
|
|
88
|
+
if item[0] == "p" and len(item) > 1:
|
|
89
|
+
item[1] = self._normalize_pubkey(item[1])
|
|
90
|
+
normalized.append(item)
|
|
91
|
+
else:
|
|
92
|
+
normalized.append(["t", str(tag)])
|
|
93
|
+
return normalized
|
|
94
|
+
|
|
95
|
+
def _close_manager(self, relay_manager):
|
|
96
|
+
close_methods = [
|
|
97
|
+
"close_all_relay_connections",
|
|
98
|
+
"close_connections",
|
|
99
|
+
]
|
|
100
|
+
for method_name in close_methods:
|
|
101
|
+
method = getattr(relay_manager, method_name, None)
|
|
102
|
+
if callable(method):
|
|
103
|
+
method()
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
@timeout(30)
|
|
107
|
+
def submit(self, event):
|
|
108
|
+
relay_urls = self._relay_list()
|
|
109
|
+
if not relay_urls:
|
|
110
|
+
logger.critical("Nostr publish failed: no relays configured")
|
|
111
|
+
return False
|
|
112
|
+
try:
|
|
113
|
+
private_key = self._private_key()
|
|
114
|
+
nostr_event = self._build_event(event)
|
|
115
|
+
recipient_pubkey = self._normalize_pubkey(self._recipient_pubkey(nostr_event))
|
|
116
|
+
dm = EncryptedDirectMessage()
|
|
117
|
+
dm.encrypt(
|
|
118
|
+
private_key.hex(),
|
|
119
|
+
cleartext_content=nostr_event.content,
|
|
120
|
+
recipient_pubkey=recipient_pubkey,
|
|
121
|
+
)
|
|
122
|
+
nostr_event = dm.to_event()
|
|
123
|
+
nostr_event.sign(private_key.hex())
|
|
124
|
+
|
|
125
|
+
orig_websocket_connect = relay_mod.websocket_connect
|
|
126
|
+
relay_mod.websocket_connect = lambda *a, **k: orig_websocket_connect(
|
|
127
|
+
*a, **{**k, "ping_timeout": min(k.get("ping_timeout", 0), k.get("ping_interval", 0))}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
relay_manager = RelayManager(timeout=self.timeout_seconds)
|
|
131
|
+
for relay_url in relay_urls:
|
|
132
|
+
relay_manager.add_relay(relay_url)
|
|
133
|
+
relay_manager.publish_event(nostr_event)
|
|
134
|
+
relay_manager.run_sync()
|
|
135
|
+
self._close_manager(relay_manager)
|
|
136
|
+
logger.info("published Nostr DM: {}".format(event.summary))
|
|
137
|
+
self.no_more_logging()
|
|
138
|
+
return True
|
|
139
|
+
except Exception as exc:
|
|
140
|
+
logger.critical("Nostr publish failed: {}".format(str(exc)))
|
|
141
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import fcntl
|
|
5
|
+
import requests
|
|
6
|
+
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
|
|
7
|
+
|
|
8
|
+
import notificationforwarder.webhook.forwarder as _webhook_module
|
|
9
|
+
from notificationforwarder.webhook.forwarder import WebhookForwarder
|
|
10
|
+
from notificationforwarder.baseclass import NotificationForwarder, timeout
|
|
11
|
+
|
|
12
|
+
logger = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Oauth2WebhookForwarder(WebhookForwarder):
|
|
16
|
+
def __init__(self, opts):
|
|
17
|
+
# Call NotificationForwarder directly to avoid infinite recursion from
|
|
18
|
+
# WebhookForwarder's super(self.__class__, self) pattern when subclassed.
|
|
19
|
+
NotificationForwarder.__init__(self, opts)
|
|
20
|
+
# Webhook attribute defaults
|
|
21
|
+
setattr(self, "url", getattr(self, "url", "http://localhost:12345"))
|
|
22
|
+
setattr(self, "username", getattr(self, "username", None))
|
|
23
|
+
setattr(self, "password", getattr(self, "password", None))
|
|
24
|
+
setattr(self, "insecure", getattr(self, "insecure", "yes"))
|
|
25
|
+
setattr(self, "headers", getattr(self, "headers", None))
|
|
26
|
+
# OAuth2 attribute defaults
|
|
27
|
+
setattr(self, "token_url", getattr(self, "token_url", None))
|
|
28
|
+
setattr(self, "client_id", getattr(self, "client_id", None))
|
|
29
|
+
setattr(self, "client_secret", getattr(self, "client_secret", None))
|
|
30
|
+
setattr(self, "token_scope", getattr(self, "token_scope", None))
|
|
31
|
+
setattr(self, "token_grant_type", getattr(self, "token_grant_type", "client_credentials"))
|
|
32
|
+
setattr(self, "token_expiry_buffer", int(getattr(self, "token_expiry_buffer", 30)))
|
|
33
|
+
|
|
34
|
+
def _token_cache_path(self):
|
|
35
|
+
return os.path.join(
|
|
36
|
+
os.environ["OMD_ROOT"], "var", "tmp",
|
|
37
|
+
"notificationforwarder_{}_oauth2token.json".format(self.forwarder_name),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def acquire_token(self):
|
|
41
|
+
cache_path = self._token_cache_path()
|
|
42
|
+
lock_path = cache_path + ".lock"
|
|
43
|
+
|
|
44
|
+
with open(lock_path, "w") as lf:
|
|
45
|
+
fcntl.flock(lf, fcntl.LOCK_EX)
|
|
46
|
+
try:
|
|
47
|
+
if os.path.exists(cache_path):
|
|
48
|
+
with open(cache_path) as f:
|
|
49
|
+
cached = json.load(f)
|
|
50
|
+
if time.time() < cached.get("expires_at", 0) - self.token_expiry_buffer:
|
|
51
|
+
logger.debug("oauth2: using cached token")
|
|
52
|
+
return cached["access_token"]
|
|
53
|
+
|
|
54
|
+
logger.debug("oauth2: acquiring new token from {}".format(self.token_url))
|
|
55
|
+
data = {
|
|
56
|
+
"grant_type": self.token_grant_type,
|
|
57
|
+
"client_id": self.client_id,
|
|
58
|
+
"client_secret": self.client_secret,
|
|
59
|
+
}
|
|
60
|
+
if self.token_scope:
|
|
61
|
+
data["scope"] = self.token_scope
|
|
62
|
+
|
|
63
|
+
resp = requests.post(
|
|
64
|
+
self.token_url,
|
|
65
|
+
data=data,
|
|
66
|
+
timeout=10,
|
|
67
|
+
verify=(self.insecure != "yes"),
|
|
68
|
+
)
|
|
69
|
+
resp.raise_for_status()
|
|
70
|
+
token_data = resp.json()
|
|
71
|
+
token = token_data["access_token"]
|
|
72
|
+
expires_in = int(token_data.get("expires_in", 3600))
|
|
73
|
+
expires_at = time.time() + expires_in
|
|
74
|
+
|
|
75
|
+
with open(cache_path, "w") as f:
|
|
76
|
+
json.dump({"access_token": token, "expires_at": expires_at}, f)
|
|
77
|
+
|
|
78
|
+
logger.debug("oauth2: token acquired, valid for {}s".format(expires_in))
|
|
79
|
+
return token
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.critical("oauth2: token acquisition failed: {}".format(e))
|
|
82
|
+
raise
|
|
83
|
+
finally:
|
|
84
|
+
fcntl.flock(lf, fcntl.LOCK_UN)
|
|
85
|
+
|
|
86
|
+
def submit_one(self, event):
|
|
87
|
+
# Sync logger into the webhook module so WebhookForwarder.submit_one can use it.
|
|
88
|
+
_webhook_module.logger = logger
|
|
89
|
+
try:
|
|
90
|
+
token = self.acquire_token()
|
|
91
|
+
except Exception:
|
|
92
|
+
return False
|
|
93
|
+
hdrs = event.forwarderopts.get("headers") or {}
|
|
94
|
+
if isinstance(hdrs, str):
|
|
95
|
+
hdrs = json.loads(hdrs)
|
|
96
|
+
hdrs["Authorization"] = "Bearer {}".format(token)
|
|
97
|
+
event.forwarderopts["headers"] = hdrs
|
|
98
|
+
return super().submit_one(event)
|