Skip to content

Commit

Permalink
feature: basic stuff implemented - topic and attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
shenek committed Nov 14, 2023
1 parent a52e3b0 commit cafb8ff
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.cache
.coverage
/.pytest_cache
*.pyc
.mypy_cache/
poetry.lock
3 changes: 3 additions & 0 deletions README.md
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.
75 changes: 75 additions & 0 deletions django_ntfy/__init__.py
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
52 changes: 52 additions & 0 deletions pyproject.toml
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 added tests/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions tests/conftest.py
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")
5 changes: 5 additions & 0 deletions tests/settings.py
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
89 changes: 89 additions & 0 deletions tests/test_ntfy_backend.py
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()

0 comments on commit cafb8ff

Please sign in to comment.