Skip to content

Instantly share code, notes, and snippets.

@sirkirby
Last active December 20, 2024 16:31
Migrate Pi-hole whitelists and blacklists to AdGuard custom rules configuration
import json
import yaml
import sys
from datetime import datetime
import re
def clean_comment(comment):
"""Clean up comment by removing newlines and extra spaces."""
if not comment:
return ""
comment = re.sub(r'\s+', ' ', comment)
comment = comment.replace('\n', ' ').replace(' - qjz9zk', '')
return comment.strip()
def is_valid_domain(domain):
"""Basic check if string could be a valid domain."""
if not domain:
return False
pattern = r'^[a-zA-Z0-9][a-zA-Z0-9-._]*[a-zA-Z0-9]$'
return bool(re.match(pattern, domain))
def convert_domain_list(domain_json, is_whitelist=False, is_regex=False):
"""Convert Pi-hole domain list to AdGuard Home rules.
Args:
domain_json: JSON string containing domain list data
is_whitelist: Boolean indicating if this is a whitelist (True) or blacklist (False)
is_regex: Boolean indicating if this is a regex list (True) or exact domain list (False)
"""
try:
data = json.loads(domain_json)
except json.JSONDecodeError:
print(f"Error: Invalid JSON format in {'regex' if is_regex else 'exact'} "
f"{'whitelist' if is_whitelist else 'blacklist'} data", file=sys.stderr)
return []
rules = []
for entry in data:
pattern = entry.get('domain', '').strip()
if not pattern:
continue
if not is_regex and not is_valid_domain(pattern):
continue
if is_regex:
# Process regex pattern
pattern = pattern.strip('/')
pattern = pattern.replace(r'\.|^', '|')
pattern = pattern.replace(r'(\.|^)', '')
pattern = pattern.replace('$', '')
rule = f"/^{pattern}$/"
else:
# Process exact domain
rule = f"||{pattern}^"
# Add whitelist prefix if needed
if is_whitelist:
rule = f"@@{rule}$important"
# Add comment if present
comment = clean_comment(entry.get('comment'))
if comment:
rule += f" # {comment}"
rules.append(rule)
return rules
class NoAliasDumper(yaml.SafeDumper):
def ignore_aliases(self, data):
return True
def create_yaml_output(rules):
"""Create YAML output in AdGuard Home format."""
# Remove any empty strings and normalize spacing
rules = [rule for rule in rules if rule.strip()]
# Create config structure
config = {
'user_rules': rules
}
return yaml.dump(config, Dumper=NoAliasDumper, default_flow_style=False,
sort_keys=False, width=float("inf"))
def read_json_file(filename, default="[]"):
"""Read JSON from file with error handling."""
try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
print(f"Warning: File {filename} not found, using empty list", file=sys.stderr)
return default
def main():
if len(sys.argv) != 5:
print("Usage: python script.py <whitelist.exact.json> <whitelist.regex.json> "
"<blacklist.exact.json> <blacklist.regex.json>")
sys.exit(1)
# Read all input files
whitelist_exact = read_json_file(sys.argv[1])
whitelist_regex = read_json_file(sys.argv[2])
blacklist_exact = read_json_file(sys.argv[3])
blacklist_regex = read_json_file(sys.argv[4])
# Convert all lists using the unified conversion function
all_rules = []
all_rules.extend(convert_domain_list(whitelist_exact, is_whitelist=True, is_regex=False))
all_rules.extend(convert_domain_list(whitelist_regex, is_whitelist=True, is_regex=True))
all_rules.extend(convert_domain_list(blacklist_exact, is_whitelist=False, is_regex=False))
all_rules.extend(convert_domain_list(blacklist_regex, is_whitelist=False, is_regex=True))
# Output YAML
print(create_yaml_output(all_rules))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment