Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Auto-merge Dependabot PRs

on:
pull_request:
types: [opened, synchronize, reopened]
pull_request_review:
types: [submitted]
# Allow manual triggering for testing
workflow_dispatch:

# Required to merge
permissions:
contents: write
pull-requests: write
checks: read

jobs:
auto-merge-dependabot:
name: Auto-merge Dependabot PR
runs-on: ubuntu-latest
# Only run for Dependabot PRs
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.user.login == 'dependabot[bot]'
Comment on lines +21 to +24
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job is configured to trigger on pull_request_review and workflow_dispatch, but the job-level if restricts execution to github.event_name == 'pull_request' only. That makes the review-trigger and manual-dispatch paths no-ops. Update the condition to allow the intended events (and use the correct payload path for each event type) so the workflow can run when a review is submitted or when manually triggered.

Suggested change
# Only run for Dependabot PRs
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.user.login == 'dependabot[bot]'
# Only run for Dependabot PRs and intended events
if: |
(github.event_name == 'pull_request' &&
github.event.pull_request.user.login == 'dependabot[bot]') ||
(github.event_name == 'pull_request_review' &&
github.event.pull_request.user.login == 'dependabot[bot]') ||
github.event_name == 'workflow_dispatch'

Copilot uses AI. Check for mistakes.
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

- name: Wait for tests
uses: actions/github-script@v7
with:
script: |
const pullNumber = context.payload.pull_request.number;
const maxAttempts = 20;
const intervalMs = 30000;

for (let attempt = 0; attempt < maxAttempts; attempt++) {
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.sha,
status: 'completed',
});

// Also check commit statuses
const { data: statuses } = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.sha,
});

const allChecks = [...checks.check_runs, ...statuses];
const pending = allChecks.filter(c => c.conclusion === null);

if (pending.length === 0) {
const failed = allChecks.filter(c => c.conclusion !== 'success');
Comment on lines +41 to +59
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listCommitStatusesForRef()returns commit statuses with astatefield (e.g.,success, failure, pending), not a conclusion field. As written, every commit status will be treated as “failed” (undefined !== 'success'), and “pending” detection won’t work. Fix by branching logic: evaluate checks.check_runsusingstatus/conclusion, and evaluate commit statuses using state(and treatpending/error/failureappropriately). Also,status: 'completed'` means you never see in-progress check runs, so the “wait” loop will not actually wait for checks to complete.

Copilot uses AI. Check for mistakes.
if (failed.length > 0) {
console.log('Some checks failed:', failed.map(f => `${f.name}: ${f.conclusion}`));
core.setFailed('Checks did not pass');
process.exit(1);
}
console.log('All checks passed!');
process.exit(0);
}

console.log(`Waiting for ${pending.length} checks...`);
await new Promise(r => setTimeout(r, intervalMs));
}

core.setFailed('Timeout waiting for checks');
process.exit(1);

- name: Enable auto-merge
run: gh pr merge --auto --squash "$PR_NUMBER"
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
40 changes: 39 additions & 1 deletion Scripts/test_deduplicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path


from deduplicate import process_content
from deduplicate import process_content, is_header, is_valid_rule


class TestDeduplicate(unittest.TestCase):
Expand Down Expand Up @@ -41,6 +41,44 @@ def test_process_content_keeps_comments(self):
self.assertEqual(headers, expected_headers)
self.assertEqual(rules, expected_rules)

def test_is_header(self):
# Valid headers with prefixes
self.assertTrue(is_header("! This is a comment"))
self.assertTrue(is_header("# This is also a comment"))
self.assertTrue(is_header("[Adblock Plus 2.0]"))
self.assertTrue(is_header("; Semicolon comment"))

# Empty lines
self.assertTrue(is_header(""))

# Non-headers
self.assertFalse(is_header("||example.com^"))
self.assertFalse(is_header("example.com##.ad"))
self.assertFalse(is_header("example.com"))
self.assertFalse(is_header("@@||example.com"))

def test_is_valid_rule(self):
# Valid rules
self.assertTrue(is_valid_rule("example.com"))
self.assertTrue(is_valid_rule("||example.com^"))
self.assertTrue(is_valid_rule("@@||example.com"))
self.assertTrue(is_valid_rule("example.com$domain=example.com"))
self.assertTrue(is_valid_rule("||example.com^$important"))

# Empty or too long
self.assertFalse(is_valid_rule(""))
long_line = "a" * 2049
self.assertFalse(is_valid_rule(long_line))

# Exactly 2048 chars should be valid
max_line = "a" * 2048
self.assertTrue(is_valid_rule(max_line))

# Invalid domain patterns
self.assertFalse(is_valid_rule("||invalid^"))
self.assertFalse(is_valid_rule("||-start.com^"))
self.assertFalse(is_valid_rule("||end-.com^"))


if __name__ == "__main__":
unittest.main()
104 changes: 104 additions & 0 deletions Scripts/test_update_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class ClientError(Exception):
sys.modules["update_lists"] = update_lists
spec.loader.exec_module(update_lists)

# Import specific functions to test
count_rules = update_lists.count_rules
load_sources = update_lists.load_sources


class AsyncIterator:
def __init__(self, seq):
Expand Down Expand Up @@ -308,6 +312,106 @@ def test_fetch_list_unexpected_error(self, mock_logger_exception):
"✗ Unexpected error for http://url: Boom"
)

def test_count_rules(self):
# Empty content
self.assertEqual(count_rules(""), 0)

# Only headers
content = "! Header 1\n! Header 2\n# Comment\n[Adblock Plus]\n"
self.assertEqual(count_rules(content), 0)

# Mixed content
content = """! Header
||example.com^
example.com##.ad
domain.com
# Another comment
||ads.example.com^
"""
# Should count: ||example.com^, example.com##.ad, domain.com, ||ads.example.com^ = 4
self.assertEqual(count_rules(content), 4)

# Content with empty lines
content = "rule1.com\n\nrule2.com\n\n"
self.assertEqual(count_rules(content), 2)

# Windows line endings should be handled
content = "rule1.com\r\nrule2.com\r\n"
self.assertEqual(count_rules(content), 2)

def test_count_rules_with_whitespace(self):
# Lines with only whitespace should not count
content = "rule1.com\n \n\trule2.com\n"
self.assertEqual(count_rules(content), 2)

def test_load_sources_template_creation(self):
"""Test load_sources creates template when config doesn't exist."""
import tempfile
import json
Comment on lines +349 to +350
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to PEP 8 style guidelines, imports should be placed at the top of the file. The import tempfile and import json statements are defined inside this test method and also repeated in test_load_sources_existing_config. To improve code readability and maintainability, please move these imports to the top-level import section of the file.


with tempfile.TemporaryDirectory() as temp_dir:
config_path = Path(temp_dir) / "sources-urls.json"

# Call load_sources - should create template
sources = load_sources(config_path)

# Verify template was created
self.assertTrue(config_path.exists())

# Verify it loads the created template
data = json.loads(config_path.read_text())
self.assertIn("sources", data)

# Verify sources is a dict with expected keys
self.assertIsInstance(sources, dict)
for url, config in sources.items():
self.assertIn("filename", config)
self.assertIn("skip_checksum", config)
self.assertIn("enabled", config)
self.assertTrue(config["enabled"])

def test_load_sources_existing_config(self):
"""Test load_sources loads existing config correctly."""
import tempfile
import json

with tempfile.TemporaryDirectory() as temp_dir:
config_path = Path(temp_dir) / "sources-urls.json"

# Create a test config
config_data = {
"sources": [
{
"url": "https://example.com/list1.txt",
"filename": "custom-name.txt",
"skip_checksum": True,
"enabled": True,
},
{
"url": "https://example.com/list2.txt",
"enabled": False, # Should be filtered out
},
{
"url": "https://example.com/list3.txt",
"enabled": True,
},
]
}
config_path.write_text(json.dumps(config_data))

sources = load_sources(config_path)

# Should have 2 sources (list2 is disabled)
self.assertEqual(len(sources), 2)

# Verify list1
self.assertIn("https://example.com/list1.txt", sources)
self.assertEqual(sources["https://example.com/list1.txt"]["filename"], "custom-name.txt")
self.assertTrue(sources["https://example.com/list1.txt"]["skip_checksum"])

# Verify list3 (no filename provided - should use sanitize)
self.assertIn("https://example.com/list3.txt", sources)


if __name__ == "__main__":
unittest.main()
32 changes: 16 additions & 16 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions lists/sources/Combination-Minimal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Adblock Plus 2.0]
! Title: Ven0m0's minimal Adblocking Filter
! Description: Combination List of multiple filters to make the internet comfortable
! Homepage: https://github.com/Ven0m0/Ven0m0-Adblock
! Raw: https://raw.githubusercontent.com/Ven0m0/Ven0m0-Adblock/main/lists/adblock/Combination-Minimal.txt
! Last modified: %timestamp%
! License: https://github.com/Ven0m0/Ven0m0-Adblock/blob/main/LICENSE

!#include General.txt
!#include Other.txt
!#include Reddit.txt
!#include Search-Engines.txt
!#include Twitter.txt
!#include Youtube.txt
13 changes: 13 additions & 0 deletions lists/sources/Combination-desktop.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Adblock Plus 2.0]
! Title: Ven0m0's minimal Adblocking Filter
! Description: Combination List of multiple filters to make the internet comfortable
! Homepage: https://github.com/Ven0m0/Ven0m0-Adblock
! Raw: https://raw.githubusercontent.com/Ven0m0/Ven0m0-Adblock/main/lists/adblock/Combination-desktop.txt
! Last modified: %timestamp%
! License: https://github.com/Ven0m0/Ven0m0-Adblock/blob/main/LICENSE

!#include General.txt
!#include Other.txt
!#include Reddit.txt
!#include Search-Engines.txt
!#include Youtube.txt
16 changes: 16 additions & 0 deletions lists/sources/Combination-mobile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Adblock Plus 2.0]
! Title: Ven0m0's minimal Adblocking Filter
! Description: Combination List of multiple filters to make the internet comfortable
! Homepage: https://github.com/Ven0m0/Ven0m0-Adblock
! Raw: https://raw.githubusercontent.com/Ven0m0/Ven0m0-Adblock/main/lists/adblock/Combination-mobile.txt
! Last modified: %timestamp%
! License: https://github.com/Ven0m0/Ven0m0-Adblock/blob/main/LICENSE

!#include General.txt
!#include Other.txt
!#include Reddit.txt
!#include Search-Engines.txt
!#include https://filters.adtidy.org/extension/ublock/filters/251_optimized.txt
!#include https://filters.adtidy.org/extension/ublock/filters/201_optimized.txt
!#include https://filters.adtidy.org/extension/ublock/filters/239_optimized.txt
!#include https://filters.adtidy.org/extension/ublock/filters/207_optimized.txt
19 changes: 19 additions & 0 deletions lists/sources/Combination.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
! [Adblock Plus 2.0]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The Adblock Plus header [Adblock Plus 2.0] is a special directive that must not be commented out. By prefixing it with !, you are turning it into a regular comment, which may cause ad blockers to misinterpret the file. Please remove the leading !.

[Adblock Plus 2.0]
References
  1. The style guide specifies AdGuard/uBlock Origin syntax for filter lists (line 177). The [Adblock Plus 2.0] header is a required part of this syntax and should not be treated as a regular comment (line 179). (link)

! Title: Ven0m0's Adblocking Filter
! Description: Combination List of multiple filters to make the internet comfortable
! Homepage: https://github.com/Ven0m0/Ven0m0-Adblock
! Raw: https://raw.githubusercontent.com/Ven0m0/Ven0m0-Adblock/main/lists/adblock/Combination.txt
! Last modified: %timestamp%
! License: https://github.com/Ven0m0/Ven0m0-Adblock/blob/main/LICENSE

!#include General.txt
!#include Other.txt
!#include Reddit.txt
!#include Search-Engines.txt
!#include Spotify.txt
!#include Twitter.txt
!#include Youtube.txt
!#include antiadblock.txt
!#include fanboy_sounds_thirdparty.txt
!#include lan-block.txt
!#include resource-abuse.txt
Loading
Loading