Skip to content

Conversation

@yiyunliu
Copy link

@yiyunliu yiyunliu commented Dec 4, 2025

Checklist

  • All new and existing tests are passing
  • (If adding features:) I have added tests to cover my changes
  • (If docs changes needed:) I have updated the documentation accordingly.
  • I have added an entry to CHANGES.rst because this is a user-facing change or an important bugfix
  • I have written proper commit message(s)

What changes does this Pull Request introduce?

I wrote a ntfy.sh backend based on what's in the Stdout subscriber object. Everything has been tested to work on with my ntfy setup, except for the new_thread callback, which i'm not sure exactly how to trigger.

ntfy.sh is quite easy to set up and satisfies my need to run the notification service locally on my VPN. SMTP, on the other hand, is kind of tricky to setup compared to the straightforward API that ntfy provides.

If the patch is considered useful, I'd be happy to start working on the checklist items. (Might need some help on the testing side)

Why is this necessary?

N/A because it's a feature?

The SMTP notification backend looks quite complicated and is tricky to
setup so I wrote a ntfy backend based on what's in the Stdout
subscriber object. Everything has been tested to work on with my ntfy
setup, except for the new_thread callback, which i'm not sure exactly
how to trigger.

It is also currently missing documentations. I will add the relevant
sections to the documentation if the patch is considered useful.
self.notify("comment %(id)s activated" % thread)

def notify(self, str):
response = requests.post(urljoin(self.conf.get("url"),self.conf.get("topic")), data = str.encode(encoding='utf-8'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need requests for this, isn't just urllib sufficient?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll go through the urllib tutorial and remove the requests dependency. The ntfy backend only needs to make post requests so it shouldn't be difficult to make the switch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the dependency on requests and the backend now calls urlopen directly for connections to the nfty server. I wonder if the proper way to do this is to extend utils/http.py to also take an optional data argument, although urlopen from urllib seems more than enough for this type of one-off notification messages.

break


class Ntfy(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to have multiple notifiers, we should probably have a common base class.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I also want to add there's a lot of overlap between the Stdout and the Ntfy objects. The latter uses the exact same plaintext message that the former emits and the only difference is that the message in Ntfy goes to an http request instead of stdout. Could add another common class for Stdout and Nfty to avoid writing the same logic twice.

@jelmer
Copy link
Member

jelmer commented Dec 4, 2025

Curious about @ix5 's thoughts as well.

My main concern here is feature creep - if ntfy.sh is sufficiently popular then I think it makes sense to support. But we don't really want to support a dozen different notification mechanisms because it's hard to keep them from regressing and would give users a bad experience.

There is already the utility library http.curl around but sadly it
doesn't seem to support passing data for POST requests
@ix5
Copy link
Member

ix5 commented Dec 5, 2025

@jelmer thank you for notifying.

We already have a PR open for a very similar thing: Feature: add webhook notification capability #724 (which sadly has languished for a while).
Since a ntfy server acts as a receiver for GET/POST requests, that PR could be extended to provide a template for ntfy as a further webhook integration. It would also have the benefit of not cluttering the config options even further.

While I like the simplicity of this PR, I think ultimately #724 is the way to go. @jelmer should we have another go at encouraging the submitter to update the PR and get it ready for merging? @yiyunliu maybe you would also be up to the task since @ Guts seems to have stepped back a bit.


(Also #457 was aimed at a different functionality, just to keep on the radar about notification backends)

Copy link
Member

@ix5 ix5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case we decide to go along with this PR, this would be my suggested changes. @yiyunliu no need to act upon this yet before a decision has been made, just letting you know.

if smtp_backend or conf.getboolean("general", "reply-notifications"):
subscribers.append(SMTP(self))

if ntfy_backend:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to introduce a ntfy_backend variable, you can just move this block below elif backend == "ntfy"


from email.utils import parseaddr, formataddr
from configparser import ConfigParser, NoOptionError, NoSectionError, DuplicateSectionError
from urllib.parse import urlparse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import, leftover?


def notify(self, str):
topic_url = urljoin(self.conf.get("url"),self.conf.get("topic"))
try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some simple retry/backoff logic here, perhaps a bit more sophisticated than time.sleep(60) as we do with SMTP?

try:
with urlopen(topic_url,str.encode(encoding='utf-8')) as response:
pass
except URLError as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I think it matters much, but other types of errors (apart from URLError) are not caught and reported here.

def notify(self, str):
topic_url = urljoin(self.conf.get("url"),self.conf.get("topic"))
try:
with urlopen(topic_url,str.encode(encoding='utf-8')) as response:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave a space between topic_url and str.encode)

Suggested change
with urlopen(topic_url,str.encode(encoding='utf-8')) as response:
with urlopen(topic_url, str.encode(encoding='utf-8')) as response:

self.notify("comment %(id)s activated" % thread)

def notify(self, str):
topic_url = urljoin(self.conf.get("url"),self.conf.get("topic"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
topic_url = urljoin(self.conf.get("url"),self.conf.get("topic"))
topic_url = urljoin(self.conf.get("url"), self.conf.get("topic"))

self.notify("\n".join(response_strs))

def _edit_comment(self, comment):
self.notify(f"comment {comment["id"]} edited: {json.dumps(comment)}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we send raw JSON to ntfy channels? I suppose the audience for ntfy is rather technically inclined but could we format this a bit more nicely?

pass
except URLError as e:
if hasattr(e, 'reason'):
logger.exception(f"Failed to reach the ntfy server.\nError code: {e.code}" )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No newlines for log output here (and below) would be my preference.

@yiyunliu
Copy link
Author

yiyunliu commented Dec 5, 2025

@yiyunliu maybe you would also be up to the task since @ Guts seems to have stepped back a bit.

I'd be happy to take a look at the draft and try rebasing the PR to and resolving some of the remaining issues. I'm not really familiar with the spec of webhook, however, so I might have trouble coming up with good test cases other than checking if it works with ntfy's webhook support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants