Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apprise Integration #2796

Merged
merged 10 commits into from
Feb 16, 2024
Merged

Apprise Integration #2796

merged 10 commits into from
Feb 16, 2024

Conversation

caronc
Copy link
Contributor

@caronc caronc commented Feb 3, 2024

Just a light stab at Apprise integrations right into SABnzbd

Multiple URLs supported by simply separating each with a space and/or comma.

Updated Screenshot
image

Rules:

  • Default Apprise URLs (comma/space separated if more then one) applies to all ✔️ boxes below.
  • Enabled categories can optionally have URLs provided that will over-ride the default Apprise URL configuration. Multiple URLs can also optionally be specified here if the intent is to target more then one location.

Consider the screenshot above:

  • A ntfy and email is sent for Job Finished, Job Failed and/or Other Message categories.
  • In the event of a Disk Full error, ONLY a Discord notice is sent instead.

@caronc
Copy link
Contributor Author

caronc commented Feb 3, 2024

@Safihre : Would you have a sample [black] configuration i would need to run to pass these config files? when i download black and run it, it formats way more then i changed/added. The config/instructions you require of black may be right in front of me, i'm just overlooking it 🙂

@thezoggy
Copy link
Contributor

thezoggy commented Feb 3, 2024

@Safihre : Would you have a sample [black] configuration i would need to run to pass these config files? when i download black and run it, it formats way more then i changed/added. The config/instructions you require of black may be right in front of me, i'm just overlooking it 🙂

you can get it from:
https://github.com/sabnzbd/sabnzbd/blob/develop/.github/workflows/integration_testing.yml#L6

@thezoggy
Copy link
Contributor

thezoggy commented Feb 3, 2024

looks like this also requires additional dependencies,

ModuleNotFoundError: No module named 'yaml'

PyYAML ?

@caronc
Copy link
Contributor Author

caronc commented Feb 3, 2024

@thezoggy : I got to step out for about 3 hrs or so, but hopefully these test cases will pass. I'll have a look if not when i get back. Thanks for your help!

I'd say don't merge this yet anyway, i cna update the AppriseAssets object to use the SABNzbd icons and other customizations.

@thezoggy
Copy link
Contributor

thezoggy commented Feb 3, 2024

looks like PyYAML does not do universal binaries for macos

PyInstaller.utils.osx.IncompatibleBinaryArchError: /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/yaml/_yaml.cpython-312-darwin.so is not a fat binary!

which apparently they decided not to do:
yaml/pyyaml#629

@Safihre
Copy link
Member

Safihre commented Feb 4, 2024

Does it really need yaml? Where is it used?

@caronc
Copy link
Contributor Author

caronc commented Feb 4, 2024

Yes, if you use the apprise:// plugin, it can fetch from a centralized Apprise API server.

I ended up having to do other things last night. I'll take another stab at it today.

I'll just move the yaml Python code over as per link Zoggy shared. The other thing i wanted to do was adapt the Assets to use your icons. I presume that might be what you want since it was the case for the nzb-notify script. It won't be much effort

Edit: I'm now seeing the problem; requirements.txt through dependencies pull in pyYAML whether i include it here or not. I don't have plans on removing the dependency upstream in Apprise. Not sure the best course of action here.

This may be usefully for testing with requirements.txt:

pyYAML --no-binary pyYAML

@caronc
Copy link
Contributor Author

caronc commented Feb 4, 2024

Had to re-add PyYAML into git actions pipe runner with the --no-binary as it simply does not build the MacOS version without it.
image

requirements.txt Outdated
PyYAML; sys_platform != 'darwin'

# Apprise Requirements
requests
Copy link
Member

Choose a reason for hiding this comment

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

Please specify the exact versions. It's important for the packaging reproducibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

requirements.txt Outdated
@@ -54,7 +55,17 @@ notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
# Uncomment line below or manually install after installing requirements.
# pygobject>=3.10.2; sys_platform != 'win32' and sys_platform != 'darwin'

# PyYAML does not have binary packages for Mac Users
PyYAML --no-binary=PyYAML; sys_platform == 'darwin'
PyYAML; sys_platform != 'darwin'
Copy link
Member

Choose a reason for hiding this comment

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

Why not have non-binary for all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's more efficient to use the binary when it's available. It's unfortunate that macos environment is impacted into a situation where they can't use it.

sabnzbd/cfg.py Outdated
apprise_prio_warning = OptionBool("apprise", "apprise_prio_warning", False)
apprise_prio_error = OptionBool("apprise", "apprise_prio_error", False)
apprise_prio_queue_done = OptionBool("apprise", "apprise_prio_queue_done", False)
apprise_prio_other = OptionBool("apprise", "apprise_prio_other", True)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we also have priorities? Since we have direct integration of Apprise now, then we could specify it, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I took this to a slightly different level so i could accommodate the power of apprise. If i only focused on PushOver Priorities, you'd only be addressing 1 of the 100+ services Apprise can adapt to. See general description in PR for updated screenshot.

app_desc="SABnzbd Notification",
app_url="http://sabnzbd.org/",
image_path_mask=os.path.join(os.path.dirname(__file__), "apprise", "apprise-{TYPE}.png"),
image_url_mask="https://raw.githubusercontent.com/sabnzbd/sabnzbd/develop/sabnzbd/apprise/apprise-{TYPE}.png",
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure we can, let me know the URL i should use. Even using the one you just shared now:

  • https://github.com/sabnzbd/sabnzbd.github.io/tree/master/images/icons becomes https://raw.githubusercontent.com/sabnzbd/sabnzbd.github.io/master/images/icons/ for direct reference.

Perhaps you man the url:

  • https://github.com/sabnzbd/sabnzbd.github.io/tree/master/images/icons (as it is now) should change to: https://sabnzbd.org/images/icons/ ?

So ... ?

    # Prepare our Asset Object
    asset = apprise.AppriseAsset(
        app_id="SABnzbd",
        app_desc="SABnzbd Notification",
        app_url="http://sabnzbd.org/",
        image_path_mask=os.path.join(os.path.dirname(__file__), "apprise", "apprise-{TYPE}.png"),
        image_url_mask="https://sabnzbd.org/images/icons/apprise/apprise-{TYPE}.png",
        image_url_logo="https://sabnzbd.org/images/icons/"
        "apple-touch-icon-120x120-precomposed.png/apple-touch-icon-180x180-precomposed.png",
    )

Copy link
Member

Choose a reason for hiding this comment

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

Yes that last one is what I mean. If you open a PR to add them to https://github.com/sabnzbd/sabnzbd.github.io/tree/master/images/icons then it will automatically be available at sabnzbd.org.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So just to follow up with this, we still need the images in this repository (somewhere), because notifications differ from where they get their data from. For Discord, Slack, etc, no problem,; they'll get their image from the web. But other services actually need their image uploaded as part of the payload for it's assembly. So the Apprise plugins are smart enough to determine if they need to go to the cloud for the error/success/info/warning message or to look on the local disk. Otherwise, it looks like we're good to go I see you merged abnzbd.github.io/309

@caronc
Copy link
Contributor Author

caronc commented Feb 6, 2024

I seemed to have broke things with the latest change, i'll have to work it out tomorrow if i have time (otherwise on the weekend). I created a new function called get_target() in the notify.py file and for some reason it can't retrieve the new dynamic fields i generated. Once get_target() works, the documented info in the new up-to-date screenshot should work great.

asset = apprise.AppriseAsset(
app_id="SABnzbd",
app_desc="SABnzbd Notification",
app_url="http://sabnzbd.org/",
Copy link
Member

Choose a reason for hiding this comment

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

https?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch! Done!

</label>
<input type="checkbox" name="${section_label}_target_${type}_enable" id="${section_label}_target_${type}_enable" value="1" <!--#if int($getVar($section_label + '_target_' + $type + '_enable')) > 0 then 'checked="checked"' else ""#--> />

<input type="text" name="${section_label}_target_${type}" id="${section_label}_target_${type}" value="$getVar($section_label + '_target_' + $type)" />
Copy link
Member

Choose a reason for hiding this comment

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

Let's add a placeholder="$T('default')" to the overrides input.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done 🙂

@Safihre
Copy link
Member

Safihre commented Feb 6, 2024

The build broke because you used str | bool | None: which isn't supported on Python 3.8.
You need to use Union[str, bool, None].

@Safihre
Copy link
Member

Safihre commented Feb 6, 2024

@thezoggy sidenote: requests is introduced with this PR so I give up my resistance 😁
Where do you see the main benefit of replacing urllib with requests?

@thezoggy
Copy link
Contributor

thezoggy commented Feb 6, 2024

@thezoggy sidenote: requests is introduced with this PR so I give up my resistance 😁 Where do you see the main benefit of replacing urllib with requests?

we already uses requests in our tests you know. :)

using requests imho is mainly just to use code that just handles all the urllib quirks and made it cleaner to use. which back in the day when we were doing python2 or early python3 it would had prob made things a lot cleaner (which is mainly when I was bringing it up). now with more modern python its a little less important as its more of a glorified wrapper for urllib3, but then there are extensions that solve various scenarios that while prob aren't great use to us but of course are great for things like notification wrappers that need to do various authentication schemes/async threading and such. I would think there prob is benefits to using it to cleanup the getipaddress/urlgrabber/rss stuff...

@caronc
Copy link
Contributor Author

caronc commented Feb 7, 2024

I'll address your concerns tomorrow, unfortunately I couldn't action it tonight.

@Safihre
Copy link
Member

Safihre commented Feb 7, 2024

I'll address your concerns tomorrow, unfortunately I couldn't action it tonight.

No rush, version 4.3.0 is still a while 🤗

@caronc
Copy link
Contributor Author

caronc commented Feb 7, 2024

I think i'm good for another round of reviews. Consider that Apprise supports file attachments too. So if you can think of edge cases (such as a failure) or whatnot where you way wish to also include all of the log files (or a core dump, or whatever you can think of) to be sent with the notification as well, that is very easy to adapt. (just food for thought)

@caronc caronc requested a review from Safihre February 7, 2024 21:03
@thezoggy
Copy link
Contributor

thezoggy commented Feb 7, 2024

can squash most of the commits for this to cleanup history a bit

also from earlier review, i noted that that notifier part should be wrapped in try to catch if something goes wrong just like we do with the rest of the notifiers

@caronc
Copy link
Contributor Author

caronc commented Feb 7, 2024

@thezoggy : I'm not sure if try/catch is necessary. Under the hood of notify and add is the catch all.

As for squash, sure, I don't mind doing it, but you can use the Squash & Merge button build right into GitHub too if you want.
Screenshot_20240207-182452.png

Either way, i have no problems actioning both of your requests! 🙂

Edit: Squashed commits into 1
Edit 2: Updated image/graphic on PR (at top) to reflect changes and current implementation

@Safihre
Copy link
Member

Safihre commented Feb 8, 2024

Indeed I always use squash and merge!
The try catch does seem like a good safety guard, as a crash in the notifier could crash a whole job post processing. Just to be safe!

@caronc
Copy link
Contributor Author

caronc commented Feb 8, 2024

a crash in the notifier could crash a whole job post processing.

No problem; try/catch added


if not test:
# Get a list of tags that are set to use the common list
target = get_target(notification_type, "apprise")
Copy link
Member

Choose a reason for hiding this comment

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

Since you use OptionStr, it's guaranteed to be a string.
I would suggest to replace these 3 lines by 1 line using the walrus operator:

        if target := get_target(notification_type, "apprise"):

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Never used this before; code shifted to conform to your suggestion.


try:
# The below notifies anything added to our list
return (
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 use 1 liner here, let's just split it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

def get_target(notification_type: str, section: str) -> Union[str, bool, None]:
"""Check target of `notification_type` in `section` if enabled is set"""
try:
return (
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 use one-liner here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

sabnzbd/api.py Outdated
@@ -657,7 +657,7 @@ def _api_warnings(name, kwargs):
LOG_JSON_RE = re.compile(rb"'(apikey|api|username|password)': '(.*?)'", re.I)
LOG_INI_HIDE_RE = re.compile(
rb"(apikey|api|user|username|password|email_pwd|email_account|email_to|email_from|pushover_token|pushover_userkey"
rb"|pushbullet_apikey|prowl_apikey|growl_password|growl_server|IPv[4|6] address|Public address IPv[4|6]-only|Local IPv6 address)\s?=.*",
rb"|apprise_urls|pushbullet_apikey|prowl_apikey|growl_password|growl_server|IPv[4|6] address|Public address IPv[4|6]-only|Local IPv6 address)\s?=.*",
Copy link
Member

Choose a reason for hiding this comment

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

Probably we need to add the other specific-URL's?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is done correctly now (i cheated a bit)

app_id="SABnzbd",
app_desc="SABnzbd Notification",
app_url="https://sabnzbd.org/",
image_path_mask=os.path.join(os.path.dirname(__file__), "apprise", "apprise-{TYPE}.png"),
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should have any images in the /sabnzbd directory, it's only for code.
We should put it in /icons/apprise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you want me to hardcode /icons/apprise or find it relative to the directory. This is the edge case with Apprise, there are like 70 services that just use the icons found on the web, but then there are like 30 that use the local icons stored on the server (True self-hosted vs pulling from the cloud).

This was why i originally stored the images here and then cross-referenced them here via both web and locally (it's now i do it with apprise, so it's just 1 directory to manage - link.

At the end of the day, to truly get the best use of Apprise, you need some images stored locally. If you want, i can just not set a mask over-ride at all, and you'll use the ones that ship with Apprise instead of some custom SABnzbd ones i rigged up quickly?

Copy link
Member

Choose a reason for hiding this comment

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

I can fix this one up when all the code is fine, we have some constants to handle this :)

Copy link
Member

@Safihre Safihre left a comment

Choose a reason for hiding this comment

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

Almost!

Comment on lines 339 to 341
if target := get_target(notification_type, "apprise"):
if isinstance(target, str):
if target:
Copy link
Member

Choose a reason for hiding this comment

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

This can be just 1 line:

Suggested change
if target := get_target(notification_type, "apprise"):
if isinstance(target, str):
if target:
if target := get_target(notification_type, "apprise"):

Copy link
Contributor Author

@caronc caronc Feb 9, 2024

Choose a reason for hiding this comment

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

The only catch is that target can return None, or False. Also if it's an empty string, we want to process it. That means an over-ride was specified, but no URLs were provided (so to use the default). If i do if target :=, how is it going to handle an empty string case (perhaps it's already skipped over at this point, so default logic won't be applied? - in short, i want to get inside that if-block if we get an empty string returned.

I'll see if i can refactor get_target a bet better so it can conform to your minimized example.

Edit: Done

requirements.txt Outdated
@@ -54,7 +55,17 @@ notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
# Uncomment line below or manually install after installing requirements.
# pygobject>=3.10.2; sys_platform != 'win32' and sys_platform != 'darwin'

# PyYAML does not have binary packages for Mac Users
PyYAML==6.0.1 --no-binary=PyYAML; sys_platform == 'darwin'
PyYAML==6.0.1; sys_platform != 'darwin'
Copy link
Member

Choose a reason for hiding this comment

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

We can remove this special darwin stuff here as this is only for run-from-source users, for who pip will get the correct version. The --no-binary in build_release will fix it for the release.

requirements.txt Show resolved Hide resolved

if not test:
# Get a list of tags that are set to use the common list
if target := get_target(notification_type, "apprise"):
Copy link
Member

Choose a reason for hiding this comment

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

Ah sorry I missed that about the empty string!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no worries; it makes a bit more sense now.. True - use Default, otherwise string means there are URLs to load.

"explain-apprise_urls": TT("Use a comma and/or space to identify more then one URL"), #: Apprise settings
"explain-apprise_extra_urls": TT(
"Optionally override the default URL(s) for specific cases. To disable a notification for a specific category:"
" simply enable it below, but do not provide it a URL to trigger off of. Alternatively, use comma and/or "
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this. What can users do here? And how is it different from unchecking a notification-type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's the same thing. You can drop this if you like. Was just trying to make it clear that anything specified in the individual entries overrides the default (does not stack with it)

@Safihre
Copy link
Member

Safihre commented Feb 13, 2024

Push a few changes, what do you think? :)
Just this last question.
And we definitely need to update: https://github.com/sabnzbd/sabnzbd.github.io/blob/master/wiki/configuration/4.3/notifications.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pyYAML is needed by Apprise. Zoggys advice to define these 2 entries was a really good one. I don't think we should remove it from the requirements.txt.

Also, the way it was gave every system that had the ability to leverage the binary package to do so yet still be compatible for the systems that couldn't

Copy link
Member

Choose a reason for hiding this comment

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

@caronc
Copy link
Contributor Author

caronc commented Feb 13, 2024

Push a few changes, what do you think? :) Just this last question.

Just the one comment on pyYAML. Not sure about the pyobject changes as it's not really related to this PR. I'll just trust you there. I can't see the GUI/HTML tweaks as I'm on my mobile phone right now, but i have no doubt you nailed them. 🙂 I can try to peek later when I'm at my PC.🚀

And we definitely need to update: https://github.com/sabnzbd/sabnzbd.github.io/blob/master/wiki/configuration/4.3/notifications.html

I'll try to have a look.

@Safihre
Copy link
Member

Safihre commented Feb 13, 2024

@caronc Could you maybe double check if everything still works with my changes? I don't have any of the API-based services myself, only windows:// :)

@caronc
Copy link
Contributor Author

caronc commented Feb 13, 2024

@caronc Could you maybe double check if everything still works with my changes? I don't have any of the API-based services myself, only windows:// :)

Absolutely! I'm at my day job, but I'll do my best to test it when i can and get back you!🚀

@Safihre
Copy link
Member

Safihre commented Feb 15, 2024

@caronc Did you manage to check if it is still working as it should? :)

@caronc
Copy link
Contributor Author

caronc commented Feb 15, 2024

It works fine for me

@Safihre Safihre merged commit ad43a18 into sabnzbd:develop Feb 16, 2024
12 checks passed
@Safihre
Copy link
Member

Safihre commented Feb 16, 2024

@caronc Thanks!
You called this PR the Basic integration. What do you think would be the advanced integration?
I also think I will add a warning that users can switch to this integration if we detect that they use sabnzb-notify.py.
We also still need to update the documentation. I created this issue for it: sabnzbd/sabnzbd.github.io#311
Otherwise I'll just forget.

@caronc
Copy link
Contributor Author

caronc commented Feb 16, 2024

I think it started off as being simple, but with notification overrides on each category and references to your own images/icons, you got the full deal. So I'd say it's "Apprise Integration" at this point now 🙂

@caronc caronc deleted the apprise-notifications branch February 17, 2024 16:32
@caronc caronc changed the title Basic Apprise Integration Apprise Integration Feb 21, 2024
@Safihre
Copy link
Member

Safihre commented Mar 11, 2024

@caronc Is there a way we can 'unittest' this?
Make it call some endpoint? Just to make sure that the integration is working, even if Apprise is updated.

@caronc
Copy link
Contributor Author

caronc commented Mar 12, 2024

I don't see why we can't. There is almost 100% test coverage with Apprise already

Are you just looking to test the function notifier.send_apprise() function?

@Safihre
Copy link
Member

Safihre commented Mar 12, 2024

Yes, test if there's no crash when actually calling it. Due to missing imports or something like that.
Or you don't have any imports in try/except?

@caronc
Copy link
Contributor Author

caronc commented Apr 1, 2024

Yes, test if there's no crash when actually calling it. Due to missing imports or something like that. Or you don't have any imports in try/except?

Yes, there is a global try/catch inside of the apprise.notify() that catches everything, but i added some test cases anyway to help you still achive what you're asking. 🚀

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants