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.
Files changed (75) hide show
  1. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/PKG-INFO +3 -3
  2. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/README.md +1 -0
  3. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/bin/notificationforwarder +2 -2
  4. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/pyproject.toml +3 -4
  5. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/baseclass.py +10 -2
  6. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/email/forwarder.py +3 -1
  7. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/nostr/formatter.py +3 -15
  8. omdnotificationforwarder-4.4/src/notificationforwarder/nostr/forwarder.py +130 -0
  9. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/spool.py +28 -18
  10. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/syslog/forwarder.py +2 -0
  11. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/telegram/forwarder.py +5 -2
  12. omdnotificationforwarder-4.4/src/notificationforwarder/util.py +37 -0
  13. omdnotificationforwarder-4.4/tests/TESTS.md +634 -0
  14. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_alertmanager.py +3 -2
  15. omdnotificationforwarder-4.4/tests/test_contracts_formatters.py +290 -0
  16. omdnotificationforwarder-4.4/tests/test_contracts_forwarders.py +978 -0
  17. omdnotificationforwarder-4.4/tests/test_contracts_reporters.py +249 -0
  18. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_formatter.py +58 -1
  19. omdnotificationforwarder-4.4/tests/test_integration_cli.py +102 -0
  20. omdnotificationforwarder-4.4/tests/test_loading_resolution.py +169 -0
  21. omdnotificationforwarder-4.4/tests/test_loggers.py +496 -0
  22. omdnotificationforwarder-4.4/tests/test_nostr.py +701 -0
  23. omdnotificationforwarder-4.4/tests/test_orchestration_flow.py +275 -0
  24. omdnotificationforwarder-4.4/tests/test_resilience_spool.py +871 -0
  25. omdnotificationforwarder-4.2/src/notificationforwarder/nostr/forwarder.py +0 -141
  26. omdnotificationforwarder-4.2/tests/test_nostr.py +0 -261
  27. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/.gitignore +0 -0
  28. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/component_loader.py +0 -0
  29. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/email/formatter.py +0 -0
  30. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/example/formatter.py +0 -0
  31. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/example/forwarder.py +0 -0
  32. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/json/__init__.py +0 -0
  33. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/json/logger.py +0 -0
  34. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/naemonlog/reporter.py +0 -0
  35. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/nostr/__init__.py +0 -0
  36. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/oauth2webhook/__init__.py +0 -0
  37. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/oauth2webhook/forwarder.py +0 -0
  38. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/rabbitmq/formatter.py +0 -0
  39. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/rabbitmq/forwarder.py +0 -0
  40. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/runtime_config.py +0 -0
  41. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/runtime_flow.py +0 -0
  42. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/syslog/formatter.py +0 -0
  43. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/text/__init__.py +0 -0
  44. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/text/logger.py +0 -0
  45. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/src/notificationforwarder/webhook/forwarder.py +0 -0
  46. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split1/forwarder.py +0 -0
  47. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split2/formatter.py +0 -0
  48. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split2/forwarder.py +0 -0
  49. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split3/formatter.py +0 -0
  50. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/lib/python/notificationforwarder/split3/forwarder.py +0 -0
  51. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/alertmanager_servicenow/formatter.py +0 -0
  52. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/bayern/formatter.py +0 -0
  53. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/datadup/formatter.py +0 -0
  54. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/datapost/formatter.py +0 -0
  55. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/discard/formatter.py +0 -0
  56. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split1/formatter.py +0 -0
  57. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py +0 -0
  58. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py +0 -0
  59. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py +0 -0
  60. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split4/formatter.py +0 -0
  61. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/split4/forwarder.py +0 -0
  62. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/ticketsystem/forwarder.py +0 -0
  63. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/ticketsystem/reporter.py +0 -0
  64. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/pythonpath/local/lib/python/notificationforwarder/vong/formatter.py +0 -0
  65. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_builtin_plugins_focus.py +0 -0
  66. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_classes.py +0 -0
  67. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_delivery_resilience.py +0 -0
  68. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_discard.py +0 -0
  69. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_logger.py +0 -0
  70. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_oauth2webhook.py +0 -0
  71. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_package.py +0 -0
  72. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_paths.py +0 -0
  73. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_reporter.py +0 -0
  74. {omdnotificationforwarder-4.2 → omdnotificationforwarder-4.4}/tests/test_runtime_foundation.py +0 -0
  75. {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.2
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: pynostr; extra == 'nostr'
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 coshsh.util import setup_logging
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.2')
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", "coshsh"]
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.2"
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
- "pynostr",
42
+ "nostr-sdk",
44
43
  ]
45
44
  test = [
46
45
  "requests",
@@ -11,7 +11,7 @@ except ImportError:
11
11
  import json
12
12
 
13
13
  import logging
14
- from coshsh.util import setup_logging
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
- logging.critical("sending mail failed: {}".format(str(e)))
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(["host", host])
17
+ tags.append(["t", "host=" + host])
29
18
  if service:
30
- tags.append(["service", service])
19
+ tags.append(["t", "service=" + service])
31
20
  if state:
32
- tags.append(["state", state])
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
@@ -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.cursor.execute(sql_create)
29
- self.connection.commit()
30
+ with self._lock:
31
+ self.cursor.execute(sql_create)
32
+ self.connection.commit()
30
33
 
31
34
  def count(self):
32
- self.cursor.execute("SELECT COUNT(*) FROM " + self.table_name)
33
- return self.cursor.fetchone()[0]
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.cursor.execute(sql_insert, (json.dumps(raw_event),))
38
- self.connection.commit()
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.cursor.execute(sql_delete, (outdated,))
44
- return self.cursor.rowcount
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.cursor.execute(sql_select, (limit,))
49
- return self.cursor.fetchall()
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
- self.cursor.execute("DELETE FROM " + self.table_name + " WHERE id = ?", (event_id,))
53
- self.connection.commit()
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.connection.commit()
67
+ with self._lock:
68
+ self.connection.commit()
60
69
 
61
70
  def close(self):
62
- if self.cursor:
63
- self.cursor.close()
64
- if self.connection:
65
- self.connection.commit()
66
- self.connection.close()
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, "hat_id", None))
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=request_parms)
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