omdnotificationforwarder 4.2__tar.gz → 4.4__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.2 → omdnotificationforwarder-4.4}/PKG-INFO +3 -3
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/README.md +1 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/bin/notificationforwarder +2 -2
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/pyproject.toml +3 -4
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/baseclass.py +10 -2
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/email/forwarder.py +3 -1
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/nostr/formatter.py +3 -15
- omdnotificationforwarder-4.4/src/notificationforwarder/nostr/forwarder.py +130 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/spool.py +28 -18
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/syslog/forwarder.py +2 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/telegram/forwarder.py +5 -2
- omdnotificationforwarder-4.4/src/notificationforwarder/util.py +37 -0
- omdnotificationforwarder-4.4/tests/TESTS.md +634 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_alertmanager.py +3 -2
- omdnotificationforwarder-4.4/tests/test_contracts_formatters.py +290 -0
- omdnotificationforwarder-4.4/tests/test_contracts_forwarders.py +978 -0
- omdnotificationforwarder-4.4/tests/test_contracts_reporters.py +249 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_formatter.py +58 -1
- omdnotificationforwarder-4.4/tests/test_integration_cli.py +102 -0
- omdnotificationforwarder-4.4/tests/test_loading_resolution.py +169 -0
- omdnotificationforwarder-4.4/tests/test_loggers.py +496 -0
- omdnotificationforwarder-4.4/tests/test_nostr.py +701 -0
- omdnotificationforwarder-4.4/tests/test_orchestration_flow.py +275 -0
- omdnotificationforwarder-4.4/tests/test_resilience_spool.py +871 -0
- omdnotificationforwarder-4.2/src/notificationforwarder/nostr/forwarder.py +0 -141
- omdnotificationforwarder-4.2/tests/test_nostr.py +0 -261
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/.gitignore +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/component_loader.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/email/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/example/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/example/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/json/__init__.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/json/logger.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/naemonlog/reporter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/nostr/__init__.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/oauth2webhook/__init__.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/oauth2webhook/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/rabbitmq/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/rabbitmq/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/runtime_config.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/runtime_flow.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/syslog/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/text/__init__.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/text/logger.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/webhook/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split1/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split2/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split2/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split3/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split3/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/alertmanager_servicenow/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/bayern/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/datadup/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/datapost/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/discard/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split1/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split4/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split4/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/ticketsystem/forwarder.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/ticketsystem/reporter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/vong/formatter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_builtin_plugins_focus.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_classes.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_delivery_resilience.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_discard.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_logger.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_oauth2webhook.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_package.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_paths.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_reporter.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_runtime_foundation.py +0 -0
- {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_webhook.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omdnotificationforwarder
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4
|
|
4
4
|
Summary: A framework for notification scripts for OMD
|
|
5
5
|
Project-URL: Homepage, https://github.com/lausser/noteventificationforhandlerwarder
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/lausser/noteventificationforhandlerwarder/issues
|
|
@@ -9,10 +9,9 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Requires-Python: >=3.6.1
|
|
12
|
-
Requires-Dist: coshsh
|
|
13
12
|
Requires-Dist: jinja2
|
|
14
13
|
Provides-Extra: nostr
|
|
15
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: nostr-sdk; extra == 'nostr'
|
|
16
15
|
Provides-Extra: test
|
|
17
16
|
Requires-Dist: flake8; extra == 'test'
|
|
18
17
|
Requires-Dist: pytest; extra == 'test'
|
|
@@ -25,6 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
25
24
|
In this framework, two aspects are in the focus. How to transport a notification to the recipient system and in which format.
|
|
26
25
|
In the beginning, Naemon or one of the other monitoring cores will execute a command line. The actual script and the individual command line parameters are defined in a command definition. Typical parameters are (i use the notation of Nagios macros) HOSTNAME, SERVICEDESC, SERVICESTATE, SERVICEOUTPUT. These snippets need to be put together to some kind of payload suitable for the receiving system. And then this payload must be transported to it. We call the two components *formatter* and *forwarder*. The formatter takes the raw input data and creates a payload and the forwarder transmits the payload to the destination.
|
|
27
26
|
What the framework does for you behind the scenes: When forwarding to a recipient fails, the event is saved in a local sqlite database for a certain time and re-sent when the script is called next time and the recipient is available again. Logging of successful and of course failed deliveries is also done automatically.
|
|
27
|
+
Logging uses the framework's native setup with the standard logfile naming and format used by the built-in tools.
|
|
28
28
|
There is also a component *reporter* which will rarely be used. It's purpose is to run additional code after a successful or failed delivery.
|
|
29
29
|
|
|
30
30
|
Let me list some of the formatter/forwarder combinations which are usually found in enterprise environments:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
In this framework, two aspects are in the focus. How to transport a notification to the recipient system and in which format.
|
|
4
4
|
In the beginning, Naemon or one of the other monitoring cores will execute a command line. The actual script and the individual command line parameters are defined in a command definition. Typical parameters are (i use the notation of Nagios macros) HOSTNAME, SERVICEDESC, SERVICESTATE, SERVICEOUTPUT. These snippets need to be put together to some kind of payload suitable for the receiving system. And then this payload must be transported to it. We call the two components *formatter* and *forwarder*. The formatter takes the raw input data and creates a payload and the forwarder transmits the payload to the destination.
|
|
5
5
|
What the framework does for you behind the scenes: When forwarding to a recipient fails, the event is saved in a local sqlite database for a certain time and re-sent when the script is called next time and the recipient is available again. Logging of successful and of course failed deliveries is also done automatically.
|
|
6
|
+
Logging uses the framework's native setup with the standard logfile naming and format used by the built-in tools.
|
|
6
7
|
There is also a component *reporter* which will rarely be used. It's purpose is to run additional code after a successful or failed delivery.
|
|
7
8
|
|
|
8
9
|
Let me list some of the formatter/forwarder combinations which are usually found in enterprise environments:
|
|
@@ -6,7 +6,7 @@ import logging.handlers
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
#import logging
|
|
9
|
-
from
|
|
9
|
+
from notificationforwarder.util import setup_logging
|
|
10
10
|
from notificationforwarder import baseclass
|
|
11
11
|
import traceback
|
|
12
12
|
|
|
@@ -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.4')
|
|
82
82
|
|
|
83
83
|
args = parser.parse_args()
|
|
84
84
|
if not hasattr(args, 'formatter'):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"
|
|
2
|
+
requires = ["hatchling"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[tool.hatch.build]
|
|
@@ -21,7 +21,7 @@ packages = ["src/notificationforwarder"]
|
|
|
21
21
|
|
|
22
22
|
[project]
|
|
23
23
|
name = "omdnotificationforwarder"
|
|
24
|
-
version = "4.
|
|
24
|
+
version = "4.4"
|
|
25
25
|
authors = [
|
|
26
26
|
{ name="Gerhard Lausser", email="lausser@yahoo.com" },
|
|
27
27
|
]
|
|
@@ -34,13 +34,12 @@ classifiers = [
|
|
|
34
34
|
"Operating System :: OS Independent",
|
|
35
35
|
]
|
|
36
36
|
dependencies = [
|
|
37
|
-
"coshsh",
|
|
38
37
|
"jinja2",
|
|
39
38
|
]
|
|
40
39
|
|
|
41
40
|
[project.optional-dependencies]
|
|
42
41
|
nostr = [
|
|
43
|
-
"
|
|
42
|
+
"nostr-sdk",
|
|
44
43
|
]
|
|
45
44
|
test = [
|
|
46
45
|
"requests",
|
{omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/baseclass.py
RENAMED
|
@@ -11,7 +11,7 @@ except ImportError:
|
|
|
11
11
|
import json
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
|
-
from
|
|
14
|
+
from notificationforwarder.util import setup_logging
|
|
15
15
|
from importlib import import_module
|
|
16
16
|
|
|
17
17
|
from notificationforwarder.component_loader import (
|
|
@@ -415,6 +415,7 @@ class NotificationForwarder(object):
|
|
|
415
415
|
with open(self.db_lock_file, "w") as lock_file:
|
|
416
416
|
locked = self.acquire_lock_with_retry(lock_file)
|
|
417
417
|
if locked:
|
|
418
|
+
self.baseclass_logs_summary = True
|
|
418
419
|
try:
|
|
419
420
|
dropped = self.spool_store.prune_expired(self.max_spool_minutes)
|
|
420
421
|
replay_attempted = 0
|
|
@@ -449,7 +450,14 @@ class NotificationForwarder(object):
|
|
|
449
450
|
for id, text in id_events:
|
|
450
451
|
raw_event = self.spool_store.decode(text)
|
|
451
452
|
formatted_event = self.format_event(raw_event)
|
|
452
|
-
if formatted_event:
|
|
453
|
+
if formatted_event and formatted_event.is_discarded:
|
|
454
|
+
deleted_trash += 1
|
|
455
|
+
logger.info("discard spooled event during replay", {
|
|
456
|
+
'event_id': id,
|
|
457
|
+
'action': 'discard_during_replay'
|
|
458
|
+
})
|
|
459
|
+
self.spool_store.delete(id)
|
|
460
|
+
elif formatted_event:
|
|
453
461
|
replay_attempted += 1
|
|
454
462
|
try:
|
|
455
463
|
result = self.submit(formatted_event)
|
|
@@ -5,6 +5,8 @@ from email.mime.text import MIMEText
|
|
|
5
5
|
from email.mime.multipart import MIMEMultipart
|
|
6
6
|
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
7
7
|
|
|
8
|
+
logger = logging.getLogger("notificationforwarder.email")
|
|
9
|
+
|
|
8
10
|
class EmailForwarder(NotificationForwarder):
|
|
9
11
|
def __init__(self, opts):
|
|
10
12
|
super(self.__class__, self).__init__(opts)
|
|
@@ -31,6 +33,6 @@ class EmailForwarder(NotificationForwarder):
|
|
|
31
33
|
server.quit()
|
|
32
34
|
return True
|
|
33
35
|
except Exception as e:
|
|
34
|
-
|
|
36
|
+
logger.critical("sending mail failed: {}".format(str(e)))
|
|
35
37
|
return False
|
|
36
38
|
|
|
@@ -1,14 +1,3 @@
|
|
|
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
1
|
import json
|
|
13
2
|
|
|
14
3
|
from notificationforwarder.baseclass import NotificationFormatter
|
|
@@ -25,11 +14,11 @@ class NostrFormatter(NotificationFormatter):
|
|
|
25
14
|
def _build_tags(self, host, service, state, extra_tags=None):
|
|
26
15
|
tags = [["t", "monitoring"]]
|
|
27
16
|
if host:
|
|
28
|
-
tags.append(["
|
|
17
|
+
tags.append(["t", "host=" + host])
|
|
29
18
|
if service:
|
|
30
|
-
tags.append(["
|
|
19
|
+
tags.append(["t", "service=" + service])
|
|
31
20
|
if state:
|
|
32
|
-
tags.append(["
|
|
21
|
+
tags.append(["t", "state=" + state])
|
|
33
22
|
if isinstance(extra_tags, str):
|
|
34
23
|
try:
|
|
35
24
|
extra_tags = json.loads(extra_tags)
|
|
@@ -54,7 +43,6 @@ class NostrFormatter(NotificationFormatter):
|
|
|
54
43
|
]
|
|
55
44
|
content = "\n".join(lines)
|
|
56
45
|
event.payload = {
|
|
57
|
-
"kind": 4,
|
|
58
46
|
"content": content,
|
|
59
47
|
"tags": self._build_tags(
|
|
60
48
|
host,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from nostr_sdk import Client, Keys, NostrSigner, PublicKey, RelayUrl, Tag, uniffi_set_event_loop
|
|
5
|
+
except ImportError as exc:
|
|
6
|
+
raise ImportError("missing nostr-sdk") from exc
|
|
7
|
+
|
|
8
|
+
from notificationforwarder.baseclass import NotificationForwarder, timeout
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NostrForwarder(NotificationForwarder):
|
|
12
|
+
def __init__(self, opts):
|
|
13
|
+
super(self.__class__, self).__init__(opts)
|
|
14
|
+
setattr(self, "relays", getattr(self, "relays", "wss://relay.damus.io"))
|
|
15
|
+
setattr(self, "relay_urls", getattr(self, "relay_urls", self.relays))
|
|
16
|
+
setattr(self, "nsec", getattr(self, "nsec", None))
|
|
17
|
+
setattr(self, "timeout_seconds", int(getattr(self, "timeout_seconds", 6)))
|
|
18
|
+
|
|
19
|
+
def _relay_list(self):
|
|
20
|
+
relays = self.relay_urls if self.relay_urls is not None else self.relays
|
|
21
|
+
if isinstance(relays, str):
|
|
22
|
+
return [relay.strip() for relay in relays.split(",") if relay.strip()]
|
|
23
|
+
if isinstance(relays, (list, tuple)):
|
|
24
|
+
return [str(relay).strip() for relay in relays if str(relay).strip()]
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
def _keys(self):
|
|
28
|
+
if not self.nsec:
|
|
29
|
+
raise ValueError("nsec is required for Nostr publishing")
|
|
30
|
+
return Keys.parse(self.nsec)
|
|
31
|
+
|
|
32
|
+
def _public_key(self, value):
|
|
33
|
+
if isinstance(value, PublicKey):
|
|
34
|
+
return value
|
|
35
|
+
if not isinstance(value, str):
|
|
36
|
+
raise ValueError("receiver public key must be a string or PublicKey")
|
|
37
|
+
return PublicKey.parse(value)
|
|
38
|
+
|
|
39
|
+
def _tags(self, formatted_event):
|
|
40
|
+
tags = []
|
|
41
|
+
payload = formatted_event.payload or {}
|
|
42
|
+
tag_values = payload.get("tags", [])
|
|
43
|
+
if isinstance(tag_values, str):
|
|
44
|
+
import json
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
tag_values = json.loads(tag_values)
|
|
48
|
+
except Exception:
|
|
49
|
+
tag_values = []
|
|
50
|
+
if isinstance(tag_values, (list, tuple)):
|
|
51
|
+
for tag in tag_values:
|
|
52
|
+
if isinstance(tag, (list, tuple)) and tag:
|
|
53
|
+
tags.append(Tag.parse(list(tag)))
|
|
54
|
+
return tags
|
|
55
|
+
|
|
56
|
+
def _configured_tags(self):
|
|
57
|
+
tag_values = getattr(self, "tags", [])
|
|
58
|
+
if isinstance(tag_values, str):
|
|
59
|
+
import json
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
tag_values = json.loads(tag_values)
|
|
63
|
+
except Exception:
|
|
64
|
+
tag_values = []
|
|
65
|
+
return tag_values if isinstance(tag_values, (list, tuple)) else []
|
|
66
|
+
|
|
67
|
+
def _nip_mode(self):
|
|
68
|
+
return getattr(self, "nip_mode", "nip04")
|
|
69
|
+
|
|
70
|
+
async def _send_private_msg(self, relay_urls, receiver_pubkey, message, tags):
|
|
71
|
+
uniffi_set_event_loop(asyncio.get_running_loop())
|
|
72
|
+
keys = self._keys()
|
|
73
|
+
signer = NostrSigner.keys(keys)
|
|
74
|
+
client = Client(signer)
|
|
75
|
+
relay_urls = [RelayUrl.parse(url) for url in relay_urls]
|
|
76
|
+
|
|
77
|
+
for relay_url in relay_urls:
|
|
78
|
+
await client.add_relay(relay_url)
|
|
79
|
+
|
|
80
|
+
await client.connect()
|
|
81
|
+
return await client.send_private_msg_to(relay_urls, receiver_pubkey, message, tags)
|
|
82
|
+
|
|
83
|
+
async def _send_nip04_dm(self, relay_urls, receiver_pubkey, message):
|
|
84
|
+
uniffi_set_event_loop(asyncio.get_running_loop())
|
|
85
|
+
keys = self._keys()
|
|
86
|
+
signer = NostrSigner.keys(keys)
|
|
87
|
+
client = Client(signer)
|
|
88
|
+
relay_urls = [RelayUrl.parse(url) for url in relay_urls]
|
|
89
|
+
|
|
90
|
+
for relay_url in relay_urls:
|
|
91
|
+
await client.add_relay(relay_url)
|
|
92
|
+
|
|
93
|
+
await client.connect()
|
|
94
|
+
return await client.send_private_msg_to(relay_urls, receiver_pubkey, message, [])
|
|
95
|
+
|
|
96
|
+
@timeout(30)
|
|
97
|
+
def submit(self, event):
|
|
98
|
+
relay_urls = self._relay_list()
|
|
99
|
+
if not relay_urls:
|
|
100
|
+
logger.critical("Nostr publish failed: no relays configured")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
payload = event.payload or {}
|
|
104
|
+
nip_mode = self._nip_mode()
|
|
105
|
+
tags = self._configured_tags() or payload.get("tags", [])
|
|
106
|
+
recipient_pubkey = None
|
|
107
|
+
for tag in tags or []:
|
|
108
|
+
if isinstance(tag, (list, tuple)) and len(tag) > 1 and tag[0] == "p":
|
|
109
|
+
recipient_pubkey = self._public_key(tag[1])
|
|
110
|
+
break
|
|
111
|
+
if recipient_pubkey is None:
|
|
112
|
+
raise ValueError("Nostr DM requires a recipient p tag")
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
message = payload.get("content", event.summary or str(payload))
|
|
116
|
+
nip_mode = self._nip_mode()
|
|
117
|
+
logger.debug("Nostr NIP mode: {}".format(nip_mode))
|
|
118
|
+
if nip_mode == "nip17":
|
|
119
|
+
# For NIP-17, use tags from payload (like [["t", "monitoring"]])
|
|
120
|
+
tags = self._tags(event)
|
|
121
|
+
asyncio.run(self._send_private_msg(relay_urls, recipient_pubkey, message, tags))
|
|
122
|
+
else:
|
|
123
|
+
# NIP-04: send without tags (nostr-sdk doesn't support NIP-04 tags easily)
|
|
124
|
+
asyncio.run(self._send_nip04_dm(relay_urls, recipient_pubkey, message))
|
|
125
|
+
logger.info("published Nostr DM: {}".format(event.summary))
|
|
126
|
+
self.no_more_logging()
|
|
127
|
+
return True
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
logger.critical("Nostr publish failed: {}".format(str(exc)))
|
|
130
|
+
return False
|
{omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/spool.py
RENAMED
|
@@ -4,6 +4,7 @@ except ImportError:
|
|
|
4
4
|
import json
|
|
5
5
|
import random
|
|
6
6
|
import sqlite3
|
|
7
|
+
import threading
|
|
7
8
|
import time
|
|
8
9
|
import fcntl
|
|
9
10
|
|
|
@@ -14,6 +15,7 @@ class SpoolStore:
|
|
|
14
15
|
self.table_name = table_name
|
|
15
16
|
self.connection = None
|
|
16
17
|
self.cursor = None
|
|
18
|
+
self._lock = threading.Lock()
|
|
17
19
|
|
|
18
20
|
def open(self):
|
|
19
21
|
self.connection = sqlite3.connect(self.db_file, check_same_thread=False)
|
|
@@ -25,45 +27,53 @@ class SpoolStore:
|
|
|
25
27
|
payload TEXT NOT NULL,
|
|
26
28
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
27
29
|
)"""
|
|
28
|
-
self.
|
|
29
|
-
|
|
30
|
+
with self._lock:
|
|
31
|
+
self.cursor.execute(sql_create)
|
|
32
|
+
self.connection.commit()
|
|
30
33
|
|
|
31
34
|
def count(self):
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
with self._lock:
|
|
36
|
+
self.cursor.execute("SELECT COUNT(*) FROM " + self.table_name)
|
|
37
|
+
return self.cursor.fetchone()[0]
|
|
34
38
|
|
|
35
39
|
def enqueue(self, raw_event):
|
|
36
40
|
sql_insert = "INSERT INTO " + self.table_name + "(payload) VALUES (?)"
|
|
37
|
-
self.
|
|
38
|
-
|
|
41
|
+
with self._lock:
|
|
42
|
+
self.cursor.execute(sql_insert, (json.dumps(raw_event),))
|
|
43
|
+
self.connection.commit()
|
|
39
44
|
|
|
40
45
|
def prune_expired(self, max_spool_minutes):
|
|
41
46
|
outdated = int(time.time() - 60 * max_spool_minutes)
|
|
42
47
|
sql_delete = "DELETE FROM " + self.table_name + " WHERE CAST(STRFTIME('%s', timestamp) AS INTEGER) < ?"
|
|
43
|
-
self.
|
|
44
|
-
|
|
48
|
+
with self._lock:
|
|
49
|
+
self.cursor.execute(sql_delete, (outdated,))
|
|
50
|
+
return self.cursor.rowcount
|
|
45
51
|
|
|
46
52
|
def fetch_batch(self, limit=10):
|
|
47
53
|
sql_select = "SELECT id, payload FROM " + self.table_name + " ORDER BY id LIMIT ?"
|
|
48
|
-
self.
|
|
49
|
-
|
|
54
|
+
with self._lock:
|
|
55
|
+
self.cursor.execute(sql_select, (limit,))
|
|
56
|
+
return self.cursor.fetchall()
|
|
50
57
|
|
|
51
58
|
def delete(self, event_id):
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
with self._lock:
|
|
60
|
+
self.cursor.execute("DELETE FROM " + self.table_name + " WHERE id = ?", (event_id,))
|
|
61
|
+
self.connection.commit()
|
|
54
62
|
|
|
55
63
|
def decode(self, text):
|
|
56
64
|
return json.loads(text)
|
|
57
65
|
|
|
58
66
|
def commit(self):
|
|
59
|
-
self.
|
|
67
|
+
with self._lock:
|
|
68
|
+
self.connection.commit()
|
|
60
69
|
|
|
61
70
|
def close(self):
|
|
62
|
-
|
|
63
|
-
self.cursor
|
|
64
|
-
|
|
65
|
-
self.connection
|
|
66
|
-
|
|
71
|
+
with self._lock:
|
|
72
|
+
if self.cursor:
|
|
73
|
+
self.cursor.close()
|
|
74
|
+
if self.connection:
|
|
75
|
+
self.connection.commit()
|
|
76
|
+
self.connection.close()
|
|
67
77
|
|
|
68
78
|
|
|
69
79
|
def acquire_lock_with_retry(lock_file, app_logger, max_attempts=3, base_delay=0.1):
|
|
@@ -3,6 +3,8 @@ import socket
|
|
|
3
3
|
import logging
|
|
4
4
|
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
5
5
|
|
|
6
|
+
logger = logging.getLogger("notificationforwarder.syslog")
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class SyslogForwarder(NotificationForwarder):
|
|
8
10
|
def __init__(self, opts):
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import requests
|
|
2
3
|
import urllib.parse
|
|
3
4
|
from notificationforwarder.baseclass import NotificationForwarder, timeout
|
|
4
5
|
|
|
6
|
+
logger = logging.getLogger("notificationforwarder.telegram")
|
|
7
|
+
|
|
5
8
|
class TelegramForwarder(NotificationForwarder):
|
|
6
9
|
def __init__(self, opts):
|
|
7
10
|
super(self.__class__, self).__init__(opts)
|
|
8
11
|
setattr(self, "bot_token", getattr(self, "bot_token", None))
|
|
9
|
-
setattr(self, "chat_id", getattr(self, "
|
|
12
|
+
setattr(self, "chat_id", getattr(self, "chat_id", None))
|
|
10
13
|
|
|
11
14
|
@timeout(30)
|
|
12
15
|
def submit(self, event):
|
|
@@ -33,7 +36,7 @@ class TelegramForwarder(NotificationForwarder):
|
|
|
33
36
|
request_params["text"] = urllib.parse.quote(event.payload)
|
|
34
37
|
request_params["parse_mode"] = "MarkdownV2"
|
|
35
38
|
request_params["chat_id"] = self.chat_id
|
|
36
|
-
response = requests.get("https://api.telegram.org/bot{}/sendMessage".format(self.bot_token), params=
|
|
39
|
+
response = requests.get("https://api.telegram.org/bot{}/sendMessage".format(self.bot_token), params=request_params)
|
|
37
40
|
if response.status_code == requests.codes.ok:
|
|
38
41
|
logger.info("success: {} result is {}, request was {}".format(event.summary, response.text, request_params))
|
|
39
42
|
return True
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.handlers import RotatingFileHandler
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_logging(logdir=".", logfile="notificationforwarder.log", scrnloglevel=logging.INFO, txtloglevel=logging.INFO, format="%(asctime)s %(process)d - %(levelname)s - %(message)s", backup_count=3):
|
|
8
|
+
logdir_path = Path(logdir)
|
|
9
|
+
logdir_path.mkdir(parents=True, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
logfile_path = Path(logfile)
|
|
12
|
+
abs_logfile = logfile_path if logfile_path.is_absolute() else logdir_path / logfile_path
|
|
13
|
+
abs_logfile.touch(exist_ok=True)
|
|
14
|
+
logger_name = abs_logfile.name[:-4] if abs_logfile.name.endswith(".log") else abs_logfile.name
|
|
15
|
+
logger = logging.getLogger(logger_name)
|
|
16
|
+
logger.setLevel(logging.DEBUG)
|
|
17
|
+
logger.propagate = False
|
|
18
|
+
|
|
19
|
+
for handler in list(logger.handlers):
|
|
20
|
+
handler.close()
|
|
21
|
+
logger.removeHandler(handler)
|
|
22
|
+
|
|
23
|
+
formatter = logging.Formatter(format)
|
|
24
|
+
|
|
25
|
+
file_handler = RotatingFileHandler(str(abs_logfile), maxBytes=20 * 1024 * 1024, backupCount=backup_count, delay=True)
|
|
26
|
+
file_handler.setFormatter(formatter)
|
|
27
|
+
file_handler.setLevel(txtloglevel)
|
|
28
|
+
logger.addHandler(file_handler)
|
|
29
|
+
|
|
30
|
+
console_handler = logging.StreamHandler(sys.stderr)
|
|
31
|
+
console_handler.setFormatter(formatter)
|
|
32
|
+
console_handler.setLevel(scrnloglevel)
|
|
33
|
+
logger.addHandler(console_handler)
|
|
34
|
+
|
|
35
|
+
logger.debug("Logger initialized.")
|
|
36
|
+
|
|
37
|
+
return logger
|