From 963875bdbbdfa843b8f10ff742cb6a8556139ada Mon Sep 17 00:00:00 2001 From: David Shen Date: Fri, 22 Mar 2024 11:08:19 -0400 Subject: [PATCH] Custom bangs --- .gitignore | 4 +++- README.md | 10 +++++++++ app/__init__.py | 1 + app/routes.py | 35 +++++++++++++++++++++++++------- app/static/bangs/00-whoogle.json | 14 +++++++++++++ test/test_routes.py | 7 +++++++ 6 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 app/static/bangs/00-whoogle.json diff --git a/.gitignore b/.gitignore index 181094f..6b3be3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv/ +.venv/ .idea/ __pycache__/ *.pyc @@ -10,7 +11,8 @@ test/static flask_session/ app/static/config app/static/custom_config -app/static/bangs +app/static/bangs/* +!app/static/bangs/00-whoogle.json # pip stuff /build/ diff --git a/README.md b/README.md index a919d65..c56867c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Contents 6. [Extra Steps](#extra-steps) 1. [Set Primary Search Engine](#set-whoogle-as-your-primary-search-engine) 2. [Custom Redirecting](#custom-redirecting) + 2. [Custom Bangs](#custom-bangs) 3. [Prevent Downtime (Heroku Only)](#prevent-downtime-heroku-only) 4. [Manual HTTPS Enforcement](#https-enforcement) 5. [Using with Firefox Containers](#using-with-firefox-containers) @@ -61,6 +62,7 @@ Contents - Randomly generated User Agent - Easy to install/deploy - DDG-style bang (i.e. `! `) searches +- User-defined [custom bangs](#custom-bangs) - Optional location-based searching (i.e. results near \) - Optional NoJS mode to view search results in a separate window with JavaScript blocked @@ -539,6 +541,14 @@ WHOOGLE_REDIRECTS="badA.com:goodA.com,badB.com:goodB.com" NOTE: Do not include "http(s)://" when defining your redirect. +### Custom Bangs +You can create your own custom bangs. By default, bangs are stored in +`app/static/bangs`. See [`00-whoogle.json`](https://github.com/benbusby/whoogle-search/blob/main/app/static/bangs/00-whoogle.json) +for an example. These are parsed in alphabetical order with later files +overriding bangs set in earlier files, with the exception that DDG bangs +(downloaded to `app/static/bangs/bangs.json`) are always parsed first. Thus, +any custom bangs will always override the DDG ones. + ### Prevent Downtime (Heroku only) Part of the deal with Heroku's free tier is that you're allocated 550 hours/month (meaning it can't stay active 24/7), and the app is temporarily shut down after 30 minutes of inactivity. Once it becomes inactive, any Whoogle searches will still work, but it'll take an extra 10-15 seconds for the app to come back online before displaying the result, which can be frustrating if you're in a hurry. diff --git a/app/__init__.py b/app/__init__.py index d3cf3ce..b108ab4 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -180,6 +180,7 @@ send_tor_signal(Signal.HEARTBEAT) warnings.simplefilter('ignore', MarkupResemblesLocatorWarning) from app import routes # noqa +routes.load_bangs() # Disable logging from imported modules logging.config.dictConfig({ diff --git a/app/routes.py b/app/routes.py index c1f6f36..a3fa868 100644 --- a/app/routes.py +++ b/app/routes.py @@ -8,6 +8,7 @@ import re import urllib.parse as urlparse import uuid import validators +import glob from datetime import datetime, timedelta from functools import wraps @@ -37,12 +38,37 @@ from cryptography.exceptions import InvalidSignature from werkzeug.datastructures import MultiDict # Load DDG bang json files only on init -bang_json = json.load(open(app.config['BANG_FILE'])) or {} +bang_json = {} ac_var = 'WHOOGLE_AUTOCOMPLETE' autocomplete_enabled = os.getenv(ac_var, '1') +def load_bangs(): + global bang_json + bangs = {} + bang_files = glob.glob(os.path.join(app.config['BANG_PATH'], '*.json')) + + # Normalize the paths + bang_files = [os.path.normpath(f) for f in bang_files] + + # Move the ddg bangs file to the beginning + ddg_bangs_file = os.path.normpath(app.config['BANG_FILE']) + bang_files = sorted([f for f in bang_files if f != ddg_bangs_file]) + bang_files.insert(0, ddg_bangs_file) + + for i, bang_file in enumerate(bang_files): + try: + bangs |= json.load(open(bang_file)) + except json.decoder.JSONDecodeError: + # Ignore decoding error only for the ddg bangs file, since this can + # occur if file is still being written + if i != 0: + raise + + bang_json = dict(sorted(bangs.items())) + + def get_search_name(tbm): for tab in app.config['HEADER_TABS'].values(): if tab['tbm'] == tbm: @@ -174,12 +200,7 @@ def before_request_func(): # Attempt to reload bangs json if not generated yet if not bang_json and os.path.getsize(app.config['BANG_FILE']) > 4: - try: - bang_json = json.load(open(app.config['BANG_FILE'])) - except json.decoder.JSONDecodeError: - # Ignore decoding error, can occur if file is still - # being written - pass + load_bangs() @app.after_request diff --git a/app/static/bangs/00-whoogle.json b/app/static/bangs/00-whoogle.json new file mode 100644 index 0000000..0998a6f --- /dev/null +++ b/app/static/bangs/00-whoogle.json @@ -0,0 +1,14 @@ +{ + "!i": { + "url": "search?q={}&tbm=isch", + "suggestion": "!i (Whoogle Images)" + }, + "!v": { + "url": "search?q={}&tbm=vid", + "suggestion": "!v (Whoogle Videos)" + }, + "!n": { + "url": "search?q={}&tbm=nws", + "suggestion": "!n (Whoogle News)" + } +} diff --git a/test/test_routes.py b/test/test_routes.py index 8cb79fa..6409f2d 100644 --- a/test/test_routes.py +++ b/test/test_routes.py @@ -48,6 +48,13 @@ def test_ddg_bang(client): assert rv.headers.get('Location').startswith('https://github.com') +def test_custom_bang(client): + # Bang at beginning of query + rv = client.get(f'/{Endpoint.search}?q=!i%20whoogle') + assert rv._status_code == 302 + assert rv.headers.get('Location').startswith('search?q=') + + def test_config(client): rv = client.post(f'/{Endpoint.config}', data=demo_config) assert rv._status_code == 302