Add custom domain monitoring and update documentation
This commit is contained in:
11
README.md
11
README.md
@@ -10,6 +10,7 @@ Adguard Monitor is designed for openclaw, which should usually NEVER make a requ
|
||||
- **Multi-Client Support**: Monitor specific IP addresses and receive nicknamed notifications (e.g., "BLOCKED: My Laptop").
|
||||
- **Strict Configuration**: Fails fast if environment variables are missing.
|
||||
- **Home Assistant Notifications**: Native mobile push messages for blocked queries.
|
||||
- **Custom Domain Monitoring**: Monitor specific domains using [list.txt](list.txt) (wildcards supported).
|
||||
- **Structured Logging**: UTF-8 encoded logging for unicode emoji support.
|
||||
|
||||
## Setup
|
||||
@@ -24,7 +25,15 @@ Adguard Monitor is designed for openclaw, which should usually NEVER make a requ
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
4. **Run the monitor**:
|
||||
4. **Custom Domains (Optional)**:
|
||||
Copy [list.example.txt](list.example.txt) to `list.txt` and add domains:
|
||||
```bash
|
||||
cp list.example.txt list.txt
|
||||
```
|
||||
- Lines starting with `!` are **negative filters** (they will be ignored).
|
||||
- Wildcards like `*.example.com` are supported.
|
||||
|
||||
5. **Run the monitor**:
|
||||
```bash
|
||||
python monitor.py --interval 15
|
||||
```
|
||||
|
||||
13
list.example.txt
Normal file
13
list.example.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# Add domains to monitor here (one per line)
|
||||
# Supports simple wildcard matches if implemented
|
||||
# These are NOT block rules, just alert rules!!!!!
|
||||
|
||||
# Example custom domains to monitor
|
||||
# Use '!' at the start of a line to exclude a domain (negative filter)
|
||||
example.com
|
||||
*.doubleclick.net
|
||||
ads.google.com
|
||||
|
||||
# Negative Filters (exclude these domains from monitoring)
|
||||
!discord.com
|
||||
!*.discord.com
|
||||
51
monitor.py
51
monitor.py
@@ -4,6 +4,7 @@ import json
|
||||
import time
|
||||
import argparse
|
||||
import os
|
||||
import fnmatch
|
||||
from dotenv import load_dotenv
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from datetime import datetime
|
||||
@@ -35,6 +36,31 @@ try:
|
||||
"Authorization": f"Bearer {HASS_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# --- Custom Domain List ---
|
||||
CUSTOM_DOMAINS = []
|
||||
NEGATIVE_FILTERS = []
|
||||
|
||||
list_file = "list.txt" if os.path.exists("list.txt") else "list.example.txt"
|
||||
|
||||
if os.path.exists(list_file):
|
||||
with open(list_file, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip().lower()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if line.startswith("!"):
|
||||
NEGATIVE_FILTERS.append(line[1:])
|
||||
else:
|
||||
CUSTOM_DOMAINS.append(line)
|
||||
|
||||
status_msg = f"Loaded {len(CUSTOM_DOMAINS)} custom domains and {len(NEGATIVE_FILTERS)} negative filters from {list_file}"
|
||||
if list_file == "list.example.txt" and not os.path.exists("list.txt"):
|
||||
status_msg += " (FALLBACK)"
|
||||
print(status_msg)
|
||||
else:
|
||||
print("Note: No domain list found. Create list.txt or list.example.txt to monitor custom domains.")
|
||||
|
||||
except (EnvironmentError, json.JSONDecodeError) as e:
|
||||
print(f"Error during startup: {e}")
|
||||
exit(1)
|
||||
@@ -95,9 +121,17 @@ def get_auth_session():
|
||||
session.auth = HTTPBasicAuth(USERNAME, PASSWORD)
|
||||
return session
|
||||
|
||||
def hour_and_minute():
|
||||
now = datetime.now()
|
||||
return now.strftime("%H:%M")
|
||||
def is_custom_match(host):
|
||||
# Check negative filters first
|
||||
for pattern in NEGATIVE_FILTERS:
|
||||
if fnmatch.fnmatchcase(host.lower(), pattern.lower()):
|
||||
return False
|
||||
|
||||
# Check custom domains
|
||||
for pattern in CUSTOM_DOMAINS:
|
||||
if fnmatch.fnmatchcase(host.lower(), pattern.lower()):
|
||||
return True
|
||||
return False
|
||||
|
||||
def fetch_and_analyze(session, verbose=False):
|
||||
stats.log_api_call()
|
||||
@@ -135,16 +169,21 @@ def fetch_and_analyze(session, verbose=False):
|
||||
if verbose:
|
||||
logger.info(f"[{client_name}] Query: {host} | Reason: {reason}")
|
||||
|
||||
if reason in ["FilteredBlackList", "FilteredParental", "FilteredSafeBrowsing"]:
|
||||
is_blocked = reason in ["FilteredBlackList", "FilteredParental", "FilteredSafeBrowsing"]
|
||||
is_custom = is_custom_match(host)
|
||||
|
||||
if is_blocked or is_custom:
|
||||
# Check for duplicate notifications within the same "event"
|
||||
# Using host + client_ip as a unique key for the last notification
|
||||
event_type = "BLOCKED" if is_blocked else "CUSTOM_MATCH"
|
||||
notification_key = f"{client_ip}:{host}:{reason}:{hour_and_minute()}"
|
||||
if stats.last_notified_key == notification_key:
|
||||
continue
|
||||
|
||||
stats.log_blocked()
|
||||
logger.warning(f"BLOCKED: {client_name} -> {host} ({reason})")
|
||||
notify_hass(client_name, host, reason)
|
||||
title = "🛡️ Adguard Blocked a Request" if is_blocked else "⚠️ Custom Domain Match"
|
||||
logger.warning(f"{event_type}: {client_name} -> {host} ({reason if is_blocked else 'Manual match'})")
|
||||
notify_hass(client_name, host, reason if is_blocked else "Custom List", title=title)
|
||||
stats.last_notified_key = notification_key
|
||||
|
||||
# Dynamic limit adjustment
|
||||
|
||||
Reference in New Issue
Block a user