Last active
December 20, 2024 16:31
Migrate Pi-hole whitelists and blacklists to AdGuard custom rules configuration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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