initial commit
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[codz]
|
*.py[codz]
|
||||||
@@ -205,3 +206,7 @@ cython_debug/
|
|||||||
marimo/_static/
|
marimo/_static/
|
||||||
marimo/_lsp/
|
marimo/_lsp/
|
||||||
__marimo__/
|
__marimo__/
|
||||||
|
=======
|
||||||
|
config.ini
|
||||||
|
/.claude/
|
||||||
|
>>>>>>> f6afbf2 (initial commit)
|
||||||
|
|||||||
231
README.md
Normal file
231
README.md
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
# GNOME Notification Forwarder
|
||||||
|
|
||||||
|
Forward all GNOME desktop notifications to Bark or Gotify.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Monitors all GNOME desktop notifications in real-time
|
||||||
|
- Forwards notifications to Bark API or Gotify automatically
|
||||||
|
- Support for multiple notification services (Bark, Gotify, or both)
|
||||||
|
- Includes application name in forwarded messages
|
||||||
|
- Runs as a systemd service for automatic startup
|
||||||
|
- Comprehensive logging
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Linux with GNOME desktop environment
|
||||||
|
- Python 3.6+
|
||||||
|
- D-Bus (pre-installed on most GNOME systems)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 1. Install Python dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install system dependencies
|
||||||
|
sudo pacman -S python-dbus python-gobject python-requests
|
||||||
|
|
||||||
|
# OR using pip (if not using Arch)
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Make the script executable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x notify_forwarder.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test the script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./notify_forwarder.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Then send a test notification:
|
||||||
|
```bash
|
||||||
|
notify-send "Test Title" "Test message body"
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the notification forwarded to your Bark app and logged in the terminal.
|
||||||
|
|
||||||
|
## Running as a Service
|
||||||
|
|
||||||
|
### Set up systemd user service
|
||||||
|
|
||||||
|
1. Create the systemd user directory if it doesn't exist:
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/systemd/user
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Copy the service file:
|
||||||
|
```bash
|
||||||
|
cp notify-forwarder.service ~/.config/systemd/user/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Reload systemd:
|
||||||
|
```bash
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Enable and start the service:
|
||||||
|
```bash
|
||||||
|
systemctl --user enable notify-forwarder.service
|
||||||
|
systemctl --user start notify-forwarder.service
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Check the service status:
|
||||||
|
```bash
|
||||||
|
systemctl --user status notify-forwarder.service
|
||||||
|
```
|
||||||
|
|
||||||
|
6. View logs:
|
||||||
|
```bash
|
||||||
|
journalctl --user -u notify-forwarder.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuration is done via environment variables in your `~/.zshrc` file.
|
||||||
|
|
||||||
|
### Choose Notification Service
|
||||||
|
|
||||||
|
Set the `NOTIFICATION_SERVICE` variable to control which service(s) to use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Options: bark, gotify, or both (default: bark)
|
||||||
|
export NOTIFICATION_SERVICE="bark"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure Bark
|
||||||
|
|
||||||
|
If using Bark (default), add the following to your `~/.zshrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BARK_URL="https://api.day.app/YOUR_BARK_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure Gotify
|
||||||
|
|
||||||
|
If using Gotify, add the following to your `~/.zshrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GOTIFY_URL="https://gotify.example.com"
|
||||||
|
export GOTIFY_TOKEN="your_gotify_app_token"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Both Services
|
||||||
|
|
||||||
|
To forward notifications to both Bark and Gotify simultaneously:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NOTIFICATION_SERVICE="both"
|
||||||
|
export BARK_URL="https://api.day.app/YOUR_BARK_KEY"
|
||||||
|
export GOTIFY_URL="https://gotify.example.com"
|
||||||
|
export GOTIFY_TOKEN="your_gotify_app_token"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apply Configuration
|
||||||
|
|
||||||
|
After editing `~/.zshrc`, reload it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart the service if it's already running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user restart notify-forwarder.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stopping the Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop the service
|
||||||
|
systemctl --user stop notify-forwarder.service
|
||||||
|
|
||||||
|
# Disable auto-start
|
||||||
|
systemctl --user disable notify-forwarder.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### No notifications being forwarded
|
||||||
|
|
||||||
|
1. Check if the service is running:
|
||||||
|
```bash
|
||||||
|
systemctl --user status notify-forwarder.service
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Check the logs:
|
||||||
|
```bash
|
||||||
|
journalctl --user -u notify-forwarder.service -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Test your notification service manually:
|
||||||
|
|
||||||
|
For Bark:
|
||||||
|
```bash
|
||||||
|
curl "https://api.day.app/YOUR_BARK_KEY/Test/Message"
|
||||||
|
```
|
||||||
|
|
||||||
|
For Gotify:
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://gotify.example.com/message?token=YOUR_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"title":"Test","message":"Test message","priority":5}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission issues
|
||||||
|
|
||||||
|
Make sure the script has execute permissions:
|
||||||
|
```bash
|
||||||
|
chmod +x notify_forwarder.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### D-Bus connection issues
|
||||||
|
|
||||||
|
Ensure you're running the service as a user service (not system service), as it needs access to your user's D-Bus session.
|
||||||
|
|
||||||
|
## Uninstallation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop and disable the service
|
||||||
|
systemctl --user stop notify-forwarder.service
|
||||||
|
systemctl --user disable notify-forwarder.service
|
||||||
|
|
||||||
|
# Remove the service file
|
||||||
|
rm ~/.config/systemd/user/notify-forwarder.service
|
||||||
|
|
||||||
|
# Reload systemd
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
|
# Remove the project directory
|
||||||
|
rm -rf /home/lostecho/Documents/notify-forwarder
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 lostecho
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
See the [LICENSE](LICENSE) file for details.
|
||||||
42
install.sh
Executable file
42
install.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Installation script for GNOME Notification Forwarder
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Installing GNOME Notification Forwarder to Bark..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Make the main script executable
|
||||||
|
chmod +x notify_forwarder.py
|
||||||
|
echo "✓ Made notify_forwarder.py executable"
|
||||||
|
|
||||||
|
# Create systemd user directory
|
||||||
|
mkdir -p ~/.config/systemd/user
|
||||||
|
echo "✓ Created systemd user directory"
|
||||||
|
|
||||||
|
# Copy service file
|
||||||
|
cp notify-forwarder.service ~/.config/systemd/user/
|
||||||
|
echo "✓ Copied service file to ~/.config/systemd/user/"
|
||||||
|
|
||||||
|
# Reload systemd
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
echo "✓ Reloaded systemd"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Installation complete!"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Install dependencies:"
|
||||||
|
echo " sudo pacman -S python-dbus python-gobject python-requests"
|
||||||
|
echo
|
||||||
|
echo "2. Start the service:"
|
||||||
|
echo " systemctl --user start notify-forwarder.service"
|
||||||
|
echo
|
||||||
|
echo "3. Enable auto-start on login:"
|
||||||
|
echo " systemctl --user enable notify-forwarder.service"
|
||||||
|
echo
|
||||||
|
echo "4. Check status:"
|
||||||
|
echo " systemctl --user status notify-forwarder.service"
|
||||||
|
echo
|
||||||
|
echo "5. View logs:"
|
||||||
|
echo " journalctl --user -u notify-forwarder.service -f"
|
||||||
12
notify-forwarder.service
Normal file
12
notify-forwarder.service
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=GNOME Notification Forwarder to Bark
|
||||||
|
After=graphical-session.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/python3 %h/Documents/notify-forwarder/notify_forwarder.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
342
notify_forwarder.py
Executable file
342
notify_forwarder.py
Executable file
@@ -0,0 +1,342 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
GNOME Notification Forwarder to Bark
|
||||||
|
Monitors GNOME desktop notifications and forwards them to Bark API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import dbus
|
||||||
|
import dbus.mainloop.glib
|
||||||
|
from gi.repository import GLib
|
||||||
|
import requests
|
||||||
|
import urllib.parse
|
||||||
|
import logging
|
||||||
|
from threading import Thread
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import configparser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Configuration file path
|
||||||
|
SCRIPT_DIR = Path(__file__).parent
|
||||||
|
CONFIG_FILE = SCRIPT_DIR / 'config.ini'
|
||||||
|
|
||||||
|
# Read configuration
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
|
||||||
|
if CONFIG_FILE.exists():
|
||||||
|
logger.info(f"Loading configuration from {CONFIG_FILE}")
|
||||||
|
config.read(CONFIG_FILE)
|
||||||
|
|
||||||
|
# Notification service configuration
|
||||||
|
NOTIFICATION_SERVICE = config.get('notification', 'service', fallback='bark').lower()
|
||||||
|
|
||||||
|
# Bark API configuration
|
||||||
|
BARK_URL = config.get('bark', 'url', fallback=None)
|
||||||
|
|
||||||
|
# Gotify configuration
|
||||||
|
GOTIFY_URL = config.get('gotify', 'url', fallback=None)
|
||||||
|
GOTIFY_TOKEN = config.get('gotify', 'token', fallback=None)
|
||||||
|
|
||||||
|
# Ntfy configuration
|
||||||
|
NTFY_SERVER = config.get('ntfy', 'server', fallback='https://ntfy.sh')
|
||||||
|
NTFY_TOPIC = config.get('ntfy', 'topic', fallback=None)
|
||||||
|
else:
|
||||||
|
# Fallback to environment variables if config file doesn't exist
|
||||||
|
logger.warning(f"Config file not found at {CONFIG_FILE}, falling back to environment variables")
|
||||||
|
NOTIFICATION_SERVICE = os.environ.get('NOTIFICATION_SERVICE', 'bark').lower()
|
||||||
|
BARK_URL = os.environ.get('BARK_URL')
|
||||||
|
GOTIFY_URL = os.environ.get('GOTIFY_URL')
|
||||||
|
GOTIFY_TOKEN = os.environ.get('GOTIFY_TOKEN')
|
||||||
|
NTFY_SERVER = os.environ.get('NTFY_SERVER', 'https://ntfy.sh')
|
||||||
|
NTFY_TOPIC = os.environ.get('NTFY_TOPIC')
|
||||||
|
|
||||||
|
# Parse notification services (support comma-separated or 'all')
|
||||||
|
if NOTIFICATION_SERVICE == 'all':
|
||||||
|
ENABLED_SERVICES = ['bark', 'gotify', 'ntfy']
|
||||||
|
else:
|
||||||
|
ENABLED_SERVICES = [s.strip() for s in NOTIFICATION_SERVICE.split(',')]
|
||||||
|
|
||||||
|
# Validate configuration based on enabled services
|
||||||
|
if 'bark' in ENABLED_SERVICES:
|
||||||
|
if not BARK_URL:
|
||||||
|
logger.error("Error: BARK_URL is not configured!")
|
||||||
|
logger.error(f"Please set 'url' in the [bark] section of {CONFIG_FILE}")
|
||||||
|
logger.error("Or set the BARK_URL environment variable")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if 'gotify' in ENABLED_SERVICES:
|
||||||
|
if not GOTIFY_URL or not GOTIFY_TOKEN:
|
||||||
|
logger.error("Error: Gotify URL and TOKEN are not configured!")
|
||||||
|
logger.error(f"Please set 'url' and 'token' in the [gotify] section of {CONFIG_FILE}")
|
||||||
|
logger.error("Or set the GOTIFY_URL and GOTIFY_TOKEN environment variables")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if 'ntfy' in ENABLED_SERVICES:
|
||||||
|
if not NTFY_TOPIC:
|
||||||
|
logger.error("Error: Ntfy TOPIC is not configured!")
|
||||||
|
logger.error(f"Please set 'topic' in the [ntfy] section of {CONFIG_FILE}")
|
||||||
|
logger.error("Or set the NTFY_TOPIC environment variable")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Validate that at least one valid service is configured
|
||||||
|
valid_services = {'bark', 'gotify', 'ntfy'}
|
||||||
|
if not any(s in valid_services for s in ENABLED_SERVICES):
|
||||||
|
logger.error(f"Error: Invalid NOTIFICATION_SERVICE '{NOTIFICATION_SERVICE}'")
|
||||||
|
logger.error("Valid options are: bark, gotify, ntfy, or combinations like 'bark,gotify' or 'all'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Deduplication cache - stores hashes of recent notifications
|
||||||
|
# Format: (hash, timestamp)
|
||||||
|
recent_notifications = deque(maxlen=100)
|
||||||
|
|
||||||
|
|
||||||
|
def send_to_bark(title, body, app_name=""):
|
||||||
|
"""
|
||||||
|
Send notification to Bark API (runs in separate thread to avoid blocking)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Notification title
|
||||||
|
body: Notification body
|
||||||
|
app_name: Application name that triggered the notification
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Construct message
|
||||||
|
if app_name:
|
||||||
|
message = f"[{app_name}] {body}" if body else f"[{app_name}]"
|
||||||
|
else:
|
||||||
|
message = body or "Notification"
|
||||||
|
|
||||||
|
# URL encode the parameters
|
||||||
|
title_encoded = urllib.parse.quote(title or "Notification")
|
||||||
|
message_encoded = urllib.parse.quote(message)
|
||||||
|
|
||||||
|
# Construct full URL
|
||||||
|
url = f"{BARK_URL}/{title_encoded}/{message_encoded}"
|
||||||
|
|
||||||
|
# Send request with timeout
|
||||||
|
response = requests.get(url, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
logger.info(f"✓ [Bark] Forwarded: {title} - {message[:50]}...")
|
||||||
|
else:
|
||||||
|
logger.error(f"✗ [Bark] Failed to forward notification. Status: {response.status_code}")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"✗ [Bark] Network error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"✗ [Bark] Error sending: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def send_to_gotify(title, body, app_name=""):
|
||||||
|
"""
|
||||||
|
Send notification to Gotify API (runs in separate thread to avoid blocking)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Notification title
|
||||||
|
body: Notification body
|
||||||
|
app_name: Application name that triggered the notification
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Construct message
|
||||||
|
if app_name:
|
||||||
|
message = f"[{app_name}] {body}" if body else f"[{app_name}]"
|
||||||
|
else:
|
||||||
|
message = body or "Notification"
|
||||||
|
|
||||||
|
# Prepare Gotify message
|
||||||
|
url = f"{GOTIFY_URL}/message"
|
||||||
|
params = {'token': GOTIFY_TOKEN}
|
||||||
|
data = {
|
||||||
|
'title': title or "Notification",
|
||||||
|
'message': message,
|
||||||
|
'priority': 5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send request with timeout
|
||||||
|
response = requests.post(url, params=params, json=data, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
logger.info(f"✓ [Gotify] Forwarded: {title} - {message[:50]}...")
|
||||||
|
else:
|
||||||
|
logger.error(f"✗ [Gotify] Failed to forward notification. Status: {response.status_code}")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"✗ [Gotify] Network error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"✗ [Gotify] Error sending: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def send_to_ntfy(title, body, app_name=""):
|
||||||
|
"""
|
||||||
|
Send notification to Ntfy (runs in separate thread to avoid blocking)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Notification title
|
||||||
|
body: Notification body
|
||||||
|
app_name: Application name that triggered the notification
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Construct message
|
||||||
|
if app_name:
|
||||||
|
message = f"[{app_name}] {body}" if body else f"[{app_name}]"
|
||||||
|
else:
|
||||||
|
message = body or "Notification"
|
||||||
|
|
||||||
|
# Prepare Ntfy request
|
||||||
|
url = f"{NTFY_SERVER}/{NTFY_TOPIC}"
|
||||||
|
headers = {
|
||||||
|
'Title': title or "Notification",
|
||||||
|
'Priority': '3',
|
||||||
|
'Tags': 'computer'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send request with timeout
|
||||||
|
response = requests.post(url, data=message.encode('utf-8'), headers=headers, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
logger.info(f"✓ [Ntfy] Forwarded: {title} - {message[:50]}...")
|
||||||
|
else:
|
||||||
|
logger.error(f"✗ [Ntfy] Failed to forward notification. Status: {response.status_code}")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"✗ [Ntfy] Network error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"✗ [Ntfy] Error sending: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def message_filter(bus, message):
|
||||||
|
"""
|
||||||
|
Filter and handle D-Bus messages
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Check if this is a method call to Notify
|
||||||
|
if message.get_member() != "Notify":
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.get_interface() != "org.freedesktop.Notifications":
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the arguments
|
||||||
|
args = message.get_args_list()
|
||||||
|
if len(args) < 5:
|
||||||
|
return
|
||||||
|
|
||||||
|
app_name = str(args[0])
|
||||||
|
replaces_id = args[1]
|
||||||
|
app_icon = args[2]
|
||||||
|
summary = str(args[3])
|
||||||
|
body = str(args[4])
|
||||||
|
|
||||||
|
# Skip empty notifications
|
||||||
|
if not summary and not body:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create a hash of the notification for deduplication
|
||||||
|
notification_str = f"{app_name}:{summary}:{body}"
|
||||||
|
notification_hash = hashlib.md5(notification_str.encode()).hexdigest()
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Check if this notification was recently processed (within 2 seconds)
|
||||||
|
for cached_hash, cached_time in recent_notifications:
|
||||||
|
if cached_hash == notification_hash and (current_time - cached_time) < 2:
|
||||||
|
# Duplicate notification, skip it
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add to cache
|
||||||
|
recent_notifications.append((notification_hash, current_time))
|
||||||
|
|
||||||
|
# Log the notification
|
||||||
|
logger.info(f"Received: [{app_name}] {summary}")
|
||||||
|
|
||||||
|
# Forward to notification service(s) in separate thread(s) to avoid blocking
|
||||||
|
if 'bark' in ENABLED_SERVICES:
|
||||||
|
thread = Thread(target=send_to_bark, args=(summary, body, app_name))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
if 'gotify' in ENABLED_SERVICES:
|
||||||
|
thread = Thread(target=send_to_gotify, args=(summary, body, app_name))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
if 'ntfy' in ENABLED_SERVICES:
|
||||||
|
thread = Thread(target=send_to_ntfy, args=(summary, body, app_name))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling message: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Main function to set up D-Bus monitoring
|
||||||
|
"""
|
||||||
|
logger.info("Starting GNOME Notification Forwarder...")
|
||||||
|
logger.info(f"Enabled services: {', '.join(ENABLED_SERVICES)}")
|
||||||
|
|
||||||
|
if 'bark' in ENABLED_SERVICES:
|
||||||
|
logger.info(f"Bark URL: {BARK_URL}")
|
||||||
|
|
||||||
|
if 'gotify' in ENABLED_SERVICES:
|
||||||
|
logger.info(f"Gotify URL: {GOTIFY_URL}")
|
||||||
|
|
||||||
|
if 'ntfy' in ENABLED_SERVICES:
|
||||||
|
logger.info(f"Ntfy server: {NTFY_SERVER}")
|
||||||
|
logger.info(f"Ntfy topic: {NTFY_TOPIC}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Set up D-Bus main loop
|
||||||
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||||
|
|
||||||
|
# Get session bus
|
||||||
|
bus = dbus.SessionBus()
|
||||||
|
|
||||||
|
# Become a monitor to intercept messages
|
||||||
|
bus.call_blocking(
|
||||||
|
'org.freedesktop.DBus',
|
||||||
|
'/org/freedesktop/DBus',
|
||||||
|
'org.freedesktop.DBus.Monitoring',
|
||||||
|
'BecomeMonitor',
|
||||||
|
'asu',
|
||||||
|
([
|
||||||
|
"type='method_call',interface='org.freedesktop.Notifications',member='Notify'"
|
||||||
|
], 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add message filter
|
||||||
|
bus.add_message_filter(message_filter)
|
||||||
|
|
||||||
|
logger.info("✓ Monitoring notifications... Press Ctrl+C to stop.")
|
||||||
|
|
||||||
|
# Run main loop
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
loop.run()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("\nStopping notification forwarder...")
|
||||||
|
except dbus.exceptions.DBusException as e:
|
||||||
|
if "org.freedesktop.DBus.Monitoring" in str(e):
|
||||||
|
logger.error("Error: Unable to become D-Bus monitor.")
|
||||||
|
logger.error("This might require additional permissions or a policy update.")
|
||||||
|
logger.error("Try running with: dbus-send or check D-Bus policies.")
|
||||||
|
else:
|
||||||
|
logger.error(f"D-Bus error: {e}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fatal error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dbus-python>=1.2.18
|
||||||
|
PyGObject>=3.42.0
|
||||||
|
requests>=2.28.0
|
||||||
Reference in New Issue
Block a user