caronc / apprise

Apprise - Push Notifications that work with just about every platform!
https://hub.docker.com/r/caronc/apprise
BSD 2-Clause "Simplified" License
11.98k stars 419 forks source link

Read Notification URLs from predefined YAML file(s) and/or URL(s) #55

Closed caronc closed 5 years ago

caronc commented 5 years ago

One would simply utilize the file://, http:// and/or https:// URLs as part of their apprise syntax; however their meaning would be interpreted as read to fetch the notifications to load.

The idea is you lock all of your tokens, passwords and usernames in a yaml file that has permissions that only you can access. It's better than passing this information on the command line.

Here would be an example:

# Open up apprise.yaml and read in the notification URLs
# From there construct all of the notifications to alert:
apprise -b "test body" -t "test title" \
    file:///path/to/yaml/config/file/apprise.yaml

http:// and https:// would work the same as file:// except they'd go to the URL you specify and hope to find a YAML file to read. Ideally you should be passing back the correct mime type for this such as one of the following:

text/yaml
text/x-yaml
application/yaml
application/x-yaml

The most minimal apprise syntax would be as follows:

# A version would be important so that I can anticipate future changes and/or
# ideas if this value is left out, then 'version: 1' would be implied. 
version: 1

# Now the meat and potatos: simply define your notification urls:
urls:
  - pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b
  - mailto://test:password@gmail.com

To take this feature a step further, the URL should be allowed to be constructed through options. This allows for an easier read (and doesn't require you to escape special characters such as slashes (/) or ampersands (&) since they otherwise conflict with the URL.

# our version definition
version: 1

urls:
  - pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b

    # adding a colon to the end of our URL now makes it something we can apply
    # additional configuration to (thanks to YAML).
  - mailto://test:password@gmail.com:

    # associate our mailto object with the tag profile_changes. Without this entry,
    # the profile gets added automatically to the 'default' tag. This will be important
    # when https://github.com/caronc/apprise/issues/34 gets implemented.
    # Right now it's just on standby (and ready to be supported). :)
    tags:
      - profile_changes

    # These over-ride anything otherwise defined in the URL string.
    args:
      # optional over-rides
      - user: caronc
      - password: /234j3jkada52:3&
      - smtp_server: example.com

If we allow all of this, then we might as well additionally support the override of the apprise asset object too. So if we take this configuration one step further:

# our version definition
version: 1

# allow one to over-ride the asset object too
asset:
  # optional asset over-ride
  app_id: MyApp
  app_desc: MyApp Description
  app_url: https://my/apps/website/
  theme: default
  default_extension: .png
  image_path_mask: /optional/path/to/{THEME}/apprise-{TYPE}-{XY}{EXTENSION}
  image_url_mask: https://my/path/to/{THEME}/apprise-{TYPE}-{XY}{EXTENSION}

# You've seen the below already; but lets just stick it in to show a complete
# configuration file:
urls:
  - pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b
  - mailto://test:password@gmail.com:
    # associate with tags. Without this entry, the
    # profile gets added automatically to the 'default'
    # tag.
    tags:
      - profile_changes

    args:
      # optional over-rides
      - smtp_server: example.com
caronc commented 5 years ago

I might map this to a different function (instead of add()) as I could see developers not wanting their users to use these new URLs directly. So maybe a simple load() function would be better and a --config (-c) for the CLI.

philborman commented 5 years ago

Useful. It would mean an app (eg LazyLibrarian) could allow the user to input/store their username, password, token, whatever, and the app would construct the url from the component parts. Would be good if the yaml could contain all the variant urls allowed for each plugin eg growl://hostname growl://hostname:portno growl://password@hostname growl://password@hostname:port
in a format that the app can complete depending what parts the user provided, so if we were only given hostname we use the first format, if we also have password we use 3rd, etc. Or maybe the url layouts should be in the details() function so the user doesn't get to edit it?

caronc commented 5 years ago

Exactly @philborman,

As an example, one could provide just the URL growl:// (and nothing more) in the YAML and then just identify all of the arguments below if you wanted ie:

urls:
  # must remember colon (:) at end of url if you plan to do things this way though
  - growl://:
    args:
      hostname: localhost
      password: clear_text_password

   # or a gmail example (thanks to email templating)
   - mailto://:
       user: myname
       password: mypassword
       hostname: gmail.com

I also thought, maybe a 'text-only file' would be handy to support too? It would be incredibly basic and ONLY support URLs:

# a simple text file version - URLs only
# use YAML for extra functionality
# pound symbols (#) would act as comments
growl://password@hostname:port
pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b

Or maybe the url layouts should be in the details() function so the user doesn't get to edit it?

Hmm... that's a tough one; if the URLs were part of the output provided by the details() function, it would only be for reference usage (I would think?). I mean, it might be helpful, but it only offers part of the puzzle. Some of the notification services (like Discord and Telegram) have entries like url://{tokena}/{tokenb}/ which would mean nothing to a brand new user unless they had first looked at the the help/wiki page to determine where these {tokens} come from. That said, I do realize that services like email:// and json:// are obvious when you see the URL examples, so for this there is value to your suggestion. This is a bit off topic of this particular issue though. I don't want to constrict templating or fixed urls because PushBullet for example has no limit to how many users/channels/devices you want to push your notification to. You just simply add another slash (/) to the end of your url and add the next thing that needs notifying.

Thoughts?

philborman commented 5 years ago

At the moment our apprise telegram notifier looks like this...
newstyle

and the old notifier looked like this... oldstyle

I think it's a lot easier for the user to handle the old style with clearly defined fields and let the app construct the url. Probably have a dropdown select box to choose the new notifier type from the service_names in apobj.details()['schemas'] and then add input boxes for the appropriate arguments for that schema.

caronc commented 5 years ago

You got a great thought process going on right now, i just want to steer it away from this thread since it's a bit off topic, but definitely worth talking about!

I brainstormed a few ideas and put them in the other ticket based on what you discussed here. But taking it in a slightly different route. I'm definitely interested in your input!

Edit: you should consider deleting that image if the telegram URL you identified IF it is actually valid. Great looking UI though!

caronc commented 5 years ago

I didn't realize how much of an undertaking configuration support & maintaining 100% code coverage would be. This latest push adds --config (-c) to the CLI in addition to --tag (-g) so you can label and control the reference to your entries.

The TEXT Based configuration file just looks like so:

# use pound/hashtag symbols to comment lines
# syntax is <tags>=<url> or jsut <url> on each line
desktop=gnome://
tv,kitchen=kodi://myuser:mypass@kitchen.hostame
tv,basement=kodi://myuser:mypass@basement.hostame

The file above is all your configuration file has to look like. If you save it in one of these locations then it will automatically be sourced for you when calling apprise from the command line:

# with respect to the above, we might do something like this:
notify -b "Notify only Kodi's in house" --tag=tv

That will cause only the tags (there are 2 of them in the example) to have the notifications sent

You can also get your configuration from another file, or even the internet:

# website
notify --config=https://myserver/my/apprise/config -b "notify everything"

# a local file and a website:
notify --config=https://myserver/my/apprise/config \
 --config=/a/path/on/the/local/pc -b "notify everything"

# everything that can be loaded will automatically be notified unless
# you specify tags which will then cause only those that match
# to be triggered:
notify --config=https://myserver/my/apprise/config \
 --config=/a/path/on/the/local/pc -b "notify everything" \
 --tag=my-admin-team

Tagging is a whole level of it's own because you might want to OR tags together or even AND them. Apprise supports this:

# assuming you got your configuration in place:
notify -b "has TagA" --tag=TagA
notify -b "has TagA OR TagB" --tag=TagA --tag=TagB

# For each item you group with the same --tag value is AND'ed
notify -b "has TagA AND TagB" --tag="TagA, TagB"
notify -b "has (TagA AND TagB) OR TagC" --tag="TagA, TagB" --tag=TagC

For developers, there is a new object called AppriseConfig() which works very similar to the AppriseAsset() object. It is merely an optional object you can add to your Apprise experience.

Up until now, you would add URLs to Apprise like so:

from apprise import Apprise

a = Apprise()
a.add('mailto://user:pass@hotmail.com', tag='email')
a.add('gnome://', tag='desktop')

# Now the only change is you can also add() the AppriseConfig object
ac = AppriseConfig()
ac.add('/local/path/on/your/server/config')
# the proper file url format is preferred and works too
ac.add('file://~.apprise')
# urls too, http an https
ac.add('http://localhost/my/apprise/config/url/path')

# This object can be simply added into our apprise object as though
# it were a notification service:
a.add(ac)

# Send off our all of our notifications
a.notify()

# filter our notifications by what we tagged:
a.notify(tag="tv")
caronc commented 5 years ago

YAML Support added now which is slightly different then how it was identified at the start of this thread (so ignore that and role with this instead). Here is the configuration in it's absolute simplest form:

#
# Minimal Configuration Example
#

# Define your URLs
urls:
  # Either on-line each entry like this:
  - json://localhost
  - xml://localhost

  # Or add a colon to the end of the URL where you can optionally provide
  # over-ride entries.  One of the most likely entry to be used here
  # is the tag entry.  This gets extended to the global tag (if defined)
  # above
  - windows://:
   # 'tag' is a special keyword that allows you to associate tags with your
   # services:
    - tag: desktop

To expand on tags, you can also identify a global entry that will be applied to ALL of the subsequent URL entries defined in the YAML file. Example

#
# Global Tag Configuration Example
#

# Define our version
version: 1

# Our global tags to associate with all of the URLs we define
tag: admin, devops

# Define your URLs (Mandatory!)
urls:
  - xml://localhost
  - json://localhost
  - kodi://user:pass@myserver

You can over-ride the AppriseAsset Object too if you know the objects you want to update using the special keyword asset.

#
# Asset Override Configuration Example
#

# Define our version
version: 1

# Define an Asset object if you wish (Optional)
asset:
  app_id: NuxRef
  app_desc: NuxRef Notification
  app_url: http://nuxref.com

# Define your URLs (Mandatory!)
urls:
  - mailto://bill:pass@protomail.com

YAML configuration gets more powerful when you want to utilize a URL more then once. Here is a more complicated example:

# if no version is specified then version 1 is presumed. Thus this is a
# completely optional field. It's a good idea to just add this line because it
# will help with future ambiguity (if it ever occurs).
version: 1

# Define an Asset object if you wish (Optional)
asset:
  app_id: AppriseTest
  app_desc: Apprise Test Notifications
  app_url: http://nuxref.com

# Optionally define some global tags to associate with ALL of your
# urls below.
tag: admin, devops

# Define your URLs (Mandatory!)
urls:
  # One-liner (no colon at the end); just the url as you'd expect it:
  - json://localhost

  # A colon grants you customization; the below associates a tag
  - xml://localhost:
    - tag: customer

 # Replication Example # 
  # The more elements you specify under a URL the more times the URL will
  # get replicated and used. Hence this entry actually could be considered
  # 2 URLs being called with just the destination email address changed:
  - mailto://george:password@gmail.com:
     - to: jason@hotmail.com
     - to: fred@live.com

  # Again... to re-iterate, the above mailto:// would actually fire two (2)
  # separate emails each with a different destination address specified.
  # Be careful when defining your arguments and differentiating between
  # when to use the dash (-) and when not to.  Each time you do, you will
  # cause another instance to be created.

  # Defining more then 1 element to a muti-set is easy, it looks like this:
  - mailto://jackson:abc123@hotmail.com:
     - to: jeff@gmail.com
       tag: jeff, customer

     - to: chris@yahoo.com
       tag: chris, customer

Nothing changes from a development perspective; the example in the earlier comment is the same for this too.

Determining which parser will be used:

Files

.yml and .yaml files are assumed to be YAML where as anything else is assumed to follow the TEXT structure.

HTTP

for HTTP requests, the Content-Type (Mime Type) is very important.
Support YAML formats:

Support TEXT formats:

Force Format

You can always force the format and over-ride anything detected by adding ?format=text or ?format=yaml to your configuration URL.

# force a file that would have otherwise been interpreted as a text file
# to be considered a YAML one:
notify --config=https://myserver/my/apprise/config?format=yaml -b "notify everything"

This applies to developers to whenever they load the AppriseConfig object:

from apprise import Apprise

# create our object
a = Apprise()

# Our Config object while explicitly setting the format to yaml
# you can pass this in as an argument to the class to save
# ourselves from calling config.add().
config = AppriseConfig('https://myserver/yaml/?format=yaml')

# add our config object into apprise
a.add(config)

# Send our notification to all of the sites loaded from the specified
# configuration website
a.notify("hello world!")
caronc commented 5 years ago

Pull request created #79

caronc commented 5 years ago

Merged into master branch; closing issue.