Exemple #1
0
def main():
    """
    Loops over all the Swarm services, checking if they need updates.

    :raises Exception when Docker Engine is not in Swarm Mode
    """
    update_delay = getenv('UPDATE_DELAY', '300')
    notification_url = getenv('NOTIFICATION_URL', '')

    try:
        client = docker.from_env()
    except ConnectionError:
        logger.error(
            'Could not connect to Docker Engine. Check https://git.io/JJujV for possible solutions'
        )
        return

    logger.info('Started checking for updates')
    apprise = Apprise()
    if len(notification_url) > 0:
        # Add notification provider from URL if provided
        apprise.add(notification_url)

    if not is_swarm_manager(client):
        raise Exception('Docker Engine is not in Swarm Mode')
    while True:
        update_services(client, apprise)
        time.sleep(float(update_delay))
def test_plugin_signal_edge_cases(mock_post):
    """
    NotifySignalAPI() Edge Cases

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Prepare our response
    response = requests.Request()
    response.status_code = requests.codes.ok

    # Prepare Mock
    mock_post.return_value = response

    # Initialize some generic (but valid) tokens
    source = '+1 (555) 123-3456'
    target = '+1 (555) 987-5432'
    body = "test body"
    title = "My Title"

    # No apikey specified
    with pytest.raises(TypeError):
        plugins.NotifySignalAPI(source=None)

    aobj = Apprise()
    assert aobj.add("signals://*****:*****@localhost:231/{}/{}?status=True".format(
            source, target))
    assert aobj.notify(title=title, body=body)

    assert mock_post.call_count == 1

    details = mock_post.call_args_list[0]
    assert details[0][0] == 'https://localhost:231/v2/send'
    payload = loads(details[1]['data'])
    # Status flag is set
    assert payload['message'] == '[i] My Title\r\ntest body'
def send_notification(message, subject):
    apprise_client = Apprise()
    if APPRISE_CONFIG_STRING:
        apprise_client.add(APPRISE_CONFIG_STRING)
    if APPRISE_CONFIG_URL:
        config = AppriseConfig()
        config.add(APPRISE_CONFIG_URL)
        apprise_client.add(config)

    res = apprise_client.notify(body=message, title=subject)
    print(res)
    if not res:
        print('Failed to send notification')
    else:
        print('Successfully sent notification')

    return res
def test_object_parsing():
    """
    API: NotifySNS Plugin() Object Parsing

    """

    # Create our object
    a = Apprise()

    # Now test failing variations of our URL
    assert a.add('sns://') is False
    assert a.add('sns://nosecret') is False
    assert a.add('sns://nosecret/noregion/') is False

    # This is valid but without valid recipients; while it's still a valid URL
    # it won't do much when the user goes to send a notification
    assert a.add('sns://norecipient/norecipient/us-west-2') is True
    assert len(a) == 1

    # Parse a good one
    assert a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True
    assert len(a) == 2

    assert a.add('sns://oh/yeah/us-west-2/12223334444') is True
    assert len(a) == 3
Exemple #5
0
def test_config_base_parse_yaml_file04(tmpdir):
    """
    API: ConfigBase.parse_yaml_file (#4)

    Test the always keyword

    """
    t = tmpdir.mkdir("always-keyword").join("apprise.yml")
    t.write("""urls:
  - pover://nsisxnvnqixq39t0cw54pxieyvtdd9@2jevtmstfg5a7hfxndiybasttxxfku:
    - tag: test1,always
  - pover://rg8ta87qngcrkc6t4qbykxktou0uug@tqs3i88xlufexwl8t4asglt4zp5wfn:
    - tag: test2
  - pover://jcqgnlyq2oetea4qg3iunahj8d5ijm@evalvutkhc8ipmz2lcgc70wtsm0qpb:
    - tag: test3""")

    # Create ourselves a config object
    ac = AppriseConfig(paths=str(t))

    # The number of configuration files that exist
    assert len(ac) == 1

    # no notifications are loaded
    assert len(ac.servers()) == 3

    # Test our ability to add Config objects to our apprise object
    a = Apprise()

    # Add our configuration object
    assert a.add(servers=ac) is True

    # Detect our 3 entry as they should have loaded successfully
    assert len(a) == 3

    # No match still matches `always` keyword
    assert sum(1 for _ in a.find('no-match')) == 1
    # Unless we explicitly do not look for that file
    assert sum(1 for _ in a.find('no-match', match_always=False)) == 0
    # Match everything
    assert sum(1 for _ in a.find('all')) == 3
    # Match test1 entry (also has `always` keyword
    assert sum(1 for _ in a.find('test1')) == 1
    assert sum(1 for _ in a.find('test1', match_always=False)) == 1
    # Match test2 entry (and test1 due to always keyword)
    assert sum(1 for _ in a.find('test2')) == 2
    assert sum(1 for _ in a.find('test2', match_always=False)) == 1
    # Match test3 entry (and test1 due to always keyword)
    assert sum(1 for _ in a.find('test3')) == 2
    assert sum(1 for _ in a.find('test3', match_always=False)) == 1
    # Match test1 or test3 entry
    assert sum(1 for _ in a.find('test1, test3')) == 2
Exemple #6
0
def test_config_base_parse_yaml_file03(tmpdir):
    """
    API: ConfigBase.parse_yaml_file (#3)

    """

    t = tmpdir.mkdir("bad-first-entry").join("apprise.yml")
    # The first entry is -tag and not <dash><space>tag
    # The element is therefore not picked up; This causes us to display
    # some warning messages to the screen complaining of this typo yet
    # still allowing us to load the URL since it is valid
    t.write("""urls:
  - pover://nsisxnvnqixq39t0cw54pxieyvtdd9@2jevtmstfg5a7hfxndiybasttxxfku:
    -tag: test1
  - pover://rg8ta87qngcrkc6t4qbykxktou0uug@tqs3i88xlufexwl8t4asglt4zp5wfn:
    - tag: test2
  - pover://jcqgnlyq2oetea4qg3iunahj8d5ijm@evalvutkhc8ipmz2lcgc70wtsm0qpb:
    - tag: test3""")

    # Create ourselves a config object
    ac = AppriseConfig(paths=str(t))

    # The number of configuration files that exist
    assert len(ac) == 1

    # no notifications lines processed is 3
    assert len(ac.servers()) == 3

    # Test our ability to add Config objects to our apprise object
    a = Apprise()

    # Add our configuration object
    assert a.add(servers=ac) is True

    # Detect our 3 entry as they should have loaded successfully
    assert len(a) == 3

    # No match
    assert sum(1 for _ in a.find('no-match')) == 0
    # Match everything
    assert sum(1 for _ in a.find('all')) == 3
    # No match for bad entry
    assert sum(1 for _ in a.find('test1')) == 0
    # Match test2 entry
    assert sum(1 for _ in a.find('test2')) == 1
    # Match test3 entry
    assert sum(1 for _ in a.find('test3')) == 1
    # Match test1 or test3 entry; (only matches test3)
    assert sum(1 for _ in a.find('test1, test3')) == 1
def test_object_parsing():
    """
    API: NotifySNS Plugin() Object Parsing

    """

    # Create our object
    a = Apprise()

    # Now test failing variations of our URL
    assert(a.add('sns://') is False)
    assert(a.add('sns://nosecret') is False)
    assert(a.add('sns://nosecret/noregion/') is False)

    # This is valid, but a rather useless URL; there is nothing to notify
    assert(a.add('sns://norecipient/norecipient/us-west-2') is True)
    assert(len(a) == 1)

    # Parse a good one
    assert(a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True)
    assert(len(a) == 2)

    assert(a.add('sns://oh/yeah/us-west-2/12223334444') is True)
    assert(len(a) == 3)
Exemple #8
0
def test_object_parsing():
    """
    API: NotifySNS Plugin() Object Parsing

    """

    # Create our object
    a = Apprise()

    # Now test failing variations of our URL
    assert a.add('sns://') is False
    assert a.add('sns://nosecret') is False
    assert a.add('sns://nosecret/noregion/') is False

    # This is valid but without valid recipients, the URL is actually useless
    assert a.add('sns://norecipient/norecipient/us-west-2') is False
    assert len(a) == 0

    # Parse a good one
    assert a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True
    assert len(a) == 1

    assert a.add('sns://oh/yeah/us-west-2/12223334444') is True
    assert len(a) == 2
Exemple #9
0
def test_smtplib_send_okay(mock_smtplib):
    """
    API: Test a successfully sent email

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Defaults to HTML
    obj = Apprise.instantiate('mailto://*****:*****@gmail.com',
                              suppress_exceptions=False)
    assert isinstance(obj, plugins.NotifyEmail)

    # Support an email simulation where we can correctly quit
    mock_smtplib.starttls.return_value = True
    mock_smtplib.login.return_value = True
    mock_smtplib.sendmail.return_value = True
    mock_smtplib.quit.return_value = True

    assert obj.notify(body='body', title='test',
                      notify_type=NotifyType.INFO) is True

    # Set Text
    obj = Apprise.instantiate('mailto://*****:*****@gmail.com?format=text',
                              suppress_exceptions=False)
    assert isinstance(obj, plugins.NotifyEmail)

    assert obj.notify(body='body', title='test',
                      notify_type=NotifyType.INFO) is True

    # Create an apprise object to work with as well
    a = Apprise()
    assert a.add('mailto://*****:*****@gmail.com?format=text')

    # Send Attachment with success
    attach = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
    assert obj.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is True

    # same results happen from our Apprise object
    assert a.notify(body='body', title='test', attach=attach) is True

    # test using an Apprise Attachment object
    assert obj.notify(body='body',
                      title='test',
                      notify_type=NotifyType.INFO,
                      attach=AppriseAttachment(attach)) is True

    # same results happen from our Apprise object
    assert a.notify(
        body='body', title='test', attach=AppriseAttachment(attach)) is True

    max_file_size = AttachBase.max_file_size
    # Now do a case where the file can't be sent

    AttachBase.max_file_size = 1
    assert obj.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is False

    # same results happen from our Apprise object
    assert a.notify(body='body', title='test', attach=attach) is False

    # Restore value
    AttachBase.max_file_size = max_file_size
Exemple #10
0
def test_apprise_tagging(mock_post, mock_get):
    """
    API: Apprise() object tagging functionality

    """

    # A request
    robj = mock.Mock()
    setattr(robj, 'raw', mock.Mock())
    # Allow raw.read() calls
    robj.raw.read.return_value = ''
    robj.text = ''
    robj.content = ''
    mock_get.return_value = robj
    mock_post.return_value = robj

    # Simulate a successful notification
    mock_get.return_value.status_code = requests.codes.ok
    mock_post.return_value.status_code = requests.codes.ok

    # Create our object
    a = Apprise()

    # An invalid addition can't add the tag
    assert a.add('averyinvalidschema://localhost', tag='uhoh') is False
    assert a.add({
        'schema': 'averyinvalidschema',
        'host': 'localhost'
    },
                 tag='uhoh') is False

    # Add entry and assign it to a tag called 'awesome'
    assert a.add('json://localhost/path1/', tag='awesome') is True
    assert a.add({
        'schema': 'json',
        'host': 'localhost',
        'fullpath': '/path1/'
    },
                 tag='awesome') is True

    # Add another notification and assign it to a tag called 'awesome'
    # and another tag called 'local'
    assert a.add('json://localhost/path2/', tag=['mmost', 'awesome']) is True

    # notify the awesome tag; this would notify both services behind the
    # scenes
    assert a.notify(title="my title", body="my body", tag='awesome') is True

    # notify all of the tags
    assert a.notify(title="my title", body="my body", tag=['awesome', 'mmost'
                                                           ]) is True

    # When we query against our loaded notifications for a tag that simply
    # isn't assigned to anything, we return None.  None (different then False)
    # tells us that we litterally had nothing to query.  We didn't fail...
    # but we also didn't do anything...
    assert a.notify(title="my title", body="my body", tag='missing') is None

    # Now to test the ability to and and/or notifications
    a = Apprise()

    # Add a tag by tuple
    assert a.add('json://localhost/tagA/', tag=("TagA", )) is True
    # Add 2 tags by string
    assert a.add('json://localhost/tagAB/', tag="TagA, TagB") is True
    # Add a tag using a set
    assert a.add('json://localhost/tagB/', tag=set(["TagB"])) is True
    # Add a tag by string (again)
    assert a.add('json://localhost/tagC/', tag="TagC") is True
    # Add 2 tags using a list
    assert a.add('json://localhost/tagCD/', tag=["TagC", "TagD"]) is True
    # Add a tag by string (again)
    assert a.add('json://localhost/tagD/', tag="TagD") is True
    # add a tag set by set (again)
    assert a.add('json://localhost/tagCDE/', tag=set(["TagC", "TagD", "TagE"
                                                      ])) is True

    # Expression: TagC and TagD
    # Matches the following only:
    #   - json://localhost/tagCD/
    #   - json://localhost/tagCDE/
    assert a.notify(title="my title", body="my body", tag=[('TagC', 'TagD')
                                                           ]) is True

    # Expression: (TagY and TagZ) or TagX
    # Matches nothing, None is returned in this case
    assert a.notify(title="my title",
                    body="my body",
                    tag=[('TagY', 'TagZ'), 'TagX']) is None

    # Expression: (TagY and TagZ) or TagA
    # Matches the following only:
    #   - json://localhost/tagAB/
    assert a.notify(title="my title",
                    body="my body",
                    tag=[('TagY', 'TagZ'), 'TagA']) is True

    # Expression: (TagE and TagD) or TagB
    # Matches the following only:
    #   - json://localhost/tagCDE/
    #   - json://localhost/tagAB/
    #   - json://localhost/tagB/
    assert a.notify(title="my title",
                    body="my body",
                    tag=[('TagE', 'TagD'), 'TagB']) is True

    # Garbage Entries in tag field just get stripped out. the below
    # is the same as notifying no tags at all. Since we have not added
    # any entries that do not have tags (that we can match against)
    # we fail.  None is returned as a way of letting us know that we
    # had Notifications to notify, but since none of them matched our tag
    # none were notified.
    assert a.notify(title="my title", body="my body", tag=[
        (object, ),
    ]) is None
Exemple #11
0
    def notify(self, servers, body, title, notify_type=NotifyType.INFO,
               body_format=NotifyFormat.MARKDOWN):
        """
        processes list of servers specified
        """

        # Decode our data
        body = decode(body)
        title = decode(title)

        # Apprise Asset Object
        asset = AppriseAsset(theme=self.default_theme)
        asset.app_id = 'NZB-Notify'
        asset.app_desc = 'NZB Notification'
        asset.app_url = 'https://github.com/caronc/nzb-notify'

        # Source Theme from GitHub Page
        asset.image_url_mask = 'https://raw.githubusercontent.com' \
                               '/caronc/nzb-notify/master/Notify' \
                               '/apprise-theme/{THEME}/apprise-{TYPE}-{XY}.png'

        asset.image_path_mask = join(
            dirname(__file__),
            'Notify', 'apprise-theme', '{THEME}',
            'apprise-{TYPE}-{XY}.png')

        # Include Image Flag
        _url = self.parse_url(self.get('IncludeImage'))

        # Define some globals to use in this function
        image_path = None
        image_url = None

        if _url:
            # Toggle our include image flag right away to True
            include_image = True

            # Get some more details
            if not re.match('^(https?|file)$', _url['schema'], re.IGNORECASE):
                self.logger.error(
                    'An invalid image url protocol (%s://) was specified.' %
                    _url['schema'],
                )
                return False

            if _url['schema'] == 'file':
                if not isfile(_url['fullpath']):
                    self.logger.error(
                        'The specified file %s was not found.' %
                        _url['fullpath'],
                    )
                    return False
                image_path = _url['fullpath']

            else:
                # We're dealing with a web request
                image_url = _url['url']

        else:
            # Dealing with the old way of doing things; just toggling a
            # true/false flag
            include_image = self.parse_bool(self.get('IncludeImage'), False)

        if isinstance(servers, basestring):
            # servers can be a list of URLs, or it can be
            # a string which will be parsed into this list
            # we wanted.
            servers = self.parse_list(self.get('Servers', ''))

        # Create our apprise object
        a = Apprise(asset=asset)

        for server in servers:

            # Add our URL
            if not a.add(server):
                # Validation Failure
                self.logger.error(
                    'Could not initialize %s instance.' % server,
                )
                continue

        # Notify our servers
        a.notify(body=body, title=title, notify_type=notify_type,
                 body_format=body_format)

        # Always return true
        return True
Exemple #12
0
def test_plugin_discord_general(mock_post):
    """
    NotifyDiscord() General Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    webhook_id = 'A' * 24
    webhook_token = 'B' * 64

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok

    # Invalid webhook id
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id=None, webhook_token=webhook_token)
    # Invalid webhook id (whitespace)
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id="  ", webhook_token=webhook_token)

    # Invalid webhook token
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id=webhook_id, webhook_token=None)
    # Invalid webhook token (whitespace)
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id=webhook_id, webhook_token="   ")

    obj = plugins.NotifyDiscord(webhook_id=webhook_id,
                                webhook_token=webhook_token,
                                footer=True,
                                thumbnail=False)

    # Test that we get a string response
    assert isinstance(obj.url(), six.string_types) is True

    # This call includes an image with it's payload:
    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is True

    # Simple Markdown Single line of text
    test_markdown = "body"
    desc, results = obj.extract_markdown_sections(test_markdown)
    assert isinstance(results, list) is True
    assert len(results) == 0

    # Test our header parsing when not lead with a header
    test_markdown = """
    A section of text that has no header at the top.
    It also has a hash tag # <- in the middle of a
    string.

    ## Heading 1
    body

    # Heading 2

    more content
    on multi-lines
    """

    desc, results = obj.extract_markdown_sections(test_markdown)
    # we have a description
    assert isinstance(desc, six.string_types) is True
    assert desc.startswith('A section of text that has no header at the top.')
    assert desc.endswith('string.')

    assert isinstance(results, list) is True
    assert len(results) == 2
    assert results[0]['name'] == 'Heading 1'
    assert results[0]['value'] == '```md\nbody\n```'
    assert results[1]['name'] == 'Heading 2'
    assert results[1]['value'] == \
        '```md\nmore content\n    on multi-lines\n```'

    # Test our header parsing
    test_markdown = "## Heading one\nbody body\n\n" + \
        "# Heading 2 ##\n\nTest\n\n" + \
        "more content\n" + \
        "even more content  \t\r\n\n\n" + \
        "# Heading 3 ##\n\n\n" + \
        "normal content\n" + \
        "# heading 4\n" + \
        "#### Heading 5"

    desc, results = obj.extract_markdown_sections(test_markdown)
    assert isinstance(results, list) is True
    # No desc details filled out
    assert isinstance(desc, six.string_types) is True
    assert not desc

    # We should have 5 sections (since there are 5 headers identified above)
    assert len(results) == 5
    assert results[0]['name'] == 'Heading one'
    assert results[0]['value'] == '```md\nbody body\n```'
    assert results[1]['name'] == 'Heading 2'
    assert results[1]['value'] == \
        '```md\nTest\n\nmore content\neven more content\n```'
    assert results[2]['name'] == 'Heading 3'
    assert results[2]['value'] == \
        '```md\nnormal content\n```'
    assert results[3]['name'] == 'heading 4'
    assert results[3]['value'] == '```\n```'
    assert results[4]['name'] == 'Heading 5'
    assert results[4]['value'] == '```\n```'

    # Create an apprise instance
    a = Apprise()

    # Our processing is slightly different when we aren't using markdown
    # as we do not pre-parse content during our notifications
    assert a.add(
        'discord://{webhook_id}/{webhook_token}/'
        '?format=markdown&footer=Yes'.format(
            webhook_id=webhook_id, webhook_token=webhook_token)) is True

    # This call includes an image with it's payload:
    plugins.NotifyDiscord.discord_max_fields = 1

    assert a.notify(body=test_markdown,
                    title='title',
                    notify_type=NotifyType.INFO,
                    body_format=NotifyFormat.TEXT) is True

    # Throw an exception on the forth call to requests.post()
    # This allows to test our batch field processing
    response = mock.Mock()
    response.content = ''
    response.status_code = requests.codes.ok
    mock_post.return_value = response
    mock_post.side_effect = [
        response, response, response,
        requests.RequestException()
    ]

    # Test our markdown
    obj = Apprise.instantiate('discord://{}/{}/?format=markdown'.format(
        webhook_id, webhook_token))
    assert isinstance(obj, plugins.NotifyDiscord)
    assert obj.notify(body=test_markdown,
                      title='title',
                      notify_type=NotifyType.INFO) is False
    mock_post.side_effect = None

    # Empty String
    desc, results = obj.extract_markdown_sections("")
    assert isinstance(results, list) is True
    assert len(results) == 0

    # No desc details filled out
    assert isinstance(desc, six.string_types) is True
    assert not desc

    # String without Heading
    test_markdown = "Just a string without any header entries.\n" + \
        "A second line"
    desc, results = obj.extract_markdown_sections(test_markdown)
    assert isinstance(results, list) is True
    assert len(results) == 0

    # No desc details filled out
    assert isinstance(desc, six.string_types) is True
    assert desc == 'Just a string without any header entries.\n' + \
        'A second line'

    # Use our test markdown string during a notification
    assert obj.notify(
        body=test_markdown, title='title', notify_type=NotifyType.INFO) is True

    # Create an apprise instance
    a = Apprise()

    # Our processing is slightly different when we aren't using markdown
    # as we do not pre-parse content during our notifications
    assert a.add(
        'discord://{webhook_id}/{webhook_token}/'
        '?format=markdown&footer=Yes'.format(
            webhook_id=webhook_id, webhook_token=webhook_token)) is True

    # This call includes an image with it's payload:
    assert a.notify(body=test_markdown,
                    title='title',
                    notify_type=NotifyType.INFO,
                    body_format=NotifyFormat.TEXT) is True

    assert a.notify(body=test_markdown,
                    title='title',
                    notify_type=NotifyType.INFO,
                    body_format=NotifyFormat.MARKDOWN) is True

    # Toggle our logo availability
    a.asset.image_url_logo = None
    assert a.notify(body='body', title='title',
                    notify_type=NotifyType.INFO) is True
Exemple #13
0
def test_apprise():
    """
    API: Apprise() object

    """
    # Caling load matix a second time which is an internal function causes it
    # to skip over content already loaded into our matrix and thefore accesses
    # other if/else parts of the code that aren't otherwise called
    __load_matrix()

    a = Apprise()

    # no items
    assert (len(a) == 0)

    # Create an Asset object
    asset = AppriseAsset(theme='default')

    # We can load the device using our asset
    a = Apprise(asset=asset)

    # We can load our servers up front as well
    servers = [
        'faast://abcdefghijklmnop-abcdefg',
        'kodi://kodi.server.local',
        'palot://1f418df7577e32b89ac6511f2eb9aa68',
    ]

    a = Apprise(servers=servers)

    # 3 servers loaded
    assert (len(a) == 3)

    # We can add another server
    assert (a.add('mmosts://mattermost.server.local/'
                  '3ccdd113474722377935511fc85d3dd4') is True)
    assert (len(a) == 4)

    # We can empty our set
    a.clear()
    assert (len(a) == 0)

    # An invalid schema
    assert (a.add('this is not a parseable url at all') is False)
    assert (len(a) == 0)

    # An unsupported schema
    assert (a.add('invalid://we.just.do.not.support.this.plugin.type') is
            False)
    assert (len(a) == 0)

    # A poorly formatted URL
    assert (a.add('json://user:@@@:bad?no.good') is False)
    assert (len(a) == 0)

    # Add a server with our asset we created earlier
    assert (a.add(
        'mmosts://mattermost.server.local/'
        '3ccdd113474722377935511fc85d3dd4',
        asset=asset) is True)

    # Clear our server listings again
    a.clear()

    # No servers to notify
    assert (a.notify(title="my title", body="my body") is False)

    class BadNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(BadNotification, self).__init__()

            # We fail whenever we're initialized
            raise TypeError()

    class GoodNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(GoodNotification,
                  self).__init__(notify_format=NotifyFormat.HTML)

        def notify(self, **kwargs):
            # Pretend everything is okay
            return True

    # Store our bad notification in our schema map
    SCHEMA_MAP['bad'] = BadNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['good'] = GoodNotification

    # Just to explain what is happening here, we would have parsed the
    # url properly but failed when we went to go and create an instance
    # of it.
    assert (a.add('bad://localhost') is False)
    assert (len(a) == 0)

    assert (a.add('good://localhost') is True)
    assert (len(a) == 1)

    # Bad Notification Type is still allowed as it is presumed the user
    # know's what their doing
    assert (a.notify(title="my title", body="my body", notify_type='bad') is
            True)

    # No Title/Body combo's
    assert (a.notify(title=None, body=None) is False)
    assert (a.notify(title='', body=None) is False)
    assert (a.notify(title=None, body='') is False)

    # As long as one is present, we're good
    assert (a.notify(title=None, body='present') is True)
    assert (a.notify(title='present', body=None) is True)
    assert (a.notify(title="present", body="present") is True)

    # Clear our server listings again
    a.clear()

    class ThrowNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

    class RuntimeNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise RuntimeError()

    class FailNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            return False

    # Store our bad notification in our schema map
    SCHEMA_MAP['throw'] = ThrowNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['fail'] = FailNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['runtime'] = RuntimeNotification

    assert (a.add('runtime://localhost') is True)
    assert (a.add('throw://localhost') is True)
    assert (a.add('fail://localhost') is True)
    assert (len(a) == 3)

    # Test when our notify both throws an exception and or just
    # simply returns False
    assert (a.notify(title="present", body="present") is False)

    # Create a Notification that throws an unexected exception
    class ThrowInstantiateNotification(NotifyBase):
        def __init__(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

    SCHEMA_MAP['throw'] = ThrowInstantiateNotification

    # Reset our object
    a.clear()
    assert (len(a) == 0)

    # Instantiate a good object
    plugin = a.instantiate('good://localhost')
    assert (isinstance(plugin, NotifyBase))

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert (len(a) == 1)

    # Reset our object again
    a.clear()
    try:
        a.instantiate('throw://localhost', suppress_exceptions=False)
        assert (False)

    except TypeError:
        assert (True)
    assert (len(a) == 0)

    assert (a.instantiate('throw://localhost', suppress_exceptions=True) is
            None)
    assert (len(a) == 0)
Exemple #14
0
import logging
import traceback
from apprise import Apprise, AppriseConfig
from flask import Flask, request


app = Flask(__name__)

gw_config = AppriseConfig()
gw_config.add('file://apprise-config.yml')

gateway = Apprise()
gateway.add(gw_config)


@app.route('/push/<string:service>/send', methods=['POST'])
def push_send(service):
    """
    Emulating a RocketChat push gateway like the official https://gateway.rocket.chat
    Returns the service name (could be anything) along with a HTTP 200 OK status

    :see: Notification factory https://github.com/RocketChat/Rocket.Chat/blob/ed092fbe490c21d64c071772ce1da66515837353/app/push-notifications/server/lib/PushNotification.js#L6
    :see: Payload extension https://github.com/raix/push/blob/234eeb12daa9b553d246c0a6edd3d06d550aa41b/lib/common/notifications.js#L67
    :see: API call https://github.com/RocketChat/Rocket.Chat/blob/88c8c8c0b0e57e2d5d66a19d71775bc0b10f424c/server/lib/cordova.js#L97
    :return: string
    """

    app.logger.info('New notification received')
    app.logger.debug(request.headers)
    app.logger.debug(request.json)
Exemple #15
0
def test_apprise():
    """
    API: Apprise() object

    """
    # Caling load matix a second time which is an internal function causes it
    # to skip over content already loaded into our matrix and thefore accesses
    # other if/else parts of the code that aren't otherwise called
    __load_matrix()

    a = Apprise()

    # no items
    assert len(a) == 0

    # Apprise object can also be directly tested with 'if' keyword
    # No entries results in a False response
    assert not a

    # Create an Asset object
    asset = AppriseAsset(theme='default')

    # We can load the device using our asset
    a = Apprise(asset=asset)

    # We can load our servers up front as well
    servers = [
        'faast://abcdefghijklmnop-abcdefg',
        'kodi://kodi.server.local',
    ]

    a = Apprise(servers=servers)

    # 2 servers loaded
    assert len(a) == 2

    # Apprise object can also be directly tested with 'if' keyword
    # At least one entry results in a True response
    assert a

    # We can retrieve our URLs this way:
    assert len(a.urls()) == 2

    # We can add another server
    assert a.add('mmosts://mattermost.server.local/'
                 '3ccdd113474722377935511fc85d3dd4') is True
    assert len(a) == 3

    # Try adding nothing but delimiters
    assert a.add(',, ,, , , , ,') is False

    # The number of servers added doesn't change
    assert len(a) == 3

    # We can pop an object off of our stack by it's indexed value:
    obj = a.pop(0)
    assert isinstance(obj, NotifyBase) is True
    assert len(a) == 2

    # We can retrieve elements from our list too by reference:
    assert isinstance(a[0].url(), six.string_types) is True

    # We can iterate over our list too:
    count = 0
    for o in a:
        assert isinstance(o.url(), six.string_types) is True
        count += 1
    # verify that we did indeed iterate over each element
    assert len(a) == count

    # We can empty our set
    a.clear()
    assert len(a) == 0

    # An invalid schema
    assert a.add('this is not a parseable url at all') is False
    assert len(a) == 0

    # An unsupported schema
    assert a.add(
        'invalid://we.just.do.not.support.this.plugin.type') is False
    assert len(a) == 0

    # A poorly formatted URL
    assert a.add('json://user:@@@:bad?no.good') is False
    assert len(a) == 0

    # Add a server with our asset we created earlier
    assert a.add('mmosts://mattermost.server.local/'
                 '3ccdd113474722377935511fc85d3dd4', asset=asset) is True

    # Clear our server listings again
    a.clear()

    # No servers to notify
    assert a.notify(title="my title", body="my body") is False

    class BadNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(BadNotification, self).__init__(**kwargs)

            # We fail whenever we're initialized
            raise TypeError()

        def url(self):
            # Support URL
            return ''

        @staticmethod
        def parse_url(url, *args, **kwargs):
            # always parseable
            return NotifyBase.parse_url(url, verify_host=False)

    class GoodNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(GoodNotification, self).__init__(
                notify_format=NotifyFormat.HTML, **kwargs)

        def url(self):
            # Support URL
            return ''

        def send(self, **kwargs):
            # Pretend everything is okay
            return True

        @staticmethod
        def parse_url(url, *args, **kwargs):
            # always parseable
            return NotifyBase.parse_url(url, verify_host=False)

    # Store our bad notification in our schema map
    SCHEMA_MAP['bad'] = BadNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['good'] = GoodNotification

    # Just to explain what is happening here, we would have parsed the
    # url properly but failed when we went to go and create an instance
    # of it.
    assert a.add('bad://localhost') is False
    assert len(a) == 0

    assert a.add('good://localhost') is True
    assert len(a) == 1

    # Bad Notification Type is still allowed as it is presumed the user
    # know's what their doing
    assert a.notify(
        title="my title", body="my body", notify_type='bad') is True

    # No Title/Body combo's
    assert a.notify(title=None, body=None) is False
    assert a.notify(title='', body=None) is False
    assert a.notify(title=None, body='') is False

    # As long as one is present, we're good
    assert a.notify(title=None, body='present') is True
    assert a.notify(title='present', body=None) is True
    assert a.notify(title="present", body="present") is True

    # Send Attachment with success
    attach = join(TEST_VAR_DIR, 'apprise-test.gif')
    assert a.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Send the attachment as an AppriseAttachment object
    assert a.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=AppriseAttachment(attach)) is True

    # test a invalid attachment
    assert a.notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach='invalid://') is False

    # Repeat the same tests above...
    # however do it by directly accessing the object; this grants the similar
    # results:
    assert a[0].notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=attach) is True

    # Send the attachment as an AppriseAttachment object
    assert a[0].notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach=AppriseAttachment(attach)) is True

    # test a invalid attachment
    assert a[0].notify(
        body='body', title='test', notify_type=NotifyType.INFO,
        attach='invalid://') is False

    # Clear our server listings again
    a.clear()

    class ThrowNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    class RuntimeNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise RuntimeError()

        def url(self):
            # Support URL
            return ''

    class FailNotification(NotifyBase):

        def notify(self, **kwargs):
            # Pretend everything is okay
            return False

        def url(self):
            # Support URL
            return ''

    # Store our bad notification in our schema map
    SCHEMA_MAP['throw'] = ThrowNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['fail'] = FailNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['runtime'] = RuntimeNotification

    assert a.add('runtime://localhost') is True
    assert a.add('throw://localhost') is True
    assert a.add('fail://localhost') is True
    assert len(a) == 3

    # Test when our notify both throws an exception and or just
    # simply returns False
    assert a.notify(title="present", body="present") is False

    # Create a Notification that throws an unexected exception
    class ThrowInstantiateNotification(NotifyBase):
        def __init__(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    SCHEMA_MAP['throw'] = ThrowInstantiateNotification

    # Reset our object
    a.clear()
    assert len(a) == 0

    # Test our socket details
    # rto = Socket Read Timeout
    # cto = Socket Connect Timeout
    plugin = a.instantiate('good://localhost?rto=5.1&cto=10')
    assert isinstance(plugin, NotifyBase)
    assert plugin.socket_connect_timeout == 10.0
    assert plugin.socket_read_timeout == 5.1

    plugin = a.instantiate('good://localhost?rto=invalid&cto=invalid')
    assert isinstance(plugin, NotifyBase)
    assert plugin.socket_connect_timeout == URLBase.socket_connect_timeout
    assert plugin.socket_read_timeout == URLBase.socket_read_timeout

    # Reset our object
    a.clear()
    assert len(a) == 0

    # Instantiate a bad object
    plugin = a.instantiate(object, tag="bad_object")
    assert plugin is None

    # Instantiate a good object
    plugin = a.instantiate('good://localhost', tag="good")
    assert isinstance(plugin, NotifyBase)

    # Test simple tagging inside of the object
    assert "good" in plugin
    assert "bad" not in plugin

    # the in (__contains__ override) is based on or'ed content; so although
    # 'bad' isn't tagged as being in the plugin, 'good' is, so the return
    # value of this is True
    assert ["bad", "good"] in plugin
    assert set(["bad", "good"]) in plugin
    assert ("bad", "good") in plugin

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert len(a) == 1

    # We can add entries as a list too (to add more then one)
    a.add([plugin, plugin, plugin])
    assert len(a) == 4

    # Reset our object again
    a.clear()
    with pytest.raises(TypeError):
        a.instantiate('throw://localhost', suppress_exceptions=False)

    assert len(a) == 0

    assert a.instantiate(
        'throw://localhost', suppress_exceptions=True) is None
    assert len(a) == 0

    #
    # We rince and repeat the same tests as above, however we do them
    # using the dict version
    #

    # Reset our object
    a.clear()
    assert len(a) == 0

    # Instantiate a good object
    plugin = a.instantiate({
        'schema': 'good',
        'host': 'localhost'}, tag="good")
    assert isinstance(plugin, NotifyBase)

    # Test simple tagging inside of the object
    assert "good" in plugin
    assert "bad" not in plugin

    # the in (__contains__ override) is based on or'ed content; so although
    # 'bad' isn't tagged as being in the plugin, 'good' is, so the return
    # value of this is True
    assert ["bad", "good"] in plugin
    assert set(["bad", "good"]) in plugin
    assert ("bad", "good") in plugin

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert len(a) == 1

    # We can add entries as a list too (to add more then one)
    a.add([plugin, plugin, plugin])
    assert len(a) == 4

    # Reset our object again
    a.clear()
    with pytest.raises(TypeError):
        a.instantiate({
            'schema': 'throw',
            'host': 'localhost'}, suppress_exceptions=False)

    assert len(a) == 0

    assert a.instantiate({
        'schema': 'throw',
        'host': 'localhost'}, suppress_exceptions=True) is None
    assert len(a) == 0
Exemple #16
0
    with open(twitch_category_id_file) as json_file:
        twitch_category_ids = load(json_file)
else:
    twitch_category_ids = {}

# get data on which channels are currently streaming
live_data = liveStreams(config_data["channels"])

# find streams that have started within the threshold.
time_offset = datetime.utcnow() - timedelta(minutes=config_data["offset"])
new_stream = namedtuple("data", "channel category")
new_streams = []

for x in (x for x in live_data
          if time_offset < twitchTime(live_data[x]["started_at"])):
    new_streams.append(
        new_stream(live_data[x]["user_name"], live_data[x]["category_name"]))

# send notifications if new streams have started
if new_streams:
    apobj = Apprise()
    for notification in config_data["notifications"]:
        apobj.add(notification)
    message = buildMessage(new_streams)
    apobj.notify(title="Twitch", body=message)

# save twitch_category_id_file only if new game has been added
if rewrite_twitch_category_ids is True:
    with open(twitch_category_id_file, "w") as f:
        dump(twitch_category_ids, f, ensure_ascii=False)
def test_plugin_signal_test_based_on_feedback(mock_post):
    """
    NotifySignalAPI() User Feedback Test

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Prepare our response
    response = requests.Request()
    response.status_code = requests.codes.ok

    # Prepare Mock
    mock_post.return_value = response

    body = "test body"
    title = "My Title"

    aobj = Apprise()
    aobj.add(
        'signal://10.0.0.112:8080/+12512222222/+12513333333/'
        '12514444444?batch=yes')

    assert aobj.notify(title=title, body=body)

    # If a batch, there is only 1 post
    assert mock_post.call_count == 1

    details = mock_post.call_args_list[0]
    assert details[0][0] == 'http://10.0.0.112:8080/v2/send'
    payload = loads(details[1]['data'])
    assert payload['message'] == 'My Title\r\ntest body'
    assert payload['number'] == "+12512222222"
    assert len(payload['recipients']) == 2
    assert "+12513333333" in payload['recipients']
    # The + is appended
    assert "+12514444444" in payload['recipients']

    # Reset our test and turn batch mode off
    mock_post.reset_mock()

    aobj = Apprise()
    aobj.add(
        'signal://10.0.0.112:8080/+12512222222/+12513333333/'
        '12514444444?batch=no')

    assert aobj.notify(title=title, body=body)

    # If a batch, there is only 1 post
    assert mock_post.call_count == 2

    details = mock_post.call_args_list[0]
    assert details[0][0] == 'http://10.0.0.112:8080/v2/send'
    payload = loads(details[1]['data'])
    assert payload['message'] == 'My Title\r\ntest body'
    assert payload['number'] == "+12512222222"
    assert len(payload['recipients']) == 1
    assert "+12513333333" in payload['recipients']

    details = mock_post.call_args_list[1]
    assert details[0][0] == 'http://10.0.0.112:8080/v2/send'
    payload = loads(details[1]['data'])
    assert payload['message'] == 'My Title\r\ntest body'
    assert payload['number'] == "+12512222222"
    assert len(payload['recipients']) == 1

    # The + is appended
    assert "+12514444444" in payload['recipients']

    mock_post.reset_mock()

    # Test group names
    aobj = Apprise()
    aobj.add(
        'signal://10.0.0.112:8080/+12513333333/@group1/@group2/'
        '12514444444?batch=yes')

    assert aobj.notify(title=title, body=body)

    # If a batch, there is only 1 post
    assert mock_post.call_count == 1

    details = mock_post.call_args_list[0]
    assert details[0][0] == 'http://10.0.0.112:8080/v2/send'
    payload = loads(details[1]['data'])
    assert payload['message'] == 'My Title\r\ntest body'
    assert payload['number'] == "+12513333333"
    assert len(payload['recipients']) == 3
    assert "+12514444444" in payload['recipients']
    # our groups
    assert "group.group1" in payload['recipients']
    assert "group.group2" in payload['recipients']
    # Groups are stored properly
    assert '/@group1' in aobj[0].url()
    assert '/@group2' in aobj[0].url()
    # Our target phone number is also in the path
    assert '/+12514444444' in aobj[0].url()
Exemple #18
0
def test_apprise_notify_formats(tmpdir):
    """
    API: Apprise() TextFormat tests

    """
    # Caling load matix a second time which is an internal function causes it
    # to skip over content already loaded into our matrix and thefore accesses
    # other if/else parts of the code that aren't otherwise called
    __load_matrix()

    a = Apprise()

    # no items
    assert (len(a) == 0)

    class TextNotification(NotifyBase):
        # set our default notification format
        notify_format = NotifyFormat.TEXT

        def __init__(self, **kwargs):
            super(TextNotification, self).__init__()

        def notify(self, **kwargs):
            # Pretend everything is okay
            return True

        def url(self):
            # Support URL
            return ''

    class HtmlNotification(NotifyBase):

        # set our default notification format
        notify_format = NotifyFormat.HTML

        def __init__(self, **kwargs):
            super(HtmlNotification, self).__init__()

        def notify(self, **kwargs):
            # Pretend everything is okay
            return True

        def url(self):
            # Support URL
            return ''

    class MarkDownNotification(NotifyBase):

        # set our default notification format
        notify_format = NotifyFormat.MARKDOWN

        def __init__(self, **kwargs):
            super(MarkDownNotification, self).__init__()

        def notify(self, **kwargs):
            # Pretend everything is okay
            return True

        def url(self):
            # Support URL
            return ''

    # Store our notifications into our schema map
    SCHEMA_MAP['text'] = TextNotification
    SCHEMA_MAP['html'] = HtmlNotification
    SCHEMA_MAP['markdown'] = MarkDownNotification

    # Test Markdown; the above calls the markdown because our good://
    # defined plugin above was defined to default to HTML which triggers
    # a markdown to take place if the body_format specified on the notify
    # call
    assert (a.add('html://localhost') is True)
    assert (a.add('html://another.server') is True)
    assert (a.add('html://and.another') is True)
    assert (a.add('text://localhost') is True)
    assert (a.add('text://another.server') is True)
    assert (a.add('text://and.another') is True)
    assert (a.add('markdown://localhost') is True)
    assert (a.add('markdown://another.server') is True)
    assert (a.add('markdown://and.another') is True)

    assert (len(a) == 9)

    assert (a.notify(title="markdown",
                     body="## Testing Markdown",
                     body_format=NotifyFormat.MARKDOWN) is True)

    assert (a.notify(title="text",
                     body="Testing Text",
                     body_format=NotifyFormat.TEXT) is True)

    assert (a.notify(title="html",
                     body="<b>HTML</b>",
                     body_format=NotifyFormat.HTML) is True)
def test_aws_topic_handling(mock_post):
    """
    API: NotifySNS Plugin() AWS Topic Handling

    """
    # Disable Throttling to speed testing
    plugins.NotifySNS.request_rate_per_sec = 0

    arn_response = \
        """
         <CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
           <CreateTopicResult>
             <TopicArn>arn:aws:sns:us-east-1:000000000000:abcd</TopicArn>
                </CreateTopicResult>
            <ResponseMetadata>
                <RequestId>604bef0f-369c-50c5-a7a4-bbd474c83d6a</RequestId>
            </ResponseMetadata>
        </CreateTopicResponse>
        """

    def post(url, data, **kwargs):
        """
        Since Publishing a token requires 2 posts, we need to return our
        response depending on what step we're on
        """

        # A request
        robj = mock.Mock()
        robj.text = ''
        robj.status_code = requests.codes.ok

        if data.find('=CreateTopic') >= 0:
            # Topic Post Failure
            robj.status_code = requests.codes.bad_request

        return robj

    # Assign ourselves a new function
    mock_post.side_effect = post

    # Create our object
    a = Apprise()

    a.add([
        # Single Topic
        'sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnl/us-west-2/TopicA',
        # Multi-Topic
        'sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnl/us-east-1/TopicA/TopicB/'
        # Topic-Mix
        'sns://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkce/us-west-2/' \
        '12223334444/TopicA'])

    # CreateTopic fails
    assert(a.notify(title='', body='test') is False)

    def post(url, data, **kwargs):
        """
        Since Publishing a token requires 2 posts, we need to return our
        response depending on what step we're on
        """

        # A request
        robj = mock.Mock()
        robj.text = ''
        robj.status_code = requests.codes.ok

        if data.find('=CreateTopic') >= 0:
            robj.text = arn_response

        # Manipulate Topic Publishing only (not phone)
        elif data.find('=Publish') >= 0 and data.find('TopicArn=') >= 0:
            # Topic Post Failure
            robj.status_code = requests.codes.bad_request

        return robj

    # Assign ourselves a new function
    mock_post.side_effect = post

    # Publish fails
    assert(a.notify(title='', body='test') is False)

    # Disable our side effect
    mock_post.side_effect = None

    # Handle case where TopicArn is missing:
    robj = mock.Mock()
    robj.text = "<CreateTopicResponse></CreateTopicResponse>"
    robj.status_code = requests.codes.ok

    # Assign ourselves a new function
    mock_post.return_value = robj
    assert(a.notify(title='', body='test') is False)

    # Handle case where we fails get a bad response
    robj = mock.Mock()
    robj.text = ''
    robj.status_code = requests.codes.bad_request
    mock_post.return_value = robj
    assert(a.notify(title='', body='test') is False)

    # Handle case where we get a valid response and TopicARN
    robj = mock.Mock()
    robj.text = arn_response
    robj.status_code = requests.codes.ok
    mock_post.return_value = robj
    # We would have failed to make Post
    assert(a.notify(title='', body='test') is True)
def test_plugin_telegram_html_formatting(mock_post):
    """
    NotifyTelegram() HTML Formatting

    """
    # on't send anything other than <b>, <i>, <a>,<code> and <pre>

    # Disable Throttling to speed testing
    plugins.NotifyTelegram.request_rate_per_sec = 0

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok
    mock_post.return_value.content = '{}'

    # Simple success response
    mock_post.return_value.content = dumps({
        "ok":
        True,
        "result": [
            {
                "update_id": 645421321,
                "message": {
                    "message_id":
                    2,
                    "from": {
                        "id": 532389719,
                        "is_bot": False,
                        "first_name": "Chris",
                        "language_code": "en-US"
                    },
                    "chat": {
                        "id": 532389719,
                        "first_name": "Chris",
                        "type": "private"
                    },
                    "date":
                    1519694394,
                    "text":
                    "/start",
                    "entities": [{
                        "offset": 0,
                        "length": 6,
                        "type": "bot_command",
                    }],
                }
            },
        ],
    })
    mock_post.return_value.status_code = requests.codes.ok

    aobj = Apprise()
    aobj.add('tgram://123456789:abcdefg_hijklmnop/')

    assert len(aobj) == 1

    assert isinstance(aobj[0], plugins.NotifyTelegram)

    # Test our HTML Conversion
    title = '<title>&apos;information&apos</title>'
    body = '<em>&quot;This is in Italic&quot</em><br/>' \
           '<h5>&emsp;&emspHeadings&nbsp;are dropped and' \
           '&nbspconverted to bold</h5>'

    assert aobj.notify(title=title, body=body, body_format=NotifyFormat.HTML)

    # 1 call to look up bot owner, and second for notification
    assert mock_post.call_count == 2

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'] == \
        '<b>\r\n<b>\'information\'</b>\r\n</b>\r\n<i>"This is in Italic"' \
        '</i>\r\n<b>      Headings are dropped and converted to bold</b>\r\n'

    mock_post.reset_mock()

    assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT)

    # owner has already been looked up, so only one call is made
    assert mock_post.call_count == 1

    payload = loads(mock_post.call_args_list[0][1]['data'])

    assert payload['text'] == \
        '<b>&lt;title&gt;&amp;apos;information&amp;apos&lt;/title&gt;</b>' \
        '\r\n&lt;em&gt;&amp;quot;This is in Italic&amp;quot&lt;/em&gt;&lt;' \
        'br/&gt;&lt;h5&gt;&amp;emsp;&amp;emspHeadings&amp;nbsp;are ' \
        'dropped and&amp;nbspconverted to bold&lt;/h5&gt;'

    # Lest test more complex HTML examples now
    mock_post.reset_mock()

    test_file_01 = os.path.join(TEST_VAR_DIR, '01_test_example.html')
    with open(test_file_01) as html_file:
        assert aobj.notify(body=html_file.read(),
                           body_format=NotifyFormat.HTML)

    # owner has already been looked up, so only one call is made
    assert mock_post.call_count == 1

    payload = loads(mock_post.call_args_list[0][1]['data'])
    assert payload['text'] == \
        '\r\n<b>Bootstrap 101 Template</b>\r\n<b>My Title</b>\r\n' \
        '<b>Heading 1</b>\r\n-Bullet 1\r\n-Bullet 2\r\n-Bullet 3\r\n' \
        '-Bullet 1\r\n-Bullet 2\r\n-Bullet 3\r\n<b>Heading 2</b>\r\n' \
        'A div entry\r\nA div entry\r\n<code>A pre entry</code>\r\n' \
        '<b>Heading 3</b>\r\n<b>Heading 4</b>\r\n<b>Heading 5</b>\r\n' \
        '<b>Heading 6</b>\r\nA set of text\r\n' \
        'Another line after the set of text\r\nMore text\r\nlabel'
Exemple #21
0
def test_apprise_config_with_apprise_obj(tmpdir):
    """
    API: ConfigBase - parse valid config

    """

    # temporary file to work with
    t = tmpdir.mkdir("apprise-obj").join("apprise")
    buf = """
    good://hostname
    localhost=good://localhost
    """
    t.write(buf)

    # Define our good:// url
    class GoodNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(GoodNotification, self).__init__(
                notify_format=NotifyFormat.HTML, **kwargs)

        def notify(self, **kwargs):
            # Pretend everything is okay
            return True

        def url(self, **kwargs):
            # support url()
            return ''

    # Store our good notification in our schema map
    NOTIFY_SCHEMA_MAP['good'] = GoodNotification

    # Create ourselves a config object
    ac = AppriseConfig(cache=False)

    # Nothing loaded yet
    assert len(ac) == 0

    # Add an item associated with tag a
    assert ac.add(configs=str(t), asset=AppriseAsset(), tag='a') is True

    # One configuration file
    assert len(ac) == 1

    # 2 services found in it
    assert len(ac.servers()) == 2

    # Pop one of them (at index 0)
    ac.server_pop(0)

    # Verify that it no longer listed
    assert len(ac.servers()) == 1

    # Test our ability to add Config objects to our apprise object
    a = Apprise()

    # Add our configuration object
    assert a.add(servers=ac) is True

    # Detect our 1 entry (originally there were 2 but we deleted one)
    assert len(a) == 1

    # Notify our service
    assert a.notify(body='apprise configuration power!') is True

    # Add our configuration object
    assert a.add(
        servers=[AppriseConfig(str(t)), AppriseConfig(str(t))]) is True

    # Detect our 5 loaded entries now; 1 from first config, and another
    # 2x2 based on adding our list above
    assert len(a) == 5

    # We can't add garbage
    assert a.add(servers=object()) is False
    assert a.add(servers=[object(), object()]) is False

    # Our length is unchanged
    assert len(a) == 5

    # reference index 0 of our list
    ref = a[0]
    assert isinstance(ref, NotifyBase) is True

    # Our length is unchanged
    assert len(a) == 5

    # pop the index
    ref_popped = a.pop(0)

    # Verify our response
    assert isinstance(ref_popped, NotifyBase) is True

    # Our length drops by 1
    assert len(a) == 4

    # Content popped is the same as one referenced by index
    # earlier
    assert ref == ref_popped

    # pop an index out of range
    try:
        a.pop(len(a))
        # We'll thrown an IndexError and not make it this far
        assert False

    except IndexError:
        # As expected
        assert True

    # Our length remains unchanged
    assert len(a) == 4

    # Reference content out of range
    try:
        a[len(a)]

        # We'll thrown an IndexError and not make it this far
        assert False

    except IndexError:
        # As expected
        assert True

    # reference index at the end of our list
    ref = a[len(a) - 1]

    # Verify our response
    assert isinstance(ref, NotifyBase) is True

    # Our length stays the same
    assert len(a) == 4

    # We can pop from the back of the list without a problem too
    ref_popped = a.pop(len(a) - 1)

    # Verify our response
    assert isinstance(ref_popped, NotifyBase) is True

    # Content popped is the same as one referenced by index
    # earlier
    assert ref == ref_popped

    # Our length drops by 1
    assert len(a) == 3

    # Now we'll test adding another element to the list so that it mixes up
    # our response object.
    # Below we add 3 different types, a ConfigBase, NotifyBase, and URL
    assert a.add(
        servers=[
            ConfigFile(path=(str(t))),
            'good://another.host',
            GoodNotification(**{'host': 'nuxref.com'})]) is True

    # Our length increases by 4 (2 entries in the config file, + 2 others)
    assert len(a) == 7

    # reference index at the end of our list
    ref = a[len(a) - 1]

    # Verify our response
    assert isinstance(ref, NotifyBase) is True

    # We can pop from the back of the list without a problem too
    ref_popped = a.pop(len(a) - 1)

    # Verify our response
    assert isinstance(ref_popped, NotifyBase) is True

    # Content popped is the same as one referenced by index
    # earlier
    assert ref == ref_popped

    # Our length drops by 1
    assert len(a) == 6

    # pop our list
    while len(a) > 0:
        assert isinstance(a.pop(len(a) - 1), NotifyBase) is True
Exemple #22
0
def test_apprise_secure_logging(mock_post):
    """
    API: Apprise() secure logging tests
    """

    # Ensure we're not running in a disabled state
    logging.disable(logging.NOTSET)

    logger.setLevel(logging.CRITICAL)

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok

    # Default Secure Logging is set to enabled
    asset = AppriseAsset()
    assert asset.secure_logging is True

    # Load our asset
    a = Apprise(asset=asset)

    with LogCapture(level=logging.DEBUG) as stream:
        # add a test server
        assert a.add("json://*****:*****@localhost") is True

        # Our servers should carry this flag
        a[0].asset.secure_logging is True

        logs = re.split(r'\r*\n', stream.getvalue().rstrip())
        assert len(logs) == 1
        entry = re.split(r'\s-\s', logs[0])
        assert len(entry) == 3
        assert entry[1] == 'DEBUG'
        assert entry[2].startswith(
            'Loaded JSON URL: json://user:****@localhost/')

    # Send notification
    assert a.notify("test") is True

    # Test our call count
    assert mock_post.call_count == 1

    # Reset
    mock_post.reset_mock()

    # Now we test the reverse configuration and turn off
    # secure logging.

    # Default Secure Logging is set to disable
    asset = AppriseAsset(secure_logging=False)
    assert asset.secure_logging is False

    # Load our asset
    a = Apprise(asset=asset)

    with LogCapture(level=logging.DEBUG) as stream:
        # add a test server
        assert a.add("json://*****:*****@localhost") is True

        # Our servers should carry this flag
        a[0].asset.secure_logging is False

        logs = re.split(r'\r*\n', stream.getvalue().rstrip())
        assert len(logs) == 1
        entry = re.split(r'\s-\s', logs[0])
        assert len(entry) == 3
        assert entry[1] == 'DEBUG'

        # Note that our password is no longer escaped (it is however
        # url encoded)
        assert entry[2].startswith(
            'Loaded JSON URL: json://user:pass1%24-3%21@localhost/')

    # Disable Logging
    logging.disable(logging.CRITICAL)
Exemple #23
0
def test_apprise_asyncio_runtime_error():
    """
    API: Apprise() AsyncIO RuntimeError handling

    """
    class GoodNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(GoodNotification,
                  self).__init__(notify_format=NotifyFormat.HTML, **kwargs)

        def url(self):
            # Support URL
            return ''

        def send(self, **kwargs):
            # Pretend everything is okay
            return True

        @staticmethod
        def parse_url(url, *args, **kwargs):
            # always parseable
            return NotifyBase.parse_url(url, verify_host=False)

    # Store our good notification in our schema map
    SCHEMA_MAP['good'] = GoodNotification

    # Create ourselves an Apprise object
    a = Apprise()

    # Add a few entries
    for _ in range(25):
        a.add('good://')

    # Python v3.6 and lower can't handle situations gracefully when an
    # event_loop isn't already established(). Test that Apprise can handle
    # these situations
    import asyncio

    # Get our event loop
    loop = asyncio.get_event_loop()

    # Adjust out event loop to not point at anything
    asyncio.set_event_loop(None)

    # With the event loop inactive, we'll fail trying to get the active loop
    with pytest.raises(RuntimeError):
        asyncio.get_event_loop()

    try:
        # Below, we internally will throw a RuntimeError() since there will
        # be no active event_loop in place. However internally it will be smart
        # enough to create a new event loop and continue...
        assert a.notify(title="title", body="body") is True

        # Verify we have an active event loop
        new_loop = asyncio.get_event_loop()

        # We didn't throw an exception above; thus we have an event loop at
        # this point
        assert new_loop

        # Close off the internal loop created inside a.notify()
        new_loop.close()

    finally:
        # Restore our event loop (in the event the above test failed)
        asyncio.set_event_loop(loop)
def test_plugin_telegram_formating_py2(mock_post):
    """
    NotifyTelegram() Python v2 Formatting Tests

    """

    # Disable Throttling to speed testing
    plugins.NotifyTelegram.request_rate_per_sec = 0

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok
    mock_post.return_value.content = '{}'

    # Simple success response
    mock_post.return_value.content = dumps({
        "ok":
        True,
        "result": [
            {
                "update_id": 645421321,
                "message": {
                    "message_id":
                    2,
                    "from": {
                        "id": 532389719,
                        "is_bot": False,
                        "first_name": "Chris",
                        "language_code": "en-US"
                    },
                    "chat": {
                        "id": 532389719,
                        "first_name": "Chris",
                        "type": "private"
                    },
                    "date":
                    1519694394,
                    "text":
                    "/start",
                    "entities": [{
                        "offset": 0,
                        "length": 6,
                        "type": "bot_command",
                    }],
                }
            },
        ],
    })
    mock_post.return_value.status_code = requests.codes.ok

    results = plugins.NotifyTelegram.parse_url(
        'tgram://123456789:abcdefg_hijklmnop/')

    instance = plugins.NotifyTelegram(**results)
    assert isinstance(instance, plugins.NotifyTelegram)

    response = instance.send(title='title', body='body')
    assert response is True
    # 1 call to look up bot owner, and second for notification
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage'

    # Reset our values
    mock_post.reset_mock()

    # Now test our HTML Conversion as TEXT)
    aobj = Apprise()
    aobj.add('tgram://123456789:abcdefg_hijklmnop/')
    assert len(aobj) == 1

    title = '馃毃 Change detected for <i>Apprise Test Title</i>'
    body = '<a href="http://localhost"><i>Apprise Body Title</i></a>' \
           ' had <a href="http://127.0.0.1">a change</a>'

    assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a TEXT mode
    assert payload['text'].encode('utf-8') == \
        '<b>\xf0\x9f\x9a\xa8 Change detected for &lt;i&gt;' \
        'Apprise Test Title&lt;/i&gt;</b>\r\n&lt;' \
        'a href="http://localhost"&gt;&lt;i&gt;Apprise Body Title' \
        '&lt;/i&gt;&lt;/a&gt; had &lt;a href="http://127.0.0.1"' \
        '&gt;a change&lt;/a&gt;'

    # Reset our values
    mock_post.reset_mock()

    # Now test our HTML Conversion as TEXT)
    aobj = Apprise()
    aobj.add('tgram://123456789:abcdefg_hijklmnop/?format=html')
    assert len(aobj) == 1

    assert aobj.notify(title=title, body=body, body_format=NotifyFormat.HTML)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'].encode('utf-8') == \
        '<b>\xf0\x9f\x9a\xa8 Change detected for <i>Apprise Test Title</i>' \
        '</b>\r\n<a href="http://localhost"><i>Apprise Body Title</i></a> ' \
        'had <a href="http://127.0.0.1">a change</a>'

    # Reset our values
    mock_post.reset_mock()

    # Now test our MARKDOWN Handling
    title = '# 馃毃 Change detected for _Apprise Test Title_'
    body = '_[Apprise Body Title](http://localhost)_' \
           ' had [a change](http://127.0.0.1)'

    aobj = Apprise()
    aobj.add('tgram://123456789:abcdefg_hijklmnop/?format=markdown')
    assert len(aobj) == 1

    assert aobj.notify(title=title,
                       body=body,
                       body_format=NotifyFormat.MARKDOWN)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'].encode('utf-8') == \
        '# \xf0\x9f\x9a\xa8 Change detected for _Apprise Test Title_\r\n_' \
        '[Apprise Body Title](http://localhost)_ had ' \
        '[a change](http://127.0.0.1)'

    # Reset our values
    mock_post.reset_mock()

    # Upstream to use HTML but input specified as Markdown
    aobj = Apprise()
    aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=html')
    assert len(aobj) == 1

    # HTML forced by the command line, but MARKDOWN specified as
    # upstream mode
    assert aobj.notify(title=title,
                       body=body,
                       body_format=NotifyFormat.MARKDOWN)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'].encode('utf-8') == \
        '<b>\r\n<b>\xf0\x9f\x9a\xa8 Change detected for ' \
        '<i>Apprise Test Title</i></b>\r\n</b>\r\n<i>' \
        '<a href="http://localhost">Apprise Body Title</a>' \
        '</i> had <a href="http://127.0.0.1">a change</a>\r\n'

    # Reset our values
    mock_post.reset_mock()

    # Now test hebrew types (outside of default utf-8)
    # 讻讜转专转 谞驻诇讗讛 translates to 'A wonderful title'
    # 讝讜 讛讜讚注讛 translates to 'This is a notification'
    title = '讻讜转专转 谞驻诇讗讛' \
            .decode('utf-8').encode('ISO-8859-8')
    body = '[_[讝讜 讛讜讚注讛](http://localhost)_' \
           .decode('utf-8').encode('ISO-8859-8')

    asset = AppriseAsset(encoding='utf-8')
    # Now test default entries
    aobj = Apprise(asset=asset)
    aobj.add('tgram://123456789:abcdefg_hijklmnop/')
    assert len(aobj) == 1

    # Our notification will fail because we'll have an encoding error
    assert not aobj.notify(title=title, body=body)
    # Nothing was even attempted to be notified
    assert mock_post.call_count == 0

    # Let's use the expected input
    asset = AppriseAsset(encoding='ISO-8859-8')

    # Now test default entries
    aobj = Apprise(asset=asset)

    aobj.add('tgram://123456789:abcdefg_hijklmnop/')
    assert len(aobj) == 1

    # Our notification will work now
    assert aobj.notify(title=title, body=body)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'].encode('utf-8') == \
        '<b>\xd7\x9b\xd7\x95\xd7\xaa\xd7\xa8\xd7\xaa '\
        '\xd7\xa0\xd7\xa4\xd7\x9c\xd7\x90\xd7\x94</b>\r\n[_[\xd7\x96\xd7\x95 '\
        '\xd7\x94\xd7\x95\xd7\x93\xd7\xa2\xd7\x94](http://localhost)_'

    # Now we'll test an edge case where a title was defined, but after
    # processing it, it was determiend there really wasn't anything there
    # at all at the end of the day.

    # Reset our values
    mock_post.reset_mock()

    # Upstream to use HTML but input specified as Markdown
    aobj = Apprise()
    aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=markdown')
    assert len(aobj) == 1

    # Now test our MARKDOWN Handling (no title defined... not really anyway)
    title = '# '
    body = '_[Apprise Body Title](http://localhost)_' \
           ' had [a change](http://127.0.0.2)'

    # MARKDOWN forced by the command line, but TEXT specified as
    # upstream mode
    assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'] == \
        '_[Apprise Body Title](http://localhost)_ had ' \
        '[a change](http://127.0.0.2)'

    # Reset our values
    mock_post.reset_mock()

    # Upstream to use HTML but input specified as Markdown
    aobj = Apprise()
    aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=markdown')
    assert len(aobj) == 1

    # Set an actual title this time
    title = '# A Great Title'
    body = '_[Apprise Body Title](http://localhost)_' \
           ' had [a change](http://127.0.0.2)'

    # MARKDOWN forced by the command line, but TEXT specified as
    # upstream mode
    assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT)

    # Test our calls
    assert mock_post.call_count == 2

    assert mock_post.call_args_list[0][0][0] == \
        'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates'
    assert mock_post.call_args_list[1][0][0] == \
        'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage'

    payload = loads(mock_post.call_args_list[1][1]['data'])

    # Test that everything is escaped properly in a HTML mode
    assert payload['text'] == \
        '# A Great Title\r\n_[Apprise Body Title](http://localhost)_ had ' \
        '[a change](http://127.0.0.2)'
def construct_apprise_object():
    apprise = Apprise()
    apprise.add(f"prowl://{PROWL_API_KEY}")
    return apprise
Exemple #26
0
def test_apprise_tagging(mock_post, mock_get):
    """
    API: Apprise() object tagging functionality

    """

    # A request
    robj = mock.Mock()
    setattr(robj, 'raw', mock.Mock())
    # Allow raw.read() calls
    robj.raw.read.return_value = ''
    robj.text = ''
    robj.content = ''
    mock_get.return_value = robj
    mock_post.return_value = robj

    # Simulate a successful notification
    mock_get.return_value.status_code = requests.codes.ok
    mock_post.return_value.status_code = requests.codes.ok

    # Create our object
    a = Apprise()

    # An invalid addition can't add the tag
    assert (a.add('averyinvalidschema://localhost', tag='uhoh') is False)
    assert (a.add({
        'schema': 'averyinvalidschema',
        'host': 'localhost'
    },
                  tag='uhoh') is False)

    # Add entry and assign it to a tag called 'awesome'
    assert (a.add('json://localhost/path1/', tag='awesome') is True)
    assert (a.add(
        {
            'schema': 'json',
            'host': 'localhost',
            'fullpath': '/path1/'
        },
        tag='awesome') is True)

    # Add another notification and assign it to a tag called 'awesome'
    # and another tag called 'local'
    assert (a.add('json://localhost/path2/', tag=['mmost', 'awesome']) is True)

    # notify the awesome tag; this would notify both services behind the
    # scenes
    assert (a.notify(title="my title", body="my body", tag='awesome') is True)

    # notify all of the tags
    assert (a.notify(
        title="my title", body="my body", tag=['awesome', 'mmost']) is True)

    # there is nothing to notify using tags 'missing'. However we intentionally
    # don't fail as there is value in identifying a tag that simply have
    # nothing to notify from while the object itself contains items
    assert (a.notify(title="my title", body="my body", tag='missing') is True)

    # Now to test the ability to and and/or notifications
    a = Apprise()

    # Add a tag by tuple
    assert (a.add('json://localhost/tagA/', tag=("TagA", )) is True)
    # Add 2 tags by string
    assert (a.add('json://localhost/tagAB/', tag="TagA, TagB") is True)
    # Add a tag using a set
    assert (a.add('json://localhost/tagB/', tag=set(["TagB"])) is True)
    # Add a tag by string (again)
    assert (a.add('json://localhost/tagC/', tag="TagC") is True)
    # Add 2 tags using a list
    assert (a.add('json://localhost/tagCD/', tag=["TagC", "TagD"]) is True)
    # Add a tag by string (again)
    assert (a.add('json://localhost/tagD/', tag="TagD") is True)
    # add a tag set by set (again)
    assert (a.add('json://localhost/tagCDE/',
                  tag=set(["TagC", "TagD", "TagE"])) is True)

    # Expression: TagC and TagD
    # Matches the following only:
    #   - json://localhost/tagCD/
    #   - json://localhost/tagCDE/
    assert (a.notify(title="my title", body="my body", tag=[('TagC', 'TagD')])
            is True)

    # Expression: (TagY and TagZ) or TagX
    # Matches nothing
    assert (a.notify(title="my title",
                     body="my body",
                     tag=[('TagY', 'TagZ'), 'TagX']) is True)

    # Expression: (TagY and TagZ) or TagA
    # Matches the following only:
    #   - json://localhost/tagAB/
    assert (a.notify(title="my title",
                     body="my body",
                     tag=[('TagY', 'TagZ'), 'TagA']) is True)

    # Expression: (TagE and TagD) or TagB
    # Matches the following only:
    #   - json://localhost/tagCDE/
    #   - json://localhost/tagAB/
    #   - json://localhost/tagB/
    assert (a.notify(title="my title",
                     body="my body",
                     tag=[('TagE', 'TagD'), 'TagB']) is True)

    # Garbage Entries
    assert (a.notify(title="my title", body="my body", tag=[
        (object, ),
    ]) is True)
Exemple #27
0
    def notify(self, servers, body, title, notify_type=NotifyType.INFO,
               body_format=NotifyFormat.MARKDOWN):
        """
        processes list of servers specified
        """

        # Apprise Asset Object
        asset = AppriseAsset(theme=self.default_theme)
        asset.app_id = 'NZB-Notify'
        asset.app_desc = 'NZB Notification'
        asset.app_url = 'https://github.com/caronc/nzb-notify'

        # Source Theme from GitHub Page
        asset.image_url_mask = 'https://raw.githubusercontent.com' \
                               '/caronc/nzb-notify/master/Notify' \
                               '/apprise-theme/{THEME}/apprise-{TYPE}-{XY}.png'

        asset.image_path_mask = join(
            dirname(__file__),
            'Notify', 'apprise-theme', '{THEME}',
            'apprise-{TYPE}-{XY}.png')

        # Include Image Flag
        _url = self.parse_url(self.get('IncludeImage'))

        # Define some globals to use in this function
        image_path = None
        image_url = None

        if _url:
            # Toggle our include image flag right away to True
            include_image = True

            # Get some more details
            if not re.match('^(https?|file)$', _url['schema'] ,re.IGNORECASE):
                self.logger.error(
                    'An invalid image url protocol (%s://) was specified.' % \
                     _url['schema'],
                )
                return False

            if _url['schema'] == 'file':
                if not isile(_url['fullpath']):
                    self.logger.error(
                        'The specified file %s was not found.' % \
                        _url['fullpath'],
                    )
                    return False
                image_path = _url['fullpath']

            else:
                # We're dealing with a web request
                image_url = _url['url']

        else:
            # Dealing with the old way of doing things; just toggling a true/false
            # flag
            include_image = self.parse_bool(self.get('IncludeImage'), False)

        if isinstance(servers, basestring):
            # servers can be a list of URLs, or it can be
            # a string which will be parsed into this list
            # we wanted.
            servers = self.parse_list(self.get('Servers', ''))

        # Create our apprise object
        a = Apprise(asset=asset)

        for server in servers:

            # Add our URL
            if not a.add(server):
                # Validation Failure
                self.logger.error(
                    'Could not initialize %s instance.' % server,
                )
                continue

        # Notify our servers
        a.notify(body=body, title=title, notify_type=notify_type,
                 body_format=body_format)

        # Always return true
        return True
Exemple #28
0
def test_apprise():
    """
    API: Apprise() object

    """
    # Caling load matix a second time which is an internal function causes it
    # to skip over content already loaded into our matrix and thefore accesses
    # other if/else parts of the code that aren't otherwise called
    __load_matrix()

    a = Apprise()

    # no items
    assert (len(a) == 0)

    # Create an Asset object
    asset = AppriseAsset(theme='default')

    # We can load the device using our asset
    a = Apprise(asset=asset)

    # We can load our servers up front as well
    servers = [
        'faast://abcdefghijklmnop-abcdefg',
        'kodi://kodi.server.local',
    ]

    a = Apprise(servers=servers)

    # 2 servers loaded
    assert (len(a) == 2)

    # We can retrieve our URLs this way:
    assert (len(a.urls()) == 2)

    # We can add another server
    assert (a.add('mmosts://mattermost.server.local/'
                  '3ccdd113474722377935511fc85d3dd4') is True)
    assert (len(a) == 3)

    # We can pop an object off of our stack by it's indexed value:
    obj = a.pop(0)
    assert (isinstance(obj, NotifyBase) is True)
    assert (len(a) == 2)

    # We can retrieve elements from our list too by reference:
    assert (isinstance(a[0].url(), six.string_types) is True)

    # We can iterate over our list too:
    count = 0
    for o in a:
        assert (isinstance(o.url(), six.string_types) is True)
        count += 1
    # verify that we did indeed iterate over each element
    assert (len(a) == count)

    # We can empty our set
    a.clear()
    assert (len(a) == 0)

    # An invalid schema
    assert (a.add('this is not a parseable url at all') is False)
    assert (len(a) == 0)

    # An unsupported schema
    assert (a.add('invalid://we.just.do.not.support.this.plugin.type') is
            False)
    assert (len(a) == 0)

    # A poorly formatted URL
    assert (a.add('json://user:@@@:bad?no.good') is False)
    assert (len(a) == 0)

    # Add a server with our asset we created earlier
    assert (a.add(
        'mmosts://mattermost.server.local/'
        '3ccdd113474722377935511fc85d3dd4',
        asset=asset) is True)

    # Clear our server listings again
    a.clear()

    # No servers to notify
    assert (a.notify(title="my title", body="my body") is False)

    class BadNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(BadNotification, self).__init__(**kwargs)

            # We fail whenever we're initialized
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    class GoodNotification(NotifyBase):
        def __init__(self, **kwargs):
            super(GoodNotification,
                  self).__init__(notify_format=NotifyFormat.HTML, **kwargs)

        def url(self):
            # Support URL
            return ''

        def notify(self, **kwargs):
            # Pretend everything is okay
            return True

    # Store our bad notification in our schema map
    SCHEMA_MAP['bad'] = BadNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['good'] = GoodNotification

    # Just to explain what is happening here, we would have parsed the
    # url properly but failed when we went to go and create an instance
    # of it.
    assert (a.add('bad://localhost') is False)
    assert (len(a) == 0)

    assert (a.add('good://localhost') is True)
    assert (len(a) == 1)

    # Bad Notification Type is still allowed as it is presumed the user
    # know's what their doing
    assert (a.notify(title="my title", body="my body", notify_type='bad') is
            True)

    # No Title/Body combo's
    assert (a.notify(title=None, body=None) is False)
    assert (a.notify(title='', body=None) is False)
    assert (a.notify(title=None, body='') is False)

    # As long as one is present, we're good
    assert (a.notify(title=None, body='present') is True)
    assert (a.notify(title='present', body=None) is True)
    assert (a.notify(title="present", body="present") is True)

    # Clear our server listings again
    a.clear()

    class ThrowNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    class RuntimeNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            raise RuntimeError()

        def url(self):
            # Support URL
            return ''

    class FailNotification(NotifyBase):
        def notify(self, **kwargs):
            # Pretend everything is okay
            return False

        def url(self):
            # Support URL
            return ''

    # Store our bad notification in our schema map
    SCHEMA_MAP['throw'] = ThrowNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['fail'] = FailNotification

    # Store our good notification in our schema map
    SCHEMA_MAP['runtime'] = RuntimeNotification

    assert (a.add('runtime://localhost') is True)
    assert (a.add('throw://localhost') is True)
    assert (a.add('fail://localhost') is True)
    assert (len(a) == 3)

    # Test when our notify both throws an exception and or just
    # simply returns False
    assert (a.notify(title="present", body="present") is False)

    # Create a Notification that throws an unexected exception
    class ThrowInstantiateNotification(NotifyBase):
        def __init__(self, **kwargs):
            # Pretend everything is okay
            raise TypeError()

        def url(self):
            # Support URL
            return ''

    SCHEMA_MAP['throw'] = ThrowInstantiateNotification

    # Reset our object
    a.clear()
    assert (len(a) == 0)

    # Instantiate a bad object
    plugin = a.instantiate(object, tag="bad_object")
    assert plugin is None

    # Instantiate a good object
    plugin = a.instantiate('good://localhost', tag="good")
    assert (isinstance(plugin, NotifyBase))

    # Test simple tagging inside of the object
    assert ("good" in plugin)
    assert ("bad" not in plugin)

    # the in (__contains__ override) is based on or'ed content; so although
    # 'bad' isn't tagged as being in the plugin, 'good' is, so the return
    # value of this is True
    assert (["bad", "good"] in plugin)
    assert (set(["bad", "good"]) in plugin)
    assert (("bad", "good") in plugin)

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert (len(a) == 1)

    # We can add entries as a list too (to add more then one)
    a.add([plugin, plugin, plugin])
    assert (len(a) == 4)

    # Reset our object again
    a.clear()
    try:
        a.instantiate('throw://localhost', suppress_exceptions=False)
        assert (False)

    except TypeError:
        assert (True)
    assert (len(a) == 0)

    assert (a.instantiate('throw://localhost', suppress_exceptions=True) is
            None)
    assert (len(a) == 0)

    #
    # We rince and repeat the same tests as above, however we do them
    # using the dict version
    #

    # Reset our object
    a.clear()
    assert (len(a) == 0)

    # Instantiate a good object
    plugin = a.instantiate({'schema': 'good', 'host': 'localhost'}, tag="good")
    assert (isinstance(plugin, NotifyBase))

    # Test simple tagging inside of the object
    assert ("good" in plugin)
    assert ("bad" not in plugin)

    # the in (__contains__ override) is based on or'ed content; so although
    # 'bad' isn't tagged as being in the plugin, 'good' is, so the return
    # value of this is True
    assert (["bad", "good"] in plugin)
    assert (set(["bad", "good"]) in plugin)
    assert (("bad", "good") in plugin)

    # We an add already substatiated instances into our Apprise object
    a.add(plugin)
    assert (len(a) == 1)

    # We can add entries as a list too (to add more then one)
    a.add([plugin, plugin, plugin])
    assert (len(a) == 4)

    # Reset our object again
    a.clear()
    try:
        a.instantiate({
            'schema': 'throw',
            'host': 'localhost'
        },
                      suppress_exceptions=False)
        assert (False)

    except TypeError:
        assert (True)
    assert (len(a) == 0)

    assert (a.instantiate({
        'schema': 'throw',
        'host': 'localhost'
    },
                          suppress_exceptions=True) is None)
    assert (len(a) == 0)
Exemple #29
0
def test_discord_plugin(mock_post):
    """
    API: NotifyDiscord() General Checks

    """
    # Disable Throttling to speed testing
    plugins.NotifyBase.request_rate_per_sec = 0

    # Initialize some generic (but valid) tokens
    webhook_id = 'A' * 24
    webhook_token = 'B' * 64

    # Prepare Mock
    mock_post.return_value = requests.Request()
    mock_post.return_value.status_code = requests.codes.ok

    # Invalid webhook id
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id=None, webhook_token=webhook_token)
    # Invalid webhook id (whitespace)
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id="  ", webhook_token=webhook_token)

    # Invalid webhook token
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id=webhook_id, webhook_token=None)
    # Invalid webhook token (whitespace)
    with pytest.raises(TypeError):
        plugins.NotifyDiscord(webhook_id=webhook_id, webhook_token="   ")

    obj = plugins.NotifyDiscord(webhook_id=webhook_id,
                                webhook_token=webhook_token,
                                footer=True,
                                thumbnail=False)

    # Test that we get a string response
    assert isinstance(obj.url(), six.string_types) is True

    # This call includes an image with it's payload:
    assert obj.notify(body='body', title='title',
                      notify_type=NotifyType.INFO) is True

    # Test our header parsing
    test_markdown = "## Heading one\nbody body\n\n" + \
        "# Heading 2 ##\n\nTest\n\n" + \
        "more content\n" + \
        "even more content  \t\r\n\n\n" + \
        "# Heading 3 ##\n\n\n" + \
        "normal content\n" + \
        "# heading 4\n" + \
        "#### Heading 5"

    results = obj.extract_markdown_sections(test_markdown)
    assert isinstance(results, list) is True
    # We should have 5 sections (since there are 5 headers identified above)
    assert len(results) == 5
    assert results[0]['name'] == 'Heading one'
    assert results[0]['value'] == '```md\nbody body\n```'
    assert results[1]['name'] == 'Heading 2'
    assert results[1]['value'] == \
        '```md\nTest\n\nmore content\neven more content\n```'
    assert results[2]['name'] == 'Heading 3'
    assert results[2]['value'] == \
        '```md\nnormal content\n```'
    assert results[3]['name'] == 'heading 4'
    assert results[3]['value'] == '```md\n\n```'
    assert results[4]['name'] == 'Heading 5'
    assert results[4]['value'] == '```md\n\n```'

    # Test our markdown
    obj = Apprise.instantiate('discord://{}/{}/?format=markdown'.format(
        webhook_id, webhook_token))
    assert isinstance(obj, plugins.NotifyDiscord)
    assert obj.notify(
        body=test_markdown, title='title', notify_type=NotifyType.INFO) is True

    # Empty String
    results = obj.extract_markdown_sections("")
    assert isinstance(results, list) is True
    assert len(results) == 0

    # String without Heading
    test_markdown = "Just a string without any header entries.\n" + \
        "A second line"
    results = obj.extract_markdown_sections(test_markdown)
    assert isinstance(results, list) is True
    assert len(results) == 0

    # Use our test markdown string during a notification
    assert obj.notify(
        body=test_markdown, title='title', notify_type=NotifyType.INFO) is True

    # Create an apprise instance
    a = Apprise()

    # Our processing is slightly different when we aren't using markdown
    # as we do not pre-parse content during our notifications
    assert a.add(
        'discord://{webhook_id}/{webhook_token}/'
        '?format=markdown&footer=Yes'.format(
            webhook_id=webhook_id, webhook_token=webhook_token)) is True

    # This call includes an image with it's payload:
    assert a.notify(body=test_markdown,
                    title='title',
                    notify_type=NotifyType.INFO,
                    body_format=NotifyFormat.TEXT) is True

    assert a.notify(body=test_markdown,
                    title='title',
                    notify_type=NotifyType.INFO,
                    body_format=NotifyFormat.MARKDOWN) is True

    # Toggle our logo availability
    a.asset.image_url_logo = None
    assert a.notify(body='body', title='title',
                    notify_type=NotifyType.INFO) is True