Push Notifications that work with just about every platform!
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Base classes and managers for notification service plugins, configuration loaders, and attachment handlers. The plugin system enables extensibility and service discovery, allowing Apprise to support over 100 notification services through a unified interface.
Base class for all notification service plugins providing common functionality and interface standardization.
class NotifyBase(URLBase):
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None, **kwargs):
"""
Send notification through this service.
Parameters:
- body (str): Notification message content
- title (str, optional): Notification title
- notify_type (NotifyType): Type of notification
- attach (AppriseAttachment, optional): File attachments
- **kwargs: Service-specific parameters
Returns:
bool: True if notification was sent successfully
"""
def url(self, privacy=False, *args, **kwargs):
"""
Generate service URL for this notification service.
Parameters:
- privacy (bool): Mask sensitive information
- *args, **kwargs: Additional URL parameters
Returns:
str: Service URL string
"""
def throttle(self, last_notification=None):
"""
Handle rate limiting for this service.
Parameters:
- last_notification (datetime, optional): Time of last notification
Returns:
float: Delay in seconds before next notification allowed
"""
@property
def enabled(self):
"""
Service availability status.
Returns:
bool: True if service is available and properly configured
"""
@property
def templates(self):
"""
URL templates supported by this service.
Returns:
tuple: URL template patterns for service configuration
"""
@property
def requirements(self):
"""
Required dependencies for this service.
Returns:
dict: Required Python packages and versions
"""
@property
def notify_format(self):
"""
Supported body format for notifications.
Returns:
NotifyFormat: Preferred format (TEXT, HTML, MARKDOWN)
"""Foundation class for URL parsing and handling used by all plugins.
class URLBase:
def parse_url(self, url, verify_host=True):
"""
Parse URL into components for service configuration.
Parameters:
- url (str): Service URL to parse
- verify_host (bool): Verify host accessibility
Returns:
dict: Parsed URL components (scheme, host, path, params, etc.)
"""
def parse_native_url(self, url):
"""
Parse service-native URL format.
Parameters:
- url (str): Native service URL
Returns:
dict: Parsed native URL components
"""
def url(self, privacy=False, *args, **kwargs):
"""
Generate standardized URL for this service.
Parameters:
- privacy (bool): Mask sensitive information
- *args, **kwargs: URL generation parameters
Returns:
str: Generated service URL
"""
def __len__(self):
"""
Get length representation of the service.
Returns:
int: Service length indicator
"""
@property
def app_id(self):
"""
Application identifier for this service.
Returns:
str: Application ID
"""
@property
def app_desc(self):
"""
Application description for this service.
Returns:
str: Application description
"""
@property
def service_name(self):
"""
Human-readable service name.
Returns:
str: Service display name
"""
@property
def service_url(self):
"""
Service homepage URL.
Returns:
str: URL to service documentation or homepage
"""
@property
def protocol(self):
"""
URL protocol/scheme for this service.
Returns:
str: Protocol identifier (e.g., 'slack', 'discord', 'mailto')
"""Singleton manager for notification service plugin discovery and loading.
class NotificationManager:
def load_modules(self, path=None, name=None):
"""
Load notification plugin modules.
Parameters:
- path (str, optional): Path to plugin directory
- name (str, optional): Specific plugin name to load
Returns:
bool: True if modules were loaded successfully
"""
def plugins(self, include_disabled=True):
"""
Get available notification plugins.
Parameters:
- include_disabled (bool): Include disabled/unavailable plugins
Returns:
dict: Available notification plugin classes keyed by protocol
"""
def schemas(self):
"""
Get supported notification URL schemas.
Returns:
list: Supported URL schemas for all loaded plugins
"""import apprise
from apprise.plugins.base import NotifyBase
from apprise import NotifyType, NotifyFormat
class NotifyCustomService(NotifyBase):
"""
Custom notification service plugin.
"""
# Service metadata
service_name = 'Custom Service'
service_url = 'https://custom-service.com'
protocol = 'custom'
# URL templates this plugin supports
templates = (
'{schema}://{user}:{password}@{host}',
'{schema}://{token}@{host}/{channel}',
)
# Required dependencies
requirements = {
'requests': '', # Any version
'custom-sdk': '>=1.0.0', # Minimum version
}
# Supported notification format
notify_format = NotifyFormat.MARKDOWN
def __init__(self, user=None, password=None, token=None,
host=None, channel=None, **kwargs):
"""
Initialize custom service plugin.
"""
super().__init__(**kwargs)
self.user = user
self.password = password
self.token = token
self.host = host
self.channel = channel
def send(self, body, title='', notify_type=NotifyType.INFO,
attach=None, **kwargs):
"""
Send notification through custom service.
"""
try:
# Implement actual notification sending logic
# This would typically make HTTP requests or API calls
# Example implementation structure:
payload = {
'message': body,
'title': title,
'type': notify_type,
'channel': self.channel
}
# Send notification using service API
# response = custom_service_api.send(payload)
# return response.success
return True # Success
except Exception as e:
self.logger.warning(f'Failed to send notification: {e}')
return False
def url(self, privacy=False, *args, **kwargs):
"""
Generate URL for this service configuration.
"""
if self.token:
# Token-based authentication
if privacy:
return f'{self.protocol}://***@{self.host}/{self.channel}'
else:
return f'{self.protocol}://{self.token}@{self.host}/{self.channel}'
else:
# User/password authentication
if privacy:
return f'{self.protocol}://***:***@{self.host}'
else:
return f'{self.protocol}://{self.user}:{self.password}@{self.host}'
@staticmethod
def parse_url(url):
"""
Parse URL into configuration parameters.
"""
# Implementation would parse the URL and return configuration dict
# This is called when loading services from URL strings
passimport apprise
# Get notification manager instance
manager = apprise.NotificationManager()
# Load all available plugins
manager.load_modules()
# Get available plugins
plugins = manager.plugins(include_disabled=False)
print(f"Available plugins: {len(plugins)}")
for protocol, plugin_class in plugins.items():
print(f" {protocol}: {plugin_class.service_name}")
# Get supported schemas
schemas = manager.schemas()
print(f"Supported URL schemas: {schemas}")import apprise
# Get detailed plugin information
apobj = apprise.Apprise()
details = apobj.details(show_requirements=True, show_disabled=False)
for service_key, service_info in details.items():
print(f"\nService: {service_info.get('service_name', service_key)}")
print(f" Protocols: {service_info.get('protocols', [])}")
print(f" Enabled: {service_info.get('enabled', False)}")
# Show requirements if any
requirements = service_info.get('requirements', {})
if requirements:
print(f" Requirements:")
for package, version in requirements.items():
print(f" {package} {version or '(any version)'}")
# Show URL templates
templates = service_info.get('templates', [])
if templates:
print(f" URL Templates:")
for template in templates:
print(f" {template}")import apprise
# Register custom plugin (if not auto-discovered)
# This would typically be done in plugin initialization
# Use custom plugin through URL
apobj = apprise.Apprise()
# Add custom service using URL
apobj.add('custom://token@api.example.com/channel-123')
# Or add with parameters
custom_url = 'custom://user:pass@api.example.com'
apobj.add(custom_url)
# Send notification through custom plugin
success = apobj.notify(
body='Test message through custom plugin',
title='Custom Plugin Test',
notify_type=apprise.NotifyType.INFO
)
if success:
print("Custom plugin notification sent successfully")import apprise
apobj = apprise.Apprise()
# Add various services to demonstrate plugin capabilities
services = [
'mailto://user:pass@gmail.com', # Email plugin
'slack://TokenA/TokenB/TokenC/Ch', # Slack plugin
'discord://webhook_id/webhook_token', # Discord plugin
'telegram://bot_token/chat_id', # Telegram plugin
]
for service_url in services:
try:
apobj.add(service_url)
except Exception as e:
print(f"Failed to add service {service_url}: {e}")
# Examine loaded services
for i, server in enumerate(apobj.servers):
print(f"\nService {i + 1}: {server.service_name}")
print(f" Protocol: {server.protocol}")
print(f" Enabled: {server.enabled}")
print(f" Format: {server.notify_format}")
print(f" Requirements: {getattr(server, 'requirements', {})}")
# Get service URL (with privacy)
service_url = server.url(privacy=True)
print(f" URL: {service_url}")
# Check rate limiting
throttle_delay = server.throttle()
if throttle_delay > 0:
print(f" Rate limit: {throttle_delay}s delay")import apprise
def diagnose_plugin_issues():
"""Diagnose common plugin configuration issues."""
apobj = apprise.Apprise()
# Test problematic service URLs
test_urls = [
'slack://invalid/token/format', # Invalid format
'discord://missing-webhook', # Incomplete URL
'mailto://badformat@', # Malformed email
'unknown://unsupported/service', # Unknown protocol
]
for url in test_urls:
try:
success = apobj.add(url)
if success:
print(f"✓ Successfully added: {url}")
else:
print(f"✗ Failed to add: {url}")
except Exception as e:
print(f"✗ Exception adding {url}: {e}")
# Check for disabled services
details = apobj.details(show_disabled=True)
disabled_services = [
name for name, info in details.items()
if not info.get('enabled', True)
]
if disabled_services:
print(f"\nDisabled services: {disabled_services}")
for service in disabled_services:
service_info = details[service]
requirements = service_info.get('requirements', {})
if requirements:
print(f" {service} requirements: {requirements}")
diagnose_plugin_issues()import apprise
# Configure services with advanced plugin features
def setup_advanced_notifications():
apobj = apprise.Apprise()
# Email with HTML support
email_url = 'mailto://user:app_password@gmail.com'
apobj.add(email_url)
# Slack with custom formatting
slack_url = 'slack://xoxb-token/channel?format=markdown'
apobj.add(slack_url)
# Discord with custom avatar
discord_url = 'discord://webhook_id/webhook_token?avatar=no'
apobj.add(discord_url)
# Send with different formats to demonstrate plugin capabilities
test_cases = [
{
'body': '<b>HTML formatted message</b>',
'title': 'HTML Test',
'body_format': apprise.NotifyFormat.HTML
},
{
'body': '**Markdown** formatted message',
'title': 'Markdown Test',
'body_format': apprise.NotifyFormat.MARKDOWN
},
{
'body': 'Plain text message',
'title': 'Text Test',
'body_format': apprise.NotifyFormat.TEXT
}
]
for test_case in test_cases:
success = apobj.notify(**test_case)
print(f"Format {test_case['body_format']}: {'✓' if success else '✗'}")
setup_advanced_notifications()Install with Tessl CLI
npx tessl i tessl/pypi-apprise