-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: basic stuff implemented - topic and attachments
- Loading branch information
Showing
8 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.cache | ||
.coverage | ||
/.pytest_cache | ||
*.pyc | ||
.mypy_cache/ | ||
poetry.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# django-ntfy | ||
|
||
Django's email backend which is used to send messages to ntfy.sh (or self hostated instace) instead of actually sending an email. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import typing | ||
|
||
import requests | ||
from django import dispatch | ||
from django.conf import settings | ||
from django.core.mail import EmailMessage | ||
from django.core.mail.backends.base import BaseEmailBackend | ||
|
||
topic_signal = dispatch.Signal() | ||
# TODO ntfy_icon_signal = dispatch.Signal() | ||
# TODO ntfy_actions_signal = dispatch.Signal() | ||
# TODO ntfy_severity_signal = dispatch.Signal() | ||
# TODO ntfy_tags_signal = dispatch.Signal() | ||
# TODO ntfy_priority_signal = dispatch.Signal() | ||
# TODO ntfy_click_signal = dispatch.Signal() | ||
# TODO ntfy_icon_signal = dispatch.Signal() | ||
|
||
|
||
def get_from_signal(signal: dispatch.Signal, message: EmailMessage, default): | ||
responses = signal.send(message) | ||
return responses[0][1] if responses else default | ||
|
||
|
||
class NtfyBackend(BaseEmailBackend): | ||
def send_ntfy_message( | ||
self, | ||
title: str, | ||
message: str, | ||
topic: str, | ||
) -> requests.Response: | ||
# TODO ntfy authentication | ||
url = settings.NTFY_BASE_URL | ||
|
||
resp = requests.post( | ||
url, | ||
json={ | ||
"topic": topic, | ||
"title": title, | ||
"message": message, | ||
}, | ||
) | ||
return resp | ||
|
||
def send_ntfy_file( | ||
self, | ||
title: str, | ||
data, | ||
filename: str, | ||
topic: str, | ||
): | ||
# TODO ntfy authentication | ||
url = f"{settings.NTFY_BASE_URL}/{topic}" | ||
|
||
requests.put( | ||
url, | ||
data=data, | ||
headers={"Filename": filename}, | ||
) | ||
|
||
def send_messages(self, email_messages: typing.List[EmailMessage]): | ||
count = 0 | ||
for message in email_messages: | ||
topic = get_from_signal(topic_signal, message, settings.NTFY_DEFAULT_TOPIC) | ||
|
||
# Send message | ||
resp = self.send_ntfy_message(message.subject, message.body, topic) | ||
|
||
# Send attachments | ||
if getattr(settings, 'NTFY_SEND_ATTACHMENTS', False): | ||
for filename, content, mimetype in message.attachments: | ||
self.send_ntfy_file(message.subject, content, filename, topic) | ||
|
||
count += 1 if resp.status_code / 100 == 2 else 0 | ||
|
||
return count |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" | ||
|
||
[tool.black] | ||
line-length = 100 | ||
skip-string-normalization = true | ||
target-version = ["py38"] | ||
|
||
[tool.ruff] | ||
line-length = 100 | ||
select = [ | ||
# Pyflakes | ||
"F", | ||
# pycodestyle | ||
"E", | ||
# isort | ||
"I", | ||
] | ||
src = ["django_ntfy", "tests"] | ||
|
||
[tool.ruff.isort] | ||
known-first-party = ["django_ntfy"] | ||
|
||
[tool.pytest.ini_options] | ||
testpaths = "tests/" | ||
DJANGO_SETTINGS_MODULE = "tests.settings" | ||
|
||
[tool.poetry] | ||
name = "django-ntfy" | ||
version = "0.1.0" | ||
description = "Django's email backend which is used to send messages to ntfy.sh" | ||
authors = ["Stepan Henek <[email protected]>"] | ||
readme = "README.md" | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.8" | ||
|
||
Django = ">=4.2.0" | ||
requests = "^2.31" | ||
|
||
[tool.poetry.dev-dependencies] | ||
black = "23.11.0" | ||
build = "~1.0.3" | ||
mypy = "^1.7" | ||
pre-commit = "~3.5.0" | ||
pytest = "~7.4.3" | ||
pytest-cov = "~4.1.0" | ||
pytest-django = "~4.7.0" | ||
responses = ">=0.24.0" | ||
ruff = "~0.1.5" | ||
types-requests = "*" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def use_ntfy_email_backend(settings): | ||
# we need to explicitly override it here | ||
settings.EMAIL_BACKEND = 'django_ntfy.NtfyBackend' | ||
|
||
|
||
@pytest.fixture | ||
def topic_signal(): | ||
topic = "altered-topic" | ||
|
||
def handler(*args, **kwargs): | ||
return topic | ||
|
||
from django_ntfy import topic_signal | ||
|
||
topic_signal.connect(handler, dispatch_uid="test_topic") | ||
|
||
yield topic | ||
|
||
topic_signal.disconnect(dispatch_uid="test_topic") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# NTFY | ||
NTFY_BASE_URL = "https://example.com/" | ||
NTFY_DEFAULT_TOPIC = "django-ntfy" | ||
|
||
NTFY_SEND_ATTACHMENTS = False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import responses | ||
from django.core import mail | ||
from responses import matchers | ||
|
||
|
||
def test_basic(settings, use_ntfy_email_backend): | ||
with responses.RequestsMock() as rsps: | ||
rsps.post( | ||
settings.NTFY_BASE_URL, | ||
status=200, | ||
match=[ | ||
matchers.json_params_matcher( | ||
{ | ||
"message": "Body", | ||
"title": "Sub", | ||
"topic": "django-ntfy", # Default topic from settings | ||
} | ||
), | ||
], | ||
) | ||
assert mail.send_mail("Sub", "Body", "[email protected]", ["[email protected]"]) == 1 | ||
|
||
|
||
def test_custom_topic(settings, use_ntfy_email_backend, topic_signal): | ||
with responses.RequestsMock() as rsps: | ||
rsps.post( | ||
settings.NTFY_BASE_URL, | ||
status=200, | ||
match=[ | ||
matchers.json_params_matcher( | ||
{ | ||
"message": "Body", | ||
"title": "Sub", | ||
"topic": "altered-topic", # Default topic from settings | ||
} | ||
), | ||
], | ||
) | ||
assert mail.send_mail("Sub", "Body", "[email protected]", ["[email protected]"]) == 1 | ||
|
||
|
||
def test_attachments(settings, use_ntfy_email_backend): | ||
settings.NTFY_SEND_ATTACHMENTS = True | ||
|
||
with mail.get_connection() as connection, responses.RequestsMock() as rsps: | ||
rsps.post( | ||
settings.NTFY_BASE_URL, | ||
status=200, | ||
match=[ | ||
matchers.json_params_matcher( | ||
{ | ||
"message": "Body1", | ||
"title": "Subject1", | ||
"topic": "django-ntfy", # Default topic from settings | ||
} | ||
), | ||
], | ||
) | ||
|
||
rsps.put( | ||
f"{settings.NTFY_BASE_URL}/django-ntfy", | ||
status=200, | ||
match=[ | ||
matchers.header_matcher( | ||
{"Filename": "File1.txt"}, | ||
) | ||
], | ||
) | ||
|
||
rsps.put( | ||
f"{settings.NTFY_BASE_URL}/django-ntfy", | ||
status=200, | ||
match=[ | ||
matchers.header_matcher( | ||
{"Filename": "File2.txt"}, | ||
) | ||
], | ||
) | ||
|
||
message = mail.EmailMessage( | ||
subject="Subject1", | ||
body="Body1", | ||
from_email="[email protected]", | ||
to=["[email protected]"], | ||
connection=connection, | ||
) | ||
message.attach("File1.txt", "Content1", "text/plain") | ||
message.attach("File2.txt", "Content1", "text/plain") | ||
message.send() |