├── .github ├── dependabot.yml └── workflows │ └── python-app.yml ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── begoneads ├── __init__.py ├── begoneads.py ├── collectors │ ├── __init__.py │ ├── base_collector.py │ ├── local_collector.py │ ├── remote_collector.py │ └── remote_collector_test.py ├── exceptions.py ├── helpers.py ├── hostsmanager.py └── hostsmanager_test.py ├── requirements.txt └── setup.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: pip 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up Python 3.10 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: "3.10" 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install flake8 pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | - name: Test with pytest 38 | run: | 39 | pytest 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | .dmypy.json 113 | dmypy.json 114 | 115 | # Pyre type checker 116 | .pyre/ 117 | 118 | # Other stuff 119 | .pypirc 120 | deb_dist/ 121 | begoneads-*.tar.gz -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Anne Douwe Bouma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format publish 2 | 3 | check-code: 4 | pycodestyle begoneads --max-line-length=120 5 | 6 | format: begoneads/**/*.py 7 | autopep8 --in-place begoneads/**/*.py 8 | 9 | publish-pypi: 10 | rm -rf dist/* 11 | python3 setup.py sdist bdist_wheel 12 | python3 -m twine upload dist/* 13 | 14 | publish: publish-pypi 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeGoneAds 2 | 3 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/anned20/begoneads.svg) 4 | ![GitHub issues](https://img.shields.io/github/issues/anned20/begoneads.svg) 5 | ![GitHub pull requests](https://img.shields.io/github/issues-pr/anned20/begoneads.svg) 6 | ![GitHub](https://img.shields.io/github/license/anned20/begoneads.svg) 7 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 8 | ![Awesome Badges](https://img.shields.io/badge/badges-awesome-green.svg) 9 | 10 | BeGoneAds is a script that puts some popular hosts file lists into the systems hosts file as a adblocker measure. 11 | 12 | See it working on asciinema: 13 | 14 | [![asciicast](https://asciinema.org/a/weDJ7SZw49HBdTl7iB0nWIYgI.svg)](https://asciinema.org/a/weDJ7SZw49HBdTl7iB0nWIYgI) 15 | 16 | ## Requirements 17 | 18 | - Python 3.6 or higher 19 | 20 | ## Installation instructions 21 | 22 | ### From PyPI 23 | 24 | To install BeGoneAds from PyPI use the following command: 25 | 26 | ```shell 27 | pip install begoneads 28 | ``` 29 | 30 | ### From source 31 | 32 | Clone this repository: 33 | 34 | ```shell 35 | git clone https://github.com/anned20/begoneads.git 36 | ``` 37 | 38 | Install the program: 39 | 40 | ```shell 41 | python setup.py install 42 | ``` 43 | 44 | ## Getting started 45 | 46 | You are now ready to use BeGoneAds: 47 | 48 | ```shell 49 | begoneads 50 | ``` 51 | 52 | You should see something like: 53 | 54 | ``` 55 | Usage: begoneads [OPTIONS] COMMAND [ARGS]... 56 | 57 | Install or uninstall BeGoneAds, the host blocker for the system hosts 58 | file 59 | 60 | Options: 61 | --help Show this message and exit. 62 | 63 | Commands: 64 | install Install or update BeGoneAds 65 | uninstall Uninstall BeGoneAds 66 | ``` 67 | 68 | ## Usage 69 | 70 | To install the hosts to your system hosts file: 71 | 72 | ```shell 73 | begoneads install 74 | ``` 75 | 76 | To install the hosts to your system hosts file with custom sources: 77 | 78 | ```shell 79 | begoneads install --sources https://www.custom.sources/hosts,http://www.and-another.one/hosts 80 | ``` 81 | 82 | To install the hosts to your system hosts file with local sources: 83 | 84 | ```shell 85 | begoneads install --local-sources path/to/hosts/file,other/path 86 | ``` 87 | 88 | The options `sources` and `local-sources` can be used together 89 | 90 | To uninstall the hosts to your system hosts file: 91 | 92 | ```shell 93 | begoneads uninstall 94 | ``` 95 | 96 | ## Sources of hosts data unified in this variant 97 | 98 | Updated `hosts` files from the following locations are always unified and 99 | included: 100 | 101 | Host file source | Home page | 102 | ----------------- | :---------: | 103 | Steven Black's ad-hoc list | [link](https://github.com/StevenBlack/hosts/blob/master/data/StevenBlack/hosts) | 104 | Malware Domain List | [link](https://www.malwaredomainlist.com/) | 105 | add.Dead | [link](https://github.com/FadeMind/hosts.extras) | 106 | add.Spam | [link](https://github.com/FadeMind/hosts.extras) | 107 | Dan Pollock | [link](https://someonewhocares.org/hosts/) | 108 | MVPS hosts file | [link](http://winhelp2002.mvps.org/) | 109 | yoyo.org | [link](https://pgl.yoyo.org/adservers/) | 110 | Mitchell Krog's - Badd Boyz Hosts | [link](https://github.com/mitchellkrogza/Badd-Boyz-Hosts) | 111 | CoinBlocker | [link](https://gitlab.com/ZeroDot1/CoinBlockerLists) | 112 | UncheckyAds | [link](https://github.com/FadeMind/hosts.extras) | 113 | add.2o7Net | [link](https://github.com/FadeMind/hosts.extras) | 114 | KADhosts | [link](https://github.com/azet12/KADhosts) | 115 | AdAway | [link](https://adaway.org/) | 116 | add.Risk | [link](https://github.com/FadeMind/hosts.extras) | 117 | 118 | ## TODO for 1.0.0 119 | 120 | - [X] Windows support 121 | - [X] Custom selection of host files 122 | - [X] Setuptools 123 | - [X] Apply own hosts 124 | - [ ] Systemd integration 125 | - [ ] Package it for Debian, Arch, CentOS, Fedora, etc. 126 | 127 | ## Testing 128 | 129 | To run the tests you use [pytest](https://pytest.org) 130 | 131 | Execute them with `pytest` in the project directory 132 | 133 | ## Built with 134 | 135 | - [requests](http://docs.python-requests.org/en/master/) - Getting the webpage 136 | - [click](https://github.com/mitsuhiko/click) - Parsing command line options 137 | - [tqdm](https://github.com/tqdm/tqdm) - Showing a fancy progress bar 138 | 139 | ## License 140 | 141 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 142 | -------------------------------------------------------------------------------- /begoneads/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anned20/begoneads/c251b897d8c07a288e19a4c6f430bd8044814045/begoneads/__init__.py -------------------------------------------------------------------------------- /begoneads/begoneads.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import re 6 | import click 7 | from begoneads.collectors.remote_collector import RemoteCollector 8 | from begoneads.collectors.local_collector import LocalCollector 9 | from begoneads.hostsmanager import HostsManager 10 | from begoneads.exceptions import NotElevatedException, InvalidSourceException 11 | from begoneads.helpers import is_admin 12 | 13 | # Default sources 14 | sources = [ 15 | 'https://www.malwaredomainlist.com/hostslist/hosts.txt', 16 | 'https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Dead/hosts', 17 | 'https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Spam/hosts', 18 | 'https://someonewhocares.org/hosts/zero/hosts', 19 | 'http://winhelp2002.mvps.org/hosts.txt', 20 | 'https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext&useip=0.0.0.0', 21 | 'https://raw.githubusercontent.com/mitchellkrogza/Badd-Boyz-Hosts/master/hosts', 22 | 'https://zerodot1.gitlab.io/CoinBlockerLists/hosts_browser', 23 | 'https://raw.githubusercontent.com/FadeMind/hosts.extras/master/UncheckyAds/hosts', 24 | 'https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.2o7Net/hosts', 25 | 'https://raw.githubusercontent.com/azet12/KADhosts/master/KADhosts.txt', 26 | 'https://raw.githubusercontent.com/AdAway/adaway.github.io/master/hosts.txt', 27 | 'https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Risk/hosts', 28 | ] 29 | 30 | 31 | @click.group() 32 | def cli(): 33 | """Install or uninstall BeGoneAds, the host blocker for the system hosts file""" 34 | 35 | 36 | @cli.command('install', short_help='Install or update BeGoneAds') 37 | @click.option('--sources', is_flag=False, default=','.join(sources), 38 | type=click.STRING, help='Sets sources to fetch from, seperated by ,') 39 | @click.option('--local-sources', is_flag=False, default='', 40 | type=click.STRING, help='Pass local hosts files to include') 41 | def install(sources, local_sources): 42 | # Check if we have sufficient permissions 43 | if not is_admin(sys.platform.startswith('win')): 44 | raise NotElevatedException( 45 | 'This program needs to be run as root to work properly') 46 | 47 | sources = [i.strip() for i in sources.split(',')] 48 | 49 | if local_sources: 50 | local_sources = [i.strip() for i in local_sources.split(',')] 51 | 52 | url_pattern = re.compile( 53 | r'^(?:http|ftp)s?://' # http:// or https:// 54 | # domain... 55 | r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' 56 | r'localhost|' # localhost... 57 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip 58 | r'(?::\d+)?' # optional port 59 | r'(?:/?|[/?]\S+)$', re.IGNORECASE 60 | ) 61 | 62 | for source in sources: 63 | if not re.match(url_pattern, source): 64 | raise InvalidSourceException(source) 65 | 66 | for source in local_sources: 67 | if not os.path.isfile(source): 68 | raise InvalidSourceException(source) 69 | 70 | # Collect hosts for hosts file 71 | remote_collector = RemoteCollector(sources) 72 | 73 | print('Collecting and parsing remote hosts') 74 | remote_hosts = remote_collector.get_result() 75 | 76 | print('Remote hosts collected') 77 | 78 | local_hosts = '' 79 | 80 | if local_sources: 81 | # Collect local host files 82 | local_collector = LocalCollector(local_sources) 83 | 84 | print('Collecting and parsing local hosts') 85 | local_hosts = local_collector.get_result() 86 | 87 | print('Local hosts collected') 88 | 89 | hosts = '\n'.join([ 90 | remote_hosts, 91 | local_hosts 92 | ]) 93 | 94 | hosts = hosts.split('\n') 95 | hosts = list(set(hosts)) 96 | hosts = '\n'.join(hosts) 97 | 98 | if sys.platform.startswith('win'): 99 | path = r'c:\windows\system32\drivers\etc\hosts' 100 | else: 101 | path = '/etc/hosts' 102 | 103 | print('Parse current hosts file') 104 | hosts_manager = HostsManager(path) 105 | 106 | print('Applying new hosts') 107 | hosts_manager.apply_hosts(hosts) 108 | 109 | print('Saving') 110 | hosts_manager.commit() 111 | 112 | print('Hosts applied') 113 | 114 | 115 | @cli.command('uninstall', short_help='Uninstall BeGoneAds') 116 | def uninstall(): 117 | # Check if we have sufficient permissions 118 | if not is_admin(sys.platform.startswith('win')): 119 | raise NotElevatedException( 120 | 'This program needs to be run as root to work properly') 121 | 122 | if sys.platform.startswith('win'): 123 | path = r'c:\windows\system32\drivers\etc\hosts' 124 | else: 125 | path = '/etc/hosts' 126 | 127 | print('Parse current hosts file') 128 | hosts_manager = HostsManager(path) 129 | 130 | print('Removing BeGoneAds') 131 | hosts_manager.remove_begoneads() 132 | 133 | print('Saving') 134 | hosts_manager.commit() 135 | 136 | print('BeGoneAds uninstalled') 137 | 138 | 139 | if __name__ == '__main__': 140 | cli() 141 | -------------------------------------------------------------------------------- /begoneads/collectors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anned20/begoneads/c251b897d8c07a288e19a4c6f430bd8044814045/begoneads/collectors/__init__.py -------------------------------------------------------------------------------- /begoneads/collectors/base_collector.py: -------------------------------------------------------------------------------- 1 | import re 2 | from tqdm import tqdm 3 | 4 | 5 | class BaseCollector(object): 6 | """A base class for all collectors 7 | 8 | Attributes: 9 | sources: list 10 | """ 11 | 12 | def __init__(self, sources): 13 | self.sources = sources 14 | 15 | def try_get_sources(self, sources): 16 | """Try and get each file""" 17 | 18 | filtered = [] 19 | 20 | for source in tqdm(sources): 21 | with open(source) as _file: 22 | filtered.append(_file.read()) 23 | 24 | return filtered 25 | 26 | def fix_ips(self, hosts): 27 | """Replace all IP addresses with 0.0.0.0""" 28 | 29 | hosts = re.sub(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', 30 | '0.0.0.0', hosts, 0, re.MULTILINE) 31 | 32 | return hosts 33 | 34 | def filter_hosts(self, hosts): 35 | """Only keep meaningful lines""" 36 | 37 | hosts = hosts.split('\n') 38 | hosts = [line for line in hosts 39 | if line.strip() != '' and not line.startswith('#')] 40 | hosts = '\n'.join(hosts) 41 | 42 | return hosts 43 | 44 | def get_result(self): 45 | """Get usable result""" 46 | 47 | sources = self.try_get_sources(self.sources) 48 | sources = '\n'.join(sources) 49 | hosts = self.fix_ips(sources) 50 | hosts = self.filter_hosts(hosts) 51 | 52 | return hosts 53 | -------------------------------------------------------------------------------- /begoneads/collectors/local_collector.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | from begoneads.collectors.base_collector import BaseCollector 3 | 4 | 5 | class LocalCollector(BaseCollector): 6 | """A class that collects local host files 7 | 8 | Attributes: 9 | sources: list 10 | """ 11 | 12 | def try_get_sources(self, sources): 13 | """Try and get each file""" 14 | 15 | filtered = [] 16 | 17 | for source in tqdm(sources): 18 | with open(source) as _file: 19 | filtered.append(_file.read()) 20 | 21 | return filtered 22 | -------------------------------------------------------------------------------- /begoneads/collectors/remote_collector.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from tqdm import tqdm 3 | from begoneads.collectors.base_collector import BaseCollector 4 | 5 | 6 | class RemoteCollector(BaseCollector): 7 | """A class that collects the remote host files 8 | 9 | Attributes: 10 | sources: list 11 | """ 12 | 13 | def try_get_sources(self, sources): 14 | """Try and get each source, don't return them if the request was not succesful""" 15 | 16 | filtered = [] 17 | 18 | for source in tqdm(sources): 19 | response = requests.get(source) 20 | 21 | if response.status_code >= 200 and response.status_code < 300: 22 | content = str(response.text) 23 | 24 | filtered.append(content) 25 | 26 | return filtered 27 | -------------------------------------------------------------------------------- /begoneads/collectors/remote_collector_test.py: -------------------------------------------------------------------------------- 1 | from begoneads.collectors.remote_collector import RemoteCollector 2 | from unittest.mock import patch 3 | 4 | collector = RemoteCollector([]) 5 | 6 | 7 | def test_try_get_sources(): 8 | with patch('requests.get') as mock_request: 9 | url = 'https://somewhere.com/that/has/hosts' 10 | 11 | mock_request.return_value.status_code = 200 12 | mock_request.return_value.text = 'response' 13 | 14 | assert collector.try_get_sources([url]) == ['response'] 15 | mock_request.assert_called_once_with(url) 16 | 17 | with patch('requests.get') as mock_request: 18 | url = 'https://somewhere.com/that/has/hosts/but/with/error' 19 | 20 | mock_request.return_value.status_code = 404 21 | 22 | assert collector.try_get_sources([url]) == [] 23 | mock_request.assert_called_once_with(url) 24 | 25 | 26 | def test_fix_ips(): 27 | before = '127.0.0.1 some.host' 28 | 29 | result = collector.fix_ips(before) 30 | 31 | assert result == '0.0.0.0 some.host' 32 | -------------------------------------------------------------------------------- /begoneads/exceptions.py: -------------------------------------------------------------------------------- 1 | class NotElevatedException(Exception): 2 | pass 3 | 4 | 5 | class InvalidSourceException(Exception): 6 | def __init__(self, source): 7 | self.message = f'{source} is not a valid source' 8 | 9 | def __str__(self): 10 | return self.message 11 | -------------------------------------------------------------------------------- /begoneads/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def is_admin(is_windows): 5 | if is_windows: 6 | try: 7 | # only windows users with admin privileges can read the C:\windows\temp 8 | _ = os.listdir(os.sep.join( 9 | [os.environ.get('SystemRoot', 'C:\\windows'), 'temp'])) 10 | 11 | return True 12 | except PermissionError: 13 | return False 14 | else: 15 | if os.geteuid() == 0: 16 | return True 17 | else: 18 | return False 19 | -------------------------------------------------------------------------------- /begoneads/hostsmanager.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | start_marker = '### START BeGoneAds ###' 5 | end_marker = '### END BeGoneAds ###' 6 | pattern = re.compile(f'(?<={start_marker})(.*)(?={end_marker})', 7 | re.MULTILINE | re.DOTALL) 8 | fullpattern = re.compile(f'(\n\n{start_marker}.*{end_marker})', 9 | re.MULTILINE | re.DOTALL) 10 | 11 | 12 | class HostsManager(object): 13 | """Manager for the system hosts file""" 14 | 15 | def __init__(self, path): 16 | self.path = path 17 | 18 | self.get_content() 19 | 20 | def get_content(self): 21 | """Get current contents of file""" 22 | 23 | file = open(self.path, 'r', encoding='utf-8') 24 | self.content = file.read() 25 | file.close() 26 | 27 | def has_begoneads(self): 28 | """Check if BeGoneAds is installed""" 29 | 30 | return re.search(pattern, self.content) 31 | 32 | def apply_hosts(self, hosts): 33 | """Apply or append BeGoneAds""" 34 | 35 | if self.has_begoneads(): 36 | self.content = re.sub(pattern, f'\n{hosts}\n', self.content) 37 | else: 38 | self.content = f'{self.content}\n\n{start_marker}\n{hosts}\n{end_marker}\n' 39 | 40 | def remove_begoneads(self): 41 | """Remove BeGoneAds""" 42 | 43 | if self.has_begoneads(): 44 | self.content = re.sub(fullpattern, '', self.content) 45 | 46 | def commit(self): 47 | """Save hosts file""" 48 | 49 | file = open(self.path, 'w', encoding='utf-8') 50 | file.write(self.content.replace('\r', '')) 51 | file.close() 52 | -------------------------------------------------------------------------------- /begoneads/hostsmanager_test.py: -------------------------------------------------------------------------------- 1 | from begoneads.hostsmanager import HostsManager, start_marker, end_marker 2 | from unittest.mock import patch, mock_open 3 | 4 | without_begoneads = ''' 5 | 127.0.0.1 localhost whatever-hostname 6 | 0.0.0.0 some.local 7 | ''' 8 | 9 | with_begoneads = f''' 10 | 127.0.0.1 localhost whatever-hostname 11 | 0.0.0.0 some.local 12 | 13 | {start_marker} 14 | 0.0.0.0 inserted.by.begoneads 15 | {end_marker} 16 | ''' 17 | 18 | 19 | def test_has_begoneads(): 20 | with patch('builtins.open', mock_open(read_data=without_begoneads)) as mock_file: 21 | hosts_manager = HostsManager('/etc/hosts') 22 | mock_file.assert_called_with('/etc/hosts', 'r', encoding='utf-8') 23 | 24 | assert hosts_manager.has_begoneads() is None 25 | 26 | with patch('builtins.open', mock_open(read_data=with_begoneads)) as mock_file: 27 | hosts_manager = HostsManager('/etc/hosts') 28 | mock_file.assert_called_with('/etc/hosts', 'r', encoding='utf-8') 29 | 30 | assert hosts_manager.has_begoneads() 31 | 32 | 33 | def test_apply_hosts(): 34 | with patch('builtins.open', mock_open(read_data=without_begoneads)) as mock_file: 35 | hosts_manager = HostsManager('/etc/hosts') 36 | mock_file.assert_called_with('/etc/hosts', 'r', encoding='utf-8') 37 | 38 | assert hosts_manager.has_begoneads() is None 39 | 40 | hosts_manager.apply_hosts('0.0.0.0 some.test') 41 | 42 | assert hosts_manager.has_begoneads() 43 | 44 | 45 | def test_remove_begoneads(): 46 | with patch('builtins.open', mock_open(read_data=with_begoneads)) as mock_file: 47 | hosts_manager = HostsManager('/etc/hosts') 48 | mock_file.assert_called_with('/etc/hosts', 'r', encoding='utf-8') 49 | 50 | assert hosts_manager.has_begoneads() 51 | 52 | hosts_manager.remove_begoneads() 53 | 54 | assert hosts_manager.has_begoneads() is None 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.1.6 2 | requests==2.31.0 3 | tqdm==4.65.0 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | def readme(): 5 | with open('README.md') as f: 6 | return f.read() 7 | 8 | 9 | setup( 10 | name='begoneads', 11 | version='0.0.10', 12 | description='BeGoneAds puts some popular hosts file lists into the hosts file as a adblocker measure.', 13 | python_requires='>=3.6', 14 | long_description=readme(), 15 | long_description_content_type="text/markdown", 16 | url='http://github.com/anned20/begoneads', 17 | entry_points={ 18 | 'console_scripts': ['begoneads=begoneads.begoneads:cli'], 19 | }, 20 | classifiers=[ 21 | 'Development Status :: 3 - Alpha', 22 | 'License :: OSI Approved :: MIT License', 23 | 'Programming Language :: Python :: 3.6', 24 | 'Programming Language :: Python :: 3.7', 25 | 'Programming Language :: Python :: 3.8', 26 | 'Topic :: System :: Networking', 27 | ], 28 | author='Anne Douwe Bouma', 29 | author_email='annedouwe@bouma.tech', 30 | license='MIT', 31 | packages=find_packages(), 32 | install_requires=[ 33 | 'click', 34 | 'requests', 35 | 'tqdm', 36 | ], 37 | zip_safe=False 38 | ) 39 | --------------------------------------------------------------------------------