Skip to content

Commit d329a5a

Browse files
committed
add option to use custom fields in post request
1 parent 4945d68 commit d329a5a

File tree

7 files changed

+203
-46
lines changed

7 files changed

+203
-46
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Notifications depend on [Apprise](https://github.com/caronc/apprise) or [Gotify]
8484
2. Add the Apprise URL or the path to your gotify instance with `/message` appended, i.e.: `http://gotify:8080/message`.
8585
3. For gofity, add the API key as a header: `{"Authentication": "Bearer <your token>"}`.
8686
4. Configure the remaining settings. **The event variables are case sensitive**.
87+
5. `Additional POST fields` allow you to add extra values that are sent along in the POST request to the notification service. This allows for changing the priority for gotify notifications or changing the look of apprise notifications. **Event variables also work in keys and values!**
8788

8889
### OpenID Connect
8990

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""add additional fields
2+
3+
Revision ID: 304eed96f8ed
4+
Revises: 0fa71b2e5d30
5+
Create Date: 2025-04-24 15:29:26.686894
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "304eed96f8ed"
17+
down_revision: Union[str, None] = "0fa71b2e5d30"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
with op.batch_alter_table("notification", schema=None) as batch_op:
25+
batch_op.add_column(
26+
sa.Column(
27+
"additional_fields", sa.JSON(), nullable=True, server_default="{}"
28+
)
29+
)
30+
31+
# ### end Alembic commands ###
32+
33+
34+
def downgrade() -> None:
35+
# ### commands auto generated by Alembic - please adjust! ###
36+
with op.batch_alter_table("notification", schema=None) as batch_op:
37+
batch_op.drop_column("additional_fields")
38+
39+
# ### end Alembic commands ###

app/internal/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import pydantic
99
from sqlmodel import JSON, Column, DateTime, Field, SQLModel, UniqueConstraint, func
1010

11+
from app.util import json_type
12+
1113

1214
class BaseModel(SQLModel):
1315
pass
@@ -201,13 +203,17 @@ class EventEnum(str, Enum):
201203
class NotificationServiceEnum(str, Enum):
202204
apprise = "apprise"
203205
gotify = "gotify"
206+
custom = "custom"
204207

205208

206209
class Notification(BaseModel, table=True):
207210
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
208211
name: str
209212
url: str
210213
headers: dict[str, str] = Field(default_factory=dict, sa_column=Column(JSON))
214+
additional_fields: dict[str, json_type.JSON] = Field(
215+
default_factory=dict, sa_column=Column(JSON)
216+
)
211217
event: EventEnum
212218
service: NotificationServiceEnum
213219
title_template: str
@@ -217,3 +223,7 @@ class Notification(BaseModel, table=True):
217223
@property
218224
def serialized_headers(self):
219225
return json.dumps(self.headers)
226+
227+
@property
228+
def serialized_additional_fields(self):
229+
return json.dumps(self.additional_fields)

app/internal/notifications.py

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import logging
23
from typing import Optional
34

@@ -11,50 +12,42 @@
1112
Notification,
1213
NotificationServiceEnum,
1314
)
15+
from app.util import json_type
1416
from app.util.db import open_session
1517

1618
logger = logging.getLogger(__name__)
1719

1820

1921
def replace_variables(
20-
title_template: str,
21-
body_template: str,
22+
template: str,
2223
username: Optional[str] = None,
2324
book_title: Optional[str] = None,
2425
book_authors: Optional[str] = None,
2526
book_narrators: Optional[str] = None,
2627
event_type: Optional[str] = None,
2728
other_replacements: dict[str, str] = {},
2829
):
29-
title = title_template
30-
body = body_template
31-
3230
if username:
33-
title = title.replace("{eventUser}", username)
34-
body = body.replace("{eventUser}", username)
31+
template = template.replace("{eventUser}", username)
3532
if book_title:
36-
title = title.replace("{bookTitle}", book_title)
37-
body = body.replace("{bookTitle}", book_title)
33+
template = template.replace("{bookTitle}", book_title)
3834
if book_authors:
39-
title = title.replace("{bookAuthors}", book_authors)
40-
body = body.replace("{bookAuthors}", book_authors)
35+
template = template.replace("{bookAuthors}", book_authors)
4136
if book_narrators:
42-
title = title.replace("{bookNarrators}", book_narrators)
43-
body = body.replace("{bookNarrators}", book_narrators)
37+
template = template.replace("{bookNarrators}", book_narrators)
4438
if event_type:
45-
title = title.replace("{eventType}", event_type)
46-
body = body.replace("{eventType}", event_type)
39+
template = template.replace("{eventType}", event_type)
4740

4841
for key, value in other_replacements.items():
49-
title = title.replace(f"{{{key}}}", value)
50-
body = body.replace(f"{{{key}}}", value)
42+
template = template.replace(f"{{{key}}}", value)
5143

52-
return title, body
44+
return template
5345

5446

5547
async def _send(
5648
title: str,
5749
body: str,
50+
additional_fields: dict[str, json_type.JSON],
5851
notification: Notification,
5952
client_session: ClientSession,
6053
):
@@ -63,13 +56,29 @@ async def _send(
6356
body_key = "message"
6457
case NotificationServiceEnum.apprise:
6558
body_key = "body"
59+
case NotificationServiceEnum.custom:
60+
body_key = ""
6661

67-
async with client_session.post(
68-
notification.url,
69-
json={
62+
if notification.service == NotificationServiceEnum.custom:
63+
json_body = {}
64+
else:
65+
json_body: dict[str, json_type.JSON] = {
7066
"title": title,
7167
body_key: body,
72-
},
68+
}
69+
70+
for key, value in additional_fields.items():
71+
if key in json_body.keys():
72+
logger.warning(
73+
f"Key '{key}' already exists in the JSON body but is passed as additional field. Overwriting with value: {value}"
74+
)
75+
json_body[key] = value
76+
77+
print(json_body)
78+
79+
async with client_session.post(
80+
notification.url,
81+
json=json_body,
7382
headers=notification.headers,
7483
) as response:
7584
response.raise_for_status()
@@ -95,8 +104,16 @@ async def send_notification(
95104
book_authors = ",".join(book.authors)
96105
book_narrators = ",".join(book.narrators)
97106

98-
title, body = replace_variables(
107+
title = replace_variables(
99108
notification.title_template,
109+
requester_username,
110+
book_title,
111+
book_authors,
112+
book_narrators,
113+
notification.event.value,
114+
other_replacements,
115+
)
116+
body = replace_variables(
100117
notification.body_template,
101118
requester_username,
102119
book_title,
@@ -105,13 +122,24 @@ async def send_notification(
105122
notification.event.value,
106123
other_replacements,
107124
)
125+
additional_fields: dict[str, json_type.JSON] = json.loads(
126+
replace_variables(
127+
json.dumps(notification.additional_fields),
128+
requester_username,
129+
book_title,
130+
book_authors,
131+
book_narrators,
132+
notification.event.value,
133+
other_replacements,
134+
)
135+
)
108136

109137
logger.info(
110138
f"Sending notification to {notification.url} with title: '{title}', event type: {notification.event.value}"
111139
)
112140

113141
async with ClientSession() as client_session:
114-
return await _send(title, body, notification, client_session)
142+
return await _send(title, body, additional_fields, notification, client_session)
115143

116144

117145
async def send_all_notifications(
@@ -144,23 +172,45 @@ async def send_manual_notification(
144172
):
145173
"""Send a notification for manual book requests"""
146174
try:
147-
title, body = replace_variables(
175+
book_authors = ",".join(book.authors)
176+
book_narrators = ",".join(book.narrators)
177+
178+
title = replace_variables(
148179
notification.title_template,
180+
requester_username,
181+
book.title,
182+
book_authors,
183+
book_narrators,
184+
notification.event.value,
185+
other_replacements,
186+
)
187+
body = replace_variables(
149188
notification.body_template,
150189
requester_username,
151190
book.title,
152-
",".join(book.authors),
153-
",".join(book.narrators),
191+
book_authors,
192+
book_narrators,
154193
notification.event.value,
155194
other_replacements,
156195
)
196+
additional_fields: dict[str, json_type.JSON] = json.loads(
197+
replace_variables(
198+
json.dumps(notification.additional_fields),
199+
requester_username,
200+
book.title,
201+
book_authors,
202+
book_narrators,
203+
notification.event.value,
204+
other_replacements,
205+
)
206+
)
157207

158208
logger.info(
159209
f"Sending manual notification to {notification.url} with title: '{title}', event type: {notification.event.value}"
160210
)
161211

162212
async with ClientSession() as client_session:
163-
await _send(title, body, notification, client_session)
213+
await _send(title, body, additional_fields, notification, client_session)
164214

165215
except Exception as e:
166216
logger.error("Failed to send notification", e)

0 commit comments

Comments
 (0)