diff --git a/README.md b/README.md index a26f6c5..6f914ad 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,11 @@ Contents 1. [Set Primary Search Engine](#set-whoogle-as-your-primary-search-engine) 2. [Prevent Downtime (Heroku Only)](#prevent-downtime-heroku-only) 3. [Manual HTTPS Enforcement](#https-enforcement) -7. [FAQ](#faq) -8. [Public Instances](#public-instances) -9. [Screenshots](#screenshots) -10. Mirrors (read-only) +7. [Contributing](#contributing) +8. [FAQ](#faq) +9. [Public Instances](#public-instances) +10. [Screenshots](#screenshots) +11. Mirrors (read-only) 1. [GitLab](https://gitlab.com/benbusby/whoogle-search) 2. [Gogs](https://gogs.benbusby.com/benbusby/whoogle-search) @@ -103,17 +104,25 @@ Sandboxed temporary instance: ```bash $ whoogle-search --help -usage: whoogle-search [-h] [--port ] [--host ] [--debug] - [--https-only] +usage: whoogle-search [-h] [--port ] [--host ] [--debug] [--https-only] [--userpass ] + [--proxyauth ] [--proxytype ] [--proxyloc ] Whoogle Search console runner optional arguments: - -h, --help show this help message and exit + -h, --help Show this help message and exit --port Specifies a port to run on (default 5000) --host Specifies the host address to use (default 127.0.0.1) --debug Activates debug mode for the server (default False) - --https-only Enforces HTTPS redirects for all requests (default False) + --https-only Enforces HTTPS redirects for all requests + --userpass + Sets a username/password basic auth combo (default None) + --proxyauth + Sets a username/password for a HTTP/SOCKS proxy (default None) + --proxytype + Sets a proxy type for all connections (default None) + --proxyloc + Sets a proxy location for all connections (default None) ``` See the [available environment variables](#environment-variables) for additional configuration. @@ -277,6 +286,7 @@ These environment variables allow setting default config values, but can be over | Variable | Description | | ------------------------------ | --------------------------------------------------------------- | +| WHOOGLE_CONFIG_DISABLE | Hide config from UI and disallow changes to config by client | | WHOOGLE_CONFIG_COUNTRY | Filter results by hosting country | | WHOOGLE_CONFIG_LANGUAGE | Set interface language | | WHOOGLE_CONFIG_SEARCH_LANGUAGE | Set search result language | @@ -286,6 +296,7 @@ These environment variables allow setting default config values, but can be over | WHOOGLE_CONFIG_ALTS | Use social media site alternatives (nitter, invidious, etc) | | WHOOGLE_CONFIG_TOR | Use Tor routing (if available) | | WHOOGLE_CONFIG_NEW_TAB | Always open results in new tab | +| WHOOGLE_CONFIG_VIEW_IMAGE | Enable View Image option | | WHOOGLE_CONFIG_GET_ONLY | Search using GET requests only | | WHOOGLE_CONFIG_URL | The root url of the instance (`https:///`) | | WHOOGLE_CONFIG_STYLE | The custom CSS to use for styling (should be single line) | @@ -355,6 +366,56 @@ Note: You should have your own domain name and [an https certificate](https://le - Pip/Pipx: Add the `--https-only` flag to the end of the `whoogle-search` command - Default `run` script: Modify the script locally to include the `--https-only` flag at the end of the python run command +## Contributing + +Under the hood, Whoogle is a basic Flask app with the following structure: + +- `app/` + - `routes.py`: Primary app entrypoint, contains all API routes + - `request.py`: Handles all outbound requests, including proxied/Tor connectivity + - `filter.py`: Functions and utilities used for filtering out content from upstream Google search results + - `utils/` + - `bangs.py`: All logic related to handling DDG-style "bang" queries + - `results.py`: Utility functions for interpreting/modifying individual search results + - `search.py`: Creates and handles new search queries + - `session.py`: Miscellaneous methods related to user sessions + - `templates/` + - `index.html`: The home page template + - `display.html`: The search results template + - `header.html`: A general "top of the page" query header for desktop and mobile + - `search.html`: An iframe-able search page + - `logo.html`: A template consisting mostly of the Whoogle logo as an SVG (separated to help keep `index.html` a bit cleaner) + - `opensearch.xml`: A template used for supporting [OpenSearch](https://developer.mozilla.org/en-US/docs/Web/OpenSearch). + - `imageresults.html`: An "exprimental" template used for supporting the "Full Size" image feature on desktop. + - `static/` + - CSS/Javascript files, should be self-explanatory + - `static/settings` + - Key-value JSON files for establishing valid configuration values + + +If you're new to the project, the easiest way to get started would be to try fixing [an open bug report](https://github.com/benbusby/whoogle-search/issues?q=is%3Aissue+is%3Aopen+label%3Abug). If there aren't any open, or if the open ones are too stale, try taking on a [feature request](https://github.com/benbusby/whoogle-search/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement). Generally speaking, if you can write something that has any potential of breaking down in the future, you should write a test for it. + +The project follows the [PEP 8 Style Guide](https://www.python.org/dev/peps/pep-0008/), but is liable to change. Static typing should always be used when possible. Function documentation is greatly appreciated, and typically follows the below format: + +```python +def contains(x: list, y: int) -> bool: + """Check a list (x) for the presence of an element (y) + + Args: + x: The list to inspect + y: The int to look for + + Returns: + bool: True if the list contains the item, otherwise False + """ + + return y in x +``` + +#### Translating + +Whoogle currently supports translations using [`translations.json`](https://github.com/benbusby/whoogle-search/blob/main/app/static/settings/languages.json). Language values in this file need to match the "value" of the according language in [`languages.json`](https://github.com/benbusby/whoogle-search/blob/main/app/static/settings/languages.json) (i.e. "lang_en" for English, "lang_es" for Spanish, etc). After you add a new set of translations to `translations.json`, open a PR with your changes and they will be merged in as soon as possible. + ## FAQ **What's the difference between this and [Searx](https://github.com/asciimoo/searx)?** @@ -379,6 +440,7 @@ A lot of the app currently piggybacks on Google's existing support for fetching - [https://whooglesearch.net/](https://whooglesearch.net/) - [https://search.flawcra.cc/](https://search.flawcra.cc/) - [https://search.exonip.de/](https://search.exonip.de/) +- [https://whoogle.silkky.cloud/](https://whoogle.silkky.cloud/) ## Screenshots #### Desktop ![Whoogle Desktop](docs/screenshot_desktop.jpg) diff --git a/app.json b/app.json index 691f8fc..47f4ca0 100644 --- a/app.json +++ b/app.json @@ -115,6 +115,11 @@ "value": "", "required": false }, + "WHOOGLE_CONFIG_VIEW_IMAGE": { + "description": "[CONFIG] Enable View Image option (set to 1 or leave blank)", + "value": "", + "required": false + }, "WHOOGLE_CONFIG_GET_ONLY": { "description": "[CONFIG] Search using GET requests only (set to 1 or leave blank)", "value": "", diff --git a/app/__init__.py b/app/__init__.py index c3d781b..06d9306 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -33,6 +33,8 @@ app.config['LANGUAGES'] = json.load(open( os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json'))) app.config['COUNTRIES'] = json.load(open( os.path.join(app.config['STATIC_FOLDER'], 'settings/countries.json'))) +app.config['TRANSLATIONS'] = json.load(open( + os.path.join(app.config['STATIC_FOLDER'], 'settings/translations.json'))) app.config['CONFIG_PATH'] = os.getenv( 'CONFIG_VOLUME', os.path.join(app.config['STATIC_FOLDER'], 'config')) diff --git a/app/filter.py b/app/filter.py index 7848a79..4fede8a 100644 --- a/app/filter.py +++ b/app/filter.py @@ -254,3 +254,65 @@ class Filter: # Replace link destination link_desc[0].replace_with(get_site_alt(link_desc[0])) + + def view_image(self, soup) -> BeautifulSoup: + """Replaces the soup with a new one that handles mobile results and + adds the link of the image full res to the results. + + Args: + soup: A BeautifulSoup object containing the image mobile results. + + Returns: + BeautifulSoup: The new BeautifulSoup object + """ + + # get some tags that are unchanged between mobile and pc versions + search_input = soup.find_all('td', attrs={'class': "O4cRJf"})[0] + search_options = soup.find_all('div', attrs={'class': "M7pB2"})[0] + cor_suggested = soup.find_all('table', attrs={'class': "By0U9"}) + next_pages = soup.find_all('table', attrs={'class': "uZgmoc"})[0] + information = soup.find_all('div', attrs={'class': "TuS8Ad"})[0] + + results = [] + # find results div + results_div = soup.find_all('div', attrs={'class': "nQvrDb"})[0] + # find all the results + results_all = results_div.find_all('div', attrs={'class': "lIMUZd"}) + + for item in results_all: + urls = item.find('a')['href'].split('&imgrefurl=') + + img_url = urlparse.unquote(urls[0].replace('/imgres?imgurl=', '')) + webpage = urlparse.unquote(urls[1].split('&')[0]) + img_tbn = urlparse.unquote(item.find('a').find('img')['src']) + results.append({ + 'domain': urlparse.urlparse(webpage).netloc, + 'img_url': img_url, + 'webpage': webpage, + 'img_tbn': img_tbn + }) + + soup = BeautifulSoup(render_template('imageresults.html', + length=len(results), + results=results, + view_label="View Image"), + features='html.parser') + # replace search input object + soup.find_all('td', + attrs={'class': "O4cRJf"})[0].replaceWith(search_input) + # replace search options object (All, Images, Videos, etc.) + soup.find_all('div', + attrs={'class': "M7pB2"})[0].replaceWith(search_options) + # replace correction suggested by google object if exists + if len(cor_suggested): + soup.find_all( + 'table', + attrs={'class': "By0U9"} + )[0].replaceWith(cor_suggested[0]) + # replace next page object at the bottom of the page + soup.find_all('table', + attrs={'class': "uZgmoc"})[0].replaceWith(next_pages) + # replace information about user connection at the bottom of the page + soup.find_all('div', + attrs={'class': "TuS8Ad"})[0].replaceWith(information) + return soup diff --git a/app/models/config.py b/app/models/config.py index 3898ae7..8413a65 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -27,7 +27,9 @@ class Config: self.tor = read_config_bool('WHOOGLE_CONFIG_TOR') self.near = os.getenv('WHOOGLE_CONFIG_NEAR', '') self.new_tab = read_config_bool('WHOOGLE_CONFIG_NEW_TAB') + self.view_image = read_config_bool('WHOOGLE_CONFIG_VIEW_IMAGE') self.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY') + self.safe_keys = [ 'lang_search', 'lang_interface', @@ -75,6 +77,19 @@ class Config: return key in self.safe_keys + def get_localization_lang(self): + """Returns the correct language to use for localization, but falls + back to english if not set. + + Returns: + str -- the localization language string + """ + if (self.lang_interface and + self.lang_interface in current_app.config['TRANSLATIONS']): + return self.lang_interface + + return 'lang_en' + def from_params(self, params) -> 'Config': """Modify user config with search parameters. This is primarily used for specifying configuration on a search-by-search basis on diff --git a/app/request.py b/app/request.py index 9239b6e..1c0c2c6 100644 --- a/app/request.py +++ b/app/request.py @@ -151,6 +151,8 @@ class Request: self.language = config.lang_search self.mobile = 'Android' in normal_ua or 'iPhone' in normal_ua self.modified_user_agent = gen_user_agent(self.mobile) + if not self.mobile: + self.modified_user_agent_mobile = gen_user_agent(True) # Set up proxy, if previously configured if os.environ.get('WHOOGLE_PROXY_LOC'): @@ -197,7 +199,8 @@ class Request: return [_.attrib['data'] for _ in root.findall('.//suggestion/[@data]')] - def send(self, base_url=SEARCH_URL, query='', attempt=0) -> Response: + def send(self, base_url=SEARCH_URL, query='', attempt=0, + force_mobile=False) -> Response: """Sends an outbound request to a URL. Optionally sends the request using Tor, if enabled by the user. @@ -211,8 +214,13 @@ class Request: Response: The Response object returned by the requests call """ + if force_mobile and not self.mobile: + modified_user_agent = self.modified_user_agent_mobile + else: + modified_user_agent = self.modified_user_agent + headers = { - 'User-Agent': self.modified_user_agent + 'User-Agent': modified_user_agent } # FIXME: Should investigate this further to ensure the consent diff --git a/app/routes.py b/app/routes.py index 2e8152a..4409901 100644 --- a/app/routes.py +++ b/app/routes.py @@ -130,6 +130,9 @@ def index(): return render_template('index.html', languages=app.config['LANGUAGES'], countries=app.config['COUNTRIES'], + translation=app.config['TRANSLATIONS'][ + g.user_config.get_localization_lang() + ], logo=render_template( 'logo.html', dark=g.user_config.dark), @@ -235,6 +238,9 @@ def search(): query=urlparse.unquote(query), search_type=search_util.search_type, config=g.user_config, + translation=app.config['TRANSLATIONS'][ + g.user_config.get_localization_lang() + ], response=response, version_number=app.config['VERSION_NUMBER'], search_header=(render_template( diff --git a/app/static/settings/countries.json b/app/static/settings/countries.json index 57c4619..da5ce2f 100644 --- a/app/static/settings/countries.json +++ b/app/static/settings/countries.json @@ -1,5 +1,5 @@ [ - {"name": "Default (none)", "value": ""}, + {"name": "-------", "value": ""}, {"name": "Afghanistan", "value": "countryAF"}, {"name": "Albania", "value": "countryAL"}, {"name": "Algeria", "value": "countryDZ"}, diff --git a/app/static/settings/languages.json b/app/static/settings/languages.json index 4666b7c..7056afa 100644 --- a/app/static/settings/languages.json +++ b/app/static/settings/languages.json @@ -1,49 +1,49 @@ [ - {"name": "Default (none specified)", "value": ""}, + {"name": "-------", "value": ""}, {"name": "English", "value": "lang_en"}, - {"name": "Afrikaans", "value": "lang_af"}, - {"name": "Arabic", "value": "lang_ar"}, - {"name": "Armenian", "value": "lang_hy"}, - {"name": "Belarusian", "value": "lang_be"}, - {"name": "Bulgarian", "value": "lang_bg"}, - {"name": "Catalan", "value": "lang_ca"}, - {"name": "Chinese (Simplified)", "value": "lang_zh-CN"}, - {"name": "Chinese (Traditional)", "value": "lang_zh-TW"}, - {"name": "Croatian", "value": "lang_hr"}, - {"name": "Czech", "value": "lang_cs"}, - {"name": "Danish", "value": "lang_da"}, - {"name": "Dutch", "value": "lang_nl"}, - {"name": "Esperanto", "value": "lang_eo"}, - {"name": "Estonian", "value": "lang_et"}, - {"name": "Filipino", "value": "lang_tl"}, - {"name": "Finnish", "value": "lang_fi"}, - {"name": "French", "value": "lang_fr"}, - {"name": "German", "value": "lang_de"}, - {"name": "Greek", "value": "lang_el"}, - {"name": "Hebrew", "value": "lang_iw"}, - {"name": "Hindi", "value": "lang_hi"}, - {"name": "Hungarian", "value": "lang_hu"}, - {"name": "Icelandic", "value": "lang_is"}, - {"name": "Indonesian", "value": "lang_id"}, - {"name": "Italian", "value": "lang_it"}, - {"name": "Japanese", "value": "lang_ja"}, - {"name": "Korean", "value": "lang_ko"}, - {"name": "Latvian", "value": "lang_lv"}, - {"name": "Lithuanian", "value": "lang_lt"}, - {"name": "Norwegian", "value": "lang_no"}, - {"name": "Persian", "value": "lang_fa"}, - {"name": "Polish", "value": "lang_pl"}, - {"name": "Portuguese", "value": "lang_pt"}, - {"name": "Romanian", "value": "lang_ro"}, - {"name": "Russian", "value": "lang_ru"}, - {"name": "Serbian", "value": "lang_sr"}, - {"name": "Slovak", "value": "lang_sk"}, - {"name": "Slovenian", "value": "lang_sl"}, - {"name": "Spanish", "value": "lang_es"}, - {"name": "Swahili", "value": "lang_sw"}, - {"name": "Swedish", "value": "lang_sv"}, - {"name": "Thai", "value": "lang_th"}, - {"name": "Turkish", "value": "lang_tr"}, - {"name": "Ukrainian", "value": "lang_uk"}, - {"name": "Vietnamese", "value": "lang_vi"} + {"name": "Afrikaans (Afrikaans)", "value": "lang_af"}, + {"name": "Arabic (عربى)", "value": "lang_ar"}, + {"name": "Armenian (հայերեն)", "value": "lang_hy"}, + {"name": "Belarusian (Беларуская)", "value": "lang_be"}, + {"name": "Bulgarian (български)", "value": "lang_bg"}, + {"name": "Catalan (Català)", "value": "lang_ca"}, + {"name": "Chinese, Simplified (简体中文)", "value": "lang_zh-CN"}, + {"name": "Chinese, Traditional (繁体中文)", "value": "lang_zh-TW"}, + {"name": "Croatian (Hrvatski)", "value": "lang_hr"}, + {"name": "Czech (čeština)", "value": "lang_cs"}, + {"name": "Danish (Dansk)", "value": "lang_da"}, + {"name": "Dutch (Nederlands)", "value": "lang_nl"}, + {"name": "Esperanto (Esperanto)", "value": "lang_eo"}, + {"name": "Estonian (Eestlane)", "value": "lang_et"}, + {"name": "Filipino (Pilipino)", "value": "lang_tl"}, + {"name": "Finnish (Suomalainen)", "value": "lang_fi"}, + {"name": "French (Français)", "value": "lang_fr"}, + {"name": "German (Deutsche)", "value": "lang_de"}, + {"name": "Greek (Ελληνικά)", "value": "lang_el"}, + {"name": "Hebrew (עִברִית)", "value": "lang_iw"}, + {"name": "Hindi (हिंदी)", "value": "lang_hi"}, + {"name": "Hungarian (Magyar)", "value": "lang_hu"}, + {"name": "Icelandic (Íslenska)", "value": "lang_is"}, + {"name": "Indonesian (Indonesian)", "value": "lang_id"}, + {"name": "Italian (Italiano)", "value": "lang_it"}, + {"name": "Japanese (日本語)", "value": "lang_ja"}, + {"name": "Korean (한국어)", "value": "lang_ko"}, + {"name": "Latvian (Latvietis)", "value": "lang_lv"}, + {"name": "Lithuanian (Lietuvis)", "value": "lang_lt"}, + {"name": "Norwegian (Norwegian)", "value": "lang_no"}, + {"name": "Persian (فارسی)", "value": "lang_fa"}, + {"name": "Polish (Polskie)", "value": "lang_pl"}, + {"name": "Portugese (Português)", "value": "lang_pt"}, + {"name": "Romanian (Română)", "value": "lang_ro"}, + {"name": "Russian (русский)", "value": "lang_ru"}, + {"name": "Serbian (Српски)", "value": "lang_sr"}, + {"name": "Slovak (Slovák)", "value": "lang_sk"}, + {"name": "Slovenian (Slovenščina)", "value": "lang_sl"}, + {"name": "Spanish (Español)", "value": "lang_es"}, + {"name": "Swahili (Kiswahili)", "value": "lang_sw"}, + {"name": "Swedish (Svenska)", "value": "lang_sv"}, + {"name": "Thai (ไทย)", "value": "lang_th"}, + {"name": "Turkish (Türk)", "value": "lang_tr"}, + {"name": "Ukranian (Український)", "value": "lang_uk"}, + {"name": "Vietnamese (Tiếng Việt)", "value": "lang_vi"} ] diff --git a/app/static/settings/translations.json b/app/static/settings/translations.json new file mode 100644 index 0000000..98e8030 --- /dev/null +++ b/app/static/settings/translations.json @@ -0,0 +1,58 @@ +{ + "lang_en": { + "search": "Search", + "config": "Configuration", + "config-country": "Filter Results by Country", + "config-country-help": "Note: If enabled, a website will only appear in the search results if it is *hosted* in the selected country.", + "config-lang": "Interface Language", + "config-lang-search": "Search Language", + "config-near": "Near", + "config-near-help": "City Name", + "config-block": "Block", + "config-block-help": "Comma-separated site list", + "config-nojs": "Show NoJS Links", + "config-dark": "Dark Mode", + "config-safe": "Safe Search", + "config-alts": "Replace Social Media Links", + "config-alts-help": "Replaces Twitter/YouTube/Instagram/etc links with privacy respecting alternatives.", + "config-new-tab": "Open Links in New Tab", + "config-images": "Full Size Image Search", + "config-images-help": "(Experimental) Adds the 'View Image' option to desktop image searches. This will cause image result thumbnails to be lower resolution.", + "config-tor": "Use Tor", + "config-get-only": "GET Requests Only", + "config-url": "Root URL", + "config-css": "Custom CSS", + "load": "Load", + "apply": "Apply", + "save-as": "Save As...", + "github-link": "View on GitHub" + }, + "lang_es": { + "search": "Buscar", + "config": "Configuración", + "config-country": "Filtrar Resultados por País", + "config-country-help": "Nota: Si está habilitado, un sitio web solo aparecerá en los resultados de búsqueda si está alojado en ese país.", + "config-lang": "Idioma de Interfaz", + "config-lang-search": "Idioma de Búsqueda", + "config-near": "Cerca", + "config-near-help": "Nombre de la Ciudad", + "config-block": "Bloquear", + "config-block-help": "Lista de sitios separados por comas", + "config-nojs": "Mostrar Enlaces NoJS", + "config-dark": "Modo Oscuro", + "config-safe": "Búsqueda Segura", + "config-alts": "Reemplazar Enlaces de Redes Sociales", + "config-alts-help": "Reemplaza los enlaces de Twitter/YouTube/Instagram/etc con alternativas que respetan la privacidad.", + "config-new-tab": "Abrir enlaces en una pestaña nueva", + "config-images": "Búsqueda de imágenes a tamaño completo", + "config-images-help": "(Experimental) Agrega la opción 'Ver imagen' a las búsquedas de imágenes de escritorio. Esto hará que las miniaturas de los resultados de la imagen aparezcan con una resolución más baja.", + "config-tor": "Usa Tor", + "config-get-only": "GET solo solicitudes", + "config-url": "URL raíz", + "config-css": "CSS personalizado", + "load": "Cargar", + "apply": "Aplicar", + "save-as": "Guardar como...", + "github-link": "Ver en GitHub" + } +} diff --git a/app/templates/display.html b/app/templates/display.html index 398e276..8c30f6e 100644 --- a/app/templates/display.html +++ b/app/templates/display.html @@ -20,7 +20,7 @@ diff --git a/app/templates/imageresults.html b/app/templates/imageresults.html new file mode 100644 index 0000000..17ee98a --- /dev/null +++ b/app/templates/imageresults.html @@ -0,0 +1,116 @@ + + + + + + + + + + + +
+
+ + Google + +
+
+
+ + + + + + + + + + +
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ + {% for i in range((length // 4) + 1) %} + + {% for j in range([length - (i*4), 4]|min) %} + + {% endfor %} + + {% endfor %} +
+ +
+
+ + +
+
+
+ +
+
+ + diff --git a/app/templates/index.html b/app/templates/index.html index 9d43988..5cba56c 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,153 +1,157 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - Whoogle Search - - -
-
- {{ logo|safe }} -
-
-
-
- -
- -
-
- {% if not config_disabled %} -
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + Whoogle Search + + +
+
+ {{ logo|safe }} +
+
+
+
+ +
+ +
+
+ {% if not config_disabled %} +
+ +
-
+
- - {% for ctry in countries %} - + {% endfor %} - -
— Note: If enabled, a website will only appear in the results if it is *hosted* in the selected country.
+ +
— {{ translation['config-country-help'] }}
- - {% for lang in languages %} {% endfor %} - +
- - {% for lang in languages %} {% endfor %} - +
- - + + +
+
+ +
-
- - -
- - + +
- - + +
- - + +
- - -
— Replaces Twitter/YouTube/Instagram/Reddit links - with Nitter/Invidious/Bibliogram/Libreddit links.
+ + +
— {{ translation['config-alts-help'] }}
- - + + +
+
+ + +
— {{ translation['config-images-help'] }}
- - + +
- - + +
- - + +
- - +
-   -   - +   +   +
-
+
-
- {% endif %} -
-
+
+ {% endif %} +
+ - + + diff --git a/app/utils/search.py b/app/utils/search.py index a856bf6..bb24e4b 100644 --- a/app/utils/search.py +++ b/app/utils/search.py @@ -119,11 +119,23 @@ class Search: self.request_params, self.config, content_filter.near) - get_body = g.user_request.send(query=full_query) + + # force mobile search when view image is true and + # the request is not already made by a mobile + view_image = ('tbm=isch' in full_query + and self.config.view_image + and not g.user_request.mobile) + + get_body = g.user_request.send(query=full_query, + force_mobile=view_image) # Produce cleanable html soup from response html_soup = bsoup(content_filter.reskin(get_body.text), 'html.parser') + # Replace current soup if view_image is active + if view_image: + html_soup = content_filter.view_image(html_soup) + # Indicate whether or not a Tor connection is active tor_banner = bsoup('', 'html.parser') if g.user_request.tor_valid: diff --git a/docker-compose.yml b/docker-compose.yml index cbdb2ae..f29586c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: whoogle-search: image: benbusby/whoogle-search container_name: whoogle-search - restart: on-failure:5 + restart: unless-stopped pids_limit: 50 mem_limit: 256mb memswap_limit: 256mb @@ -41,4 +41,3 @@ services: #- whoogle.env ports: - 5000:5000 - restart: unless-stopped diff --git a/test/test_misc.py b/test/test_misc.py index 65a4ed0..13dde2e 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -1,5 +1,6 @@ from cryptography.fernet import Fernet +from app import app from app.utils.session import generate_user_key, valid_user_session @@ -15,6 +16,18 @@ def test_valid_session(client): assert valid_user_session(session) +def test_valid_translation_keys(client): + valid_lang_keys = [_['value'] for _ in app.config['LANGUAGES']] + en_keys = app.config['TRANSLATIONS']['lang_en'].keys() + for translation_key in app.config['TRANSLATIONS']: + # Ensure the translation is using a valid language value + assert translation_key in valid_lang_keys + + # Ensure all translations match the same size/content of the original + # English translation + assert app.config['TRANSLATIONS'][translation_key].keys() == en_keys + + def test_query_decryption(client): # FIXME: Handle decryption errors in search.py and rewrite test # This previously was used to test swapping decryption keys between diff --git a/whoogle.env b/whoogle.env index 3a0f88b..594ec67 100644 --- a/whoogle.env +++ b/whoogle.env @@ -46,6 +46,9 @@ # Open results in new tab #WHOOGLE_CONFIG_NEW_TAB=1 +# Enable View Image option +#WHOOGLE_CONFIG_VIEW_IMAGE=1 + # Search using GET requests only (exposes query in logs) #WHOOGLE_CONFIG_GET_ONLY=1