Merge remote-tracking branch 'origin/main' into heroku-app

This commit is contained in:
Ben Busby 2021-05-24 17:05:40 -04:00
commit 31214b50d4
No known key found for this signature in database
GPG Key ID: 3B08611DF6E62ED2
17 changed files with 548 additions and 183 deletions

View File

@ -26,10 +26,11 @@ Contents
1. [Set Primary Search Engine](#set-whoogle-as-your-primary-search-engine) 1. [Set Primary Search Engine](#set-whoogle-as-your-primary-search-engine)
2. [Prevent Downtime (Heroku Only)](#prevent-downtime-heroku-only) 2. [Prevent Downtime (Heroku Only)](#prevent-downtime-heroku-only)
3. [Manual HTTPS Enforcement](#https-enforcement) 3. [Manual HTTPS Enforcement](#https-enforcement)
7. [FAQ](#faq) 7. [Contributing](#contributing)
8. [Public Instances](#public-instances) 8. [FAQ](#faq)
9. [Screenshots](#screenshots) 9. [Public Instances](#public-instances)
10. Mirrors (read-only) 10. [Screenshots](#screenshots)
11. Mirrors (read-only)
1. [GitLab](https://gitlab.com/benbusby/whoogle-search) 1. [GitLab](https://gitlab.com/benbusby/whoogle-search)
2. [Gogs](https://gogs.benbusby.com/benbusby/whoogle-search) 2. [Gogs](https://gogs.benbusby.com/benbusby/whoogle-search)
@ -103,17 +104,25 @@ Sandboxed temporary instance:
```bash ```bash
$ whoogle-search --help $ whoogle-search --help
usage: whoogle-search [-h] [--port <port number>] [--host <ip address>] [--debug] usage: whoogle-search [-h] [--port <port number>] [--host <ip address>] [--debug] [--https-only] [--userpass <username:password>]
[--https-only] [--proxyauth <username:password>] [--proxytype <socks4|socks5|http>] [--proxyloc <location:port>]
Whoogle Search console runner Whoogle Search console runner
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help Show this help message and exit
--port <port number> Specifies a port to run on (default 5000) --port <port number> Specifies a port to run on (default 5000)
--host <ip address> Specifies the host address to use (default 127.0.0.1) --host <ip address> Specifies the host address to use (default 127.0.0.1)
--debug Activates debug mode for the server (default False) --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 <username:password>
Sets a username/password basic auth combo (default None)
--proxyauth <username:password>
Sets a username/password for a HTTP/SOCKS proxy (default None)
--proxytype <socks4|socks5|http>
Sets a proxy type for all connections (default None)
--proxyloc <location:port>
Sets a proxy location for all connections (default None)
``` ```
See the [available environment variables](#environment-variables) for additional configuration. 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 | | 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_COUNTRY | Filter results by hosting country |
| WHOOGLE_CONFIG_LANGUAGE | Set interface language | | WHOOGLE_CONFIG_LANGUAGE | Set interface language |
| WHOOGLE_CONFIG_SEARCH_LANGUAGE | Set search result 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_ALTS | Use social media site alternatives (nitter, invidious, etc) |
| WHOOGLE_CONFIG_TOR | Use Tor routing (if available) | | WHOOGLE_CONFIG_TOR | Use Tor routing (if available) |
| WHOOGLE_CONFIG_NEW_TAB | Always open results in new tab | | 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_GET_ONLY | Search using GET requests only |
| WHOOGLE_CONFIG_URL | The root url of the instance (`https://<your url>/`) | | WHOOGLE_CONFIG_URL | The root url of the instance (`https://<your url>/`) |
| WHOOGLE_CONFIG_STYLE | The custom CSS to use for styling (should be single line) | | 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 - 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 - 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|js>`
- 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 ## FAQ
**What's the difference between this and [Searx](https://github.com/asciimoo/searx)?** **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://whooglesearch.net/](https://whooglesearch.net/)
- [https://search.flawcra.cc/](https://search.flawcra.cc/) - [https://search.flawcra.cc/](https://search.flawcra.cc/)
- [https://search.exonip.de/](https://search.exonip.de/) - [https://search.exonip.de/](https://search.exonip.de/)
- [https://whoogle.silkky.cloud/](https://whoogle.silkky.cloud/)
## Screenshots ## Screenshots
#### Desktop #### Desktop
![Whoogle Desktop](docs/screenshot_desktop.jpg) ![Whoogle Desktop](docs/screenshot_desktop.jpg)

View File

@ -115,6 +115,11 @@
"value": "", "value": "",
"required": false "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": { "WHOOGLE_CONFIG_GET_ONLY": {
"description": "[CONFIG] Search using GET requests only (set to 1 or leave blank)", "description": "[CONFIG] Search using GET requests only (set to 1 or leave blank)",
"value": "", "value": "",

View File

@ -33,6 +33,8 @@ app.config['LANGUAGES'] = json.load(open(
os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json'))) os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json')))
app.config['COUNTRIES'] = json.load(open( app.config['COUNTRIES'] = json.load(open(
os.path.join(app.config['STATIC_FOLDER'], 'settings/countries.json'))) 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( app.config['CONFIG_PATH'] = os.getenv(
'CONFIG_VOLUME', 'CONFIG_VOLUME',
os.path.join(app.config['STATIC_FOLDER'], 'config')) os.path.join(app.config['STATIC_FOLDER'], 'config'))

View File

@ -254,3 +254,65 @@ class Filter:
# Replace link destination # Replace link destination
link_desc[0].replace_with(get_site_alt(link_desc[0])) 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

View File

@ -27,7 +27,9 @@ class Config:
self.tor = read_config_bool('WHOOGLE_CONFIG_TOR') self.tor = read_config_bool('WHOOGLE_CONFIG_TOR')
self.near = os.getenv('WHOOGLE_CONFIG_NEAR', '') self.near = os.getenv('WHOOGLE_CONFIG_NEAR', '')
self.new_tab = read_config_bool('WHOOGLE_CONFIG_NEW_TAB') 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.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY')
self.safe_keys = [ self.safe_keys = [
'lang_search', 'lang_search',
'lang_interface', 'lang_interface',
@ -75,6 +77,19 @@ class Config:
return key in self.safe_keys 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': def from_params(self, params) -> 'Config':
"""Modify user config with search parameters. This is primarily """Modify user config with search parameters. This is primarily
used for specifying configuration on a search-by-search basis on used for specifying configuration on a search-by-search basis on

View File

@ -151,6 +151,8 @@ class Request:
self.language = config.lang_search self.language = config.lang_search
self.mobile = 'Android' in normal_ua or 'iPhone' in normal_ua self.mobile = 'Android' in normal_ua or 'iPhone' in normal_ua
self.modified_user_agent = gen_user_agent(self.mobile) 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 # Set up proxy, if previously configured
if os.environ.get('WHOOGLE_PROXY_LOC'): if os.environ.get('WHOOGLE_PROXY_LOC'):
@ -197,7 +199,8 @@ class Request:
return [_.attrib['data'] for _ in return [_.attrib['data'] for _ in
root.findall('.//suggestion/[@data]')] 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 """Sends an outbound request to a URL. Optionally sends the request
using Tor, if enabled by the user. using Tor, if enabled by the user.
@ -211,8 +214,13 @@ class Request:
Response: The Response object returned by the requests call 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 = { headers = {
'User-Agent': self.modified_user_agent 'User-Agent': modified_user_agent
} }
# FIXME: Should investigate this further to ensure the consent # FIXME: Should investigate this further to ensure the consent

View File

@ -130,6 +130,9 @@ def index():
return render_template('index.html', return render_template('index.html',
languages=app.config['LANGUAGES'], languages=app.config['LANGUAGES'],
countries=app.config['COUNTRIES'], countries=app.config['COUNTRIES'],
translation=app.config['TRANSLATIONS'][
g.user_config.get_localization_lang()
],
logo=render_template( logo=render_template(
'logo.html', 'logo.html',
dark=g.user_config.dark), dark=g.user_config.dark),
@ -235,6 +238,9 @@ def search():
query=urlparse.unquote(query), query=urlparse.unquote(query),
search_type=search_util.search_type, search_type=search_util.search_type,
config=g.user_config, config=g.user_config,
translation=app.config['TRANSLATIONS'][
g.user_config.get_localization_lang()
],
response=response, response=response,
version_number=app.config['VERSION_NUMBER'], version_number=app.config['VERSION_NUMBER'],
search_header=(render_template( search_header=(render_template(

View File

@ -1,5 +1,5 @@
[ [
{"name": "Default (none)", "value": ""}, {"name": "-------", "value": ""},
{"name": "Afghanistan", "value": "countryAF"}, {"name": "Afghanistan", "value": "countryAF"},
{"name": "Albania", "value": "countryAL"}, {"name": "Albania", "value": "countryAL"},
{"name": "Algeria", "value": "countryDZ"}, {"name": "Algeria", "value": "countryDZ"},

View File

@ -1,49 +1,49 @@
[ [
{"name": "Default (none specified)", "value": ""}, {"name": "-------", "value": ""},
{"name": "English", "value": "lang_en"}, {"name": "English", "value": "lang_en"},
{"name": "Afrikaans", "value": "lang_af"}, {"name": "Afrikaans (Afrikaans)", "value": "lang_af"},
{"name": "Arabic", "value": "lang_ar"}, {"name": "Arabic (عربى)", "value": "lang_ar"},
{"name": "Armenian", "value": "lang_hy"}, {"name": "Armenian (հայերեն)", "value": "lang_hy"},
{"name": "Belarusian", "value": "lang_be"}, {"name": "Belarusian (Беларуская)", "value": "lang_be"},
{"name": "Bulgarian", "value": "lang_bg"}, {"name": "Bulgarian (български)", "value": "lang_bg"},
{"name": "Catalan", "value": "lang_ca"}, {"name": "Catalan (Català)", "value": "lang_ca"},
{"name": "Chinese (Simplified)", "value": "lang_zh-CN"}, {"name": "Chinese, Simplified (简体中文)", "value": "lang_zh-CN"},
{"name": "Chinese (Traditional)", "value": "lang_zh-TW"}, {"name": "Chinese, Traditional (繁体中文)", "value": "lang_zh-TW"},
{"name": "Croatian", "value": "lang_hr"}, {"name": "Croatian (Hrvatski)", "value": "lang_hr"},
{"name": "Czech", "value": "lang_cs"}, {"name": "Czech (čeština)", "value": "lang_cs"},
{"name": "Danish", "value": "lang_da"}, {"name": "Danish (Dansk)", "value": "lang_da"},
{"name": "Dutch", "value": "lang_nl"}, {"name": "Dutch (Nederlands)", "value": "lang_nl"},
{"name": "Esperanto", "value": "lang_eo"}, {"name": "Esperanto (Esperanto)", "value": "lang_eo"},
{"name": "Estonian", "value": "lang_et"}, {"name": "Estonian (Eestlane)", "value": "lang_et"},
{"name": "Filipino", "value": "lang_tl"}, {"name": "Filipino (Pilipino)", "value": "lang_tl"},
{"name": "Finnish", "value": "lang_fi"}, {"name": "Finnish (Suomalainen)", "value": "lang_fi"},
{"name": "French", "value": "lang_fr"}, {"name": "French (Français)", "value": "lang_fr"},
{"name": "German", "value": "lang_de"}, {"name": "German (Deutsche)", "value": "lang_de"},
{"name": "Greek", "value": "lang_el"}, {"name": "Greek (Ελληνικά)", "value": "lang_el"},
{"name": "Hebrew", "value": "lang_iw"}, {"name": "Hebrew (עִברִית)", "value": "lang_iw"},
{"name": "Hindi", "value": "lang_hi"}, {"name": "Hindi (हिंदी)", "value": "lang_hi"},
{"name": "Hungarian", "value": "lang_hu"}, {"name": "Hungarian (Magyar)", "value": "lang_hu"},
{"name": "Icelandic", "value": "lang_is"}, {"name": "Icelandic (Íslenska)", "value": "lang_is"},
{"name": "Indonesian", "value": "lang_id"}, {"name": "Indonesian (Indonesian)", "value": "lang_id"},
{"name": "Italian", "value": "lang_it"}, {"name": "Italian (Italiano)", "value": "lang_it"},
{"name": "Japanese", "value": "lang_ja"}, {"name": "Japanese (日本語)", "value": "lang_ja"},
{"name": "Korean", "value": "lang_ko"}, {"name": "Korean (한국어)", "value": "lang_ko"},
{"name": "Latvian", "value": "lang_lv"}, {"name": "Latvian (Latvietis)", "value": "lang_lv"},
{"name": "Lithuanian", "value": "lang_lt"}, {"name": "Lithuanian (Lietuvis)", "value": "lang_lt"},
{"name": "Norwegian", "value": "lang_no"}, {"name": "Norwegian (Norwegian)", "value": "lang_no"},
{"name": "Persian", "value": "lang_fa"}, {"name": "Persian (فارسی)", "value": "lang_fa"},
{"name": "Polish", "value": "lang_pl"}, {"name": "Polish (Polskie)", "value": "lang_pl"},
{"name": "Portuguese", "value": "lang_pt"}, {"name": "Portugese (Português)", "value": "lang_pt"},
{"name": "Romanian", "value": "lang_ro"}, {"name": "Romanian (Română)", "value": "lang_ro"},
{"name": "Russian", "value": "lang_ru"}, {"name": "Russian (русский)", "value": "lang_ru"},
{"name": "Serbian", "value": "lang_sr"}, {"name": "Serbian (Српски)", "value": "lang_sr"},
{"name": "Slovak", "value": "lang_sk"}, {"name": "Slovak (Slovák)", "value": "lang_sk"},
{"name": "Slovenian", "value": "lang_sl"}, {"name": "Slovenian (Slovenščina)", "value": "lang_sl"},
{"name": "Spanish", "value": "lang_es"}, {"name": "Spanish (Español)", "value": "lang_es"},
{"name": "Swahili", "value": "lang_sw"}, {"name": "Swahili (Kiswahili)", "value": "lang_sw"},
{"name": "Swedish", "value": "lang_sv"}, {"name": "Swedish (Svenska)", "value": "lang_sv"},
{"name": "Thai", "value": "lang_th"}, {"name": "Thai (ไทย)", "value": "lang_th"},
{"name": "Turkish", "value": "lang_tr"}, {"name": "Turkish (Türk)", "value": "lang_tr"},
{"name": "Ukrainian", "value": "lang_uk"}, {"name": "Ukranian (Український)", "value": "lang_uk"},
{"name": "Vietnamese", "value": "lang_vi"} {"name": "Vietnamese (Tiếng Việt)", "value": "lang_vi"}
] ]

View File

@ -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"
}
}

View File

@ -20,7 +20,7 @@
<footer> <footer>
<p style="color: {{ 'var(--whoogle-dark-text)' if config.dark else 'var(--whoogle-text)' }};"> <p style="color: {{ 'var(--whoogle-dark-text)' if config.dark else 'var(--whoogle-text)' }};">
Whoogle Search v{{ version_number }} || Whoogle Search v{{ version_number }} ||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">View on GitHub</a> <a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
</p> </p>
</footer> </footer>
<script src="static/js/autocomplete.js"></script> <script src="static/js/autocomplete.js"></script>

View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta content="application/xhtml+xml; charset=utf-8" http-equiv="Content-Type"/>
<meta content="no-cache" name="Cache-Control"/>
<title>
</title>
<style>
a{text-decoration:none;color:inherit}a:hover{text-decoration:underline}a img{border:0}body{font-family:Roboto,Helvetica,Arial,sans-serif;padding:8px;margin:0 auto;max-width:700px;min-width:240px;}.FbhRzb{border-left:thin solid #dadce0;border-right:thin solid #dadce0;border-top:thin solid #dadce0;height:40px;overflow:hidden}.n692Zd{margin-bottom:10px}.cvifge{height:40px;border-spacing:0;width:100%;}.QvGUP{height:40px;padding:0 8px 0 8px;vertical-align:top}.O4cRJf{height:40px;width:100%;padding:0;padding-right:16px}.O1ePr{height:40px;padding:0;vertical-align:top}.kgJEQe{height:36px;width:98px;vertical-align:top;margin-top:4px}.lXLRf{vertical-align:top}.MhzMZd{border:0;vertical-align:middle;font-size:14px;height:40px;padding:0;width:100%;padding-left:16px}.xB0fq{height:40px;border:none;font-size:14px;background-color:#4285f4;color:#fff;padding:0 16px;margin:0;vertical-align:top;cursor:pointer}.xB0fq:focus{border:1px solid #000}.M7pB2{border:thin solid #dadce0;margin:0 0 3px 0;font-size:13px;font-weight:500;height:40px}.euZec{width:100%;height:40px;text-align:center;border-spacing:0}table.euZec td{padding:0;width:25%}.QIqI7{display:inline-block;padding-top:4px;font-weight:bold;color:#4285f4}.EY24We{border-bottom:2px solid #4285f4}.CsQyDc{display:inline-block;color:#70757a}.TuS8Ad{font-size:14px}.HddGcc{padding:8px;color:#70757a}.dzp8ae{font-weight:bold;color:#3c4043}.rEM8G{color:#70757a}.bookcf{table-layout:fixed;width:100%;border-spacing:0}.InWNIe{text-align:center}.uZgmoc{border:thin solid #dadce0;color:#70757a;font-size:14px;text-align:center;table-layout:fixed;width:100%}.frGj1b{display:block;padding:12px 0 12px 0;width:100%}.BnJWBc{text-align:center;padding:6px 0 13px 0;height:35px}.e3goi{vertical-align:top;padding:0;height:180px}.GpQGbf{margin:auto;border-collapse:collapse;border-spacing:0;width:100%}
</style>
</head>
<body>
<style>
.X6ZCif{color:#202124;font-size:11px;line-height:16px;display:inline-block;padding-top:2px;overflow:hidden;padding-bottom:4px;width:100%}.TwVfHd{border-radius:16px;border:thin solid #dadce0;display:inline-block;padding:8px 8px;margin-right:8px;margin-bottom:4px}.yekiAe{background-color:#dadce0}.svla5d{width:100%}.ezO2md{border:thin solid #dadce0;padding:12px 16px 12px 16px;margin-bottom:10px;font-family:Roboto,Helvetica,Arial,sans-serif}.lIMUZd{font-family:Roboto,Helvetica,Arial,sans-serif}.TxbwNb{border-spacing:0}.K35ahc{width:100%}.owohpf{text-align:center}.RAyV4b{width:162px;height:140px;line-height:140px;overflow:'hidden';text-align:center;}.t0fcAb{text-align:center;margin:auto;vertical-align:middle;width:100%;height:100%;object-fit: contain}.Tor4Ec{padding-top:2px;padding-bottom:8px;}.fYyStc{word-break:break-word}.ynsChf{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.Fj3V3b{color:#1967D2;font-size:14px;line-height:20px}.FrIlee{color:#202124;font-size:11px;line-height:16px}.F9iS2e{color:#70757a;font-size:11px;line-height:16px}.WMQ2Le{color:#70757a;font-size:12px;line-height:16px}.x3G5ab{color:#202124;font-size:12px;line-height:16px}.fuLhoc{color:#1967D2;font-size:18px;line-height:24px}.epoveb{font-size:32px;line-height:40px;font-weight:400;color:#202124}.dXDvrc{color:#0d652d;font-size:14px;line-height:20px;word-wrap:break-word}.dloBPe{font-weight:bold}.YVIcad{color:#70757a}.JkVVdd{color:#ea4335}.oXZRFd{color:#ea4335}.MQHtg{color:#fbbc04}.pyMRrb{color:#1e8e3e}.EtTZid{color:#1e8e3e}.M3vVJe{color:#1967D2}.qXLe6d{display:block}.NHQNef{font-style:italic}.Cb8Z7c{white-space:pre}a.ZWRArf{text-decoration:none}a .CVA68e:hover{text-decoration:underline}
</style>
<div class="n692Zd">
<div class="BnJWBc">
<a class="lXLRf" href="/?safe=off&amp;gbv=1&amp;output=images&amp;ie=UTF-8&amp;tbm=isch&amp;sa=X&amp;ved=0ahUKEwjhh7TZyd_vAhWShf0HHeYzCmsQPAgC">
<img alt="Google" class="kgJEQe" src="/images/branding/searchlogo/1x/googlelogo_desk_heirloom_color_150x55dp.gif"/>
</a>
</div>
<div class="FbhRzb">
<form action="/search">
<input name="safe" type="hidden" value="off"/>
<input name="gbv" type="hidden" value="1"/>
<input name="ie" type="hidden" value="ISO-8859-1"/>
<input name="tbm" type="hidden" value="isch"/>
<input name="oq" type="hidden"/>
<input name="aqs" type="hidden"/>
<table class="cvifge">
<tr>
<td class="O4cRJf">
<!-- search input -->
</td>
</tr>
</table>
</form>
</div>
<div class="M7pB2">
<!-- search options -->
</div>
</div>
<!-- <div class="X6ZCif"> Not present in mobile
</div> -->
<div>
<div>
<div>
<div class="lIMUZd">
<table class="By0U9">
<!-- correction suggested -->
</table>
</div>
</div>
</div>
<table class="GpQGbf">
{% for i in range((length // 4) + 1) %}
<tr>
{% for j in range([length - (i*4), 4]|min) %}
<td align="center" class="e3goi">
<div class="svla5d">
<div>
<div class="lIMUZd">
<div>
<table class="TxbwNb">
<tr>
<td>
<a href="{{ results[(i*4)+j].webpage }}">
<div class="RAyV4b">
<img alt="" class="t0fcAb" src="{{ results[(i*4)+j].img_tbn }}"/>
</div>
</a>
</td>
</tr>
<tr>
<td>
<a href="{{ results[(i*4)+j].webpage }}">
<div class="Tor4Ec">
<span class="qXLe6d x3G5ab">
<span class="fYyStc">
{{ results[(i*4)+j].domain }}
</span>
</span>
</div>
</a>
<a href="{{ results[(i*4)+j].img_url }}">
<div class="Tor4Ec">
<span class="qXLe6d F9iS2e">
<span class="fYyStc">
{{ view_label }}
</span>
</span>
</div>
</a>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
<table class="uZgmoc">
<!-- next page object -->
</table>
<br/>
<div class="TuS8Ad">
<!-- information about user connection -->
<div>
</div>
</body>
</html>

View File

@ -1,153 +1,157 @@
<html> <html>
<head> <head>
<link rel="apple-touch-icon" sizes="57x57" href="static/img/favicon/apple-icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="static/img/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="static/img/favicon/apple-icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="static/img/favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="static/img/favicon/apple-icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="static/img/favicon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="static/img/favicon/apple-icon-76x76.png"> <link rel="apple-touch-icon" sizes="76x76" href="static/img/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="static/img/favicon/apple-icon-114x114.png"> <link rel="apple-touch-icon" sizes="114x114" href="static/img/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="static/img/favicon/apple-icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="static/img/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="static/img/favicon/apple-icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="static/img/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="static/img/favicon/apple-icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="static/img/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="static/img/favicon/apple-icon-180x180.png"> <link rel="apple-touch-icon" sizes="180x180" href="static/img/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="static/img/favicon/android-icon-192x192.png"> <link rel="icon" type="image/png" sizes="192x192" href="static/img/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="static/img/favicon/favicon-96x96.png"> <link rel="icon" type="image/png" sizes="96x96" href="static/img/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon/favicon-16x16.png">
<link rel="manifest" href="static/img/favicon/manifest.json"> <link rel="manifest" href="static/img/favicon/manifest.json">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png"> <meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png">
<script type="text/javascript" src="static/js/autocomplete.js"></script> <script type="text/javascript" src="static/js/autocomplete.js"></script>
<script type="text/javascript" src="static/js/controller.js"></script> <script type="text/javascript" src="static/js/controller.js"></script>
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search"> <link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="static/css/variables.css"> <link rel="stylesheet" href="static/css/variables.css">
<link rel="stylesheet" href="static/css/main.css"> <link rel="stylesheet" href="static/css/main.css">
<link rel="stylesheet" href="static/css/{{ 'dark' if config.dark else 'light' }}-theme.css"/> <link rel="stylesheet" href="static/css/{{ 'dark' if config.dark else 'light' }}-theme.css"/>
<noscript> <noscript>
<style> <style>
#main { display: inherit !important; } #main { display: inherit !important; }
.content { max-height: 720px; padding: 18px; border-radius: 10px; } .content { max-height: 720px; padding: 18px; border-radius: 10px; }
.collapsible { display: none; } .collapsible { display: none; }
</style> </style>
</noscript> </noscript>
<style>{{ config.style }}</style> <style>{{ config.style }}</style>
<title>Whoogle Search</title> <title>Whoogle Search</title>
</head> </head>
<body id="main" style="display: none; background-color: {{ '#000' if config.dark else '#fff' }}"> <body id="main" style="display: none; background-color: {{ '#000' if config.dark else '#fff' }}">
<div class="search-container"> <div class="search-container">
<div class="logo-container"> <div class="logo-container">
{{ logo|safe }} {{ logo|safe }}
</div> </div>
<form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}"> <form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
<div class="search-fields"> <div class="search-fields">
<div class="autocomplete"> <div class="autocomplete">
<input <input
type="text" type="text"
name="q" name="q"
id="search-bar" id="search-bar"
class="home-search" class="home-search"
autofocus="autofocus" autofocus="autofocus"
autocapitalize="none" autocapitalize="none"
spellcheck="false" spellcheck="false"
autocorrect="off" autocorrect="off"
autocomplete="off"> autocomplete="off">
</div> </div>
<input type="submit" id="search-submit" value="Search"> <input type="submit" id="search-submit" value="{{ translation['search'] }}">
</div> </div>
</form> </form>
{% if not config_disabled %} {% if not config_disabled %}
<br/> <br/>
<button id="config-collapsible" class="collapsible">Configuration</button> <button id="config-collapsible" class="collapsible">{{ translation['config'] }}</button>
<div class="content"> <div class="content">
<div class="config-fields"> <div class="config-fields">
<form id="config-form" action="config" method="post"> <form id="config-form" action="config" method="post">
<div class="config-div config-div-ctry"> <div class="config-div config-div-ctry">
<label for="config-ctry">Filter Results by Country: </label> <label for="config-ctry">{{ translation['config-country'] }}: </label>
<select name="ctry" id="config-ctry"> <select name="ctry" id="config-ctry">
{% for ctry in countries %} {% for ctry in countries %}
<option value="{{ ctry.value }}" <option value="{{ ctry.value }}"
{% if ctry.value in config.ctry %} {% if ctry.value in config.ctry %}
selected selected
{% endif %}> {% endif %}>
{{ ctry.name }} {{ ctry.name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
<div><span class="info-text">Note: If enabled, a website will only appear in the results if it is *hosted* in the selected country.</span></div> <div><span class="info-text">{{ translation['config-country-help'] }}</span></div>
</div> </div>
<div class="config-div config-div-lang"> <div class="config-div config-div-lang">
<label for="config-lang-interface">Interface Language: </label> <label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
<select name="lang_interface" id="config-lang-interface"> <select name="lang_interface" id="config-lang-interface">
{% for lang in languages %} {% for lang in languages %}
<option value="{{ lang.value }}" <option value="{{ lang.value }}"
{% if lang.value in config.lang_interface %} {% if lang.value in config.lang_interface %}
selected selected
{% endif %}> {% endif %}>
{{ lang.name }} {{ lang.name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="config-div config-div-search-lang"> <div class="config-div config-div-search-lang">
<label for="config-lang-search">Search Language: </label> <label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
<select name="lang_search" id="config-lang-search"> <select name="lang_search" id="config-lang-search">
{% for lang in languages %} {% for lang in languages %}
<option value="{{ lang.value }}" <option value="{{ lang.value }}"
{% if lang.value in config.lang_search %} {% if lang.value in config.lang_search %}
selected selected
{% endif %}> {% endif %}>
{{ lang.name }} {{ lang.name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="config-div config-div-near"> <div class="config-div config-div-near">
<label for="config-near">Near: </label> <label for="config-near">{{ translation['config-near'] }}: </label>
<input type="text" name="near" id="config-near" placeholder="City Name" value="{{ config.near }}"> <input type="text" name="near" id="config-near" placeholder="City Name" value="{{ config.near }}">
</div>
<div class="config-div config-div-block">
<label for="config-block">{{ translation['config-block'] }}: </label>
<input type="text" name="block" id="config-block" placeholder="Comma-separated site list" value="{{ config.block }}">
</div> </div>
<div class="config-div config-div-block">
<label for="config-block">Block: </label>
<input type="text" name="block" id="config-block" placeholder="Comma-separated site list" value="{{ config.block }}">
</div>
<div class="config-div config-div-nojs"> <div class="config-div config-div-nojs">
<label for="config-nojs">Show NoJS Links: </label> <label for="config-nojs">{{ translation['config-nojs'] }}: </label>
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}> <input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>
</div> </div>
<div class="config-div config-div-dark"> <div class="config-div config-div-dark">
<label for="config-dark">Dark Mode: </label> <label for="config-dark">{{ translation['config-dark'] }}: </label>
<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}> <input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>
</div> </div>
<div class="config-div config-div-safe"> <div class="config-div config-div-safe">
<label for="config-safe">Safe Search: </label> <label for="config-safe">{{ translation['config-safe'] }}: </label>
<input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}> <input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}>
</div> </div>
<div class="config-div config-div-alts"> <div class="config-div config-div-alts">
<label class="tooltip" for="config-alts">Replace Social Media Links: </label> <label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label>
<input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}> <input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}>
<div><span class="info-text"> — Replaces Twitter/YouTube/Instagram/Reddit links <div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div>
with Nitter/Invidious/Bibliogram/Libreddit links.</span></div>
</div> </div>
<div class="config-div config-div-new-tab"> <div class="config-div config-div-new-tab">
<label for="config-new-tab">Open Links in New Tab: </label> <label for="config-new-tab">{{ translation['config-new-tab'] }}: </label>
<input type="checkbox" name="new_tab" id="config-new-tab" {{ 'checked' if config.new_tab else '' }}> <input type="checkbox" name="new_tab" id="config-new-tab" {{ 'checked' if config.new_tab else '' }}>
</div>
<div class="config-div config-div-view-image">
<label for="config-view-image">{{ translation['config-images'] }}: </label>
<input type="checkbox" name="view_image" id="config-view-image" {{ 'checked' if config.view_image else '' }}>
<div><span class="info-text"> — {{ translation['config-images-help'] }}</span></div>
</div> </div>
<div class="config-div config-div-tor"> <div class="config-div config-div-tor">
<label for="config-tor">Use Tor: {{ '' if tor_available else 'Unavailable' }}</label> <label for="config-tor">{{ translation['config-tor'] }}: {{ '' if tor_available else 'Unavailable' }}</label>
<input type="checkbox" name="tor" id="config-tor" {{ '' if tor_available else 'hidden' }} {{ 'checked' if config.tor else '' }}> <input type="checkbox" name="tor" id="config-tor" {{ '' if tor_available else 'hidden' }} {{ 'checked' if config.tor else '' }}>
</div> </div>
<div class="config-div config-div-get-only"> <div class="config-div config-div-get-only">
<label for="config-get-only">GET Requests Only: </label> <label for="config-get-only">{{ translation['config-get-only'] }}: </label>
<input type="checkbox" name="get_only" id="config-get-only" {{ 'checked' if config.get_only else '' }}> <input type="checkbox" name="get_only" id="config-get-only" {{ 'checked' if config.get_only else '' }}>
</div> </div>
<div class="config-div config-div-root-url"> <div class="config-div config-div-root-url">
<label for="config-url">Root URL: </label> <label for="config-url">{{ translation['config-url'] }}: </label>
<input type="text" name="url" id="config-url" value="{{ config.url }}"> <input type="text" name="url" id="config-url" value="{{ config.url }}">
</div> </div>
<div class="config-div config-div-custom-css"> <div class="config-div config-div-custom-css">
<label for="config-style">Custom CSS:</label> <label for="config-style">{{ translation['config-css'] }}:</label>
<textarea <textarea
name="style" name="style"
id="config-style" id="config-style"
autocapitalize="off" autocapitalize="off"
autocomplete="off" autocomplete="off"
@ -155,23 +159,23 @@
autocorrect="off" autocorrect="off"
value=""> value="">
{{ config.style }} {{ config.style }}
</textarea> </textarea>
</div> </div>
<div class="config-div"> <div class="config-div">
<input type="submit" id="config-load" value="Load">&nbsp; <input type="submit" id="config-load" value="{{ translation['load'] }}">&nbsp;
<input type="submit" id="config-submit" value="Apply">&nbsp; <input type="submit" id="config-submit" value="{{ translation['apply'] }}">&nbsp;
<input type="submit" id="config-save" value="Save As..."> <input type="submit" id="config-save" value="{{ translation['save-as'] }}">
</div> </div>
</form> </form>
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<footer> <footer>
<p style="color: {{ 'var(--whoogle-dark-text)' if config.dark else 'var(--whoogle-text)' }};"> <p style="color: {{ 'var(--whoogle-dark-text)' if config.dark else 'var(--whoogle-text)' }};">
Whoogle Search v{{ version_number }} || Whoogle Search v{{ version_number }} ||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">View on GitHub</a> <a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
</p> </p>
</footer> </footer>
</body> </body>
</html> </html>

View File

@ -119,11 +119,23 @@ class Search:
self.request_params, self.request_params,
self.config, self.config,
content_filter.near) 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 # Produce cleanable html soup from response
html_soup = bsoup(content_filter.reskin(get_body.text), 'html.parser') 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 # Indicate whether or not a Tor connection is active
tor_banner = bsoup('', 'html.parser') tor_banner = bsoup('', 'html.parser')
if g.user_request.tor_valid: if g.user_request.tor_valid:

View File

@ -6,7 +6,7 @@ services:
whoogle-search: whoogle-search:
image: benbusby/whoogle-search image: benbusby/whoogle-search
container_name: whoogle-search container_name: whoogle-search
restart: on-failure:5 restart: unless-stopped
pids_limit: 50 pids_limit: 50
mem_limit: 256mb mem_limit: 256mb
memswap_limit: 256mb memswap_limit: 256mb
@ -41,4 +41,3 @@ services:
#- whoogle.env #- whoogle.env
ports: ports:
- 5000:5000 - 5000:5000
restart: unless-stopped

View File

@ -1,5 +1,6 @@
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from app import app
from app.utils.session import generate_user_key, valid_user_session 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) 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): def test_query_decryption(client):
# FIXME: Handle decryption errors in search.py and rewrite test # FIXME: Handle decryption errors in search.py and rewrite test
# This previously was used to test swapping decryption keys between # This previously was used to test swapping decryption keys between

View File

@ -46,6 +46,9 @@
# Open results in new tab # Open results in new tab
#WHOOGLE_CONFIG_NEW_TAB=1 #WHOOGLE_CONFIG_NEW_TAB=1
# Enable View Image option
#WHOOGLE_CONFIG_VIEW_IMAGE=1
# Search using GET requests only (exposes query in logs) # Search using GET requests only (exposes query in logs)
#WHOOGLE_CONFIG_GET_ONLY=1 #WHOOGLE_CONFIG_GET_ONLY=1