mirror of https://github.com/caronc/apprise
Chris Caron
4 years ago
committed by
GitHub
8 changed files with 1088 additions and 11 deletions
@ -0,0 +1,839 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
# |
||||||
|
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com> |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# This code is licensed under the MIT License. |
||||||
|
# |
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
# of this software and associated documentation files(the "Software"), to deal |
||||||
|
# in the Software without restriction, including without limitation the rights |
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell |
||||||
|
# copies of the Software, and to permit persons to whom the Software is |
||||||
|
# furnished to do so, subject to the following conditions : |
||||||
|
# |
||||||
|
# The above copyright notice and this permission notice shall be included in |
||||||
|
# all copies or substantial portions of the Software. |
||||||
|
# |
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE |
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
# THE SOFTWARE. |
||||||
|
|
||||||
|
# For LaMetric to work, you need to first setup a custom application on their |
||||||
|
# website. it can be done as follows: |
||||||
|
|
||||||
|
# Cloud Mode: |
||||||
|
# 1. Sign Up and login to the developer webpage https://developer.lametric.com |
||||||
|
# 2. Create a **Notification App** if you haven't already done so from: |
||||||
|
# https://developer.lametric.com/applications/sources |
||||||
|
# 3. Provide it an app name, a description and privacy URL (which can point to |
||||||
|
# anywhere; I set mine to `http://localhost`). No permissions are |
||||||
|
# required. |
||||||
|
# 4. Access your newly created app so that you can acquire both the |
||||||
|
# **Client ID** and the **Client Secret** here: |
||||||
|
# https://developer.lametric.com/applications/sources |
||||||
|
|
||||||
|
# Device Mode: |
||||||
|
# - Sign Up and login to the developer webpage https://developer.lametric.com |
||||||
|
# - Locate your Device API Key; you can find it here: |
||||||
|
# https://developer.lametric.com/user/devices |
||||||
|
# - From here you can get your your API Key for the device you plan to notify. |
||||||
|
# - Your devices IP Address can be found in LaMetric Time app at: |
||||||
|
# Settings -> Wi-Fi -> IP Address |
||||||
|
|
||||||
|
# A great source for API examples (Device Mode): |
||||||
|
# - https://lametric-documentation.readthedocs.io/en/latest/reference-docs\ |
||||||
|
# /device-notifications.html |
||||||
|
|
||||||
|
# A great source for API examples (Cloud Mode): |
||||||
|
# - https://lametric-documentation.readthedocs.io/en/latest/reference-docs\ |
||||||
|
# /lametric-cloud-reference.html |
||||||
|
|
||||||
|
# A great source for the icon reference: |
||||||
|
# - https://developer.lametric.com/icons |
||||||
|
import six |
||||||
|
import requests |
||||||
|
from json import dumps |
||||||
|
from .NotifyBase import NotifyBase |
||||||
|
from ..URLBase import PrivacyMode |
||||||
|
from ..common import NotifyType |
||||||
|
from ..utils import validate_regex |
||||||
|
from ..AppriseLocale import gettext_lazy as _ |
||||||
|
from ..utils import is_hostname |
||||||
|
from ..utils import is_ipaddr |
||||||
|
|
||||||
|
|
||||||
|
class LametricMode(object): |
||||||
|
""" |
||||||
|
Define Lametric Notification Modes |
||||||
|
""" |
||||||
|
# App posts upstream to the developer API on Lametric's website |
||||||
|
CLOUD = "cloud" |
||||||
|
|
||||||
|
# Device mode posts directly to the device that you identify |
||||||
|
DEVICE = "device" |
||||||
|
|
||||||
|
|
||||||
|
LAMETRIC_MODES = ( |
||||||
|
LametricMode.CLOUD, |
||||||
|
LametricMode.DEVICE, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
class LametricPriority(object): |
||||||
|
""" |
||||||
|
Priority of the message |
||||||
|
""" |
||||||
|
|
||||||
|
# info: this priority means that notification will be displayed on the |
||||||
|
# same “level” as all other notifications on the device that come |
||||||
|
# from apps (for example facebook app). This notification will not |
||||||
|
# be shown when screensaver is active. By default message is sent |
||||||
|
# with "info" priority. This level of notification should be used |
||||||
|
# for notifications like news, weather, temperature, etc. |
||||||
|
INFO = 'info' |
||||||
|
|
||||||
|
# warning: notifications with this priority will interrupt ones sent with |
||||||
|
# lower priority (“info”). Should be used to notify the user |
||||||
|
# about something important but not critical. For example, |
||||||
|
# events like “someone is coming home” should use this priority |
||||||
|
# when sending notifications from smart home. |
||||||
|
WARNING = 'warning' |
||||||
|
|
||||||
|
# critical: the most important notifications. Interrupts notification |
||||||
|
# with priority info or warning and is displayed even if |
||||||
|
# screensaver is active. Use with care as these notifications |
||||||
|
# can pop in the middle of the night. Must be used only for |
||||||
|
# really important notifications like notifications from smoke |
||||||
|
# detectors, water leak sensors, etc. Use it for events that |
||||||
|
# require human interaction immediately. |
||||||
|
CRITICAL = 'critical' |
||||||
|
|
||||||
|
|
||||||
|
LAMETRIC_PRIORITIES = ( |
||||||
|
LametricPriority.INFO, |
||||||
|
LametricPriority.WARNING, |
||||||
|
LametricPriority.CRITICAL, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
class LametricIconType(object): |
||||||
|
""" |
||||||
|
Represents the nature of notification. |
||||||
|
""" |
||||||
|
|
||||||
|
# info - "i" icon will be displayed prior to the notification. Means that |
||||||
|
# notification contains information, no need to take actions on it. |
||||||
|
INFO = 'info' |
||||||
|
|
||||||
|
# alert: "!!!" icon will be displayed prior to the notification. Use it |
||||||
|
# when you want the user to pay attention to that notification as |
||||||
|
# it indicates that something bad happened and user must take |
||||||
|
# immediate action. |
||||||
|
ALERT = 'alert' |
||||||
|
|
||||||
|
# none: no notification icon will be shown. |
||||||
|
NONE = 'none' |
||||||
|
|
||||||
|
|
||||||
|
LAMETRIC_ICON_TYPES = ( |
||||||
|
LametricIconType.INFO, |
||||||
|
LametricIconType.ALERT, |
||||||
|
LametricIconType.NONE, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
class LametricSoundCategory(object): |
||||||
|
""" |
||||||
|
Define Sound Categories |
||||||
|
""" |
||||||
|
NOTIFICATIONS = "notifications" |
||||||
|
ALARMS = "alarms" |
||||||
|
|
||||||
|
|
||||||
|
class LametricSound(object): |
||||||
|
""" |
||||||
|
There are 2 categories of sounds, to make things simple we just lump them |
||||||
|
all togther in one class object. |
||||||
|
|
||||||
|
Syntax is (Category, (AlarmID, Alias1, Alias2, ...)) |
||||||
|
""" |
||||||
|
|
||||||
|
# Alarm Category Sounds |
||||||
|
ALARM01 = (LametricSoundCategory.ALARMS, ('alarm1', 'a1', 'a01')) |
||||||
|
ALARM02 = (LametricSoundCategory.ALARMS, ('alarm2', 'a2', 'a02')) |
||||||
|
ALARM03 = (LametricSoundCategory.ALARMS, ('alarm3', 'a3', 'a03')) |
||||||
|
ALARM04 = (LametricSoundCategory.ALARMS, ('alarm4', 'a4', 'a04')) |
||||||
|
ALARM05 = (LametricSoundCategory.ALARMS, ('alarm5', 'a5', 'a05')) |
||||||
|
ALARM06 = (LametricSoundCategory.ALARMS, ('alarm6', 'a6', 'a06')) |
||||||
|
ALARM07 = (LametricSoundCategory.ALARMS, ('alarm7', 'a7', 'a07')) |
||||||
|
ALARM08 = (LametricSoundCategory.ALARMS, ('alarm8', 'a8', 'a08')) |
||||||
|
ALARM09 = (LametricSoundCategory.ALARMS, ('alarm9', 'a9', 'a09')) |
||||||
|
ALARM10 = (LametricSoundCategory.ALARMS, ('alarm10', 'a10')) |
||||||
|
ALARM11 = (LametricSoundCategory.ALARMS, ('alarm11', 'a11')) |
||||||
|
ALARM12 = (LametricSoundCategory.ALARMS, ('alarm12', 'a12')) |
||||||
|
ALARM13 = (LametricSoundCategory.ALARMS, ('alarm13', 'a13')) |
||||||
|
|
||||||
|
# Notification Category Sounds |
||||||
|
BICYCLE = (LametricSoundCategory.NOTIFICATIONS, ('bicycle', 'bike')) |
||||||
|
CAR = (LametricSoundCategory.NOTIFICATIONS, ('car', )) |
||||||
|
CASH = (LametricSoundCategory.NOTIFICATIONS, ('cash', )) |
||||||
|
CAT = (LametricSoundCategory.NOTIFICATIONS, ('cat', )) |
||||||
|
DOG01 = (LametricSoundCategory.NOTIFICATIONS, ('dog', 'dog1', 'dog01')) |
||||||
|
DOG02 = (LametricSoundCategory.NOTIFICATIONS, ('dog2', 'dog02')) |
||||||
|
ENERGY = (LametricSoundCategory.NOTIFICATIONS, ('energy', )) |
||||||
|
KNOCK = (LametricSoundCategory.NOTIFICATIONS, ('knock-knock', 'knock')) |
||||||
|
EMAIL = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'letter_email', 'letter', 'email')) |
||||||
|
LOSE01 = (LametricSoundCategory.NOTIFICATIONS, ('lose1', 'lose01', 'lose')) |
||||||
|
LOSE02 = (LametricSoundCategory.NOTIFICATIONS, ('lose2', 'lose02')) |
||||||
|
NEGATIVE01 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'negative1', 'negative01', 'neg01', 'neg1', '-')) |
||||||
|
NEGATIVE02 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'negative2', 'negative02', 'neg02', 'neg2', '--')) |
||||||
|
NEGATIVE03 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'negative3', 'negative03', 'neg03', 'neg3', '---')) |
||||||
|
NEGATIVE04 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'negative4', 'negative04', 'neg04', 'neg4', '----')) |
||||||
|
NEGATIVE05 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'negative5', 'negative05', 'neg05', 'neg5', '-----')) |
||||||
|
NOTIFICATION01 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'notification', 'notification1', 'notification01', 'not01', 'not1')) |
||||||
|
NOTIFICATION02 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'notification2', 'notification02', 'not02', 'not2')) |
||||||
|
NOTIFICATION03 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'notification3', 'notification03', 'not03', 'not3')) |
||||||
|
NOTIFICATION04 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'notification4', 'notification04', 'not04', 'not4')) |
||||||
|
OPEN_DOOR = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'open_door', 'open', 'door')) |
||||||
|
POSITIVE01 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'positive1', 'positive01', 'pos01', 'p1', '+')) |
||||||
|
POSITIVE02 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'positive2', 'positive02', 'pos02', 'p2', '++')) |
||||||
|
POSITIVE03 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'positive3', 'positive03', 'pos03', 'p3', '+++')) |
||||||
|
POSITIVE04 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'positive4', 'positive04', 'pos04', 'p4', '++++')) |
||||||
|
POSITIVE05 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'positive5', 'positive05', 'pos05', 'p5', '+++++')) |
||||||
|
POSITIVE06 = (LametricSoundCategory.NOTIFICATIONS, ( |
||||||
|
'positive6', 'positive06', 'pos06', 'p6', '++++++')) |
||||||
|
STATISTIC = (LametricSoundCategory.NOTIFICATIONS, ('statistic', 'stat')) |
||||||
|
THUNDER = (LametricSoundCategory.NOTIFICATIONS, ('thunder')) |
||||||
|
WATER01 = (LametricSoundCategory.NOTIFICATIONS, ('water1', 'water01')) |
||||||
|
WATER02 = (LametricSoundCategory.NOTIFICATIONS, ('water2', 'water02')) |
||||||
|
WIN01 = (LametricSoundCategory.NOTIFICATIONS, ('win', 'win01', 'win1')) |
||||||
|
WIN02 = (LametricSoundCategory.NOTIFICATIONS, ('win2', 'win02')) |
||||||
|
WIND = (LametricSoundCategory.NOTIFICATIONS, ('wind', )) |
||||||
|
WIND_SHORT = (LametricSoundCategory.NOTIFICATIONS, ('wind_short', )) |
||||||
|
|
||||||
|
|
||||||
|
# A listing of all the sounds; the order DOES matter, content is read from |
||||||
|
# top down and then right to left (over aliases). Longer similar sounding |
||||||
|
# elements should be placed higher in the list over others. for example |
||||||
|
# ALARM10 should come before ALARM01 (because ALARM01 can match on 'alarm1' |
||||||
|
# which is very close to 'alarm10' |
||||||
|
LAMETRIC_SOUNDS = ( |
||||||
|
# Alarm Category Entries |
||||||
|
LametricSound.ALARM13, LametricSound.ALARM12, LametricSound.ALARM11, |
||||||
|
LametricSound.ALARM10, LametricSound.ALARM09, LametricSound.ALARM08, |
||||||
|
LametricSound.ALARM07, LametricSound.ALARM06, LametricSound.ALARM05, |
||||||
|
LametricSound.ALARM04, LametricSound.ALARM03, LametricSound.ALARM02, |
||||||
|
LametricSound.ALARM01, |
||||||
|
|
||||||
|
# Notification Category Entries |
||||||
|
LametricSound.BICYCLE, LametricSound.CAR, LametricSound.CASH, |
||||||
|
LametricSound.CAT, LametricSound.DOG02, LametricSound.DOG01, |
||||||
|
LametricSound.ENERGY, LametricSound.KNOCK, LametricSound.EMAIL, |
||||||
|
LametricSound.LOSE02, LametricSound.LOSE01, LametricSound.NEGATIVE01, |
||||||
|
LametricSound.NEGATIVE02, LametricSound.NEGATIVE03, |
||||||
|
LametricSound.NEGATIVE04, LametricSound.NEGATIVE05, |
||||||
|
LametricSound.NOTIFICATION04, LametricSound.NOTIFICATION03, |
||||||
|
LametricSound.NOTIFICATION02, LametricSound.NOTIFICATION01, |
||||||
|
LametricSound.OPEN_DOOR, LametricSound.POSITIVE01, |
||||||
|
LametricSound.POSITIVE02, LametricSound.POSITIVE03, |
||||||
|
LametricSound.POSITIVE04, LametricSound.POSITIVE05, |
||||||
|
LametricSound.POSITIVE01, LametricSound.STATISTIC, LametricSound.THUNDER, |
||||||
|
LametricSound.WATER02, LametricSound.WATER01, LametricSound.WIND, |
||||||
|
LametricSound.WIND_SHORT, LametricSound.WIN01, LametricSound.WIN02, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
class NotifyLametric(NotifyBase): |
||||||
|
""" |
||||||
|
A wrapper for LaMetric Notifications |
||||||
|
""" |
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification |
||||||
|
service_name = 'LaMetric' |
||||||
|
|
||||||
|
# The services URL |
||||||
|
service_url = 'https://lametric.com' |
||||||
|
|
||||||
|
# The default protocol |
||||||
|
protocol = 'lametric' |
||||||
|
|
||||||
|
# The default secure protocol |
||||||
|
secure_protocol = 'lametrics' |
||||||
|
|
||||||
|
# Allow 300 requests per minute. |
||||||
|
# 60/300 = 0.2 |
||||||
|
request_rate_per_sec = 0.20 |
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol |
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_lametric' |
||||||
|
|
||||||
|
# Lametric does have titles when creating a message |
||||||
|
title_maxlen = 0 |
||||||
|
|
||||||
|
# URL used for notifying Lametric App's created in the Dev Portal |
||||||
|
cloud_notify_url = 'https://developer.lametric.com/api/v1' \ |
||||||
|
'/dev/widget/update/com.lametric.{client_id}' |
||||||
|
|
||||||
|
# URL used for local notifications directly to the device |
||||||
|
device_notify_url = '{schema}://{host}{port}/api/v2/device/notifications' |
||||||
|
|
||||||
|
# LaMetric Default port |
||||||
|
default_device_port = 8080 |
||||||
|
|
||||||
|
# The Device User ID |
||||||
|
default_device_user = 'dev' |
||||||
|
|
||||||
|
# Track all icon mappings back to Apprise Icon NotifyType's |
||||||
|
# See: https://developer.lametric.com/icons |
||||||
|
# Icon ID looks like <prefix>XXX, where <prefix> is: |
||||||
|
# - "i" (for static icon) |
||||||
|
# - "a" (for animation) |
||||||
|
# - XXX - is the number of the icon and can be found at: |
||||||
|
# https://developer.lametric.com/icons |
||||||
|
lametric_icon_id_mapping = { |
||||||
|
# 620/Info |
||||||
|
NotifyType.INFO: 'i620', |
||||||
|
# 9182/info_good |
||||||
|
NotifyType.SUCCESS: 'i9182', |
||||||
|
# 9183/info_caution |
||||||
|
NotifyType.WARNING: 'i9183', |
||||||
|
# 9184/info_error |
||||||
|
NotifyType.FAILURE: 'i9184', |
||||||
|
} |
||||||
|
|
||||||
|
# Define object templates |
||||||
|
templates = ( |
||||||
|
# App Mode |
||||||
|
'{schema}://{client_id}@{secret}', |
||||||
|
|
||||||
|
# Device Mode |
||||||
|
'{schema}://{apikey}@{host}', |
||||||
|
'{schema}://{apikey}@{host}:{port}', |
||||||
|
'{schema}://{user}:{apikey}@{host}:{port}', |
||||||
|
) |
||||||
|
|
||||||
|
# Define our template tokens |
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{ |
||||||
|
'apikey': { |
||||||
|
'name': _('Device API Key'), |
||||||
|
'type': 'string', |
||||||
|
'private': True, |
||||||
|
}, |
||||||
|
'host': { |
||||||
|
'name': _('Hostname'), |
||||||
|
'type': 'string', |
||||||
|
'required': True, |
||||||
|
}, |
||||||
|
'port': { |
||||||
|
'name': _('Port'), |
||||||
|
'type': 'int', |
||||||
|
'min': 1, |
||||||
|
'max': 65535, |
||||||
|
}, |
||||||
|
'user': { |
||||||
|
'name': _('Username'), |
||||||
|
'type': 'string', |
||||||
|
}, |
||||||
|
'client_id': { |
||||||
|
'name': _('Client ID'), |
||||||
|
'type': 'string', |
||||||
|
'private': True, |
||||||
|
'regex': (r'^[a-z0-9-]+$', 'i'), |
||||||
|
}, |
||||||
|
'secret': { |
||||||
|
'name': _('Client Secret'), |
||||||
|
'type': 'string', |
||||||
|
'private': True, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
# Define our template arguments |
||||||
|
template_args = dict(NotifyBase.template_args, **{ |
||||||
|
'oauth_id': { |
||||||
|
'alias_of': 'client_id', |
||||||
|
}, |
||||||
|
'oauth_secret': { |
||||||
|
'alias_of': 'secret', |
||||||
|
}, |
||||||
|
'apikey': { |
||||||
|
'alias_of': 'apikey', |
||||||
|
}, |
||||||
|
'priority': { |
||||||
|
'name': _('Priority'), |
||||||
|
'type': 'choice:string', |
||||||
|
'values': LAMETRIC_PRIORITIES, |
||||||
|
'default': LametricPriority.INFO, |
||||||
|
}, |
||||||
|
'icon_type': { |
||||||
|
'name': _('Icon Type'), |
||||||
|
'type': 'choice:string', |
||||||
|
'values': LAMETRIC_ICON_TYPES, |
||||||
|
'default': LametricIconType.NONE, |
||||||
|
}, |
||||||
|
'mode': { |
||||||
|
'name': _('Mode'), |
||||||
|
'type': 'choice:string', |
||||||
|
'values': LAMETRIC_MODES, |
||||||
|
'default': LametricMode.DEVICE, |
||||||
|
}, |
||||||
|
'sound': { |
||||||
|
'name': _('Sound'), |
||||||
|
'type': 'string', |
||||||
|
}, |
||||||
|
# Lifetime is in seconds |
||||||
|
'cycles': { |
||||||
|
'name': _('Cycles'), |
||||||
|
'type': 'int', |
||||||
|
'min': 0, |
||||||
|
'default': 1, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
def __init__(self, apikey=None, client_id=None, secret=None, priority=None, |
||||||
|
icon_type=None, sound=None, mode=None, cycles=None, **kwargs): |
||||||
|
""" |
||||||
|
Initialize LaMetric Object |
||||||
|
""" |
||||||
|
super(NotifyLametric, self).__init__(**kwargs) |
||||||
|
|
||||||
|
self.mode = mode.strip().lower() \ |
||||||
|
if isinstance(mode, six.string_types) \ |
||||||
|
else self.template_args['mode']['default'] |
||||||
|
|
||||||
|
if self.mode not in LAMETRIC_MODES: |
||||||
|
msg = 'An invalid LaMetric Mode ({}) was specified.'.format(mode) |
||||||
|
self.logger.warning(msg) |
||||||
|
raise TypeError(msg) |
||||||
|
|
||||||
|
# Default Cloud Arguments |
||||||
|
self.secret = None |
||||||
|
self.client_id = None |
||||||
|
|
||||||
|
# Default Device Arguments |
||||||
|
self.apikey = None |
||||||
|
|
||||||
|
if self.mode == LametricMode.CLOUD: |
||||||
|
# Client ID |
||||||
|
self.client_id = validate_regex( |
||||||
|
client_id, *self.template_tokens['client_id']['regex']) |
||||||
|
if not self.client_id: |
||||||
|
msg = 'An invalid LaMetric Client OAuth2 ID ' \ |
||||||
|
'({}) was specified.'.format(client_id) |
||||||
|
self.logger.warning(msg) |
||||||
|
raise TypeError(msg) |
||||||
|
|
||||||
|
# Client Secret |
||||||
|
self.secret = validate_regex(secret) |
||||||
|
if not self.secret: |
||||||
|
msg = 'An invalid LaMetric Client OAuth2 Secret ' \ |
||||||
|
'({}) was specified.'.format(secret) |
||||||
|
self.logger.warning(msg) |
||||||
|
raise TypeError(msg) |
||||||
|
|
||||||
|
else: # LametricMode.DEVICE |
||||||
|
|
||||||
|
# API Key |
||||||
|
self.apikey = validate_regex(apikey) |
||||||
|
if not self.apikey: |
||||||
|
msg = 'An invalid LaMetric Device API Key ' \ |
||||||
|
'({}) was specified.'.format(apikey) |
||||||
|
self.logger.warning(msg) |
||||||
|
raise TypeError(msg) |
||||||
|
|
||||||
|
if priority not in LAMETRIC_PRIORITIES: |
||||||
|
self.priority = self.template_args['priority']['default'] |
||||||
|
|
||||||
|
else: |
||||||
|
self.priority = priority |
||||||
|
|
||||||
|
if icon_type not in LAMETRIC_ICON_TYPES: |
||||||
|
self.icon_type = self.template_args['icon_type']['default'] |
||||||
|
|
||||||
|
else: |
||||||
|
self.icon_type = icon_type |
||||||
|
|
||||||
|
# The number of times the message should be displayed |
||||||
|
self.cycles = self.template_args['cycles']['default'] \ |
||||||
|
if not (isinstance(cycles, int) and |
||||||
|
cycles > self.template_args['cycles']['min']) else cycles |
||||||
|
|
||||||
|
self.sound = None |
||||||
|
if isinstance(sound, six.string_types): |
||||||
|
# If sound is set, get it's match |
||||||
|
self.sound = self.sound_lookup(sound.strip().lower()) |
||||||
|
if self.sound is None: |
||||||
|
self.logger.warning( |
||||||
|
'An invalid LaMetric sound ({}) was specified.'.format( |
||||||
|
sound)) |
||||||
|
return |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def sound_lookup(lookup): |
||||||
|
""" |
||||||
|
A simple match function that takes string and returns the |
||||||
|
LametricSound object it was found in. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
for x in LAMETRIC_SOUNDS: |
||||||
|
match = next((f for f in x[1] if f.startswith(lookup)), None) |
||||||
|
if match: |
||||||
|
# We're done |
||||||
|
return x |
||||||
|
|
||||||
|
# No match was found |
||||||
|
return None |
||||||
|
|
||||||
|
def _cloud_notification_payload(self, body, notify_type, headers): |
||||||
|
""" |
||||||
|
Return URL and payload for cloud directed requests |
||||||
|
""" |
||||||
|
|
||||||
|
# Update header entries |
||||||
|
headers.update({ |
||||||
|
'X-Access-Token': self.secret, |
||||||
|
'Cache-Control': 'no-cache', |
||||||
|
}) |
||||||
|
|
||||||
|
if self.sound: |
||||||
|
self.logger.warning( |
||||||
|
'LaMetric sound setting is unavailable in Cloud mode') |
||||||
|
|
||||||
|
if self.priority != self.template_args['priority']['default']: |
||||||
|
self.logger.warning( |
||||||
|
'LaMetric priority setting is unavailable in Cloud mode') |
||||||
|
|
||||||
|
if self.icon_type != self.template_args['icon_type']['default']: |
||||||
|
self.logger.warning( |
||||||
|
'LaMetric icon_type setting is unavailable in Cloud mode') |
||||||
|
|
||||||
|
if self.cycles != self.template_args['cycles']['default']: |
||||||
|
self.logger.warning( |
||||||
|
'LaMetric cycle settings is unavailable in Cloud mode') |
||||||
|
|
||||||
|
# Cloud Notifications don't have as much functionality |
||||||
|
# You can not set priority and/or sound |
||||||
|
payload = { |
||||||
|
"frames": [ |
||||||
|
{ |
||||||
|
"icon": self.lametric_icon_id_mapping[notify_type], |
||||||
|
"text": body, |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
# Prepare our Cloud Notify URL |
||||||
|
notify_url = self.cloud_notify_url.format(client_id=self.client_id) |
||||||
|
|
||||||
|
# Return request parameters |
||||||
|
return (notify_url, None, payload) |
||||||
|
|
||||||
|
def _device_notification_payload(self, body, notify_type, headers): |
||||||
|
""" |
||||||
|
Return URL and Payload for Device directed requests |
||||||
|
""" |
||||||
|
|
||||||
|
# Our Payload |
||||||
|
payload = { |
||||||
|
# Priority of the message |
||||||
|
"priority": self.priority, |
||||||
|
|
||||||
|
# Icon Type: Represents the nature of notification |
||||||
|
"icon_type": self.icon_type, |
||||||
|
|
||||||
|
# The time notification lives in queue to be displayed in |
||||||
|
# milliseconds (ms). The default lifetime is 2 minutes (120000ms). |
||||||
|
# If notification stayed in queue for longer than lifetime |
||||||
|
# milliseconds - it will not be displayed. |
||||||
|
"lifetime": 120000, |
||||||
|
|
||||||
|
"model": { |
||||||
|
# cycles - the number of times message should be displayed. If |
||||||
|
# cycles is set to 0, notification will stay on the screen |
||||||
|
# until user dismisses it manually. By default it is set to 1. |
||||||
|
"cycles": self.cycles, |
||||||
|
"frames": [ |
||||||
|
{ |
||||||
|
"icon": self.lametric_icon_id_mapping[notify_type], |
||||||
|
"text": body, |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if self.sound: |
||||||
|
# Sound was set, so add it to the payload |
||||||
|
payload["model"]["sound"] = { |
||||||
|
# The sound category |
||||||
|
"category": self.sound[0], |
||||||
|
|
||||||
|
# The first element of our tuple is always the id |
||||||
|
"id": self.sound[1][0], |
||||||
|
|
||||||
|
# repeat - defines the number of times sound must be played. |
||||||
|
# If set to 0 sound will be played until notification is |
||||||
|
# dismissed. By default the value is set to 1. |
||||||
|
"repeat": 1, |
||||||
|
} |
||||||
|
|
||||||
|
if not self.user: |
||||||
|
# Use default user if there wasn't one otherwise specified |
||||||
|
self.user = self.default_device_user |
||||||
|
|
||||||
|
# Prepare our authentication |
||||||
|
auth = (self.user, self.password) |
||||||
|
|
||||||
|
# Prepare our Direct Access Notify URL |
||||||
|
notify_url = self.device_notify_url.format( |
||||||
|
schema="https" if self.secure else "http", |
||||||
|
host=self.host, |
||||||
|
port=':{}'.format( |
||||||
|
self.port if self.port else self.default_device_port)) |
||||||
|
|
||||||
|
# Return request parameters |
||||||
|
return (notify_url, auth, payload) |
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): |
||||||
|
""" |
||||||
|
Perform LaMetric Notification |
||||||
|
""" |
||||||
|
|
||||||
|
# Prepare our headers: |
||||||
|
headers = { |
||||||
|
'User-Agent': self.app_id, |
||||||
|
'Content-Type': 'application/json', |
||||||
|
'Accept': 'application/json', |
||||||
|
} |
||||||
|
|
||||||
|
# Depending on the mode, the payload is gathered by |
||||||
|
# - _device_notification_payload() |
||||||
|
# - _cloud_notification_payload() |
||||||
|
(notify_url, auth, payload) = getattr( |
||||||
|
self, '_{}_notification_payload'.format(self.mode))( |
||||||
|
body=body, notify_type=notify_type, headers=headers) |
||||||
|
|
||||||
|
self.logger.debug('LaMetric POST URL: %s (cert_verify=%r)' % ( |
||||||
|
notify_url, self.verify_certificate, |
||||||
|
)) |
||||||
|
self.logger.debug('LaMetric Payload: %s' % str(payload)) |
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made |
||||||
|
self.throttle() |
||||||
|
|
||||||
|
try: |
||||||
|
r = requests.post( |
||||||
|
notify_url, |
||||||
|
data=dumps(payload), |
||||||
|
headers=headers, |
||||||
|
auth=auth, |
||||||
|
verify=self.verify_certificate, |
||||||
|
timeout=self.request_timeout, |
||||||
|
) |
||||||
|
# An ideal response would be: |
||||||
|
# { |
||||||
|
# "success": { |
||||||
|
# "id": "<notification id>" |
||||||
|
# } |
||||||
|
# } |
||||||
|
|
||||||
|
if r.status_code not in ( |
||||||
|
requests.codes.created, requests.codes.ok): |
||||||
|
# We had a problem |
||||||
|
status_str = \ |
||||||
|
NotifyLametric.http_response_code_lookup(r.status_code) |
||||||
|
|
||||||
|
self.logger.warning( |
||||||
|
'Failed to send LaMetric notification: ' |
||||||
|
'{}{}error={}.'.format( |
||||||
|
status_str, |
||||||
|
', ' if status_str else '', |
||||||
|
r.status_code)) |
||||||
|
|
||||||
|
self.logger.debug('Response Details:\r\n{}'.format(r.content)) |
||||||
|
|
||||||
|
# Return; we're done |
||||||
|
return False |
||||||
|
|
||||||
|
else: |
||||||
|
self.logger.info('Sent LaMetric notification.') |
||||||
|
|
||||||
|
except requests.RequestException as e: |
||||||
|
self.logger.warning( |
||||||
|
'A Connection error occurred sending LaMetric ' |
||||||
|
'notification to %s.' % self.host) |
||||||
|
self.logger.debug('Socket Exception: %s' % str(e)) |
||||||
|
|
||||||
|
# Return; we're done |
||||||
|
return False |
||||||
|
|
||||||
|
return True |
||||||
|
|
||||||
|
def url(self, privacy=False, *args, **kwargs): |
||||||
|
""" |
||||||
|
Returns the URL built dynamically based on specified arguments. |
||||||
|
""" |
||||||
|
|
||||||
|
# Define any URL parameters |
||||||
|
params = { |
||||||
|
'mode': self.mode, |
||||||
|
} |
||||||
|
|
||||||
|
# Extend our parameters |
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) |
||||||
|
|
||||||
|
if self.mode == LametricMode.CLOUD: |
||||||
|
# Upstream/LaMetric App Return |
||||||
|
return '{schema}://{client_id}@{secret}/?{params}'.format( |
||||||
|
schema=self.protocol, |
||||||
|
client_id=self.pprint(self.client_id, privacy, safe=''), |
||||||
|
secret=self.pprint( |
||||||
|
self.secret, privacy, mode=PrivacyMode.Secret, safe=''), |
||||||
|
params=NotifyLametric.urlencode(params)) |
||||||
|
|
||||||
|
# |
||||||
|
# If we reach here then we're dealing with LametricMode.DEVICE |
||||||
|
# |
||||||
|
if self.priority != self.template_args['priority']['default']: |
||||||
|
params['priority'] = self.priority |
||||||
|
|
||||||
|
if self.icon_type != self.template_args['icon_type']['default']: |
||||||
|
params['icon_type'] = self.icon_type |
||||||
|
|
||||||
|
if self.cycles != self.template_args['cycles']['default']: |
||||||
|
params['cycles'] = self.cycles |
||||||
|
|
||||||
|
if self.sound: |
||||||
|
# Store our sound entry |
||||||
|
# The first element of our tuple is always the id |
||||||
|
params['sound'] = self.sound[1][0] |
||||||
|
|
||||||
|
auth = '' |
||||||
|
if self.user and self.password: |
||||||
|
auth = '{user}:{apikey}@'.format( |
||||||
|
user=NotifyLametric.quote(self.user, safe=''), |
||||||
|
apikey=self.pprint(self.apikey, privacy, safe=''), |
||||||
|
) |
||||||
|
else: # self.apikey is set |
||||||
|
auth = '{apikey}@'.format( |
||||||
|
apikey=self.pprint(self.apikey, privacy, safe=''), |
||||||
|
) |
||||||
|
|
||||||
|
# Local Return |
||||||
|
return '{schema}://{auth}{hostname}{port}/?{params}'.format( |
||||||
|
schema=self.secure_protocol if self.secure else self.protocol, |
||||||
|
auth=auth, |
||||||
|
# never encode hostname since we're expecting it to be a valid one |
||||||
|
hostname=self.host, |
||||||
|
port='' if self.port is None |
||||||
|
or self.port == self.default_device_port |
||||||
|
else ':{}'.format(self.port), |
||||||
|
params=NotifyLametric.urlencode(params), |
||||||
|
) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def parse_url(url): |
||||||
|
""" |
||||||
|
Parses the URL and returns enough arguments that can allow |
||||||
|
us to substantiate this object. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
results = NotifyBase.parse_url(url, verify_host=False) |
||||||
|
if not results: |
||||||
|
# We're done early as we couldn't load the results |
||||||
|
return results |
||||||
|
|
||||||
|
if results.get('user') and not results.get('password'): |
||||||
|
# Handle URL like: |
||||||
|
# schema://user@host |
||||||
|
|
||||||
|
# This becomes the password |
||||||
|
results['password'] = results['user'] |
||||||
|
results['user'] = None |
||||||
|
|
||||||
|
# Priority Handling |
||||||
|
if 'priority' in results['qsd'] and len(results['qsd']['priority']): |
||||||
|
results['priority'] = results['qsd']['priority'].strip().lower() |
||||||
|
|
||||||
|
# Icon Type |
||||||
|
if 'icon_type' in results['qsd'] and len(results['qsd']['icon_type']): |
||||||
|
results['icon_type'] = results['qsd']['icon_type'].strip().lower() |
||||||
|
|
||||||
|
# Sound |
||||||
|
if 'sound' in results['qsd'] and len(results['qsd']['sound']): |
||||||
|
results['sound'] = results['qsd']['sound'].strip().lower() |
||||||
|
|
||||||
|
# We can detect the mode based on the validity of the hostname |
||||||
|
results['mode'] = LametricMode.DEVICE \ |
||||||
|
if (is_hostname(results['host']) or |
||||||
|
is_ipaddr(results['host'])) else LametricMode.CLOUD |
||||||
|
|
||||||
|
# Mode override |
||||||
|
if 'mode' in results['qsd'] and len(results['qsd']['mode']): |
||||||
|
results['mode'] = NotifyLametric.unquote(results['qsd']['mode']) |
||||||
|
|
||||||
|
# API Key (Device Mode) |
||||||
|
if results['mode'] == LametricMode.DEVICE: |
||||||
|
if 'apikey' in results['qsd'] and len(results['qsd']['apikey']): |
||||||
|
# Extract API Key from an argument |
||||||
|
results['apikey'] = \ |
||||||
|
NotifyLametric.unquote(results['qsd']['apikey']) |
||||||
|
|
||||||
|
else: |
||||||
|
results['apikey'] = \ |
||||||
|
NotifyLametric.unquote(results['password']) |
||||||
|
|
||||||
|
elif results['mode'] == LametricMode.CLOUD: |
||||||
|
# OAuth2 ID (Cloud Mode) |
||||||
|
if 'oauth_id' in results['qsd'] \ |
||||||
|
and len(results['qsd']['oauth_id']): |
||||||
|
|
||||||
|
# Extract the OAuth2 Key from an argument |
||||||
|
results['client_id'] = \ |
||||||
|
NotifyLametric.unquote(results['qsd']['oauth_id']) |
||||||
|
|
||||||
|
else: |
||||||
|
results['client_id'] = \ |
||||||
|
NotifyLametric.unquote(results['password']) |
||||||
|
|
||||||
|
# OAuth2 Secret (Cloud Mode) |
||||||
|
if 'oauth_secret' in results['qsd'] and \ |
||||||
|
len(results['qsd']['oauth_secret']): |
||||||
|
# Extract the API Secret from an argument |
||||||
|
results['secret'] = \ |
||||||
|
NotifyLametric.unquote(results['qsd']['oauth_secret']) |
||||||
|
|
||||||
|
else: |
||||||
|
results['secret'] = \ |
||||||
|
NotifyLametric.unquote(results['host']) |
||||||
|
|
||||||
|
# Set cycles |
||||||
|
try: |
||||||
|
results['cycles'] = abs(int(results['qsd'].get('cycles'))) |
||||||
|
|
||||||
|
except (TypeError, ValueError): |
||||||
|
# Not a valid integer; ignore entry |
||||||
|
pass |
||||||
|
|
||||||
|
return results |
Loading…
Reference in new issue