Skip to content

Commit cf2b9bb

Browse files
author
blocklist
committed
release blocklist
0 parents  commit cf2b9bb

43 files changed

Lines changed: 148274 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/FUNDING.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
custom: ['https://ph00lt0.github.io/blocklist/#donations']
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
name: Broken website or app
3+
about: Report a broken website or app caused by this blocklist
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Tool used (pick one)**:
11+
- [ ] uBlock Origin
12+
- [ ] Adguard for iOS
13+
- [ ] Adguard for Android
14+
- [ ] Adguard Browser add-on/extension
15+
- [ ] Brave Ad Block
16+
- [ ] PiHole
17+
- [ ] Adguard Home
18+
19+
[comment]: <> (Change one of the boxes to - [x] to "select" it.)
20+
21+
22+
**What service are you trying to use?'**
23+
24+
25+
**What does not work?**
26+
27+
28+
**Which rule you believe is causing this?**

.github/copilot-instructions.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<!-- Copilot instructions for contributors and AI coding agents -->
2+
# Copilot / AI agent instructions — blocklist
3+
4+
Purpose: make a new AI coding agent productive quickly in this repo by describing the
5+
architecture, developer workflows, important conventions, and concrete examples.
6+
7+
- **Big picture**: The canonical source is the repository root `blocklist.txt`. Small CLI scripts in the repo root (e.g. `cleanup.py`, `insert.py`, `import.py`, `badfilter.py`) mutate or generate derived artifacts. The `modules/` package contains reusable logic (validation, formatting, update/export).
8+
9+
- **Primary data flow**:
10+
- Authors edit `blocklist.txt` (source-of-truth).
11+
- Run `cleanup.py` to normalize, remove duplicates and regenerate downstream outputs.
12+
- `modules/update.py:update_blocklists()` extracts first-party domains and writes derived files such as `domains.txt`, `rpz-blocklist.txt`, `hosts-blocklist.txt`, `pihole-blocklist.txt`, `unbound-blocklist.txt`, `wildcard-blocklist.txt`, and `little-snitch-blocklist.lsrules`.
13+
14+
- **Key files**:
15+
- [blocklist.txt](../blocklist.txt) — canonical input list (headers begin with `! ` and are preserved).
16+
- [header.txt](../header.txt) — header text injected into all generated lists by `modules/update.py`.
17+
- [modules/clean.py](../modules/clean.py)`format_record()` and `cleanup_file()` implement canonical normalization rules.
18+
- [modules/validate.py](../modules/validate.py) — central validation rules for acceptable records.
19+
- [modules/update.py](../modules/update.py) — exports domain lists in multiple formats.
20+
- CLI scripts: `cleanup.py` (clean + update), `insert.py` (append records interactively), `import.py` (import from file), `badfilter.py` (mark existing records `$badfilter`).
21+
22+
- **How to run (dev workflows)**:
23+
- Create / use virtualenv and install deps:
24+
25+
python3 -m venv blocklist
26+
source blocklist/bin/activate
27+
pip install -r requirements.txt
28+
29+
- Quick common commands:
30+
31+
- Clean + regenerate all derived lists: `python3 cleanup.py`
32+
- Insert one or more records interactively: `python3 insert.py example.com` or `python3 insert.py` then type records
33+
- Bulk import from file: `python3 import.py path/to/file.txt`
34+
- Mark records as badfilter: `python3 badfilter.py example.com`
35+
36+
- **Project-specific conventions & patterns**:
37+
- Records use Adblock/uBlock-style syntax: domains are normalized to the `||domain^` form; whitelist entries use `@@` prefix; special element/cosmetic rules are accepted (see `modules/validate.py`).
38+
- `modules/clean.py:format_record()` contains the exact normalization rules to follow for new code that touches record strings — reuse it rather than ad-hoc string ops.
39+
- `can_be_added()` in `modules/status.py` checks against the full text of `blocklist.txt` to prevent duplicates, whitelists, or `$badfilter` entries — any insertion code should call it.
40+
- Headers: lines starting with `! ` are treated as metadata and preserved by `cleanup_file()`.
41+
42+
- **Integration points & outputs**:
43+
- Generated outputs are in repository root and are consumed externally by DNS and firewall tools (PiHole, AdGuard, Little Snitch). The export logic lives in `modules/update.py` — modify formats there when adding new targets.
44+
45+
- **Dependencies & environment notes**:
46+
- Declared in `requirements.txt`: `dnspython`, `pyfiglet`, `python-whois`, `validators`, etc.
47+
- Some validation attempts use network lookups (DNS / whois). Tests or CI should avoid hitting the network; prefer mocking `modules.validate.is_registered` when writing unit tests.
48+
49+
- **Guidance for edits and PRs**:
50+
- Prefer small, focused changes that preserve `blocklist.txt` semantics.
51+
- When changing normalization rules, update `modules/clean.py:format_record()` and add a short example in tests or the PR description showing before/after for 2–3 representative records.
52+
- When adding a new export format, add it in `modules/update.py` and include a sample output file in a `samples/` directory for review.
53+
54+
- **Examples to reference while coding**:
55+
- Use `format_record()` before validating or writing records (see [insert.py](../insert.py) and [import.py](../import.py)).
56+
- Use `update_blocklists()` to regenerate outputs (called by `cleanup.py`).
57+
58+
If any section is unclear or you'd like more examples (e.g., representative before/after normalization cases or a small test harness), tell me which part to expand and I will iterate.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import re
2+
import dns.resolver
3+
import requests
4+
5+
BLOCKLIST = "blocklist.txt"
6+
OUTPUT = "candidates_for_removal.txt"
7+
IANA_TLDS_URL = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt"
8+
9+
# --- Load valid TLDs from IANA ---
10+
iana_tlds = {
11+
line.strip().lower()
12+
for line in requests.get(IANA_TLDS_URL).text.splitlines()
13+
if line and not line.startswith("#")
14+
}
15+
16+
# RFC-ish domain regex (labels, dots, no leading/trailing hyphen)
17+
DOMAIN_RE = re.compile(
18+
r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})+$"
19+
)
20+
21+
MODIFIER_KEYWORDS = (
22+
"$removeparam",
23+
"$csp",
24+
"$redirect",
25+
"$rewrite",
26+
"$removeheader",
27+
"$header",
28+
"$cookie",
29+
"$queryprune",
30+
"$badfilter",
31+
"$third-party",
32+
"$domain",
33+
)
34+
35+
IP_RE = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
36+
37+
38+
def extract_domain(line: str) -> str | None:
39+
line = line.strip()
40+
41+
# Comments / empty
42+
if not line or line.startswith(("!", "#", "//",)):
43+
return None
44+
45+
# Pure modifier rules and file rules (no host)
46+
if line.startswith("$"):
47+
return None
48+
49+
# File filter and
50+
if line.startswith(("^", "@@^")):
51+
return None
52+
53+
# pattern urls
54+
if "*" in line:
55+
return None
56+
57+
# Whitelisting rules
58+
if line.startswith("@@||"):
59+
return None
60+
61+
# Cosmetic filters
62+
if "##" in line or "#@" in line:
63+
return None
64+
65+
# If it contains known modifier keywords but no obvious host pattern,
66+
# treat it as a non-domain rule (e.g. *$removeparam=...).
67+
if any(k in line for k in MODIFIER_KEYWORDS):
68+
if not line.startswith("||") and not line.startswith(("|http://", "|https://")):
69+
return None
70+
71+
# uBO / Adblock network filters
72+
73+
# ||example.com^
74+
if line.startswith("||"):
75+
core = line[2:]
76+
core = core.split("^", 1)[0]
77+
core = core.split("/", 1)[0]
78+
return core.lower() or None
79+
80+
# |http://example.com^ or |https://example.com^
81+
if line.startswith("|http://") or line.startswith("|https://"):
82+
core = line[1:] # drop leading |
83+
core = core.split("://", 1)[1]
84+
core = core.split("^", 1)[0]
85+
core = core.split("/", 1)[0]
86+
return core.lower() or None
87+
88+
# Hosts file style: "0.0.0.0 example.com"
89+
parts = line.split()
90+
if len(parts) >= 2 and IP_RE.match(parts[0]):
91+
return parts[1].lower()
92+
93+
# If the line contains a "$" but we got here, it's likely a rule with modifiers
94+
# but no clear host pattern we support → skip to avoid false positives.
95+
if "$" in line:
96+
return None
97+
98+
# Plain domain line (no modifiers, no IP, no cosmetics)
99+
return line.lower()
100+
101+
102+
def is_valid_syntax(domain: str) -> bool:
103+
return bool(DOMAIN_RE.match(domain))
104+
105+
106+
def has_valid_tld(domain: str) -> bool:
107+
tld = domain.split(".")[-1]
108+
return tld.lower() in iana_tlds
109+
110+
111+
def resolves(domain: str) -> bool:
112+
for rtype in ("A", "AAAA", "MX", "CNAME"):
113+
try:
114+
dns.resolver.resolve(domain, rtype)
115+
return True
116+
except Exception:
117+
pass
118+
return False
119+
120+
121+
def main():
122+
candidates: list[str] = []
123+
124+
with open(BLOCKLIST, encoding="utf-8") as f:
125+
for raw in f:
126+
raw = raw.rstrip("\n")
127+
dom = extract_domain(raw)
128+
if dom is None:
129+
continue # comment / cosmetic / modifier / non-domain rule
130+
131+
reason = None
132+
if not is_valid_syntax(dom):
133+
reason = "invalid syntax"
134+
elif not has_valid_tld(dom):
135+
reason = "invalid TLD"
136+
elif not resolves(dom):
137+
reason = "does not resolve"
138+
139+
if reason:
140+
candidates.append(f"{raw}")
141+
142+
with open(OUTPUT, "w", encoding="utf-8") as f:
143+
if candidates:
144+
f.write("\n".join(candidates) + "\n")
145+
else:
146+
f.write("# no candidates\n")
147+
148+
149+
if __name__ == "__main__":
150+
main()
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Validate Blocklist
2+
3+
on:
4+
push:
5+
paths:
6+
- "blocklist.txt"
7+
pull_request:
8+
paths:
9+
- "blocklist.txt"
10+
workflow_dispatch:
11+
12+
jobs:
13+
validate:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.11"
24+
25+
- name: Install dependencies
26+
run: |
27+
pip install dnspython requests
28+
29+
- name: Run validator
30+
run: |
31+
python3 .github/scripts/validate-blocklist.py
32+
33+
- name: Commit candidate list if changed
34+
run: |
35+
if [[ -n "$(git status --porcelain)" ]]; then
36+
git config --global user.name "github-actions"
37+
git config --global user.email "actions@github.com"
38+
git add candidates_for_removal.txt
39+
git commit -m "Update removal candidates"
40+
git push
41+
fi

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
.idea
3+
domains-to-insert.txt
4+
blocklist/*
5+
__pycache__/*
6+
*/__pycache__/*

CONTRIBUTING.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Getting started
2+
3+
## Use both repositories
4+
5+
```zsh
6+
git clone git@gitlab.com:ph00lt0/blocklists.git
7+
cd blocklists
8+
git remote add github git@github.com:ph00lt0/blocklists.git
9+
```
10+
11+
## Init project
12+
13+
```zsh
14+
python3 -m venv blocklist
15+
source blocklist/bin/activate
16+
pip install -r requirements.txt
17+
# add simple command to open project and venv by just typing block
18+
echo "$(pwd)" > ~/.blocklist
19+
echo "alias block='cd \$(cat ~/.blocklist) && source \$(cat ~/.blocklist)/blocklist/bin/activate'" >> ~/.zshrc
20+
echo "alias block='cd \$(cat ~/.blocklist) && source \$(cat ~/.blocklist)/blocklist/bin/activate'" >> ~/.bashrc
21+
echo "python cleanup.py" > .git/hooks/pre-commit
22+
chmod +x .git/hooks/pre-commit
23+
```
24+
25+
## Insert
26+
27+
```zsh
28+
block
29+
python insert.py
30+
```
31+
32+
Insert supports, domains and filepaths.
33+
34+
If you enter a domain name, https:// or http and trailing / are automatically being removed if included.
35+
So https://annoyingtrackers.com/ becomes: annoyingtrackers.com
36+
37+
Hit enter, and the domain will be added to the list.
38+
39+
## Badlist
40+
41+
```zsh
42+
block
43+
python badlist.py
44+
```
45+
46+
Badlist supports filepaths and domains.
47+
48+
## Made a mistake?
49+
50+
Make your changes in blocklist.txt and commit.
51+
52+
## Remove a URL parameter (legacy)
53+
54+
```zsh
55+
./insert-filter-remove-parameter.sh
56+
```
57+
58+
OR
59+
60+
```zsh
61+
insert-filter-remove-parameter-domain.sh
62+
```
63+
Carefully choose whether to remove a parameter from only a single domain or globally. A clear tracker can be removed from all websites, but a UID may be required for some websites to operate. Generic UID names should not be removed globally.
64+
65+
Depending on the script enter the domain name and the parameter to remove. The script will do the rest.
66+
67+
## Remove element by class or id (legacy)
68+
69+
```zsh
70+
71+
./insert-filter-remove-element.sh
72+
```
73+
74+
- Enter the domain
75+
- Enter the identifier, following given instructions
76+
77+
Hit enter, and the rule will be added to the blocklist. Note that this won't be in the pihole blocklist and work for DNS only filters
78+
79+
## Add filter to remove a class from element (legacy)
80+
81+
```zsh
82+
./insert-filter-remove-class.sh
83+
```
84+
85+
Do you want to remove a class from an element? For example a class making the body disappear to show a cookie banner?
86+
87+
- Enter the domain name, the class, and the element to remove it from
88+
89+
Hit enter, and the class removal rule will be added
90+
Note that all rules are set to stay active, so that they will continue to work on page refreshes as well as with async content.
91+
92+
## Add filter to remove hidden overflow (legacy)
93+
94+
```zsh
95+
./insert-filter-remove-overflow-hidden.sh
96+
```
97+
98+
- Enter the domain name
99+
100+
Hit enter, and the hidden overflow will be removed from the body

0 commit comments

Comments
 (0)