Files
OSINT-Framework/tools/remove_dead_links.py
T
s0lrayandPaperclip 74f037bd30 Remove 113 confirmed dead links from arf.json
Ran comprehensive link audit across all 1,113 URLs in arf.json.
Removed nodes where the URL has definitively failed (DNS resolution
failure, connection refused, or HTTP 404). Left in place: .onion
Tor hidden services, 403 bot-blocked sites (site still exists),
5xx server errors (transient), and timeouts.

Audit summary:
- Total URLs checked: 1,113
- OK: 829
- Dead (removed): 113  (DNS failure: 61, connection refused: 8, HTTP 404: 49, minus 5 already deduplicated)
- Redirected (kept, flagged in report): 374
- Slow >10s (kept): 1

Also adds tooling:
- tools/link_checker.py  -- async URL checker, writes JSON report
- tools/remove_dead_links.py  -- removes confirmed dead nodes from arf.json
- tools/link_audit_report.json  -- full audit results

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 18:29:19 -04:00

90 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""
Remove confirmed dead links from arf.json.
Targets: DNS failures, connection refused, HTTP 404.
Leaves alone: .onion, 403 (bot-block), 5xx (transient), timeouts.
"""
import json
import sys
REPORT_PATH = "tools/link_audit_report.json"
ARF_PATH = "public/arf.json"
def get_dead_urls(report_path: str) -> set:
with open(report_path) as f:
report = json.load(f)
dead_urls = set()
for d in report["dead"]:
url = d.get("url", "")
err = d.get("error") or ""
status = d.get("status")
# Skip .onion (Tor hidden services - expected failure from clearnet)
if ".onion" in url:
continue
# DNS failure = truly dead
if "nodename nor servname" in err:
dead_urls.add(url)
# Connection refused = truly dead
elif "Connection refused" in err:
dead_urls.add(url)
# 404 = page gone
elif status == 404:
dead_urls.add(url)
return dead_urls
def remove_nodes(tree, dead_urls: set) -> tuple:
"""Recursively remove leaf nodes whose URL is dead. Returns (cleaned_tree, removed_count)."""
removed = 0
if not isinstance(tree, dict):
return tree, 0
children = tree.get("children", [])
if not children:
# Leaf node - check URL
url = tree.get("url", "")
if url in dead_urls:
return None, 1
return tree, 0
# Non-leaf: recurse into children
new_children = []
for child in children:
cleaned, n = remove_nodes(child, dead_urls)
removed += n
if cleaned is not None:
new_children.append(cleaned)
tree = dict(tree)
tree["children"] = new_children
return tree, removed
def main():
dead_urls = get_dead_urls(REPORT_PATH)
print(f"Confirmed dead URLs to remove: {len(dead_urls)}")
with open(ARF_PATH) as f:
data = json.load(f)
cleaned, removed = remove_nodes(data, dead_urls)
print(f"Removed {removed} nodes from arf.json")
with open(ARF_PATH, "w") as f:
json.dump(cleaned, f, indent=2, separators=(",", ": "))
f.write("\n")
print(f"Wrote cleaned data to {ARF_PATH}")
return 0
if __name__ == "__main__":
sys.exit(main())