├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .pylintrc ├── .vscode └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── cast ├── original.cast └── short.cast ├── data └── docker_inspect_pihole.json ├── ph5lt ├── __init__.py ├── __main__.py ├── allowlists.py ├── app.py ├── banner.py ├── blocklists.py ├── constants.py ├── prompts.py ├── stats.py └── utils.py ├── poetry.lock ├── pyproject.toml └── tests ├── test_prompts.py └── test_utils.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Forms suck. But they can help a ton. Going free-form below is okay if you're certain you're providing all of the relevant information (or things aren't applicable) 11 | 12 | ## Environment 13 | Describe the system on which this issue occurred: 14 | 15 | **pihole5-list-tool** _(ex: 0.4.2, `pip3 show pihole5-list-tool`)_ **:** 16 | 17 | **OS** _(ex: Ubuntu 20.20, `lsb_release -a`)_ **:** 18 | 19 | **Python** _(ex: 3.8.2, `python --version`)_ **:** 20 | 21 | **Using Docker?** Y/N 22 | .....**version** _(ex: 19.03.10, `docker -v`)_ **:** 23 | 24 | 25 | ## Issue Details 26 | Briefly explain the issue and provide any pertinent stack traces, environment, etc. 27 | 28 | **Expected behavior** 29 | A clear and concise description of what you expected to happen. 30 | 31 | **Actual behavior** 32 | What did our problem child do this time? 33 | 34 | **Supporting details** 35 | Attach or paste in stack traces, screenshots, and the such... 36 | 37 | **Anything else** 38 | Anything else you feel is valuable that wasn't asked for? 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # mac crap & other 2 | *.DS_Store 3 | /.vagrant 4 | Homestead.json 5 | Homestead.yaml 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS,setup.py 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=1 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=raw-checker-failed, 64 | bad-inline-option, 65 | locally-disabled, 66 | file-ignored, 67 | suppressed-message, 68 | useless-suppression, 69 | deprecated-pragma, 70 | use-symbolic-message-instead, 71 | duplicate-code 72 | 73 | 74 | # Enable the message, report, category or checker with the given id(s). You can 75 | # either give multiple identifier separated by comma (,) or put this option 76 | # multiple time (only on the command line, not in the configuration file where 77 | # it should appear only once). See also the "--disable" option for examples. 78 | enable=c-extension-no-member 79 | 80 | 81 | [REPORTS] 82 | 83 | # Python expression which should return a score less than or equal to 10. You 84 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 85 | # which contain the number of messages in each category, as well as 'statement' 86 | # which is the total number of statements analyzed. This score is used by the 87 | # global evaluation report (RP0004). 88 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 89 | 90 | # Template used to display messages. This is a python new-style format string 91 | # used to format the message information. See doc for all details. 92 | #msg-template= 93 | 94 | # Set the output format. Available formats are text, parseable, colorized, json 95 | # and msvs (visual studio). You can also give a reporter class, e.g. 96 | # mypackage.mymodule.MyReporterClass. 97 | output-format=text 98 | 99 | # Tells whether to display a full report or only the messages. 100 | reports=no 101 | 102 | # Activate the evaluation score. 103 | score=yes 104 | 105 | 106 | [REFACTORING] 107 | 108 | # Maximum number of nested blocks for function / method body 109 | max-nested-blocks=5 110 | 111 | # Complete name of functions that never returns. When checking for 112 | # inconsistent-return-statements if a never returning function is called then 113 | # it will be considered as an explicit return statement and no message will be 114 | # printed. 115 | never-returning-functions=sys.exit 116 | 117 | 118 | [STRING] 119 | 120 | # This flag controls whether inconsistent-quotes generates a warning when the 121 | # character used as a quote delimiter is used inconsistently within a module. 122 | check-quote-consistency=no 123 | 124 | # This flag controls whether the implicit-str-concat should generate a warning 125 | # on implicit string concatenation in sequences defined over several lines. 126 | check-str-concat-over-line-jumps=no 127 | 128 | 129 | [BASIC] 130 | 131 | # Naming style matching correct argument names. 132 | argument-naming-style=snake_case 133 | 134 | # Regular expression matching correct argument names. Overrides argument- 135 | # naming-style. 136 | #argument-rgx= 137 | 138 | # Naming style matching correct attribute names. 139 | attr-naming-style=snake_case 140 | 141 | # Regular expression matching correct attribute names. Overrides attr-naming- 142 | # style. 143 | #attr-rgx= 144 | 145 | # Bad variable names which should always be refused, separated by a comma. 146 | bad-names=foo, 147 | bar, 148 | baz, 149 | toto, 150 | tutu, 151 | tata 152 | 153 | # Bad variable names regexes, separated by a comma. If names match any regex, 154 | # they will always be refused 155 | bad-names-rgxs= 156 | 157 | # Naming style matching correct class attribute names. 158 | class-attribute-naming-style=any 159 | 160 | # Regular expression matching correct class attribute names. Overrides class- 161 | # attribute-naming-style. 162 | #class-attribute-rgx= 163 | 164 | # Naming style matching correct class names. 165 | class-naming-style=PascalCase 166 | 167 | # Regular expression matching correct class names. Overrides class-naming- 168 | # style. 169 | #class-rgx= 170 | 171 | # Naming style matching correct constant names. 172 | const-naming-style=UPPER_CASE 173 | 174 | # Regular expression matching correct constant names. Overrides const-naming- 175 | # style. 176 | #const-rgx= 177 | 178 | # Minimum line length for functions/classes that require docstrings, shorter 179 | # ones are exempt. 180 | docstring-min-length=-1 181 | 182 | # Naming style matching correct function names. 183 | function-naming-style=snake_case 184 | 185 | # Regular expression matching correct function names. Overrides function- 186 | # naming-style. 187 | #function-rgx= 188 | 189 | # Good variable names which should always be accepted, separated by a comma. 190 | good-names=i, 191 | j, 192 | k, 193 | ex, 194 | Run, 195 | _ 196 | 197 | # Good variable names regexes, separated by a comma. If names match any regex, 198 | # they will always be accepted 199 | good-names-rgxs= 200 | 201 | # Include a hint for the correct naming format with invalid-name. 202 | include-naming-hint=no 203 | 204 | # Naming style matching correct inline iteration names. 205 | inlinevar-naming-style=any 206 | 207 | # Regular expression matching correct inline iteration names. Overrides 208 | # inlinevar-naming-style. 209 | #inlinevar-rgx= 210 | 211 | # Naming style matching correct method names. 212 | method-naming-style=snake_case 213 | 214 | # Regular expression matching correct method names. Overrides method-naming- 215 | # style. 216 | #method-rgx= 217 | 218 | # Naming style matching correct module names. 219 | module-naming-style=snake_case 220 | 221 | # Regular expression matching correct module names. Overrides module-naming- 222 | # style. 223 | #module-rgx= 224 | 225 | # Colon-delimited sets of names that determine each other's naming style when 226 | # the name regexes allow several styles. 227 | name-group= 228 | 229 | # Regular expression which should only match function or class names that do 230 | # not require a docstring. 231 | no-docstring-rgx=^_ 232 | 233 | # List of decorators that produce properties, such as abc.abstractproperty. Add 234 | # to this list to register other decorators that produce valid properties. 235 | # These decorators are taken in consideration only for invalid-name. 236 | property-classes=abc.abstractproperty 237 | 238 | # Naming style matching correct variable names. 239 | variable-naming-style=snake_case 240 | 241 | # Regular expression matching correct variable names. Overrides variable- 242 | # naming-style. 243 | #variable-rgx= 244 | 245 | 246 | [SIMILARITIES] 247 | 248 | # Ignore comments when computing similarities. 249 | ignore-comments=yes 250 | 251 | # Ignore docstrings when computing similarities. 252 | ignore-docstrings=yes 253 | 254 | # Ignore imports when computing similarities. 255 | ignore-imports=no 256 | 257 | # Minimum lines number of a similarity. 258 | min-similarity-lines=4 259 | 260 | 261 | [TYPECHECK] 262 | 263 | # List of decorators that produce context managers, such as 264 | # contextlib.contextmanager. Add to this list to register other decorators that 265 | # produce valid context managers. 266 | contextmanager-decorators=contextlib.contextmanager 267 | 268 | # List of members which are set dynamically and missed by pylint inference 269 | # system, and so shouldn't trigger E1101 when accessed. Python regular 270 | # expressions are accepted. 271 | generated-members= 272 | 273 | # Tells whether missing members accessed in mixin class should be ignored. A 274 | # mixin class is detected if its name ends with "mixin" (case insensitive). 275 | ignore-mixin-members=yes 276 | 277 | # Tells whether to warn about missing members when the owner of the attribute 278 | # is inferred to be None. 279 | ignore-none=yes 280 | 281 | # This flag controls whether pylint should warn about no-member and similar 282 | # checks whenever an opaque object is returned when inferring. The inference 283 | # can return multiple potential results while evaluating a Python object, but 284 | # some branches might not be evaluated, which results in partial inference. In 285 | # that case, it might be useful to still emit no-member and other checks for 286 | # the rest of the inferred objects. 287 | ignore-on-opaque-inference=yes 288 | 289 | # List of class names for which member attributes should not be checked (useful 290 | # for classes with dynamically set attributes). This supports the use of 291 | # qualified names. 292 | ignored-classes=optparse.Values,thread._local,_thread._local 293 | 294 | # List of module names for which member attributes should not be checked 295 | # (useful for modules/projects where namespaces are manipulated during runtime 296 | # and thus existing member attributes cannot be deduced by static analysis). It 297 | # supports qualified module names, as well as Unix pattern matching. 298 | ignored-modules= 299 | 300 | # Show a hint with possible names when a member name was not found. The aspect 301 | # of finding the hint is based on edit distance. 302 | missing-member-hint=yes 303 | 304 | # The minimum edit distance a name should have in order to be considered a 305 | # similar match for a missing member name. 306 | missing-member-hint-distance=1 307 | 308 | # The total number of similar names that should be taken in consideration when 309 | # showing a hint for a missing member. 310 | missing-member-max-choices=1 311 | 312 | # List of decorators that change the signature of a decorated function. 313 | signature-mutators= 314 | 315 | 316 | [VARIABLES] 317 | 318 | # List of additional names supposed to be defined in builtins. Remember that 319 | # you should avoid defining new builtins when possible. 320 | additional-builtins= 321 | 322 | # Tells whether unused global variables should be treated as a violation. 323 | allow-global-unused-variables=yes 324 | 325 | # List of strings which can identify a callback function by name. A callback 326 | # name must start or end with one of those strings. 327 | callbacks=cb_, 328 | _cb 329 | 330 | # A regular expression matching the name of dummy variables (i.e. expected to 331 | # not be used). 332 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 333 | 334 | # Argument names that match this expression will be ignored. Default to name 335 | # with leading underscore. 336 | ignored-argument-names=_.*|^ignored_|^unused_ 337 | 338 | # Tells whether we should check for unused import in __init__ files. 339 | init-import=no 340 | 341 | # List of qualified module names which can have objects that can redefine 342 | # builtins. 343 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 344 | 345 | 346 | [FORMAT] 347 | 348 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 349 | expected-line-ending-format= 350 | 351 | # Regexp for a line that is allowed to be longer than the limit. 352 | ignore-long-lines=^\s*(# )??$ 353 | 354 | # Number of spaces of indent required inside a hanging or continued line. 355 | indent-after-paren=4 356 | 357 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 358 | # tab). 359 | indent-string=' ' 360 | 361 | # Maximum number of characters on a single line. 362 | max-line-length=200 363 | 364 | # Maximum number of lines in a module. 365 | max-module-lines=1000 366 | 367 | # List of optional constructs for which whitespace checking is disabled. `dict- 368 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 369 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 370 | # `empty-line` allows space-only lines. 371 | # no-space-check=trailing-comma, 372 | # dict-separator 373 | 374 | # Allow the body of a class to be on the same line as the declaration if body 375 | # contains single statement. 376 | single-line-class-stmt=no 377 | 378 | # Allow the body of an if to be on the same line as the test if there is no 379 | # else. 380 | single-line-if-stmt=no 381 | 382 | 383 | [LOGGING] 384 | 385 | # The type of string formatting that logging methods do. `old` means using % 386 | # formatting, `new` is for `{}` formatting. 387 | logging-format-style=old 388 | 389 | # Logging modules to check that the string format arguments are in logging 390 | # function parameter format. 391 | logging-modules=logging 392 | 393 | 394 | [SPELLING] 395 | 396 | # Limits count of emitted suggestions for spelling mistakes. 397 | max-spelling-suggestions=4 398 | 399 | # Spelling dictionary name. Available dictionaries: none. To make it work, 400 | # install the python-enchant package. 401 | spelling-dict= 402 | 403 | # List of comma separated words that should not be checked. 404 | spelling-ignore-words= 405 | 406 | # A path to a file that contains the private dictionary; one word per line. 407 | spelling-private-dict-file= 408 | 409 | # Tells whether to store unknown words to the private dictionary (see the 410 | # --spelling-private-dict-file option) instead of raising a message. 411 | spelling-store-unknown-words=no 412 | 413 | 414 | [MISCELLANEOUS] 415 | 416 | # List of note tags to take in consideration, separated by a comma. 417 | notes=FIXME, 418 | XXX, 419 | TODO 420 | 421 | # Regular expression of note tags to take in consideration. 422 | #notes-rgx= 423 | 424 | 425 | [DESIGN] 426 | 427 | # Maximum number of arguments for function / method. 428 | max-args=5 429 | 430 | # Maximum number of attributes for a class (see R0902). 431 | max-attributes=7 432 | 433 | # Maximum number of boolean expressions in an if statement (see R0916). 434 | max-bool-expr=5 435 | 436 | # Maximum number of branch for function / method body. 437 | max-branches=20 438 | 439 | # Maximum number of locals for function / method body. 440 | max-locals=15 441 | 442 | # Maximum number of parents for a class (see R0901). 443 | max-parents=7 444 | 445 | # Maximum number of public methods for a class (see R0904). 446 | max-public-methods=20 447 | 448 | # Maximum number of return / yield for function / method body. 449 | max-returns=6 450 | 451 | # Maximum number of statements in function / method body. 452 | max-statements=50 453 | 454 | # Minimum number of public methods for a class (see R0903). 455 | min-public-methods=1 456 | 457 | 458 | [CLASSES] 459 | 460 | # List of method names used to declare (i.e. assign) instance attributes. 461 | defining-attr-methods=__init__, 462 | __new__, 463 | setUp, 464 | __post_init__ 465 | 466 | # List of member names, which should be excluded from the protected access 467 | # warning. 468 | exclude-protected=_asdict, 469 | _fields, 470 | _replace, 471 | _source, 472 | _make 473 | 474 | # List of valid names for the first argument in a class method. 475 | valid-classmethod-first-arg=cls 476 | 477 | # List of valid names for the first argument in a metaclass class method. 478 | valid-metaclass-classmethod-first-arg=cls 479 | 480 | 481 | [IMPORTS] 482 | 483 | # List of modules that can be imported at any level, not just the top level 484 | # one. 485 | allow-any-import-level= 486 | 487 | # Allow wildcard imports from modules that define __all__. 488 | allow-wildcard-with-all=no 489 | 490 | # Analyse import fallback blocks. This can be used to support both Python 2 and 491 | # 3 compatible code, which means that the block might have code that exists 492 | # only in one or another interpreter, leading to false positives when analysed. 493 | analyse-fallback-blocks=no 494 | 495 | # Deprecated modules which should not be used, separated by a comma. 496 | deprecated-modules=optparse,tkinter.tix 497 | 498 | # Create a graph of external dependencies in the given file (report RP0402 must 499 | # not be disabled). 500 | ext-import-graph= 501 | 502 | # Create a graph of every (i.e. internal and external) dependencies in the 503 | # given file (report RP0402 must not be disabled). 504 | import-graph= 505 | 506 | # Create a graph of internal dependencies in the given file (report RP0402 must 507 | # not be disabled). 508 | int-import-graph= 509 | 510 | # Force import order to recognize a module as part of the standard 511 | # compatibility libraries. 512 | known-standard-library= 513 | 514 | # Force import order to recognize a module as part of a third party library. 515 | known-third-party=enchant 516 | 517 | # Couples of modules and preferred modules, separated by a comma. 518 | preferred-modules= 519 | 520 | 521 | [EXCEPTIONS] 522 | 523 | # Exceptions that will emit a warning when being caught. Defaults to 524 | # "BaseException, Exception". 525 | overgeneral-exceptions=builtins.BaseException,builtins.Exception 526 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.autopep8Args": [ 3 | " --max-line-length" 4 | ], 5 | "python.linting.flake8Enabled": false, 6 | "python.linting.enabled": true, 7 | "python.linting.pylintEnabled": true 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jessedp 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 | clean-pyc: 2 | @find . -name '*.pyc' -exec rm --force {} + 3 | @find . -name '*.pyo' -exec rm --force {} + 4 | @find . -name '*~' -exec rm --force {} + 5 | 6 | clean-build: 7 | rm --force --recursive build/ 8 | rm --force --recursive dist/ 9 | rm --force --recursive __pycache__/ 10 | rm --force --recursive *.egg-info 11 | 12 | lint: 13 | poetry run black --target-version py37 ph5lt 14 | poetry run pylint ph5lt 15 | 16 | test: clean-pyc lint 17 | poetry run coverage run -m pytest 18 | # coverage report --include=ph5lt\/* 19 | # python3 -m pytest 20 | 21 | covreport: 22 | poetry run coverage report --include=ph5lt\/* -m 23 | 24 | run: 25 | poetry run python3 -m ph5lt 26 | 27 | update: 28 | poetry update 29 | 30 | build: clean-build lint 31 | poetry build 32 | 33 | package: build 34 | poetry build 35 | 36 | publishTest: package 37 | poetry publish -r testpypi 38 | 39 | # run outside of the venv/poetry 40 | installTest: 41 | pip3 install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pihole5-list-tool --upgrade 42 | 43 | # run outside of the venv/poetry 44 | uninstall: 45 | pip3 uninstall pihole5-list-tool 46 | 47 | publish: package 48 | poetry publish 49 | 50 | reqs: 51 | poetry install 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pihole5-list-tool 2 | 3 | [![PyPI version](https://badge.fury.io/py/pihole5-list-tool.svg)](https://badge.fury.io/py/pihole5-list-tool) 4 | [![PyPI downloads](https://img.shields.io/pypi/dm/pihole5-list-tool)](https://pypi.org/project/pihole5-list-tool/) 5 | 6 | This tool provides bulk operations to manage your [Pi-hole 5](https://pi-hole.net/) **Allow lists** and **Block/Ad lists**. 7 | 8 | ## Features: 9 | 10 | - **Allow lists** can be [added](#allowlists) from [anudeepND's allowlist](https://github.com/anudeepND/whitelist), files, or manual entry 11 | - **Block/Ad lists** can be [added](#adblocklists) from [firebog.net](https://firebog.net/), files, or manual entry 12 | - **Removes** lists it adds (or all of them) 13 | - **Reset** lists to Pi-hole defaults 14 | - **Stats** provides some quick sums and groupings 15 | - **Docker** if you're running the [pihole docker image](https://hub.docker.com/r/pihole/pihole/) (or one named `pihole`), it should be detected 16 | and offered as a default option 17 | 18 | ## requirements 19 | 20 | - working [pi-hole 5+](https://pi-hole.net) installation 21 | - [python 3.7+](https://python.org/) (available by default on Raspbian 10, probably available on your system) 22 | 23 | ## installation 24 | 25 | ```bash 26 | $ sudo pip3 install pihole5-list-tool --upgrade 27 | ``` 28 | 29 | _Note:_ 30 | 31 | - If the `pip3` command doesn't work, try using `pip` instead. Here are additional [options /workarounds](https://stackoverflow.com/questions/40832533/pip-or-pip3-to-install-packages-for-python-3) (and glimpses into Python peculiarities) 32 | - You **must** use `sudo` 33 | 34 | ## usage / running 35 | 36 | Simply run: 37 | 38 | ```bash 39 | $ sudo pihole5-list-tool 40 | ``` 41 | 42 | This is what installing and running it basically looks like (many features have been added since this): 43 | 44 | [![asciicast](https://asciinema.org/a/331296.svg)](https://asciinema.org/a/331296) 45 | 46 | ## supported sources 47 | 48 | _TL; DR_ - some maintained online lists, anything you can paste, or a file 49 | 50 | ### allowlists 51 | 52 | Currently the only source for maintained whitelists is [anudeepND's allowlist](https://github.com/anudeepND/whitelist). They are presented as: 53 | 54 | - **Allowlist Only** - Domains that are safe to allow i.e does not contain any tracking or advertising sites. This fixes many problems like YouTube watch history, videos on news sites and so on. 55 | 56 | - **Allowlist+Optional** - These are needed depending on the service you use. They may contain some tracking sites but sometimes it's necessary to add bad domains to make a few services to work. 57 | 58 | - **Allowlist+Referral** - People who use services like Slickdeals and Fatwallet need a few sites (most of them are either trackers or ads) to be whitelisted to work properly. This contains some analytics and ad serving sites like doubleclick.net and others. If you don't know what these services are, stay away from this list. Domains that are safe to whitelist i.e does not contain any tracking or advertising sites. This fixes many problems like YouTube watch history, videos on news sites and so on. 59 | 60 | ### ad/blocklists 61 | 62 | Currently the only source for maintained blocklists is [firebog.net](https://firebog.net/) 63 | 64 | - **Non-crossed lists**: For when someone is usually around to whitelist falsely blocked sites 65 | - **Ticked lists**: For when installing Pi-hole where no one will be whitelisting falsely blocked sites 66 | - **All lists**: For those who will always be around to whitelist falsely blocked sites 67 | 68 | ### file/paste 69 | 70 | Both list types allow providing either a **pasted in list** or a **file** as your source of lists. 71 | 72 | ### Finishing up 73 | 74 | After adding lists, they must be loaded by running: 75 | 76 | ```bash 77 | $ pihole -g 78 | ``` 79 | 80 | This tool will offer to do that for you. 81 | 82 | When that finishes, you'll see each of listed in the **Web Admin** interface along with a comment to help identify them. 83 | 84 | **NOTE:** If you need/want the blocklists added from [firebog.net](https://firebog.net/) (and more) continually updated, check out [pihole-updatelists](https://github.com/jacklul/pihole-updatelists) which will also run great on a Pi. 85 | -------------------------------------------------------------------------------- /cast/original.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 190, "height": 51, "timestamp": 1589776929, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.067973, "o", "\u001b]0;pi@pintsize: ~\u0007\u001b[01;32mpi@pintsize\u001b[00m:\u001b[01;34m~ $\u001b[00m "] 3 | [1.922072, "o", "sudo pip3 install pihole5-list-tool "] 4 | [2.614419, "o", "\r\n"] 5 | [6.033153, "o", "Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple\r\n"] 6 | [6.138095, "o", "Collecting pihole5-list-tool\r\n"] 7 | [11.84796, "o", " Using cached https://files.pythonhosted.org/packages/4d/de/cda930a0f8db10733746c6352e2af3ac4a19b894e3a5385107a2434aacb9/pihole5_list_tool-0.2.1-py3-none-any.whl\r\n"] 8 | [11.981521, "o", "Requirement already satisfied: PyInquirer in /usr/local/lib/python3.7/dist-packages (from pihole5-list-tool) (1.0.3)\r\n"] 9 | [11.99444, "o", "Requirement already satisfied: ansicolors in /usr/local/lib/python3.7/dist-packages (from pihole5-list-tool) (1.1.8)\r\n"] 10 | [12.000493, "o", "Requirement already satisfied: prompt-toolkit==1.0.14 in /usr/local/lib/python3.7/dist-packages (from PyInquirer->pihole5-list-tool) (1.0.14)\r\n"] 11 | [12.010335, "o", "Requirement already satisfied: Pygments>=2.2.0 in /usr/lib/python3/dist-packages (from PyInquirer->pihole5-list-tool) (2.3.1)\r\n"] 12 | [12.01446, "o", "Requirement already satisfied: regex>=2016.11.21 in /usr/local/lib/python3.7/dist-packages (from PyInquirer->pihole5-list-tool) (2020.5.14)\r\n"] 13 | [12.024527, "o", "Requirement already satisfied: wcwidth in /usr/lib/python3/dist-packages (from prompt-toolkit==1.0.14->PyInquirer->pihole5-list-tool) (0.1.7)\r\n"] 14 | [12.029789, "o", "Requirement already satisfied: six>=1.9.0 in /usr/lib/python3/dist-packages (from prompt-toolkit==1.0.14->PyInquirer->pihole5-list-tool) (1.12.0)\r\n"] 15 | [12.381579, "o", "Installing collected packages: pihole5-list-tool\r\n"] 16 | [12.406994, "o", "Successfully installed pihole5-list-tool-0.2.1\r\n"] 17 | [12.575263, "o", "\u001b]0;pi@pintsize: ~\u0007\u001b[01;32mpi@pintsize\u001b[00m:\u001b[01;34m~ $\u001b[00m "] 18 | [13.76945, "o", "sudo pihole5-list-tool"] 19 | [15.566646, "o", "\r\n"] 20 | [16.219897, "o", "\u001b[38;2;182;16;66m\r\n ┌──────────────────────────────────────────┐\u001b[0m\r\n"] 21 | [16.220477, "o", "\u001b[38;2;182;16;66m │ \u001b[0m\u001b[38;2;255;255;255mπ-hole 5 list tool v.0.2.1\u001b[0m\u001b[38;2;182;16;66m │ \u001b[0m\r\n"] 22 | [16.220698, "o", "\u001b[38;2;182;16;66m └──────────────────────────────────────────┘\r\n\u001b[0m\r\n"] 23 | [16.264216, "o", "\u001b[?1l\u001b[6n"] 24 | [16.269945, "o", "\u001b[?2004h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Gravity Db to Update: \u001b[0;38;5;214;1m/etc/pihole/gravity.db\u001b[47D\u001b[47C\u001b[0m\u001b[?12l\u001b[?25h"] 25 | [16.297095, "o", "\u001b[?25l\u001b[0m\u001b[?12l\u001b[?25h"] 26 | [17.98921, "o", "\u001b[?25l\u001b[47D\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Gravity Db to Update: \u001b[0;38;5;214;1m/etc/pihole/gravity.db\u001b[47D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 27 | [17.990081, "o", "\u001b[?2004l"] 28 | [18.048557, "o", "\u001b[?1l\u001b[6n"] 29 | [18.07006, "o", "\u001b[?2004h\u001b[?1000h\u001b[?1015h\u001b[?1006h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Where are the block lists coming from? \u001b[0m (Use arrow keys)\u001b[0m\r\r\n\u001b[0;38;5;214;1m ❯ \u001b[0mFirebog | Non-crossed lists: For when someone is usually around to whitelist falsely blocked sites\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0mr\u001b[0me\u001b[0mb\u001b[0mo\u001b[0mg\u001b[0m \u001b[0m|\u001b[0m \u001b[0mT\u001b[0mi\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m:\u001b[0m \u001b[0mF\u001b[0mo\u001b[0mr\u001b[0m \u001b[0mw\u001b[0mh\u001b[0me\u001b[0mn\u001b[0m \u001b[0mi\u001b[0mn\u001b[0ms\u001b[0mt\u001b[0ma\u001b[0ml\u001b[0ml\u001b[0mi\u001b[0mn\u001b[0mg\u001b[0m \u001b[0mP\u001b[0mi\u001b[0m-\u001b[0mh\u001b[0mo\u001b[0ml\u001b[0me\u001b[0m \u001b[0mw\u001b[0mh\u001b[0me\u001b[0mr\u001b[0me\u001b[0m \u001b[0mn\u001b[0mo\u001b[0m \u001b[0mo\u001b[0mn\u001b[0me\u001b[0m \u001b[0mw\u001b[0mi\u001b[0ml\u001b[0ml\u001b[0m \u001b[0mb\u001b[0me\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0mi\u001b[0mn\u001b[0mg\u001b[0m \u001b[0mf\u001b[0ma\u001b[0ml\u001b[0ms\u001b[0me\u001b[0ml\u001b[0my\u001b[0m \u001b[0mb\u001b[0ml\u001b[0mo\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ms\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ms\u001b[0m\r\r\n"] 30 | [18.070343, "o", "\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0mr\u001b[0me\u001b[0mb\u001b[0mo\u001b[0mg\u001b[0m \u001b[0m|\u001b[0m \u001b[0mA\u001b[0ml\u001b[0ml\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m:\u001b[0m \u001b[0mF\u001b[0mo\u001b[0mr\u001b[0m \u001b[0mt\u001b[0mh\u001b[0mo\u001b[0ms\u001b[0me\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mo\u001b[0m \u001b[0mw\u001b[0mi\u001b[0ml\u001b[0ml\u001b[0m \u001b[0ma\u001b[0ml\u001b[0mw\u001b[0ma\u001b[0my\u001b[0ms\u001b[0m \u001b[0mb\u001b[0me\u001b[0m \u001b[0ma\u001b[0mr\u001b[0mo\u001b[0mu\u001b[0mn\u001b[0md\u001b[0m \u001b[0mt\u001b[0mo\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0m \u001b[0mf\u001b[0ma\u001b[0ml\u001b[0ms\u001b[0me\u001b[0ml\u001b[0my\u001b[0m \u001b[0mb\u001b[0ml\u001b[0mo\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ms\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ms\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0ml\u001b[0me\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m|\u001b[0m \u001b[0mA\u001b[0m \u001b[0mf\u001b[0mi\u001b[0ml\u001b[0me\u001b[0m \u001b[0mw\u001b[0mi\u001b[0mt\u001b[0mh\u001b[0m \u001b[0mu\u001b[0mr\u001b[0ml\u001b[0ms\u001b[0m \u001b[0mo\u001b[0mf\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m,\u001b[0m \u001b[0m1\u001b[0m \u001b[0mp\u001b[0me\u001b[0mr\u001b[0m \u001b[0ml\u001b[0mi\u001b[0mn\u001b[0me\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mP\u001b[0ma\u001b[0ms\u001b[0mt\u001b[0me\u001b[0m \u001b[0m \u001b[0m \u001b[0m|\u001b[0m \u001b[0mP\u001b[0ma\u001b[0ms\u001b[0mt\u001b[0me\u001b[0m \u001b[0mu\u001b[0mr\u001b[0ml\u001b[0ms\u001b[0m \u001b[0mo\u001b[0mf\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m,\u001b[0m \u001b[0m1\u001b[0m \u001b[0mp\u001b[0me\u001b[0mr\u001b[0m \u001b[0ml\u001b[0mi\u001b[0mn\u001b[0me\u001b[0m \u001b[0m-\u001b[0m \u001b[0mo\u001b[0mp\u001b[0me\u001b[0mn\u001b[0ms\u001b[0m \u001b[0me\u001b[0md\u001b[0mi\u001b[0mt\u001b[0mo"] 31 | [18.070477, "o", "\u001b[0mr\u001b[0m,\u001b[0m \u001b[0ms\u001b[0ma\u001b[0mv\u001b[0me\u001b[0m,\u001b[0m \u001b[0mc\u001b[0ml\u001b[0mo\u001b[0ms\u001b[0me\u001b[72D\u001b[5A\u001b[0m"] 32 | [18.113663, "o", "\u001b[?25l\u001b[0m"] 33 | [19.830287, "o", "\u001b[?25l\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0mr\u001b[0me\u001b[0mb\u001b[0mo\u001b[0mg\u001b[0m \u001b[0m|\u001b[0m \u001b[0mN\u001b[0mo\u001b[0mn\u001b[0m-\u001b[0mc\u001b[0mr\u001b[0mo\u001b[0ms\u001b[0ms\u001b[0me\u001b[0md\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m:\u001b[0m \u001b[0mF\u001b[0mo\u001b[0mr\u001b[0m \u001b[0mw\u001b[0mh\u001b[0me\u001b[0mn\u001b[0m \u001b[0ms\u001b[0mo\u001b[0mm\u001b[0me\u001b[0mo\u001b[0mn\u001b[0me\u001b[0m \u001b[0mi\u001b[0ms\u001b[0m \u001b[0mu\u001b[0ms\u001b[0mu\u001b[0ma\u001b[0ml\u001b[0ml\u001b[0my\u001b[0m \u001b[0ma\u001b[0mr\u001b[0mo\u001b[0mu\u001b[0mn\u001b[0md\u001b[0m \u001b[0mt\u001b[0mo\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0m \u001b[0mf\u001b[0ma\u001b[0ml\u001b[0ms\u001b[0me\u001b[0ml\u001b[0my\u001b[0m \u001b[0mb\u001b[0ml\u001b[0mo\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ms\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ms\u001b[0m\r\r\n"] 34 | [19.83081, "o", "\u001b[0;38;5;214;1m ❯ \u001b[0mFirebog | Ticked lists: For when installing Pi-hole where no one will be whitelisting falsely blocked sites\u001b[2A\u001b[110D\u001b[0m"] 35 | [20.35165, "o", "\u001b[?25l\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Where are the block lists coming from? \u001b[0;38;5;214;1m Firebog | Ticked lists: For when installing Pi-hole where no one will be whitelisting falsely blocked sites\u001b[149D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 36 | [20.352601, "o", "\u001b[?1000l\u001b[?1015l\u001b[?1006l\u001b[?2004l"] 37 | [20.629451, "o", "\u001b[?1l"] 38 | [20.629676, "o", "\u001b[6n"] 39 | [20.633786, "o", "\u001b[?2004h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Add 34 block lists to /etc/pihole/gravity.db? \u001b[0m (Y/n)\u001b[54D\u001b[54C\u001b[0m\u001b[?12l\u001b[?25h"] 40 | [20.714799, "o", "\u001b[?25l\u001b[0m\r\r\n\r\r\n\r"] 41 | [20.715169, "o", "\r\n\r\r\n\u001b[4A\u001b[54C\u001b[0m\u001b[?12l\u001b[?25h"] 42 | [22.259721, "o", "\u001b[?25l\u001b[54D\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Add 34 block lists to /etc/pihole/gravity.db? \u001b[0m (Y/n)\u001b[54D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 43 | [22.260656, "o", "\u001b[?2004l"] 44 | [22.299604, "o", "\u001b[38;2;0;255;0m1 block lists added! 33 already existed.\u001b[0m\r\n"] 45 | [22.36086, "o", "\u001b[?1l"] 46 | [22.361385, "o", "\u001b[6n"] 47 | [22.368148, "o", "\u001b[?2004h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Update Gravity for immediate affect? \u001b[0m (Y/n)\u001b[45D\u001b[45C\u001b[0m\u001b[?12l\u001b[?25h"] 48 | [22.41157, "o", "\u001b[?25l\u001b[0m\r\r\n\r\r\n"] 49 | [22.411923, "o", "\u001b[2A\u001b[45C\u001b[0m\u001b[?12l\u001b[?25h"] 50 | [26.230523, "o", "\u001b[?25l\u001b[45D\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Update Gravity for immediate affect? \u001b[0m (Y/n)\u001b[45D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 51 | [26.231736, "o", "\u001b[?2004l"] 52 | [26.232281, "o", "\r\n"] 53 | [26.367297, "o", " [i] \u001b[1mNeutrino emissions detected\u001b[0m...\r\n"] 54 | [26.40923, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Pulling blocklist source list into range\r\n\r\n"] 55 | [26.409632, "o", " [i] Preparing new gravity database..."] 56 | [27.254055, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Preparing new gravity database\r\n"] 57 | [27.26329, "o", " [i] Target: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\r\n"] 58 | [27.272956, "o", " [i] Status: Pending..."] 59 | [27.577438, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 60 | [27.989323, "o", " [i] Received 57739 domains\r\n"] 61 | [27.989706, "o", "\r\n"] 62 | [27.990109, "o", " [i] Target: https://mirror1.malwaredomains.com/files/justdomains\r\n"] 63 | [27.994374, "o", " [i] Status: Pending..."] 64 | [28.356902, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 65 | [28.527621, "o", " [i] Received 26857 domains\r\n"] 66 | [28.528142, "o", "\r\n"] 67 | [28.528471, "o", " [i] Target: https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt\r\n"] 68 | [28.532732, "o", " [i] Status: Pending..."] 69 | [28.735353, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 70 | [28.770209, "o", " [i] Received 34 domains\r\n"] 71 | [28.770709, "o", "\r\n"] 72 | [28.771012, "o", " [i] Target: https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt\r\n"] 73 | [28.775327, "o", " [i] Status: Pending..."] 74 | [28.982671, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 75 | [29.06939, "o", " [i] Received 2701 domains\r\n"] 76 | [29.069974, "o", "\r\n"] 77 | [29.070527, "o", " [i] Target: https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADhosts_without_controversies.txt\r\n"] 78 | [29.076615, "o", " [i] Status: Pending..."] 79 | [29.33469, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 80 | [29.495625, "o", " [i] Received 10786 domains\r\n"] 81 | [29.496066, "o", "\r\n"] 82 | [29.496457, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Spam/hosts\r\n"] 83 | [29.500882, "o", " [i] Status: Pending..."] 84 | [29.666937, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 85 | [29.719081, "o", " [i] Received 73 domains\r\n"] 86 | [29.719447, "o", "\r\n"] 87 | [29.719939, "o", " [i] Target: https://v.firebog.net/hosts/static/w3kbl.txt\r\n"] 88 | [29.724402, "o", " [i] Status: Pending..."] 89 | [29.875798, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 90 | [29.91669, "o", " [i] Received 779 domains\r\n"] 91 | [29.917041, "o", "\r\n"] 92 | [29.917595, "o", " [i] Target: https://adaway.org/hosts.txt\r\n"] 93 | [29.922048, "o", " [i] Status: Pending..."] 94 | [30.309866, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 95 | [30.448962, "o", " [i] Received 12182 domains\r\n"] 96 | [30.449342, "o", "\r\n"] 97 | [30.449871, "o", " [i] Target: https://v.firebog.net/hosts/AdguardDNS.txt\r\n"] 98 | [30.454314, "o", " [i] Status: Pending..."] 99 | [30.608744, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 100 | [30.803659, "o", " [i] Received 36530 domains\r\n"] 101 | [30.804067, "o", "\r\n"] 102 | [30.804472, "o", " [i] Target: https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt\r\n"] 103 | [30.808917, "o", " [i] Status: Pending..."] 104 | [31.0465, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 105 | [31.473866, "o", " [i] Received 42594 domains\r\n"] 106 | [31.47451, "o", "\r\n"] 107 | [31.474734, "o", " [i] Target: https://v.firebog.net/hosts/Easylist.txt\r\n"] 108 | [31.479226, "o", " [i] Status: Pending..."] 109 | [31.652858, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 110 | [31.717438, "o", " [i] Received 2691 domains\r\n"] 111 | [31.717903, "o", "\r\n"] 112 | [31.718316, "o", " [i] Target: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext\r\n"] 113 | [31.722707, "o", " [i] Status: Pending..."] 114 | [32.274879, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 115 | [32.404038, "o", " [i] Received 3354 domains\r\n"] 116 | [32.404471, "o", "\r\n"] 117 | [32.404886, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/UncheckyAds/hosts\r\n"] 118 | [32.409337, "o", " [i] Status: Pending..."] 119 | [32.569703, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 120 | [32.63715, "o", " [i] Received 10 domains\r\n"] 121 | [32.637836, "o", "\r\n"] 122 | [32.638222, "o", " [i] Target: https://raw.githubusercontent.com/bigdargon/hostsVN/master/hosts\r\n"] 123 | [32.642588, "o", " [i] Status: Pending..."] 124 | [32.971426, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 125 | [33.221627, "o", " [i] Received 19127 domains\r\n"] 126 | [33.222118, "o", "\r\n"] 127 | [33.222511, "o", " [i] Target: https://v.firebog.net/hosts/Easyprivacy.txt\r\n"] 128 | [33.226991, "o", " [i] Status: Pending..."] 129 | [33.376851, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 130 | [33.446497, "o", " [i] Received 2726 domains\r\n"] 131 | [33.446938, "o", "\r\n"] 132 | [33.447331, "o", " [i] Target: https://v.firebog.net/hosts/Prigent-Ads.txt\r\n"] 133 | [33.451729, "o", " [i] Status: Pending..."] 134 | [38.675503, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 135 | [38.796042, "o", " [i] Received 3670 domains\r\n"] 136 | [38.796622, "o", "\r\n"] 137 | [38.797191, "o", " [i] Target: https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt\r\n"] 138 | [38.803485, "o", " [i] Status: Pending..."] 139 | [39.106738, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 140 | [39.244039, "o", " [i] Received 13407 domains\r\n"] 141 | [39.244508, "o", "\r\n"] 142 | [39.24494, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.2o7Net/hosts\r\n"] 143 | [39.249491, "o", " [i] Status: Pending..."] 144 | [39.419536, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 145 | [39.501451, "o", " [i] Received 1286 domains\r\n"] 146 | [39.501901, "o", "\r\n"] 147 | [39.502333, "o", " [i] Target: https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt\r\n"] 148 | [39.506753, "o", " [i] Status: Pending..."] 149 | [40.738694, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 150 | [40.813997, "o", " [i] Received 364 domains\r\n"] 151 | [40.814487, "o", "\r\n"] 152 | [40.814897, "o", " [i] Target: https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt\r\n"] 153 | [40.819315, "o", " [i] Status: Pending..."] 154 | [40.981247, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 155 | [41.782664, "o", " [i] Received 130342 domains\r\n"] 156 | [41.783113, "o", "\r\n"] 157 | [41.783545, "o", " [i] Target: https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt\r\n"] 158 | [41.788044, "o", " [i] Status: Pending..."] 159 | [42.305923, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 160 | [42.633186, "o", " [i] Received 37327 domains\r\n"] 161 | [42.63364, "o", "\r\n"] 162 | [42.634081, "o", " [i] Target: https://raw.githubusercontent.com/InnoScorpio/W3C_annual_most_used_survey_blocklist/master/Top500_Domains.txt\r\n"] 163 | [42.638476, "o", " [i] Status: Pending..."] 164 | [42.796587, "o", "\r\u001b[K [\u001b[91m✗\u001b[0m] Status: Not found\r\n"] 165 | [42.796922, "o", " [\u001b[91m✗\u001b[0m] List download failed: \u001b[91mno cached list available\u001b[0m\r\n"] 166 | [42.797295, "o", "\r\n"] 167 | [42.797823, "o", " [i] Target: https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt\r\n"] 168 | [42.802203, "o", " [i] Status: Pending..."] 169 | [42.970717, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 170 | [43.073301, "o", " [i] Received 119 domains\r\n"] 171 | [43.073793, "o", "\r\n"] 172 | [43.074211, "o", " [i] Target: https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt\r\n"] 173 | [43.07865, "o", " [i] Status: Pending..."] 174 | [43.736477, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 175 | [43.873288, "o", " [i] Received 602 domains\r\n"] 176 | [43.873734, "o", "\r\n"] 177 | [43.874157, "o", " [i] Target: https://s3.amazonaws.com/lists.disconnect.me/simple_malvertising.txt\r\n"] 178 | [43.878644, "o", " [i] Status: Pending..."] 179 | [44.091321, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 180 | [44.190269, "o", " [i] Received 2735 domains\r\n"] 181 | [44.190758, "o", "\r\n"] 182 | [44.191167, "o", " [i] Target: https://v.firebog.net/hosts/Prigent-Malware.txt\r\n"] 183 | [44.195522, "o", " [i] Status: Pending..."] 184 | [44.361262, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 185 | [44.739086, "o", " [i] Received 47318 domains\r\n"] 186 | [44.739552, "o", "\r\n"] 187 | [44.739971, "o", " [i] Target: https://mirror.cedia.org.ec/malwaredomains/immortal_domains.txt\r\n"] 188 | [44.744372, "o", " [i] Status: Pending..."] 189 | [45.347557, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 190 | [45.532745, "o", " [i] Received 3196 domains\r\n"] 191 | [45.53336, "o", "\r\n"] 192 | [45.53392, "o", " [i] Target: https://www.malwaredomainlist.com/hostslist/hosts.txt\r\n"] 193 | [45.540264, "o", " [i] Status: Pending..."] 194 | [45.790201, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 195 | [45.891478, "o", " [i] Received 1104 domains\r\n"] 196 | [45.891952, "o", "\r\n"] 197 | [45.892424, "o", " [i] Target: https://bitbucket.org/ethanr/dns-blacklists/raw/8575c9f96e5b4a1308f2f12394abd86d0927a4a0/bad_lists/Mandiant_APT1_Report_Appendix_D.txt\r\n"] 198 | [45.896784, "o", " [i] Status: Pending..."] 199 | [46.156516, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 200 | [46.269343, "o", " [i] Received 2046 domains\r\n"] 201 | [46.270074, "o", "\r\n"] 202 | [46.270765, "o", " [i] Target: https://phishing.army/download/phishing_army_blocklist_extended.txt\r\n"] 203 | [46.279498, "o", " [i] Status: Pending..."] 204 | [46.473949, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 205 | [46.636199, "o", " [i] Received 13105 domains\r\n"] 206 | [46.636673, "o", "\r\n"] 207 | [46.637097, "o", " [i] Target: https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-malware.txt\r\n"] 208 | [46.641564, "o", " [i] Status: Pending..."] 209 | [46.925743, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 210 | [47.045025, "o", " [i] Received 371 domains\r\n"] 211 | [47.045493, "o", "\r\n"] 212 | [47.045914, "o", " [i] Target: https://v.firebog.net/hosts/Shalla-mal.txt\r\n"] 213 | [47.05044, "o", " [i] Status: Pending..."] 214 | [47.239533, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 215 | [47.496877, "o", " [i] Received 19593 domains\r\n"] 216 | [47.497344, "o", "\r\n"] 217 | [47.497849, "o", " [i] Target: https://raw.githubusercontent.com/Spam404/lists/master/main-blacklist.txt\r\n"] 218 | [47.502351, "o", " [i] Status: Pending..."] 219 | [47.699147, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 220 | [47.867334, "o", " [i] Received 8183 domains\r\n"] 221 | [47.867898, "o", "\r\n"] 222 | [47.868316, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Risk/hosts\r\n"] 223 | [47.872788, "o", " [i] Status: Pending..."] 224 | [48.043211, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: Retrieval successful\r\n"] 225 | [48.211852, "o", " [i] Received 2568 domains\r\n"] 226 | [48.212354, "o", "\r\n"] 227 | [48.212743, "o", " [i] Target: https://urlhaus.abuse.ch/downloads/hostfile/\r\n"] 228 | [48.217132, "o", " [i] Status: Pending..."] 229 | [48.482252, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 230 | [48.589212, "o", " [i] Received 732 domains\r\n"] 231 | [48.589675, "o", "\r\n"] 232 | [48.590103, "o", " [i] Target: https://zerodot1.gitlab.io/CoinBlockerLists/hosts_browser\r\n"] 233 | [48.594499, "o", " [i] Status: Pending..."] 234 | [48.808374, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Status: No changes detected\r\n"] 235 | [48.968057, "o", " [i] Received 3310 domains\r\n"] 236 | [48.968796, "o", "\r\n"] 237 | [48.969272, "o", " [i] Storing downloaded domains in new gravity database..."] 238 | [52.364762, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Storing downloaded domains in new gravity database\r\n"] 239 | [52.420347, "o", " [i] Building tree..."] 240 | [55.246747, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Building tree\r\n"] 241 | [55.2473, "o", " [i] Swapping databases..."] 242 | [55.336365, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Swapping databases\r\n"] 243 | [60.336392, "o", " [i] Number of gravity domains: 509561 (\u001b[1m406664 unique domains\u001b[0m)\r\n"] 244 | [60.388115, "o", " [i] Number of exact blacklisted domains: 4\r\n"] 245 | [60.3971, "o", " [i] Number of regex blacklist filters: 0\r\n"] 246 | [60.40614, "o", " [i] Number of exact whitelisted domains: 23\r\n"] 247 | [60.415246, "o", " [i] Number of regex whitelist filters: 1\r\n"] 248 | [60.430245, "o", " [i] Flushing DNS cache..."] 249 | [60.450508, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Flushing DNS cache\r\n"] 250 | [60.451767, "o", " [i] Cleaning up stray matter..."] 251 | [60.470011, "o", "\r\u001b[K [\u001b[32m✓\u001b[0m] Cleaning up stray matter\r\n"] 252 | [60.483769, "o", "\r\n"] 253 | [60.501066, "o", " [\u001b[32m✓\u001b[0m] DNS service is running\r\n"] 254 | [60.508846, "o", " [\u001b[32m✓\u001b[0m] Pi-hole blocking is Enabled\r\n"] 255 | [60.632885, "o", "\u001b]0;pi@pintsize: ~\u0007\u001b[01;32mpi@pintsize\u001b[00m:\u001b[01;34m~ $\u001b[00m "] 256 | [60.845076, "o", "exit\r\n"] 257 | -------------------------------------------------------------------------------- /cast/short.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 190, "height": 51, "timestamp": 1589776929, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.067973, "o", "\u001b]0;pi@pintsize: ~\u0007\u001b[01;32mpi@pintsize\u001b[00m:\u001b[01;34m~ $\u001b[00m "] 3 | [0.5679730000000001, "o", "sudo pip3 install pihole5-list-tool "] 4 | [1.067973, "o", "\r\n"] 5 | [1.567973, "o", "Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple\r\n"] 6 | [1.6729149999999995, "o", "Collecting pihole5-list-tool\r\n"] 7 | [2.1729149999999997, "o", " Using cached https://files.pythonhosted.org/packages/4d/de/cda930a0f8db10733746c6352e2af3ac4a19b894e3a5385107a2434aacb9/pihole5_list_tool-0.2.1-py3-none-any.whl\r\n"] 8 | [2.306476, "o", "Requirement already satisfied: PyInquirer in /usr/local/lib/python3.7/dist-packages (from pihole5-list-tool) (1.0.3)\r\n"] 9 | [2.319395, "o", "Requirement already satisfied: ansicolors in /usr/local/lib/python3.7/dist-packages (from pihole5-list-tool) (1.1.8)\r\n"] 10 | [2.3254479999999997, "o", "Requirement already satisfied: prompt-toolkit==1.0.14 in /usr/local/lib/python3.7/dist-packages (from PyInquirer->pihole5-list-tool) (1.0.14)\r\n"] 11 | [2.3352899999999988, "o", "Requirement already satisfied: Pygments>=2.2.0 in /usr/lib/python3/dist-packages (from PyInquirer->pihole5-list-tool) (2.3.1)\r\n"] 12 | [2.339414999999999, "o", "Requirement already satisfied: regex>=2016.11.21 in /usr/local/lib/python3.7/dist-packages (from PyInquirer->pihole5-list-tool) (2020.5.14)\r\n"] 13 | [2.349482, "o", "Requirement already satisfied: wcwidth in /usr/lib/python3/dist-packages (from prompt-toolkit==1.0.14->PyInquirer->pihole5-list-tool) (0.1.7)\r\n"] 14 | [2.3547439999999984, "o", "Requirement already satisfied: six>=1.9.0 in /usr/lib/python3/dist-packages (from prompt-toolkit==1.0.14->PyInquirer->pihole5-list-tool) (1.12.0)\r\n"] 15 | [2.7065339999999996, "o", "Installing collected packages: pihole5-list-tool\r\n"] 16 | [2.7319489999999984, "o", "Successfully installed pihole5-list-tool-0.2.1\r\n"] 17 | [2.900217999999999, "o", "\u001b]0;pi@pintsize: ~\u0007\u001b[01;32mpi@pintsize\u001b[00m:\u001b[01;34m~ $\u001b[00m "] 18 | [3.400217999999999, "o", "sudo pihole5-list-tool"] 19 | [3.900217999999999, "o", "\r\n"] 20 | [4.400217999999999, "o", "\u001b[38;2;182;16;66m\r\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u001b[0m\r\n"] 21 | [4.400797999999998, "o", "\u001b[38;2;182;16;66m \u2502 \u001b[0m\u001b[38;2;255;255;255m\u03c0-hole 5 list tool v.0.2.1\u001b[0m\u001b[38;2;182;16;66m \u2502 \u001b[0m\r\n"] 22 | [4.401018999999998, "o", "\u001b[38;2;182;16;66m \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n\u001b[0m\r\n"] 23 | [4.444537, "o", "\u001b[?1l\u001b[6n"] 24 | [4.450265999999999, "o", "\u001b[?2004h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Gravity Db to Update: \u001b[0;38;5;214;1m/etc/pihole/gravity.db\u001b[47D\u001b[47C\u001b[0m\u001b[?12l\u001b[?25h"] 25 | [4.477415999999998, "o", "\u001b[?25l\u001b[0m\u001b[?12l\u001b[?25h"] 26 | [4.977415999999998, "o", "\u001b[?25l\u001b[47D\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Gravity Db to Update: \u001b[0;38;5;214;1m/etc/pihole/gravity.db\u001b[47D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 27 | [4.978286999999998, "o", "\u001b[?2004l"] 28 | [5.036762999999997, "o", "\u001b[?1l\u001b[6n"] 29 | [5.058266, "o", "\u001b[?2004h\u001b[?1000h\u001b[?1015h\u001b[?1006h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Where are the block lists coming from? \u001b[0m (Use arrow keys)\u001b[0m\r\r\n\u001b[0;38;5;214;1m \u276f \u001b[0mFirebog | Non-crossed lists: For when someone is usually around to whitelist falsely blocked sites\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0mr\u001b[0me\u001b[0mb\u001b[0mo\u001b[0mg\u001b[0m \u001b[0m|\u001b[0m \u001b[0mT\u001b[0mi\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m:\u001b[0m \u001b[0mF\u001b[0mo\u001b[0mr\u001b[0m \u001b[0mw\u001b[0mh\u001b[0me\u001b[0mn\u001b[0m \u001b[0mi\u001b[0mn\u001b[0ms\u001b[0mt\u001b[0ma\u001b[0ml\u001b[0ml\u001b[0mi\u001b[0mn\u001b[0mg\u001b[0m \u001b[0mP\u001b[0mi\u001b[0m-\u001b[0mh\u001b[0mo\u001b[0ml\u001b[0me\u001b[0m \u001b[0mw\u001b[0mh\u001b[0me\u001b[0mr\u001b[0me\u001b[0m \u001b[0mn\u001b[0mo\u001b[0m \u001b[0mo\u001b[0mn\u001b[0me\u001b[0m \u001b[0mw\u001b[0mi\u001b[0ml\u001b[0ml\u001b[0m \u001b[0mb\u001b[0me\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0mi\u001b[0mn\u001b[0mg\u001b[0m \u001b[0mf\u001b[0ma\u001b[0ml\u001b[0ms\u001b[0me\u001b[0ml\u001b[0my\u001b[0m \u001b[0mb\u001b[0ml\u001b[0mo\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ms\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ms\u001b[0m\r\r\n"] 30 | [5.058548999999999, "o", "\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0mr\u001b[0me\u001b[0mb\u001b[0mo\u001b[0mg\u001b[0m \u001b[0m|\u001b[0m \u001b[0mA\u001b[0ml\u001b[0ml\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m:\u001b[0m \u001b[0mF\u001b[0mo\u001b[0mr\u001b[0m \u001b[0mt\u001b[0mh\u001b[0mo\u001b[0ms\u001b[0me\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mo\u001b[0m \u001b[0mw\u001b[0mi\u001b[0ml\u001b[0ml\u001b[0m \u001b[0ma\u001b[0ml\u001b[0mw\u001b[0ma\u001b[0my\u001b[0ms\u001b[0m \u001b[0mb\u001b[0me\u001b[0m \u001b[0ma\u001b[0mr\u001b[0mo\u001b[0mu\u001b[0mn\u001b[0md\u001b[0m \u001b[0mt\u001b[0mo\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0m \u001b[0mf\u001b[0ma\u001b[0ml\u001b[0ms\u001b[0me\u001b[0ml\u001b[0my\u001b[0m \u001b[0mb\u001b[0ml\u001b[0mo\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ms\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ms\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0ml\u001b[0me\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m|\u001b[0m \u001b[0mA\u001b[0m \u001b[0mf\u001b[0mi\u001b[0ml\u001b[0me\u001b[0m \u001b[0mw\u001b[0mi\u001b[0mt\u001b[0mh\u001b[0m \u001b[0mu\u001b[0mr\u001b[0ml\u001b[0ms\u001b[0m \u001b[0mo\u001b[0mf\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m,\u001b[0m \u001b[0m1\u001b[0m \u001b[0mp\u001b[0me\u001b[0mr\u001b[0m \u001b[0ml\u001b[0mi\u001b[0mn\u001b[0me\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mP\u001b[0ma\u001b[0ms\u001b[0mt\u001b[0me\u001b[0m \u001b[0m \u001b[0m \u001b[0m|\u001b[0m \u001b[0mP\u001b[0ma\u001b[0ms\u001b[0mt\u001b[0me\u001b[0m \u001b[0mu\u001b[0mr\u001b[0ml\u001b[0ms\u001b[0m \u001b[0mo\u001b[0mf\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m,\u001b[0m \u001b[0m1\u001b[0m \u001b[0mp\u001b[0me\u001b[0mr\u001b[0m \u001b[0ml\u001b[0mi\u001b[0mn\u001b[0me\u001b[0m \u001b[0m-\u001b[0m \u001b[0mo\u001b[0mp\u001b[0me\u001b[0mn\u001b[0ms\u001b[0m \u001b[0me\u001b[0md\u001b[0mi\u001b[0mt\u001b[0mo"] 31 | [5.0586829999999985, "o", "\u001b[0mr\u001b[0m,\u001b[0m \u001b[0ms\u001b[0ma\u001b[0mv\u001b[0me\u001b[0m,\u001b[0m \u001b[0mc\u001b[0ml\u001b[0mo\u001b[0ms\u001b[0me\u001b[72D\u001b[5A\u001b[0m"] 32 | [5.101868999999997, "o", "\u001b[?25l\u001b[0m"] 33 | [5.601868999999997, "o", "\u001b[?25l\u001b[0m\r\r\n\u001b[0m \u001b[0m \u001b[0m \u001b[0mF\u001b[0mi\u001b[0mr\u001b[0me\u001b[0mb\u001b[0mo\u001b[0mg\u001b[0m \u001b[0m|\u001b[0m \u001b[0mN\u001b[0mo\u001b[0mn\u001b[0m-\u001b[0mc\u001b[0mr\u001b[0mo\u001b[0ms\u001b[0ms\u001b[0me\u001b[0md\u001b[0m \u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0ms\u001b[0m:\u001b[0m \u001b[0mF\u001b[0mo\u001b[0mr\u001b[0m \u001b[0mw\u001b[0mh\u001b[0me\u001b[0mn\u001b[0m \u001b[0ms\u001b[0mo\u001b[0mm\u001b[0me\u001b[0mo\u001b[0mn\u001b[0me\u001b[0m \u001b[0mi\u001b[0ms\u001b[0m \u001b[0mu\u001b[0ms\u001b[0mu\u001b[0ma\u001b[0ml\u001b[0ml\u001b[0my\u001b[0m \u001b[0ma\u001b[0mr\u001b[0mo\u001b[0mu\u001b[0mn\u001b[0md\u001b[0m \u001b[0mt\u001b[0mo\u001b[0m \u001b[0mw\u001b[0mh\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ml\u001b[0mi\u001b[0ms\u001b[0mt\u001b[0m \u001b[0mf\u001b[0ma\u001b[0ml\u001b[0ms\u001b[0me\u001b[0ml\u001b[0my\u001b[0m \u001b[0mb\u001b[0ml\u001b[0mo\u001b[0mc\u001b[0mk\u001b[0me\u001b[0md\u001b[0m \u001b[0ms\u001b[0mi\u001b[0mt\u001b[0me\u001b[0ms\u001b[0m\r\r\n"] 34 | [5.602391999999998, "o", "\u001b[0;38;5;214;1m \u276f \u001b[0mFirebog | Ticked lists: For when installing Pi-hole where no one will be whitelisting falsely blocked sites\u001b[2A\u001b[110D\u001b[0m"] 35 | [6.102391999999998, "o", "\u001b[?25l\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Where are the block lists coming from? \u001b[0;38;5;214;1m Firebog | Ticked lists: For when installing Pi-hole where no one will be whitelisting falsely blocked sites\u001b[149D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 36 | [6.103342999999999, "o", "\u001b[?1000l\u001b[?1015l\u001b[?1006l\u001b[?2004l"] 37 | [6.3801929999999984, "o", "\u001b[?1l"] 38 | [6.380417999999999, "o", "\u001b[6n"] 39 | [6.3845279999999995, "o", "\u001b[?2004h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Add 34 block lists to /etc/pihole/gravity.db? \u001b[0m (Y/n)\u001b[54D\u001b[54C\u001b[0m\u001b[?12l\u001b[?25h"] 40 | [6.465540999999998, "o", "\u001b[?25l\u001b[0m\r\r\n\r\r\n\r"] 41 | [6.465910999999998, "o", "\r\n\r\r\n\u001b[4A\u001b[54C\u001b[0m\u001b[?12l\u001b[?25h"] 42 | [6.965910999999998, "o", "\u001b[?25l\u001b[54D\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Add 34 block lists to /etc/pihole/gravity.db? \u001b[0m (Y/n)\u001b[54D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 43 | [6.966846, "o", "\u001b[?2004l"] 44 | [7.005793999999998, "o", "\u001b[38;2;0;255;0m1 block lists added! 33 already existed.\u001b[0m\r\n"] 45 | [7.067049999999998, "o", "\u001b[?1l"] 46 | [7.067574999999998, "o", "\u001b[6n"] 47 | [7.074338000000001, "o", "\u001b[?2004h\u001b[?25l\u001b[?7l\u001b[0m\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Update Gravity for immediate affect? \u001b[0m (Y/n)\u001b[45D\u001b[45C\u001b[0m\u001b[?12l\u001b[?25h"] 48 | [7.1177600000000005, "o", "\u001b[?25l\u001b[0m\r\r\n\r\r\n"] 49 | [7.118113000000001, "o", "\u001b[2A\u001b[45C\u001b[0m\u001b[?12l\u001b[?25h"] 50 | [7.618113000000001, "o", "\u001b[?25l\u001b[45D\u001b[0m\u001b[J\u001b[0;38;5;67m?\u001b[0;1m Update Gravity for immediate affect? \u001b[0m (Y/n)\u001b[45D\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"] 51 | [7.619326000000001, "o", "\u001b[?2004l"] 52 | [7.619871, "o", "\r\n"] 53 | [7.754887, "o", " [i] \u001b[1mNeutrino emissions detected\u001b[0m...\r\n"] 54 | [7.79682, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Pulling blocklist source list into range\r\n\r\n"] 55 | [7.797221999999998, "o", " [i] Preparing new gravity database..."] 56 | [8.297221999999998, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Preparing new gravity database\r\n"] 57 | [8.306456999999998, "o", " [i] Target: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\r\n"] 58 | [8.316122999999997, "o", " [i] Status: Pending..."] 59 | [8.620604999999998, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 60 | [9.032489999999996, "o", " [i] Received 57739 domains\r\n"] 61 | [9.032872999999999, "o", "\r\n"] 62 | [9.033275999999997, "o", " [i] Target: https://mirror1.malwaredomains.com/files/justdomains\r\n"] 63 | [9.037540999999997, "o", " [i] Status: Pending..."] 64 | [9.400068999999998, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 65 | [9.570787999999997, "o", " [i] Received 26857 domains\r\n"] 66 | [9.571308999999996, "o", "\r\n"] 67 | [9.571637999999997, "o", " [i] Target: https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt\r\n"] 68 | [9.575898999999996, "o", " [i] Status: Pending..."] 69 | [9.778519999999997, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 70 | [9.813375999999998, "o", " [i] Received 34 domains\r\n"] 71 | [9.813875999999997, "o", "\r\n"] 72 | [9.814178999999996, "o", " [i] Target: https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt\r\n"] 73 | [9.818493999999998, "o", " [i] Status: Pending..."] 74 | [10.025837999999997, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 75 | [10.112556999999995, "o", " [i] Received 2701 domains\r\n"] 76 | [10.113140999999995, "o", "\r\n"] 77 | [10.113693999999995, "o", " [i] Target: https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADhosts_without_controversies.txt\r\n"] 78 | [10.119781999999997, "o", " [i] Status: Pending..."] 79 | [10.377856999999995, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 80 | [10.538791999999997, "o", " [i] Received 10786 domains\r\n"] 81 | [10.539232999999996, "o", "\r\n"] 82 | [10.539623999999996, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Spam/hosts\r\n"] 83 | [10.544048999999998, "o", " [i] Status: Pending..."] 84 | [10.710103999999998, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 85 | [10.762247999999996, "o", " [i] Received 73 domains\r\n"] 86 | [10.762613999999996, "o", "\r\n"] 87 | [10.763105999999997, "o", " [i] Target: https://v.firebog.net/hosts/static/w3kbl.txt\r\n"] 88 | [10.767568999999998, "o", " [i] Status: Pending..."] 89 | [10.918964999999996, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 90 | [10.959856999999996, "o", " [i] Received 779 domains\r\n"] 91 | [10.960207999999998, "o", "\r\n"] 92 | [10.960761999999995, "o", " [i] Target: https://adaway.org/hosts.txt\r\n"] 93 | [10.965214999999997, "o", " [i] Status: Pending..."] 94 | [11.353032999999996, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 95 | [11.492128999999998, "o", " [i] Received 12182 domains\r\n"] 96 | [11.492508999999998, "o", "\r\n"] 97 | [11.493037999999999, "o", " [i] Target: https://v.firebog.net/hosts/AdguardDNS.txt\r\n"] 98 | [11.497480999999997, "o", " [i] Status: Pending..."] 99 | [11.651910999999998, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 100 | [11.846825999999997, "o", " [i] Received 36530 domains\r\n"] 101 | [11.847233999999997, "o", "\r\n"] 102 | [11.847638999999997, "o", " [i] Target: https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt\r\n"] 103 | [11.852083999999998, "o", " [i] Status: Pending..."] 104 | [12.089666999999999, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 105 | [12.517032999999998, "o", " [i] Received 42594 domains\r\n"] 106 | [12.517676999999996, "o", "\r\n"] 107 | [12.517900999999998, "o", " [i] Target: https://v.firebog.net/hosts/Easylist.txt\r\n"] 108 | [12.522392999999997, "o", " [i] Status: Pending..."] 109 | [12.696024999999995, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 110 | [12.760604999999998, "o", " [i] Received 2691 domains\r\n"] 111 | [12.761069999999997, "o", "\r\n"] 112 | [12.761482999999998, "o", " [i] Target: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext\r\n"] 113 | [12.765873999999997, "o", " [i] Status: Pending..."] 114 | [13.265873999999997, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 115 | [13.395032999999998, "o", " [i] Received 3354 domains\r\n"] 116 | [13.395465999999999, "o", "\r\n"] 117 | [13.395880999999996, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/UncheckyAds/hosts\r\n"] 118 | [13.400331999999999, "o", " [i] Status: Pending..."] 119 | [13.560697999999995, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 120 | [13.628144999999996, "o", " [i] Received 10 domains\r\n"] 121 | [13.628830999999998, "o", "\r\n"] 122 | [13.629216999999997, "o", " [i] Target: https://raw.githubusercontent.com/bigdargon/hostsVN/master/hosts\r\n"] 123 | [13.633583000000002, "o", " [i] Status: Pending..."] 124 | [13.962420999999999, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 125 | [14.212621999999996, "o", " [i] Received 19127 domains\r\n"] 126 | [14.213113, "o", "\r\n"] 127 | [14.213505999999995, "o", " [i] Target: https://v.firebog.net/hosts/Easyprivacy.txt\r\n"] 128 | [14.217985999999996, "o", " [i] Status: Pending..."] 129 | [14.367846, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 130 | [14.437491999999999, "o", " [i] Received 2726 domains\r\n"] 131 | [14.437933000000001, "o", "\r\n"] 132 | [14.438325999999996, "o", " [i] Target: https://v.firebog.net/hosts/Prigent-Ads.txt\r\n"] 133 | [14.442723999999998, "o", " [i] Status: Pending..."] 134 | [14.942723999999998, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 135 | [15.063263, "o", " [i] Received 3670 domains\r\n"] 136 | [15.063842999999999, "o", "\r\n"] 137 | [15.064411999999997, "o", " [i] Target: https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt\r\n"] 138 | [15.070706000000001, "o", " [i] Status: Pending..."] 139 | [15.373959, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 140 | [15.51126, "o", " [i] Received 13407 domains\r\n"] 141 | [15.511729000000003, "o", "\r\n"] 142 | [15.512160999999999, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.2o7Net/hosts\r\n"] 143 | [15.516711999999998, "o", " [i] Status: Pending..."] 144 | [15.686757, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 145 | [15.768672000000002, "o", " [i] Received 1286 domains\r\n"] 146 | [15.769121999999996, "o", "\r\n"] 147 | [15.769554, "o", " [i] Target: https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/hosts/spy.txt\r\n"] 148 | [15.773974000000003, "o", " [i] Status: Pending..."] 149 | [16.273974000000003, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 150 | [16.349277, "o", " [i] Received 364 domains\r\n"] 151 | [16.349767, "o", "\r\n"] 152 | [16.350177000000002, "o", " [i] Target: https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt\r\n"] 153 | [16.354595000000003, "o", " [i] Status: Pending..."] 154 | [16.516527000000004, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 155 | [17.016527000000004, "o", " [i] Received 130342 domains\r\n"] 156 | [17.016976000000007, "o", "\r\n"] 157 | [17.017408000000003, "o", " [i] Target: https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt\r\n"] 158 | [17.021907000000006, "o", " [i] Status: Pending..."] 159 | [17.521907000000006, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 160 | [17.849170000000008, "o", " [i] Received 37327 domains\r\n"] 161 | [17.849624000000006, "o", "\r\n"] 162 | [17.850065000000008, "o", " [i] Target: https://raw.githubusercontent.com/InnoScorpio/W3C_annual_most_used_survey_blocklist/master/Top500_Domains.txt\r\n"] 163 | [17.854460000000003, "o", " [i] Status: Pending..."] 164 | [18.01257100000001, "o", "\r\u001b[K [\u001b[91m\u2717\u001b[0m] Status: Not found\r\n"] 165 | [18.012906000000008, "o", " [\u001b[91m\u2717\u001b[0m] List download failed: \u001b[91mno cached list available\u001b[0m\r\n"] 166 | [18.013279000000004, "o", "\r\n"] 167 | [18.013807000000007, "o", " [i] Target: https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt\r\n"] 168 | [18.018187000000005, "o", " [i] Status: Pending..."] 169 | [18.186701000000006, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 170 | [18.289285000000007, "o", " [i] Received 119 domains\r\n"] 171 | [18.289777000000008, "o", "\r\n"] 172 | [18.290195000000004, "o", " [i] Target: https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt\r\n"] 173 | [18.29463400000001, "o", " [i] Status: Pending..."] 174 | [18.79463400000001, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 175 | [18.93144500000001, "o", " [i] Received 602 domains\r\n"] 176 | [18.931891000000007, "o", "\r\n"] 177 | [18.932314000000005, "o", " [i] Target: https://s3.amazonaws.com/lists.disconnect.me/simple_malvertising.txt\r\n"] 178 | [18.93680100000001, "o", " [i] Status: Pending..."] 179 | [19.14947800000001, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 180 | [19.24842600000001, "o", " [i] Received 2735 domains\r\n"] 181 | [19.24891500000001, "o", "\r\n"] 182 | [19.24932400000001, "o", " [i] Target: https://v.firebog.net/hosts/Prigent-Malware.txt\r\n"] 183 | [19.253679000000005, "o", " [i] Status: Pending..."] 184 | [19.419419000000012, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 185 | [19.79724300000001, "o", " [i] Received 47318 domains\r\n"] 186 | [19.79770900000001, "o", "\r\n"] 187 | [19.798128000000005, "o", " [i] Target: https://mirror.cedia.org.ec/malwaredomains/immortal_domains.txt\r\n"] 188 | [19.802529000000007, "o", " [i] Status: Pending..."] 189 | [20.302529000000007, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 190 | [20.487717000000004, "o", " [i] Received 3196 domains\r\n"] 191 | [20.488332000000007, "o", "\r\n"] 192 | [20.488892000000007, "o", " [i] Target: https://www.malwaredomainlist.com/hostslist/hosts.txt\r\n"] 193 | [20.495236000000006, "o", " [i] Status: Pending..."] 194 | [20.74517300000001, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 195 | [20.846450000000004, "o", " [i] Received 1104 domains\r\n"] 196 | [20.84692400000001, "o", "\r\n"] 197 | [20.847396000000003, "o", " [i] Target: https://bitbucket.org/ethanr/dns-blacklists/raw/8575c9f96e5b4a1308f2f12394abd86d0927a4a0/bad_lists/Mandiant_APT1_Report_Appendix_D.txt\r\n"] 198 | [20.851756, "o", " [i] Status: Pending..."] 199 | [21.11148800000001, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 200 | [21.224315000000004, "o", " [i] Received 2046 domains\r\n"] 201 | [21.225046000000006, "o", "\r\n"] 202 | [21.225737000000002, "o", " [i] Target: https://phishing.army/download/phishing_army_blocklist_extended.txt\r\n"] 203 | [21.23447, "o", " [i] Status: Pending..."] 204 | [21.428921000000003, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 205 | [21.591171000000003, "o", " [i] Received 13105 domains\r\n"] 206 | [21.591645000000007, "o", "\r\n"] 207 | [21.592069000000002, "o", " [i] Target: https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-malware.txt\r\n"] 208 | [21.596536000000008, "o", " [i] Status: Pending..."] 209 | [21.880715000000002, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 210 | [21.999997000000008, "o", " [i] Received 371 domains\r\n"] 211 | [22.000465000000005, "o", "\r\n"] 212 | [22.00088600000001, "o", " [i] Target: https://v.firebog.net/hosts/Shalla-mal.txt\r\n"] 213 | [22.005412000000007, "o", " [i] Status: Pending..."] 214 | [22.194505000000007, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 215 | [22.451849000000003, "o", " [i] Received 19593 domains\r\n"] 216 | [22.452316000000003, "o", "\r\n"] 217 | [22.452821000000007, "o", " [i] Target: https://raw.githubusercontent.com/Spam404/lists/master/main-blacklist.txt\r\n"] 218 | [22.457323000000002, "o", " [i] Status: Pending..."] 219 | [22.65411900000001, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 220 | [22.822306000000005, "o", " [i] Received 8183 domains\r\n"] 221 | [22.82287, "o", "\r\n"] 222 | [22.823288000000005, "o", " [i] Target: https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Risk/hosts\r\n"] 223 | [22.827760000000005, "o", " [i] Status: Pending..."] 224 | [22.998183000000004, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: Retrieval successful\r\n"] 225 | [23.166824000000005, "o", " [i] Received 2568 domains\r\n"] 226 | [23.167326000000003, "o", "\r\n"] 227 | [23.16771500000001, "o", " [i] Target: https://urlhaus.abuse.ch/downloads/hostfile/\r\n"] 228 | [23.172104000000004, "o", " [i] Status: Pending..."] 229 | [23.437224000000008, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 230 | [23.54418400000001, "o", " [i] Received 732 domains\r\n"] 231 | [23.544647000000005, "o", "\r\n"] 232 | [23.545075000000004, "o", " [i] Target: https://zerodot1.gitlab.io/CoinBlockerLists/hosts_browser\r\n"] 233 | [23.549471000000004, "o", " [i] Status: Pending..."] 234 | [23.763346000000006, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Status: No changes detected\r\n"] 235 | [23.923029000000007, "o", " [i] Received 3310 domains\r\n"] 236 | [23.923768000000003, "o", "\r\n"] 237 | [23.924244, "o", " [i] Storing downloaded domains in new gravity database..."] 238 | [24.424244, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Storing downloaded domains in new gravity database\r\n"] 239 | [24.479829000000002, "o", " [i] Building tree..."] 240 | [24.979829000000002, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Building tree\r\n"] 241 | [24.980382000000006, "o", " [i] Swapping databases..."] 242 | [25.069447000000004, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Swapping databases\r\n"] 243 | [25.569447000000004, "o", " [i] Number of gravity domains: 509561 (\u001b[1m406664 unique domains\u001b[0m)\r\n"] 244 | [25.621170000000006, "o", " [i] Number of exact blacklisted domains: 4\r\n"] 245 | [25.63015500000001, "o", " [i] Number of regex blacklist filters: 0\r\n"] 246 | [25.639195000000008, "o", " [i] Number of exact whitelisted domains: 23\r\n"] 247 | [25.64830100000001, "o", " [i] Number of regex whitelist filters: 1\r\n"] 248 | [25.663300000000007, "o", " [i] Flushing DNS cache..."] 249 | [25.683563000000007, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Flushing DNS cache\r\n"] 250 | [25.684822000000004, "o", " [i] Cleaning up stray matter..."] 251 | [25.703066000000007, "o", "\r\u001b[K [\u001b[32m\u2713\u001b[0m] Cleaning up stray matter\r\n"] 252 | [25.71682400000001, "o", "\r\n"] 253 | [25.73412100000001, "o", " [\u001b[32m\u2713\u001b[0m] DNS service is running\r\n"] 254 | [25.741901000000006, "o", " [\u001b[32m\u2713\u001b[0m] Pi-hole blocking is Enabled\r\n"] 255 | [25.86594000000001, "o", "\u001b]0;pi@pintsize: ~\u0007\u001b[01;32mpi@pintsize\u001b[00m:\u001b[01;34m~ $\u001b[00m "] 256 | [26.078131000000006, "o", "exit\r\n"] 257 | -------------------------------------------------------------------------------- /data/docker_inspect_pihole.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": "a4b9efe31d3af68518bcd445bbc07c8debcec260b2c127487d786b57604d0718", 4 | "Created": "2020-06-07T03:50:32.176409668Z", 5 | "Path": "/s6-init", 6 | "Args": [], 7 | "State": { 8 | "Status": "running", 9 | "Running": true, 10 | "Paused": false, 11 | "Restarting": false, 12 | "OOMKilled": false, 13 | "Dead": false, 14 | "Pid": 151915, 15 | "ExitCode": 0, 16 | "Error": "", 17 | "StartedAt": "2020-06-07T03:50:32.781342202Z", 18 | "FinishedAt": "0001-01-01T00:00:00Z", 19 | "Health": { 20 | "Status": "healthy", 21 | "FailingStreak": 0, 22 | "Log": [ 23 | { 24 | "Start": "2020-06-06T23:51:02.781966055-04:00", 25 | "End": "2020-06-06T23:51:03.146172996-04:00", 26 | "ExitCode": 0, 27 | "Output": "\n; <<>> DiG 9.10.3-P4-Debian <<>> +norecurse +retry=0 @127.0.0.1 pi.hole\n; (1 server found)\n;; global options: +cmd\n;; Got answer:\n;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23277\n;; flags: qr aa ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1\n\n;; OPT PSEUDOSECTION:\n; EDNS: version: 0, flags:; udp: 4096\n;; QUESTION SECTION:\n;pi.hole.\t\t\tIN\tA\n\n;; ANSWER SECTION:\npi.hole.\t\t2\tIN\tA\t0.0.0.0\n\n;; Query time: 0 msec\n;; SERVER: 127.0.0.1#53(127.0.0.1)\n;; WHEN: Sat Jun 06 23:51:03 EDT 2020\n;; MSG SIZE rcvd: 52\n\n" 28 | } 29 | ] 30 | } 31 | }, 32 | "Image": "sha256:788fa841f00633e23f5c40775dad6844262b6546d021441626639984cd5f710a", 33 | "ResolvConfPath": "/var/lib/docker/containers/a4b9efe31d3af68518bcd445bbc07c8debcec260b2c127487d786b57604d0718/resolv.conf", 34 | "HostnamePath": "/var/lib/docker/containers/a4b9efe31d3af68518bcd445bbc07c8debcec260b2c127487d786b57604d0718/hostname", 35 | "HostsPath": "/var/lib/docker/containers/a4b9efe31d3af68518bcd445bbc07c8debcec260b2c127487d786b57604d0718/hosts", 36 | "LogPath": "/var/lib/docker/containers/a4b9efe31d3af68518bcd445bbc07c8debcec260b2c127487d786b57604d0718/a4b9efe31d3af68518bcd445bbc07c8debcec260b2c127487d786b57604d0718-json.log", 37 | "Name": "/pihole", 38 | "RestartCount": 0, 39 | "Driver": "overlay2", 40 | "Platform": "linux", 41 | "MountLabel": "", 42 | "ProcessLabel": "", 43 | "AppArmorProfile": "docker-default", 44 | "ExecIDs": null, 45 | "HostConfig": { 46 | "Binds": [ 47 | "/home/jesse/projects/pihole/etc-dnsmasq.d:/etc/dnsmasq.d:rw", 48 | "/home/jesse/projects/pihole/etc-pihole:/etc/pihole:rw" 49 | ], 50 | "ContainerIDFile": "", 51 | "LogConfig": { 52 | "Type": "json-file", 53 | "Config": {} 54 | }, 55 | "NetworkMode": "pihole_default", 56 | "PortBindings": { 57 | "443/tcp": [ 58 | { 59 | "HostIp": "", 60 | "HostPort": "443" 61 | } 62 | ], 63 | "53/tcp": [ 64 | { 65 | "HostIp": "", 66 | "HostPort": "53" 67 | } 68 | ], 69 | "53/udp": [ 70 | { 71 | "HostIp": "", 72 | "HostPort": "53" 73 | } 74 | ], 75 | "67/udp": [ 76 | { 77 | "HostIp": "", 78 | "HostPort": "67" 79 | } 80 | ], 81 | "80/tcp": [ 82 | { 83 | "HostIp": "", 84 | "HostPort": "80" 85 | } 86 | ] 87 | }, 88 | "RestartPolicy": { 89 | "Name": "unless-stopped", 90 | "MaximumRetryCount": 0 91 | }, 92 | "AutoRemove": false, 93 | "VolumeDriver": "", 94 | "VolumesFrom": [], 95 | "CapAdd": [ 96 | "NET_ADMIN" 97 | ], 98 | "CapDrop": null, 99 | "Capabilities": null, 100 | "Dns": [ 101 | "127.0.0.1", 102 | "1.1.1.1" 103 | ], 104 | "DnsOptions": null, 105 | "DnsSearch": null, 106 | "ExtraHosts": null, 107 | "GroupAdd": null, 108 | "IpcMode": "shareable", 109 | "Cgroup": "", 110 | "Links": null, 111 | "OomScoreAdj": 0, 112 | "PidMode": "", 113 | "Privileged": false, 114 | "PublishAllPorts": false, 115 | "ReadonlyRootfs": false, 116 | "SecurityOpt": null, 117 | "UTSMode": "", 118 | "UsernsMode": "", 119 | "ShmSize": 67108864, 120 | "Runtime": "runc", 121 | "ConsoleSize": [ 122 | 0, 123 | 0 124 | ], 125 | "Isolation": "", 126 | "CpuShares": 0, 127 | "Memory": 0, 128 | "NanoCpus": 0, 129 | "CgroupParent": "", 130 | "BlkioWeight": 0, 131 | "BlkioWeightDevice": null, 132 | "BlkioDeviceReadBps": null, 133 | "BlkioDeviceWriteBps": null, 134 | "BlkioDeviceReadIOps": null, 135 | "BlkioDeviceWriteIOps": null, 136 | "CpuPeriod": 0, 137 | "CpuQuota": 0, 138 | "CpuRealtimePeriod": 0, 139 | "CpuRealtimeRuntime": 0, 140 | "CpusetCpus": "", 141 | "CpusetMems": "", 142 | "Devices": null, 143 | "DeviceCgroupRules": null, 144 | "DeviceRequests": null, 145 | "KernelMemory": 0, 146 | "KernelMemoryTCP": 0, 147 | "MemoryReservation": 0, 148 | "MemorySwap": 0, 149 | "MemorySwappiness": null, 150 | "OomKillDisable": false, 151 | "PidsLimit": null, 152 | "Ulimits": null, 153 | "CpuCount": 0, 154 | "CpuPercent": 0, 155 | "IOMaximumIOps": 0, 156 | "IOMaximumBandwidth": 0, 157 | "MaskedPaths": [ 158 | "/proc/asound", 159 | "/proc/acpi", 160 | "/proc/kcore", 161 | "/proc/keys", 162 | "/proc/latency_stats", 163 | "/proc/timer_list", 164 | "/proc/timer_stats", 165 | "/proc/sched_debug", 166 | "/proc/scsi", 167 | "/sys/firmware" 168 | ], 169 | "ReadonlyPaths": [ 170 | "/proc/bus", 171 | "/proc/fs", 172 | "/proc/irq", 173 | "/proc/sys", 174 | "/proc/sysrq-trigger" 175 | ] 176 | }, 177 | "GraphDriver": { 178 | "Data": { 179 | "LowerDir": "/var/lib/docker/overlay2/7939c3a3000c80bf84ef243a84c7d9f7b31fbada480b4debbed2ffe51c926b53-init/diff:/var/lib/docker/overlay2/341bca578d432f25535aef79fa1fcc29a6904851d2424dc99f7cffe4fbd7e83c/diff:/var/lib/docker/overlay2/f2e2ecd2285aee0f16a53b18ab417c6c349c17566d2514ff5e896ebb84528f8a/diff:/var/lib/docker/overlay2/32ed4581a98c1b56cd999b1685c1f2b0745532141bd57e44f5205827ac7fc049/diff:/var/lib/docker/overlay2/75eaff6af183f69f223b24d2d91d9ad9701834480e169fad715b676b7dc256b9/diff:/var/lib/docker/overlay2/24a555fa01592a9e8fd38e21796f8d06bb55dc1467541b0569d3f83ebe9663ed/diff:/var/lib/docker/overlay2/90d4ec0e0bb97bd4df1737971990165f97c4a2e4e8ac2c003b710372497849d7/diff:/var/lib/docker/overlay2/029f6fc1d3e10d33a70be8c0aa70f1f22975a44e6323d5a271bb8dae4415de95/diff:/var/lib/docker/overlay2/daa61aa5455b883486d3a85748882f73af5ee87bde94323269ae4e9559d5d40f/diff:/var/lib/docker/overlay2/84e09e221a11265a7ae583a9e6bb0ff3bdd36bf4dd0c382879eed104f186b0a9/diff:/var/lib/docker/overlay2/f927fbded8ea23287f6c1de6293f737bc7192debcf4ebde275fdfc4b214e3071/diff:/var/lib/docker/overlay2/794dbc793fc3657e4fb7e133af4d535ac57559c87f7093c4f9f85fe45c376891/diff:/var/lib/docker/overlay2/b5ba3781afe16293bb3a7dd853e85699f80a6fca230958f9ca0876821e138f78/diff:/var/lib/docker/overlay2/b653044c795cba69ef84976cd6378d5e433e4bc793eed47770606fce33d7c3e5/diff", 180 | "MergedDir": "/var/lib/docker/overlay2/7939c3a3000c80bf84ef243a84c7d9f7b31fbada480b4debbed2ffe51c926b53/merged", 181 | "UpperDir": "/var/lib/docker/overlay2/7939c3a3000c80bf84ef243a84c7d9f7b31fbada480b4debbed2ffe51c926b53/diff", 182 | "WorkDir": "/var/lib/docker/overlay2/7939c3a3000c80bf84ef243a84c7d9f7b31fbada480b4debbed2ffe51c926b53/work" 183 | }, 184 | "Name": "overlay2" 185 | }, 186 | "Mounts": [ 187 | { 188 | "Type": "bind", 189 | "Source": "/home/jesse/projects/pihole/etc-dnsmasq.d", 190 | "Destination": "/etc/dnsmasq.d", 191 | "Mode": "rw", 192 | "RW": true, 193 | "Propagation": "rprivate" 194 | }, 195 | { 196 | "Type": "bind", 197 | "Source": "/home/jesse/projects/pihole/etc-pihole", 198 | "Destination": "/etc/pihole", 199 | "Mode": "rw", 200 | "RW": true, 201 | "Propagation": "rprivate" 202 | } 203 | ], 204 | "Config": { 205 | "Hostname": "a4b9efe31d3a", 206 | "Domainname": "", 207 | "User": "", 208 | "AttachStdin": false, 209 | "AttachStdout": false, 210 | "AttachStderr": false, 211 | "ExposedPorts": { 212 | "443/tcp": {}, 213 | "53/tcp": {}, 214 | "53/udp": {}, 215 | "67/udp": {}, 216 | "80/tcp": {} 217 | }, 218 | "Tty": false, 219 | "OpenStdin": false, 220 | "StdinOnce": false, 221 | "Env": [ 222 | "TZ=America/New_York", 223 | "WEBPASSWORD=mypihole", 224 | "SERVERIP=192.168.1.1", 225 | "PATH=/opt/pihole:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 226 | "ARCH=amd64", 227 | "S6OVERLAY_RELEASE=https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz", 228 | "PIHOLE_INSTALL=/root/ph_install.sh", 229 | "PHP_ENV_CONFIG=/etc/lighttpd/conf-enabled/15-fastcgi-php.conf", 230 | "PHP_ERROR_LOG=/var/log/lighttpd/error.log", 231 | "IPv6=True", 232 | "S6_LOGGING=0", 233 | "S6_KEEP_ENV=1", 234 | "S6_BEHAVIOUR_IF_STAGE2_FAILS=2", 235 | "ServerIP=0.0.0.0", 236 | "FTL_CMD=no-daemon", 237 | "DNSMASQ_USER=root", 238 | "VERSION=v5.0" 239 | ], 240 | "Cmd": null, 241 | "Healthcheck": { 242 | "Test": [ 243 | "CMD-SHELL", 244 | "dig +norecurse +retry=0 @127.0.0.1 pi.hole || exit 1" 245 | ] 246 | }, 247 | "Image": "pihole/pihole:latest", 248 | "Volumes": { 249 | "/etc/dnsmasq.d": {}, 250 | "/etc/pihole": {} 251 | }, 252 | "WorkingDir": "", 253 | "Entrypoint": [ 254 | "/s6-init" 255 | ], 256 | "OnBuild": null, 257 | "Labels": { 258 | "com.docker.compose.config-hash": "07028e544ab5b2de2336828789f6dc925543d0828ce334e2e3b8f7beff60a0bd", 259 | "com.docker.compose.container-number": "1", 260 | "com.docker.compose.oneoff": "False", 261 | "com.docker.compose.project": "pihole", 262 | "com.docker.compose.project.config_files": "docker-compose.yml", 263 | "com.docker.compose.project.working_dir": "/home/jesse/projects/pihole", 264 | "com.docker.compose.service": "pihole", 265 | "com.docker.compose.version": "1.25.0", 266 | "image": "pihole/pihole:v5.0_amd64", 267 | "maintainer": "adam@diginc.us", 268 | "url": "https://www.github.com/pi-hole/docker-pi-hole" 269 | } 270 | }, 271 | "NetworkSettings": { 272 | "Bridge": "", 273 | "SandboxID": "df127b0120a58247c7ebcbfaff8a1598caf8d1e030bff79b788f5753d774a766", 274 | "HairpinMode": false, 275 | "LinkLocalIPv6Address": "", 276 | "LinkLocalIPv6PrefixLen": 0, 277 | "Ports": { 278 | "443/tcp": [ 279 | { 280 | "HostIp": "0.0.0.0", 281 | "HostPort": "443" 282 | } 283 | ], 284 | "53/tcp": [ 285 | { 286 | "HostIp": "0.0.0.0", 287 | "HostPort": "53" 288 | } 289 | ], 290 | "53/udp": [ 291 | { 292 | "HostIp": "0.0.0.0", 293 | "HostPort": "53" 294 | } 295 | ], 296 | "67/udp": [ 297 | { 298 | "HostIp": "0.0.0.0", 299 | "HostPort": "67" 300 | } 301 | ], 302 | "80/tcp": [ 303 | { 304 | "HostIp": "0.0.0.0", 305 | "HostPort": "80" 306 | } 307 | ] 308 | }, 309 | "SandboxKey": "/var/run/docker/netns/df127b0120a5", 310 | "SecondaryIPAddresses": null, 311 | "SecondaryIPv6Addresses": null, 312 | "EndpointID": "", 313 | "Gateway": "", 314 | "GlobalIPv6Address": "", 315 | "GlobalIPv6PrefixLen": 0, 316 | "IPAddress": "", 317 | "IPPrefixLen": 0, 318 | "IPv6Gateway": "", 319 | "MacAddress": "", 320 | "Networks": { 321 | "pihole_default": { 322 | "IPAMConfig": null, 323 | "Links": null, 324 | "Aliases": [ 325 | "pihole", 326 | "a4b9efe31d3a" 327 | ], 328 | "NetworkID": "a89d90e0933c49c6629300ce125a5dab02f20e90de0fde5a263459378da6e2e7", 329 | "EndpointID": "9a02827abb9c295b64e769759acaf51861651603c7c837d3caa3eb238698006e", 330 | "Gateway": "172.19.0.1", 331 | "IPAddress": "172.19.0.2", 332 | "IPPrefixLen": 16, 333 | "IPv6Gateway": "", 334 | "GlobalIPv6Address": "", 335 | "GlobalIPv6PrefixLen": 0, 336 | "MacAddress": "02:42:ac:13:00:02", 337 | "DriverOpts": null 338 | } 339 | } 340 | } 341 | } 342 | ] 343 | -------------------------------------------------------------------------------- /ph5lt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessedp/pihole5-list-tool/3a37a79285ba537adf020306d584fb0d90efe758/ph5lt/__init__.py -------------------------------------------------------------------------------- /ph5lt/__main__.py: -------------------------------------------------------------------------------- 1 | """main startup""" 2 | #!/usr/bin/python3 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | from ph5lt.app import main 7 | 8 | if __name__ == "__main__": 9 | sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) 10 | sys.exit(main()) 11 | -------------------------------------------------------------------------------- /ph5lt/allowlists.py: -------------------------------------------------------------------------------- 1 | """ add/remove/reset blocklists """ 2 | import requests 3 | from InquirerPy.separator import Separator 4 | 5 | from ph5lt import prompts 6 | from ph5lt import constants 7 | from ph5lt import utils 8 | 9 | ANUDEEP_ALLOWLIST = ( 10 | "https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/whitelist.txt" 11 | ) 12 | whiteLists = { 13 | constants.W_ANUDEEP_ALLOW: { 14 | "url": ANUDEEP_ALLOWLIST, 15 | "comment": "AnudeepND | Allowlist Only", 16 | }, 17 | constants.W_ANUDEEP_REFERRAL: { 18 | "url": "https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/referral-sites.txt", 19 | "comment": "AnudeepND | Allowlist+Referral", 20 | }, 21 | constants.W_ANUDEEP_OPTIONAL: { 22 | "url": "https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/optional-list.txt", 23 | "comment": "AnudeepND | Allowlist+Optional", 24 | }, 25 | } 26 | 27 | 28 | def manage_allowlists(cur): 29 | """what to do to allowlists""" 30 | questions = [ 31 | { 32 | "name": "action", 33 | "type": "list", 34 | "default": "add", 35 | "message": "Allowlist action:", 36 | "choices": [ 37 | { 38 | "name": "Add a list", 39 | "value": "add", 40 | }, 41 | Separator(), 42 | { 43 | "name": "Remove Lists Added by This Tool", 44 | "value": "remove", 45 | }, 46 | { 47 | "name": "Remove ALL Allowlists", 48 | "value": "empty", 49 | }, 50 | ], 51 | } 52 | ] 53 | 54 | result = prompts.key_prompt(questions) 55 | action = result["action"] 56 | if action == "add": 57 | return add(cur) 58 | 59 | if action == "empty": 60 | return empty(cur) 61 | 62 | if action == "remove": 63 | return remove(cur) 64 | 65 | return False 66 | 67 | 68 | def add(cur): 69 | """prompt for and process allowlists""" 70 | source = prompts.ask_allowlist() 71 | 72 | utils.warn_long_running() 73 | 74 | import_list = [] 75 | 76 | if source in whiteLists: 77 | url_source = whiteLists[source] 78 | resp = requests.get(url=url_source["url"], timeout=15) # 15 seconds 79 | import_list = utils.process_lines(resp.text, url_source["comment"], False) 80 | # This breaks if we add a new whitelist setup 81 | if source != ANUDEEP_ALLOWLIST: 82 | resp = requests.get(url=ANUDEEP_ALLOWLIST, timeout=15) 83 | import_list += utils.process_lines(resp.text, url_source["comment"], False) 84 | 85 | if source == constants.FILE: 86 | fname = prompts.ask_import_file() 87 | with open(fname, encoding="utf-8") as import_file: 88 | import_list = utils.process_lines( 89 | import_file.read(), f"File: {fname}", False 90 | ) 91 | 92 | if source == constants.PASTE: 93 | import_list = prompts.ask_paste() 94 | import_list = utils.process_lines( 95 | import_list, "Pasted content", utils.validate_host 96 | ) 97 | 98 | if len(import_list) == 0: 99 | utils.warn("No valid urls found, try again") 100 | return False 101 | 102 | if not prompts.confirm(f"Add {len(import_list)} white lists?"): 103 | return False 104 | 105 | added = 0 106 | exists = 0 107 | 108 | for item in import_list: 109 | cur.execute("SELECT COUNT(*) FROM domainlist WHERE domain = ?", (item["url"],)) 110 | 111 | cnt = cur.fetchone() 112 | 113 | if cnt[0] > 0: 114 | exists += 1 115 | else: 116 | # 0 = exact whitelist 117 | # 2 = regex whitelist 118 | domain_type = 0 119 | if item["type"] == constants.REGEX: 120 | domain_type = 2 121 | 122 | vals = (item["url"], domain_type, item["comment"] + " [ph5lt]") 123 | cur.execute( 124 | "INSERT OR IGNORE INTO domainlist (domain, type, comment) VALUES (?,?,?)", 125 | vals, 126 | ) 127 | added += 1 128 | utils.success(f"{added} allowlists added! {exists} already existed.") 129 | return True 130 | 131 | 132 | def empty(cur): 133 | """remove all block lists""" 134 | utils.danger( 135 | """ 136 | This will REMOVE ALL manually added allowlists! 137 | You probably DO NOT want to do this! 138 | """ 139 | ) 140 | 141 | if prompts.confirm("Are you sure?", "n"): 142 | cur.execute("DELETE FROM domainlist WHERE type in (0,2)") 143 | return True 144 | 145 | return False 146 | 147 | 148 | ##### Deal with my sloppiness... 149 | def remove(cur): 150 | """remove lists we added""" 151 | 152 | utils.info( 153 | """ 154 | This will try to remove blocklists added by this tool. Removal is done 155 | based on the comment for each list. If you've never changed any comments 156 | or used other tools, this is 100% safe. 157 | """ 158 | ) 159 | 160 | if prompts.confirm("Are you sure?", "n"): 161 | cur.execute( 162 | "DELETE FROM domainlist WHERE comment LIKE '%AndeepND |%' OR comment LIKE '%[ph5lt]'" 163 | ) 164 | return True 165 | 166 | return False 167 | -------------------------------------------------------------------------------- /ph5lt/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2020 jessedp 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining 7 | # a copy of this software and associated documentation files (the 8 | # "Software"), to deal in the Software without restriction, including 9 | # without limitation the rights to use, copy, modify, merge, publish, 10 | # distribute, sublicense, and/or sell copies of the Software, and to 11 | # permit persons to whom the Software is furnished to do so, subject to 12 | # the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be 15 | # included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | 27 | """Makes bulk adding DNS blocklists and allowlists to Pi-hole 5 a breeze""" 28 | 29 | import os 30 | import sys 31 | import sqlite3 32 | 33 | from ph5lt import constants 34 | from ph5lt import prompts 35 | from ph5lt import allowlists 36 | from ph5lt import blocklists 37 | from ph5lt import utils 38 | from ph5lt import banner 39 | from ph5lt import stats 40 | 41 | 42 | def main(): 43 | """main method""" 44 | conn = None 45 | try: 46 | utils.clear() 47 | banner.display() 48 | 49 | use_docker = False 50 | docker = utils.find_docker() 51 | 52 | if docker[0] is True: 53 | utils.success(f"+ Found Running Docker config: {docker[1]}") 54 | use_docker = prompts.confirm("Use Docker-ized config?", "n") 55 | if use_docker: 56 | db_file = docker[1] 57 | 58 | if not use_docker: 59 | print() 60 | db_file = prompts.ask_db() 61 | 62 | # ask_db validates the db, pass this connection round for easy access & "global" mgmt 63 | conn = sqlite3.connect(db_file) 64 | cur = conn.cursor() 65 | 66 | default = constants.BLOCKLIST 67 | option = "" 68 | any_save = False 69 | while option != constants.EXIT: 70 | stats.stat_bar(cur) 71 | option = prompts.main_menu(default) 72 | save = False 73 | 74 | if option == constants.BLOCKLIST: 75 | save = blocklists.manage_blocklists(cur) 76 | 77 | if option == constants.ALLOWLIST: 78 | save = allowlists.manage_allowlists(cur) 79 | 80 | if option == constants.STATS: 81 | stats.header(cur) 82 | 83 | if save: 84 | any_save = True 85 | default = constants.EXIT 86 | conn.commit() 87 | if option == constants.ALLOWLIST: 88 | stats.allow_header(cur) 89 | 90 | if option == constants.BLOCKLIST: 91 | stats.block_header(cur) 92 | 93 | if prompts.confirm("Are you finished?"): 94 | break 95 | 96 | conn.close() 97 | if any_save: 98 | update_gravity(use_docker) 99 | 100 | utils.info("\n\tBye!\n") 101 | 102 | except (KeyboardInterrupt, KeyError): 103 | if conn: 104 | conn.close() 105 | sys.exit(0) 106 | 107 | 108 | def update_gravity(use_docker): 109 | """various ways of updating the gravity db""" 110 | 111 | if prompts.confirm("Update Gravity for immediate effect?"): 112 | print() 113 | if use_docker: 114 | os.system('docker exec pihole bash "/usr/local/bin/pihole" "-g"') 115 | else: 116 | os.system("pihole -g") 117 | else: 118 | print() 119 | if use_docker: 120 | utils.info( 121 | "Update Gravity through the web interface or by running:\n\t" 122 | + '# docker exec pihole bash "/usr/local/bin/pihole" "-g"' 123 | ) 124 | 125 | else: 126 | utils.info( 127 | "Update Gravity through the web interface or by running:\n\t# pihole -g" 128 | ) 129 | 130 | 131 | if __name__ == "__main__": 132 | try: 133 | main() 134 | except sqlite3.OperationalError as err: 135 | utils.danger("\n\tDatabase error!") 136 | utils.danger(f"\t{err}") 137 | except (KeyboardInterrupt, KeyError): 138 | sys.exit(0) 139 | -------------------------------------------------------------------------------- /ph5lt/banner.py: -------------------------------------------------------------------------------- 1 | """ silly ansi banner""" 2 | 3 | from colors import color 4 | 5 | from ph5lt import utils 6 | 7 | __version__ = "0.6.2" 8 | 9 | 10 | def display(): 11 | """display the banner""" 12 | print(color(" ┌──────────────────────────────────────────┐", fg="#b61042")) 13 | print( 14 | color(" │ ", fg="#b61042") 15 | + color(f"π-hole 5 list tool v{__version__}", "#FFF") 16 | + color(" │", fg="#b61042") 17 | ) 18 | print(color(" └──────────────────────────────────────────┘", fg="#b61042")) 19 | utils.info(" https://github.com/jessedp/pihole5-list-tool\n") 20 | -------------------------------------------------------------------------------- /ph5lt/blocklists.py: -------------------------------------------------------------------------------- 1 | """ add/remove/reset blocklists """ 2 | import requests 3 | from InquirerPy.separator import Separator 4 | 5 | 6 | from ph5lt import prompts 7 | from ph5lt import constants 8 | from ph5lt import utils 9 | 10 | # PiHole 5.1 installation defaults 11 | DEFAULT_LISTS = [ 12 | "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", 13 | "https://mirror1.malwaredomains.com/files/justdomains", 14 | ] 15 | 16 | blockLists = { 17 | constants.B_FIREBOG_NOCROSS: { 18 | "url": "https://v.firebog.net/hosts/lists.php?type=nocross", 19 | "comment": "Firebog | Non-crossed lists", 20 | }, 21 | constants.B_FIREBOG_ALL: { 22 | "url": "https://v.firebog.net/hosts/lists.php?type=all", 23 | "comment": "Firebog | All lists", 24 | }, 25 | constants.B_FIREBOG_TICKED: { 26 | "url": "https://v.firebog.net/hosts/lists.php?type=tick", 27 | "comment": "Firebog | Ticked lists", 28 | }, 29 | } 30 | 31 | 32 | def manage_blocklists(cur): 33 | """what to do to blocklists""" 34 | questions = [ 35 | { 36 | "name": "action", 37 | "type": "list", 38 | "default": "add", 39 | "message": "Blocklist action:", 40 | "choices": [ 41 | { 42 | "name": "Add a list", 43 | "value": "add", 44 | }, 45 | Separator(), 46 | { 47 | "name": "Remove Lists Added by This Tool", 48 | "value": "remove", 49 | }, 50 | { 51 | "name": "Reset to Pihole defaults", 52 | "value": "reset", 53 | }, 54 | { 55 | "name": "Remove ALL Blocklists", 56 | "value": "empty", 57 | }, 58 | ], 59 | } 60 | ] 61 | 62 | result = prompts.key_prompt(questions) 63 | action = result["action"] 64 | if action == "add": 65 | return add(cur) 66 | 67 | if action == "reset": 68 | return reset(cur) 69 | 70 | if action == "empty": 71 | return empty(cur) 72 | 73 | if action == "remove": 74 | return remove(cur) 75 | 76 | return False 77 | 78 | 79 | def add(cur): 80 | """prompt for and process blocklists""" 81 | source = prompts.ask_blocklist() 82 | 83 | utils.warn_long_running() 84 | 85 | import_list = [] 86 | 87 | if source in blockLists: 88 | url_source = blockLists[source] 89 | resp = requests.get(url=url_source["url"], timeout=15) # 15 seconds 90 | import_list = utils.process_lines(resp.text, url_source["comment"]) 91 | 92 | if source == constants.FILE: 93 | fname = prompts.ask_import_file() 94 | with open(fname, encoding="utf-8") as import_file: 95 | import_list = utils.process_lines(import_file.read(), f"File: {fname}") 96 | 97 | if source == constants.PASTE: 98 | import_list = prompts.ask_paste() 99 | import_list = utils.process_lines(import_list, "Pasted content") 100 | 101 | if len(import_list) == 0: 102 | utils.warn("No valid urls found, try again") 103 | return False 104 | 105 | if not prompts.confirm(f"Add {len(import_list)} block lists?"): 106 | return False 107 | 108 | added = 0 109 | exists = 0 110 | for item in import_list: 111 | cur.execute("SELECT COUNT(*) FROM adlist WHERE address = ?", (item["url"],)) 112 | 113 | cnt = cur.fetchone() 114 | 115 | if cnt[0] > 0: 116 | exists += 1 117 | else: 118 | added += 1 119 | vals = (item["url"], item["comment"] + " [ph5lt]") 120 | cur.execute( 121 | "INSERT OR IGNORE INTO adlist (address, comment) VALUES (?,?)", vals 122 | ) 123 | 124 | utils.success(f"{added} block lists added! {exists} already existed.") 125 | return True 126 | 127 | 128 | def reset(cur): 129 | """reset block lists to pihole install default""" 130 | utils.info("\nThis will replace ALL blocklists with these defaults:") 131 | 132 | for url in DEFAULT_LISTS: 133 | utils.info(" - " + url) 134 | print() 135 | 136 | if prompts.confirm("Are you sure?", "n"): 137 | cur.execute("DELETE FROM adlist") 138 | for url in DEFAULT_LISTS: 139 | vals = (url, "Pi-hole defaults") 140 | cur.execute( 141 | "INSERT OR IGNORE INTO adlist (address, comment) VALUES (?,?)", vals 142 | ) 143 | return True 144 | 145 | return False 146 | 147 | 148 | def empty(cur): 149 | """remove all block lists""" 150 | utils.danger("\n\tThis will REMOVE ALL blocklists!\n") 151 | 152 | if prompts.confirm("Are you sure?", "n"): 153 | cur.execute("DELETE FROM adlist") 154 | return True 155 | 156 | return False 157 | 158 | 159 | ##### Deal with my sloppiness... 160 | def remove(cur): 161 | """remove lists we added""" 162 | 163 | utils.info( 164 | """ 165 | This will try to remove blocklists added by this tool. Removal is done 166 | based on the comment for each list. If you've never changed any comments 167 | or used other tools, this is 100% safe. 168 | ** defaults are not removed 169 | """ 170 | ) 171 | 172 | if prompts.confirm("Are you sure?", "n"): 173 | cur.execute( 174 | "DELETE FROM adlist WHERE comment LIKE '%Firebog |%' OR comment LIKE '%[ph5lt]'" 175 | ) 176 | return True 177 | 178 | return False 179 | -------------------------------------------------------------------------------- /ph5lt/constants.py: -------------------------------------------------------------------------------- 1 | """Constant like things""" 2 | DEFAULT_DB = "/etc/pihole/gravity.db" 3 | 4 | BLOCKLIST = 1 5 | ALLOWLIST = 2 6 | STATS = 3 7 | EXIT = 0 8 | 9 | # item types 10 | URL = 1 11 | REGEX = 2 12 | 13 | 14 | FILE = 100 15 | PASTE = 101 16 | 17 | # BLOCKLIST SOURCES 18 | B_FIREBOG_NOCROSS = 1 19 | B_FIREBOG_TICKED = 2 20 | B_FIREBOG_ALL = 3 21 | 22 | # ALLOWLIST SOURCES 23 | W_ANUDEEP_ALLOW = 1 24 | W_ANUDEEP_REFERRAL = 2 25 | W_ANUDEEP_OPTIONAL = 3 26 | -------------------------------------------------------------------------------- /ph5lt/prompts.py: -------------------------------------------------------------------------------- 1 | """ prompts for inquirer """ 2 | import os 3 | import sqlite3 4 | from InquirerPy import prompt 5 | from InquirerPy.separator import Separator 6 | 7 | from ph5lt import constants 8 | from ph5lt import utils 9 | 10 | 11 | def check_db(path): 12 | """validate we have a good file""" 13 | if not os.path.exists(path): 14 | utils.warn(" DOES NOT EXIST!") 15 | return False 16 | try: 17 | conn = sqlite3.connect(path) 18 | except (sqlite3.DatabaseError, sqlite3.OperationalError): 19 | utils.warn(" EXISTS, BUT IS NOT A SQLITE DATABASE!") 20 | return False 21 | 22 | try: 23 | sqldb = conn.cursor() 24 | _ = sqldb.execute("select count(*) from info") 25 | conn.close() 26 | except (sqlite3.DatabaseError, sqlite3.OperationalError): 27 | utils.warn(" IS NOT A PI-HOLE GRAVITY DB!") 28 | return False 29 | return True 30 | 31 | 32 | def key_prompt(questions): 33 | """prompt wrapper to handle ctrl+c""" 34 | resp = prompt(questions) 35 | if len(resp) != len(questions): 36 | raise KeyboardInterrupt 37 | return resp 38 | 39 | 40 | def ask_db(): 41 | """prompt for gravity db file""" 42 | questions = [ 43 | { 44 | "name": "gravitydb", 45 | "type": "input", 46 | "default": constants.DEFAULT_DB, 47 | "message": "Gravity Db to Update:", 48 | "validate": lambda answer: "Please enter the full path to the │gravity.db│ (in your pi-hole configuration directory)." 49 | if not check_db(answer) 50 | else True, 51 | } 52 | ] 53 | result = key_prompt(questions) 54 | return result["gravitydb"] 55 | 56 | 57 | def main_menu(default=constants.BLOCKLIST): 58 | """prompt for allow/block list""" 59 | questions = [ 60 | { 61 | "name": "main", 62 | "type": "list", 63 | "default": default, 64 | "message": "Options:", 65 | "choices": [ 66 | { 67 | "name": "Manage Blocklists", 68 | "value": constants.BLOCKLIST, 69 | "short": "Blocklists", 70 | }, 71 | { 72 | "name": "Manage Allowlists", 73 | "value": constants.ALLOWLIST, 74 | "short": "Allowlists", 75 | }, 76 | { 77 | "name": "Full Stats", 78 | "value": constants.STATS, 79 | "short": "Full Stats", 80 | }, 81 | { 82 | "name": "Exit", 83 | "value": constants.EXIT, 84 | "short": "Exit", 85 | }, 86 | ], 87 | } 88 | ] 89 | 90 | result = key_prompt(questions) 91 | return result["main"] 92 | 93 | 94 | def ask_blocklist(): 95 | """prompt for which blocklist to use""" 96 | questions = [ 97 | { 98 | "name": "source", 99 | "type": "list", 100 | "message": "Where are the block lists coming from?", 101 | "choices": [ 102 | { 103 | "name": """Firebog | Non-crossed lists : Use when someone is usually around to allow 104 | falsely blocked sites""", 105 | "value": constants.B_FIREBOG_NOCROSS, 106 | "short": "Firebog (no cross)", 107 | }, 108 | { 109 | "name": """Firebog | Ticked lists : Use where no one will be allowing falsely 110 | blocked sites""", 111 | "value": constants.B_FIREBOG_TICKED, 112 | "short": "Firebog (ticked)", 113 | }, 114 | { 115 | "name": """Firebog | All lists : Use when someone will always be around to 116 | allow falsely blocked sites""", 117 | "value": constants.B_FIREBOG_ALL, 118 | "short": "Firebog (all)", 119 | }, 120 | Separator(), 121 | { 122 | "name": "File | A file with urls of lists, 1 per line", 123 | "value": constants.FILE, 124 | "short": "File", 125 | }, 126 | { 127 | "name": "Paste | Paste urls of lists, 1 per line - opens editor, save, close", 128 | "value": constants.PASTE, 129 | "short": "Paste", 130 | }, 131 | ], 132 | } 133 | ] 134 | 135 | result = key_prompt(questions) 136 | return result["source"] 137 | 138 | 139 | def ask_allowlist(): 140 | """prompt for which allow to use""" 141 | questions = [ 142 | { 143 | "name": "source", 144 | "type": "list", 145 | "message": "Where are the allowlists coming from?", 146 | "choices": [ 147 | { 148 | "name": """AnudeepND | Allowlist Only : 149 | Domains that are safe to allow i.e does not contain any tracking or 150 | advertising sites. This fixes many problems like YouTube watch history, 151 | videos on news sites and so on.""", 152 | "value": constants.W_ANUDEEP_ALLOW, 153 | "short": "AnudeepND (Allowlist)", 154 | }, 155 | { 156 | "name": """AnudeepND | Allowlist+Optional : 157 | These are needed depending on the service you use. They may contain some 158 | tracking sites but sometimes it's necessary to add bad domains to make a 159 | few services to work.""", 160 | "value": constants.W_ANUDEEP_OPTIONAL, 161 | "short": '"AndueepND (Allowlist+Optional)', 162 | }, 163 | { 164 | "name": """AnudeepND | Allowlist+Referral : 165 | People who use services like Slickdeals and Fatwallet need a few sites 166 | (most of them are either trackers or ads) to be whitelisted to work 167 | properly. This contains some analytics and ad serving sites like 168 | doubleclick.net and others. If you don't know what these services are, 169 | stay away from this list. Domains that are safe to whitelist i.e does 170 | not contain any tracking or advertising sites. This fixes many problems 171 | like YouTube watch history, videos on news sites and so on.""", 172 | "value": constants.W_ANUDEEP_REFERRAL, 173 | "short": "AndeepND (Allowlist+Referral)", 174 | }, 175 | Separator(), 176 | { 177 | "name": "File | A file with urls of lists, 1 per line", 178 | "value": constants.FILE, 179 | "short": "File", 180 | }, 181 | { 182 | "name": "Paste | Paste urls of lists, 1 per line - opens editor, save, close", 183 | "value": constants.PASTE, 184 | "short": "Paste", 185 | }, 186 | ], 187 | } 188 | ] 189 | 190 | result = key_prompt(questions) 191 | return result["source"] 192 | 193 | 194 | def ask_import_file(): 195 | """prompt for file to import from""" 196 | questions = [ 197 | { 198 | "name": "file", 199 | "type": "input", 200 | "message": "File to import", 201 | "validate": lambda value: "Please enter a valid file name." 202 | if not os.path.exists(value) 203 | else True, 204 | } 205 | ] 206 | result = key_prompt(questions) 207 | return result["file"] 208 | 209 | 210 | def ask_paste(): 211 | """prompt for acquiring pasted list""" 212 | questions = [ 213 | { 214 | "name": "content", 215 | "type": "input", 216 | "message": "Please Paste your of URLS, then:", 217 | "multiline": True, 218 | "instruction": "ESC + Enter to continue", 219 | } 220 | ] 221 | result = key_prompt(questions) 222 | return result["content"] 223 | 224 | 225 | def confirm(message, default="y"): 226 | """generic y/n confirm prompt""" 227 | questions = [ 228 | { 229 | "name": "confirm", 230 | "type": "confirm", 231 | "message": message, 232 | "default": default == "y", 233 | } 234 | ] 235 | result = key_prompt(questions) 236 | return result["confirm"] 237 | -------------------------------------------------------------------------------- /ph5lt/stats.py: -------------------------------------------------------------------------------- 1 | """ variaous stats and stat displays """ 2 | 3 | from terminaltables import AsciiTable, SingleTable 4 | from colors import color 5 | 6 | from ph5lt import utils 7 | 8 | stats = { 9 | "total_adlist": "SELECT COUNT(*) FROM adlist", 10 | "total_adlist_enabled": "SELECT COUNT(*) FROM adlist WHERE enabled = 1", 11 | "total_adlist_disabled": "SELECT COUNT(*) FROM adlist WHERE enabled = 0", 12 | "our_adlist": "SELECT COUNT(*) FROM adlist WHERE comment LIKE '%Firebog |%' OR comment LIKE '%[ph5lt]'", 13 | "our_adlist_enabled": "SELECT COUNT(*) FROM adlist WHERE enabled = 1 AND (comment LIKE '%Firebog |%' OR comment LIKE '%[ph5lt]')", 14 | "our_adlist_disabled": "SELECT COUNT(*) FROM adlist WHERE enabled = 0 AND (comment LIKE '%Firebog |%' OR comment LIKE '%[ph5lt]')", 15 | "other_adlist": "SELECT COUNT(*) FROM adlist WHERE comment NOT LIKE '%Firebog |%' AND comment NOT LIKE '%[ph5lt]'", 16 | "other_adlist_enabled": "SELECT COUNT(*) FROM adlist WHERE enabled = 1 AND comment NOT LIKE '%Firebog |%' AND comment NOT LIKE '%[ph5lt]'", 17 | "other_adlist_disabled": "SELECT COUNT(*) FROM adlist WHERE enabled = 0 AND comment NOT LIKE '%Firebog |%' AND comment NOT LIKE '%[ph5lt]'", 18 | "total_allow": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2)", 19 | "total_allow_enabled": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND enabled = 1", 20 | "total_allow_disabled": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND enabled = 0", 21 | "our_allow": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND comment LIKE '%AndeepND |%' OR comment LIKE '%[ph5lt]'", 22 | "our_allow_enabled": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND enabled = 1 AND (comment LIKE '%AndeepND |%' OR comment LIKE '%[ph5lt]')", 23 | "our_allow_disabled": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND enabled = 0 AND (comment LIKE '%AndeepND |%' OR comment LIKE '%[ph5lt]')", 24 | "other_allow": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND comment NOT LIKE '%AndeepND |%' AND comment NOT LIKE '%[ph5lt]'", 25 | "other_allow_enabled": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND enabled = 1 AND comment NOT LIKE '%AndeepND |%' AND comment NOT LIKE '%[ph5lt]'", 26 | "other_allow_disabled": "SELECT COUNT(*) FROM domainlist WHERE type IN (0,2) AND enabled = 0 AND comment NOT LIKE '%AndeepND |%' AND comment NOT LIKE '%[ph5lt]'", 27 | } 28 | 29 | 30 | def get(cur, name): 31 | """get stats using prebuilt statements""" 32 | if name not in stats: 33 | return -1 34 | cur.execute(stats[name]) 35 | return str(cur.fetchone()[0]) 36 | 37 | 38 | def adlist_top3_by_comment(cur): 39 | """top 3 adlists by comment""" 40 | sql = "SELECT comment, count(*) FROM adlist GROUP BY comment LIMIT 3" 41 | cur.execute(sql) 42 | return cur.fetchall() 43 | 44 | 45 | def allow_top3_by_comment(cur): 46 | """top 3 allow lists by comment""" 47 | sql = "SELECT comment, count(*) FROM domainlist WHERE type IN (0,2) GROUP BY comment LIMIT 3" 48 | cur.execute(sql) 49 | return cur.fetchall() 50 | 51 | 52 | def stat_bar(cur): 53 | """one-liner stat bar""" 54 | # Block : All=X Ours=Y Oth=Z | Allow : All=X Ours=Y Oth=Z 55 | data = [] 56 | 57 | data.append("Blocks Enabled: All=" + str(get(cur, "total_adlist_enabled"))) 58 | data.append("│") 59 | data.append("Ours=" + str(get(cur, "our_adlist_enabled"))) 60 | # data.append("│") 61 | # data.append("Other=" + str(get(cur, "other_adlist_enabled"))) 62 | 63 | data.append("│") 64 | data.append("Allows Enabled: All=" + str(get(cur, "total_allow_enabled"))) 65 | data.append("│") 66 | data.append("Ours=" + str(get(cur, "our_allow_enabled"))) 67 | # data.append("│") 68 | # data.append("Other=" + str(get(cur, "other_allow_enabled"))) 69 | 70 | table = SingleTable([data]) 71 | 72 | table.inner_heading_row_border = False 73 | table.outer_border = False 74 | table.inner_row_border = False 75 | table.inner_column_border = False 76 | table.padding_left = 2 77 | 78 | print() 79 | print(color(table.table, bg="#505050", fg="white")) 80 | print() 81 | 82 | 83 | def header(cur): 84 | """a stats overview header""" 85 | print() 86 | block_header(cur) 87 | # utils.info("──────────────────────────────────────────────────────────────") 88 | print() 89 | allow_header(cur) 90 | print() 91 | 92 | 93 | def allow_header(cur): 94 | """allow portion of header""" 95 | 96 | block_data = [ 97 | [ 98 | "Total :", 99 | get(cur, "total_allow_enabled") + "/" + get(cur, "total_allow"), 100 | ], 101 | ["Our Lists :", get(cur, "our_allow_enabled") + "/" + get(cur, "our_allow")], 102 | [ 103 | "Others :", 104 | get(cur, "other_allow_enabled") + "/" + get(cur, "other_allow"), 105 | ], 106 | ] 107 | block_table = AsciiTable(block_data) 108 | 109 | block_table.inner_heading_row_border = False 110 | block_table.outer_border = False 111 | block_table.inner_row_border = False 112 | block_table.inner_column_border = False 113 | 114 | rows = allow_top3_by_comment(cur) 115 | t3_block_data = [] 116 | for row in rows: 117 | t3_block_data.append([row[0], row[1]]) 118 | 119 | t3_block_table = AsciiTable(t3_block_data) 120 | 121 | t3_block_table.inner_heading_row_border = False 122 | t3_block_table.outer_border = False 123 | t3_block_table.inner_row_border = False 124 | t3_block_table.inner_column_border = False 125 | 126 | table_data = [ 127 | ["Allowlist Stats", "Top 3 by Comment"], 128 | [block_table.table, t3_block_table.table], 129 | ] 130 | 131 | table = SingleTable(table_data) 132 | table.padding_left = 2 133 | table.outer_border = False 134 | 135 | utils.info(table.table) 136 | 137 | 138 | def block_header(cur): 139 | """block portion of header""" 140 | 141 | block_data = [ 142 | [ 143 | "Total :", 144 | get(cur, "total_adlist_enabled") + "/" + get(cur, "total_adlist"), 145 | ], 146 | ["Our Lists :", get(cur, "our_adlist_enabled") + "/" + get(cur, "our_adlist")], 147 | [ 148 | "Others :", 149 | get(cur, "other_adlist_enabled") + "/" + get(cur, "other_adlist"), 150 | ], 151 | ] 152 | block_table = AsciiTable(block_data) 153 | 154 | block_table.inner_heading_row_border = False 155 | block_table.outer_border = False 156 | block_table.inner_row_border = False 157 | block_table.inner_column_border = False 158 | 159 | rows = adlist_top3_by_comment(cur) 160 | t3_block_data = [] 161 | for row in rows: 162 | t3_block_data.append([row[0], row[1]]) 163 | 164 | t3_block_table = AsciiTable(t3_block_data) 165 | 166 | t3_block_table.inner_heading_row_border = False 167 | t3_block_table.outer_border = False 168 | t3_block_table.inner_row_border = False 169 | t3_block_table.inner_column_border = False 170 | 171 | table_data = [ 172 | ["Ad/Blocklist Stats", "Top 3 by Comment"], 173 | [block_table.table, t3_block_table.table], 174 | [], 175 | ] 176 | 177 | table = SingleTable(table_data) 178 | table.padding_left = 2 179 | table.outer_border = False 180 | 181 | utils.info(table.table) 182 | -------------------------------------------------------------------------------- /ph5lt/utils.py: -------------------------------------------------------------------------------- 1 | """Utils""" 2 | import sys 3 | import os 4 | import subprocess 5 | from subprocess import CalledProcessError 6 | import re 7 | import json 8 | from urllib.parse import urlparse 9 | from json.decoder import JSONDecodeError 10 | from colors import color 11 | 12 | from ph5lt import constants 13 | 14 | 15 | def valid_url(url): 16 | """make sure we have a valid url""" 17 | parts = urlparse(url.strip()) 18 | return parts.scheme != "" and parts.netloc != "" 19 | 20 | 21 | def validate_host(value): 22 | """Make sure we at least have "site.com" and the TLD is long enough""" 23 | parts = value.split(".") 24 | return len(parts) > 1 and len(parts[len(parts) - 1]) > 1 25 | 26 | 27 | def validate_regex(value): 28 | """see if we have a valid regex""" 29 | try: 30 | re.compile(value) 31 | return True 32 | except re.error: 33 | return False 34 | 35 | 36 | def process_lines(data, comment, full_url_only=True): 37 | """massage the lines so we have good ones""" 38 | new_data = [] 39 | extra_comment = "" 40 | for line in data.split("\n"): 41 | line = line.strip() 42 | if line == "": 43 | extra_comment = "" 44 | continue 45 | # comments! 46 | if line.startswith("#"): 47 | extra_comment += line[1:].strip() + " " 48 | continue 49 | 50 | full_comment = comment 51 | if extra_comment.strip() != "": 52 | full_comment += " - " + extra_comment 53 | 54 | if full_url_only: 55 | if valid_url(line): 56 | new_data.append( 57 | {"url": line, "comment": full_comment, "type": constants.URL} 58 | ) 59 | continue 60 | else: 61 | if validate_host(line): 62 | new_data.append( 63 | {"url": line, "comment": full_comment, "type": constants.URL} 64 | ) 65 | continue 66 | 67 | if validate_regex(line): 68 | new_data.append( 69 | {"url": line, "comment": full_comment, "type": constants.REGEX} 70 | ) 71 | continue 72 | 73 | warn(f"Skipping: {line}") 74 | 75 | return new_data 76 | 77 | 78 | def find_docker(): 79 | """try to find a running docker image and its config""" 80 | # return [True, '/etc/pihole/gravity.db'] 81 | try: 82 | result = subprocess.run( 83 | ["docker", "inspect", "pihole"], 84 | stdout=subprocess.PIPE, 85 | stderr=subprocess.DEVNULL, 86 | check=True, 87 | ) 88 | except (CalledProcessError, FileNotFoundError): 89 | warn("! docker not found running, continuing...") 90 | return [False, None] 91 | 92 | if result.returncode != 0: 93 | warn("! docker pihole image not found running, continuing...") 94 | return [False, None] 95 | 96 | try: 97 | config = json.loads(result.stdout) 98 | except JSONDecodeError: 99 | return [False, None] 100 | 101 | if ( 102 | not config[0] 103 | or not config[0]["HostConfig"] 104 | or not config[0]["HostConfig"]["Binds"] 105 | ): 106 | warn("! unable to find config for running docker pihole image, continuing...") 107 | return [False, None] 108 | 109 | for row in config[0]["HostConfig"]["Binds"]: 110 | parts = row.split(":") 111 | if parts[1].startswith("/etc/pihole"): 112 | path = f"{parts[0]}/gravity.db" 113 | if os.path.exists(path): 114 | return [True, path] 115 | 116 | warn("! unable to find config for running docker pihole image, continuing...") 117 | return [False, result.stdout] 118 | 119 | 120 | def clear(): 121 | """helperto clear screen""" 122 | # for windows 123 | if os.name == "nt": 124 | _ = os.system("cls") 125 | # for mac and linux(here, os.name is 'posix') 126 | else: 127 | _ = os.system("clear") 128 | 129 | 130 | def warn_long_running(): 131 | """display a warning so people don't hit enter and accept defaults all willy nilly""" 132 | danger( 133 | """ 134 | Do not hit ENTER or Y if a step seems to hang! 135 | Use CTRL+C if you're sure it's hung and report it. 136 | """ 137 | ) 138 | 139 | 140 | def warn(msg): 141 | """print styled WARNING messages""" 142 | print(color(msg, fg="yellow")) 143 | 144 | 145 | def success(msg): 146 | """print styled SUCCESS messages""" 147 | print(color(msg, fg="lime")) 148 | 149 | 150 | def info(msg): 151 | """print styled INFO messages""" 152 | print(color(msg, fg="#5DADE2")) 153 | 154 | 155 | def danger(msg): 156 | """print styled DANGER messages""" 157 | print(color(msg, fg="orangered")) 158 | 159 | 160 | def die(msg): 161 | """exit the program in style""" 162 | danger(msg) 163 | sys.exit(-1) 164 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "ansicolors" 5 | version = "1.1.8" 6 | description = "ANSI colors for Python" 7 | optional = false 8 | python-versions = "*" 9 | files = [ 10 | {file = "ansicolors-1.1.8-py2.py3-none-any.whl", hash = "sha256:00d2dde5a675579325902536738dd27e4fac1fd68f773fe36c21044eb559e187"}, 11 | {file = "ansicolors-1.1.8.zip", hash = "sha256:99f94f5e3348a0bcd43c82e5fc4414013ccc19d70bd939ad71e0133ce9c372e0"}, 12 | ] 13 | 14 | [[package]] 15 | name = "astroid" 16 | version = "3.0.1" 17 | description = "An abstract syntax tree for Python with inference support." 18 | optional = false 19 | python-versions = ">=3.8.0" 20 | files = [ 21 | {file = "astroid-3.0.1-py3-none-any.whl", hash = "sha256:7d5895c9825e18079c5aeac0572bc2e4c83205c95d416e0b4fee8bc361d2d9ca"}, 22 | {file = "astroid-3.0.1.tar.gz", hash = "sha256:86b0bb7d7da0be1a7c4aedb7974e391b32d4ed89e33de6ed6902b4b15c97577e"}, 23 | ] 24 | 25 | [package.dependencies] 26 | typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} 27 | 28 | [[package]] 29 | name = "black" 30 | version = "23.11.0" 31 | description = "The uncompromising code formatter." 32 | optional = false 33 | python-versions = ">=3.8" 34 | files = [ 35 | {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, 36 | {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, 37 | {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, 38 | {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, 39 | {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, 40 | {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, 41 | {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, 42 | {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, 43 | {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, 44 | {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, 45 | {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, 46 | {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, 47 | {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, 48 | {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, 49 | {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, 50 | {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, 51 | {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, 52 | {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, 53 | ] 54 | 55 | [package.dependencies] 56 | click = ">=8.0.0" 57 | mypy-extensions = ">=0.4.3" 58 | packaging = ">=22.0" 59 | pathspec = ">=0.9.0" 60 | platformdirs = ">=2" 61 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 62 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 63 | 64 | [package.extras] 65 | colorama = ["colorama (>=0.4.3)"] 66 | d = ["aiohttp (>=3.7.4)"] 67 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 68 | uvloop = ["uvloop (>=0.15.2)"] 69 | 70 | [[package]] 71 | name = "certifi" 72 | version = "2023.11.17" 73 | description = "Python package for providing Mozilla's CA Bundle." 74 | optional = false 75 | python-versions = ">=3.6" 76 | files = [ 77 | {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, 78 | {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, 79 | ] 80 | 81 | [[package]] 82 | name = "charset-normalizer" 83 | version = "3.3.2" 84 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 85 | optional = false 86 | python-versions = ">=3.7.0" 87 | files = [ 88 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 89 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 90 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 91 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 92 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 93 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 94 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 95 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 96 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 97 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 98 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 99 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 100 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 101 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 102 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 103 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 104 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 105 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 106 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 107 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 108 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 109 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 110 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 111 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 112 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 113 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 114 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 115 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 116 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 117 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 118 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 119 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 120 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 121 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 122 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 123 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 124 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 125 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 126 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 127 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 128 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 129 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 130 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 131 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 132 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 133 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 134 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 135 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 136 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 137 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 138 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 139 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 140 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 141 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 142 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 143 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 144 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 145 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 146 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 147 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 148 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 149 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 150 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 151 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 152 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 153 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 154 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 155 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 156 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 157 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 158 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 159 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 160 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 161 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 162 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 163 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 164 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 165 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 166 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 167 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 168 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 169 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 170 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 171 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 172 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 173 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 174 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 175 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 176 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 177 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 178 | ] 179 | 180 | [[package]] 181 | name = "click" 182 | version = "8.1.7" 183 | description = "Composable command line interface toolkit" 184 | optional = false 185 | python-versions = ">=3.7" 186 | files = [ 187 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 188 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 189 | ] 190 | 191 | [package.dependencies] 192 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 193 | 194 | [[package]] 195 | name = "colorama" 196 | version = "0.4.6" 197 | description = "Cross-platform colored terminal text." 198 | optional = false 199 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 200 | files = [ 201 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 202 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 203 | ] 204 | 205 | [[package]] 206 | name = "coverage" 207 | version = "7.3.2" 208 | description = "Code coverage measurement for Python" 209 | optional = false 210 | python-versions = ">=3.8" 211 | files = [ 212 | {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, 213 | {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, 214 | {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, 215 | {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, 216 | {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, 217 | {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, 218 | {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, 219 | {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, 220 | {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, 221 | {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, 222 | {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, 223 | {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, 224 | {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, 225 | {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, 226 | {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, 227 | {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, 228 | {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, 229 | {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, 230 | {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, 231 | {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, 232 | {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, 233 | {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, 234 | {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, 235 | {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, 236 | {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, 237 | {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, 238 | {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, 239 | {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, 240 | {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, 241 | {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, 242 | {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, 243 | {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, 244 | {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, 245 | {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, 246 | {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, 247 | {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, 248 | {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, 249 | {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, 250 | {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, 251 | {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, 252 | {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, 253 | {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, 254 | {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, 255 | {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, 256 | {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, 257 | {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, 258 | {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, 259 | {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, 260 | {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, 261 | {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, 262 | {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, 263 | {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, 264 | ] 265 | 266 | [package.extras] 267 | toml = ["tomli"] 268 | 269 | [[package]] 270 | name = "dill" 271 | version = "0.3.7" 272 | description = "serialize all of Python" 273 | optional = false 274 | python-versions = ">=3.7" 275 | files = [ 276 | {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, 277 | {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, 278 | ] 279 | 280 | [package.extras] 281 | graph = ["objgraph (>=1.7.2)"] 282 | 283 | [[package]] 284 | name = "exceptiongroup" 285 | version = "1.2.0" 286 | description = "Backport of PEP 654 (exception groups)" 287 | optional = false 288 | python-versions = ">=3.7" 289 | files = [ 290 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 291 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 292 | ] 293 | 294 | [package.extras] 295 | test = ["pytest (>=6)"] 296 | 297 | [[package]] 298 | name = "idna" 299 | version = "3.5" 300 | description = "Internationalized Domain Names in Applications (IDNA)" 301 | optional = false 302 | python-versions = ">=3.5" 303 | files = [ 304 | {file = "idna-3.5-py3-none-any.whl", hash = "sha256:79b8f0ac92d2351be5f6122356c9a592c96d81c9a79e4b488bf2a6a15f88057a"}, 305 | {file = "idna-3.5.tar.gz", hash = "sha256:27009fe2735bf8723353582d48575b23c533cc2c2de7b5a68908d91b5eb18d08"}, 306 | ] 307 | 308 | [[package]] 309 | name = "iniconfig" 310 | version = "2.0.0" 311 | description = "brain-dead simple config-ini parsing" 312 | optional = false 313 | python-versions = ">=3.7" 314 | files = [ 315 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 316 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 317 | ] 318 | 319 | [[package]] 320 | name = "inquirerpy" 321 | version = "0.3.4" 322 | description = "Python port of Inquirer.js (A collection of common interactive command-line user interfaces)" 323 | optional = false 324 | python-versions = ">=3.7,<4.0" 325 | files = [ 326 | {file = "InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4"}, 327 | {file = "InquirerPy-0.3.4.tar.gz", hash = "sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e"}, 328 | ] 329 | 330 | [package.dependencies] 331 | pfzy = ">=0.3.1,<0.4.0" 332 | prompt-toolkit = ">=3.0.1,<4.0.0" 333 | 334 | [package.extras] 335 | docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] 336 | 337 | [[package]] 338 | name = "isort" 339 | version = "5.12.0" 340 | description = "A Python utility / library to sort Python imports." 341 | optional = false 342 | python-versions = ">=3.8.0" 343 | files = [ 344 | {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, 345 | {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, 346 | ] 347 | 348 | [package.extras] 349 | colors = ["colorama (>=0.4.3)"] 350 | pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] 351 | plugins = ["setuptools"] 352 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 353 | 354 | [[package]] 355 | name = "mccabe" 356 | version = "0.7.0" 357 | description = "McCabe checker, plugin for flake8" 358 | optional = false 359 | python-versions = ">=3.6" 360 | files = [ 361 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 362 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 363 | ] 364 | 365 | [[package]] 366 | name = "mypy-extensions" 367 | version = "1.0.0" 368 | description = "Type system extensions for programs checked with the mypy type checker." 369 | optional = false 370 | python-versions = ">=3.5" 371 | files = [ 372 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 373 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 374 | ] 375 | 376 | [[package]] 377 | name = "packaging" 378 | version = "23.2" 379 | description = "Core utilities for Python packages" 380 | optional = false 381 | python-versions = ">=3.7" 382 | files = [ 383 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 384 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 385 | ] 386 | 387 | [[package]] 388 | name = "pathspec" 389 | version = "0.11.2" 390 | description = "Utility library for gitignore style pattern matching of file paths." 391 | optional = false 392 | python-versions = ">=3.7" 393 | files = [ 394 | {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, 395 | {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, 396 | ] 397 | 398 | [[package]] 399 | name = "pfzy" 400 | version = "0.3.4" 401 | description = "Python port of the fzy fuzzy string matching algorithm" 402 | optional = false 403 | python-versions = ">=3.7,<4.0" 404 | files = [ 405 | {file = "pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96"}, 406 | {file = "pfzy-0.3.4.tar.gz", hash = "sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1"}, 407 | ] 408 | 409 | [package.extras] 410 | docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] 411 | 412 | [[package]] 413 | name = "platformdirs" 414 | version = "4.0.0" 415 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 416 | optional = false 417 | python-versions = ">=3.7" 418 | files = [ 419 | {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, 420 | {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, 421 | ] 422 | 423 | [package.extras] 424 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] 425 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] 426 | 427 | [[package]] 428 | name = "pluggy" 429 | version = "1.3.0" 430 | description = "plugin and hook calling mechanisms for python" 431 | optional = false 432 | python-versions = ">=3.8" 433 | files = [ 434 | {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, 435 | {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, 436 | ] 437 | 438 | [package.extras] 439 | dev = ["pre-commit", "tox"] 440 | testing = ["pytest", "pytest-benchmark"] 441 | 442 | [[package]] 443 | name = "prompt-toolkit" 444 | version = "3.0.41" 445 | description = "Library for building powerful interactive command lines in Python" 446 | optional = false 447 | python-versions = ">=3.7.0" 448 | files = [ 449 | {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, 450 | {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, 451 | ] 452 | 453 | [package.dependencies] 454 | wcwidth = "*" 455 | 456 | [[package]] 457 | name = "pylint" 458 | version = "3.0.2" 459 | description = "python code static checker" 460 | optional = false 461 | python-versions = ">=3.8.0" 462 | files = [ 463 | {file = "pylint-3.0.2-py3-none-any.whl", hash = "sha256:60ed5f3a9ff8b61839ff0348b3624ceeb9e6c2a92c514d81c9cc273da3b6bcda"}, 464 | {file = "pylint-3.0.2.tar.gz", hash = "sha256:0d4c286ef6d2f66c8bfb527a7f8a629009e42c99707dec821a03e1b51a4c1496"}, 465 | ] 466 | 467 | [package.dependencies] 468 | astroid = ">=3.0.1,<=3.1.0-dev0" 469 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} 470 | dill = [ 471 | {version = ">=0.2", markers = "python_version < \"3.11\""}, 472 | {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, 473 | {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, 474 | ] 475 | isort = ">=4.2.5,<6" 476 | mccabe = ">=0.6,<0.8" 477 | platformdirs = ">=2.2.0" 478 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 479 | tomlkit = ">=0.10.1" 480 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 481 | 482 | [package.extras] 483 | spelling = ["pyenchant (>=3.2,<4.0)"] 484 | testutils = ["gitpython (>3)"] 485 | 486 | [[package]] 487 | name = "pytest" 488 | version = "7.4.3" 489 | description = "pytest: simple powerful testing with Python" 490 | optional = false 491 | python-versions = ">=3.7" 492 | files = [ 493 | {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, 494 | {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, 495 | ] 496 | 497 | [package.dependencies] 498 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 499 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 500 | iniconfig = "*" 501 | packaging = "*" 502 | pluggy = ">=0.12,<2.0" 503 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 504 | 505 | [package.extras] 506 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 507 | 508 | [[package]] 509 | name = "pytest-subprocess" 510 | version = "1.5.0" 511 | description = "A plugin to fake subprocess for pytest" 512 | optional = false 513 | python-versions = ">=3.6" 514 | files = [ 515 | {file = "pytest-subprocess-1.5.0.tar.gz", hash = "sha256:d7693b96f588f39b84c7b2b5c04287459246dfae6be1dd4098937a728ad4fbe3"}, 516 | {file = "pytest_subprocess-1.5.0-py3-none-any.whl", hash = "sha256:dfd75b10af6800a89a9b758f2e2eceff9de082a27bd1388521271b6e8bde298b"}, 517 | ] 518 | 519 | [package.dependencies] 520 | pytest = ">=4.0.0" 521 | 522 | [package.extras] 523 | dev = ["changelogd", "nox"] 524 | docs = ["changelogd", "furo", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-napoleon"] 525 | test = ["Pygments (>=2.0)", "anyio", "coverage", "docutils (>=0.12)", "pytest (>=4.0)", "pytest-asyncio (>=0.15.1)", "pytest-rerunfailures"] 526 | 527 | [[package]] 528 | name = "requests" 529 | version = "2.31.0" 530 | description = "Python HTTP for Humans." 531 | optional = false 532 | python-versions = ">=3.7" 533 | files = [ 534 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 535 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 536 | ] 537 | 538 | [package.dependencies] 539 | certifi = ">=2017.4.17" 540 | charset-normalizer = ">=2,<4" 541 | idna = ">=2.5,<4" 542 | urllib3 = ">=1.21.1,<3" 543 | 544 | [package.extras] 545 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 546 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 547 | 548 | [[package]] 549 | name = "terminaltables" 550 | version = "3.1.10" 551 | description = "Generate simple tables in terminals from a nested list of strings." 552 | optional = false 553 | python-versions = ">=2.6" 554 | files = [ 555 | {file = "terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874"}, 556 | {file = "terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543"}, 557 | ] 558 | 559 | [[package]] 560 | name = "tomli" 561 | version = "2.0.1" 562 | description = "A lil' TOML parser" 563 | optional = false 564 | python-versions = ">=3.7" 565 | files = [ 566 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 567 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 568 | ] 569 | 570 | [[package]] 571 | name = "tomlkit" 572 | version = "0.12.3" 573 | description = "Style preserving TOML library" 574 | optional = false 575 | python-versions = ">=3.7" 576 | files = [ 577 | {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, 578 | {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, 579 | ] 580 | 581 | [[package]] 582 | name = "typing-extensions" 583 | version = "4.8.0" 584 | description = "Backported and Experimental Type Hints for Python 3.8+" 585 | optional = false 586 | python-versions = ">=3.8" 587 | files = [ 588 | {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, 589 | {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, 590 | ] 591 | 592 | [[package]] 593 | name = "urllib3" 594 | version = "2.1.0" 595 | description = "HTTP library with thread-safe connection pooling, file post, and more." 596 | optional = false 597 | python-versions = ">=3.8" 598 | files = [ 599 | {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, 600 | {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, 601 | ] 602 | 603 | [package.extras] 604 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 605 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 606 | zstd = ["zstandard (>=0.18.0)"] 607 | 608 | [[package]] 609 | name = "wcwidth" 610 | version = "0.2.12" 611 | description = "Measures the displayed width of unicode strings in a terminal" 612 | optional = false 613 | python-versions = "*" 614 | files = [ 615 | {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, 616 | {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, 617 | ] 618 | 619 | [metadata] 620 | lock-version = "2.0" 621 | python-versions = "^3.8" 622 | content-hash = "8794cf3781041d7fd160069e00c4be116464427edbf326eee81283b37f81fe7b" 623 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pihole5-list-tool" 3 | version = "0.6.4" 4 | description = "A tool for quickly and easily bulk adding allowlists and ad/blocklists to a Pi-hole 5 installation" 5 | authors = ["jesse "] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/jessedp/pihole5-list-tool" 9 | 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | "Topic :: Utilities", 15 | "Topic :: Internet :: Name Service (DNS)", 16 | ] 17 | keywords = [ 18 | "pihole", 19 | "pi-hole", 20 | "blacklist", 21 | "blocklist", 22 | "whitelist", 23 | "allowlist", 24 | "adlist", 25 | ] 26 | packages = [{ include = "ph5lt" }] 27 | 28 | 29 | [tool.poetry.scripts] 30 | pihole5-list-tool = 'ph5lt.app:main' 31 | 32 | [tool.poetry.urls] 33 | "Bug Tracker" = "https://github.com/jessedp/pihole5-list-tool/issues" 34 | "Source Code" = "https://github.com/jessedp/pihole5-list-tool" 35 | 36 | [tool.poetry.dependencies] 37 | python = "^3.8" 38 | requests = "^2.31.0" 39 | inquirerpy = "^0.3.4" 40 | ansicolors = "^1.1.8" 41 | terminaltables = "^3.1.10" 42 | 43 | [tool.poetry.group.dev.dependencies] 44 | black = "^23.11.0" 45 | pylint = "^3.0.2" 46 | coverage = "^7.3.2" 47 | pytest = "^7.4.3" 48 | pytest-subprocess = "^1.5.0" 49 | 50 | 51 | [[tool.poetry.source]] 52 | name = "testpypi" 53 | url = "https://test.pypi.org/legacy/" 54 | priority = "primary" 55 | 56 | 57 | [[tool.poetry.source]] 58 | name = "PyPI" 59 | priority = "primary" 60 | 61 | [tool.black] 62 | target-version = ["py37"] 63 | 64 | [tool.semantic_release] 65 | version_variable = ["ph5lt/banner.py:__version__", "pyproject.toml:version"] 66 | 67 | build_command = "pip install poetry && poetry build" 68 | 69 | [build-system] 70 | requires = ["poetry-core"] 71 | build-backend = "poetry.core.masonry.api" 72 | -------------------------------------------------------------------------------- /tests/test_prompts.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | from unittest.mock import patch 4 | 5 | from ph5lt import prompts 6 | 7 | 8 | @patch("os.path.exists") 9 | def test_check_db_FNF(self): 10 | os.path.exists.value = False 11 | result = prompts.check_db("blah") 12 | assert result is False 13 | 14 | 15 | @patch("sqlite3.connect") 16 | def test_check_db_SqlDbError(self): 17 | sqlite3.connect.side_effect = sqlite3.DatabaseError("nope") 18 | result = prompts.check_db("blah") 19 | assert result is False 20 | 21 | 22 | @patch("sqlite3.connect") 23 | def test_check_db_OperationalError(self): 24 | sqlite3.connect.side_effect = sqlite3.OperationalError("nope") 25 | result = prompts.check_db("blah") 26 | assert result is False 27 | 28 | 29 | def BROKEN_test_check_db_WrongDb(): 30 | """ can't get this mocked: 31 | TypeError: catching classes that do not inherit from BaseException is not allowed 32 | """ 33 | # sqlite3.connect.value = True 34 | with patch("prompts.sqlite3.connect().cursor()") as mocksql: 35 | mocksql.side_effect = sqlite3.OperationalError 36 | result = prompts.check_db("blah") 37 | assert result is False 38 | 39 | 40 | def test_check_db_SaulGoodMan(): 41 | """ Works, but really shouldn't ?? """ 42 | result = prompts.check_db("blah") 43 | assert result is False 44 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | from subprocess import CalledProcessError 4 | from unittest.mock import patch 5 | 6 | from ph5lt import utils 7 | 8 | class TestUtils: 9 | def test_valid_url(self): 10 | assert utils.valid_url("") is False 11 | assert utils.valid_url("pihole") is False 12 | assert utils.valid_url("pihole.net") is False 13 | assert utils.valid_url("http://pihole.net") is True 14 | assert utils.valid_url("http://pihole.net/v5") is True 15 | assert utils.valid_url("http://pihole.net/v5?install=trye") is True 16 | 17 | def test_validate_host(self): 18 | test1 = "nope" 19 | test2 = "nope.c" 20 | test3 = "nope.com" 21 | assert utils.validate_host(test1) is False 22 | assert utils.validate_host(test2) is False 23 | assert utils.validate_host(test3) is True 24 | 25 | # TODO: enforce pi-url regex 26 | def test_validate_regex(self): 27 | assert utils.validate_regex("github") is True 28 | 29 | def test_process_lines_empty(self): 30 | new_list = utils.process_lines("", "", True) 31 | assert len(new_list) == 0 32 | 33 | def test_process_lines_full_url(self): 34 | comment = "MyComment" 35 | new_list = utils.process_lines( 36 | """ 37 | http://google.com 38 | invalid 39 | http://github.com 40 | """, 41 | comment, 42 | True, 43 | ) 44 | assert len(new_list) == 2 45 | 46 | assert new_list[1]["url"] == "http://github.com" 47 | assert new_list[1]["comment"] == comment 48 | 49 | # TODO: Breakout host/url/regexes 50 | def test_process_lines_any(self): 51 | comment = "MyComment" 52 | new_list = utils.process_lines( 53 | """ 54 | github 55 | github.com 56 | http://github.com 57 | http://github.com/test 58 | http://github.com/test?f08s 59 | """, 60 | comment, 61 | True, 62 | ) 63 | assert len(new_list) == 3 64 | 65 | # assert new_list[1]["url"] == "http://github.com" 66 | assert new_list[1]["comment"] == comment 67 | 68 | 69 | def test_process_file(tmp_path): 70 | comment = "MyComment" 71 | tmpdir = tmp_path / "ph5lt" 72 | tmpdir.mkdir() 73 | 74 | tmpfile = tmpdir / "imports.txt" 75 | urls = """ 76 | http://github.com 77 | http://github.com/test 78 | http://github.com/test?f08s 79 | """ 80 | tmpfile.write_text(urls) 81 | impfile = open(tmpfile) 82 | new_list = utils.process_lines(impfile.read(), comment, True) 83 | assert len(new_list) == 3 84 | 85 | # assert new_list[1]["url"] == "http://github.com" 86 | assert new_list[1]["comment"] == comment 87 | 88 | 89 | def test_find_docker_not_installed(fake_process): 90 | def raise_FNF(process): 91 | raise FileNotFoundError 92 | 93 | fake_process.register_subprocess( 94 | ["docker", "inspect", "pihole"], stdout="not running FNF", callback=raise_FNF 95 | ) 96 | result = utils.find_docker() 97 | assert result == [False, None] 98 | 99 | def raise_CPE(process): 100 | raise CalledProcessError(returncode=1, cmd="test") 101 | 102 | fake_process.register_subprocess( 103 | ["docker", "inspect", "pihole"], stdout="not running CPE", callback=raise_CPE 104 | ) 105 | result = utils.find_docker() 106 | assert result == [False, None] 107 | 108 | 109 | def test_find_docker_image_not_running(fake_process): 110 | fake_process.register_subprocess( 111 | ["docker", "inspect", "pihole"], stdout="not running", returncode=1 112 | ) 113 | result = utils.find_docker() 114 | assert result == [False, None] 115 | 116 | 117 | def BROKEN_test_find_docker_image_not_found(fake_process): 118 | """ Not actually launching mock process """ 119 | fake_process.register_subprocess(["docker", "inspect", "pihole"], stdout="bad json") 120 | result = utils.find_docker() 121 | assert result == [False, None] 122 | 123 | 124 | @patch("os.path.exists") 125 | def BROKEN_test_find_docker_image_found(fake_process, shared_datadir): 126 | """ Not actually launching mock process """ 127 | path = "/home/jesse/projects/pihole/etc-pihole" 128 | 129 | output = (shared_datadir / "docker_inspect_pihole.json").read_text().strip() 130 | 131 | fake_process.register_subprocess( 132 | ["docker", "inspect", "pihole"], stdout=output, 133 | ) 134 | 135 | result = utils.find_docker() 136 | # os.path.exists.assert_called_once_with(path) 137 | 138 | assert result == [True, path] 139 | --------------------------------------------------------------------------------