Merge remote-tracking branch 'origin/main' into feature/public-instance-sessions

This commit is contained in:
Ben Busby 2021-11-01 17:05:15 -06:00
commit 50d592bcec
No known key found for this signature in database
GPG Key ID: 339B7B7EB5333D14
13 changed files with 363 additions and 309 deletions

View File

@ -9,8 +9,6 @@ import os
from stem import Signal, SocketError from stem import Signal, SocketError
from stem.control import Controller from stem.control import Controller
SEARCH_URL = 'https://www.google.com/search?gbv=1&num=' + str(
os.getenv('WHOOGLE_RESULTS_PER_PAGE', 10)) + '&q='
MAPS_URL = 'https://maps.google.com/maps' MAPS_URL = 'https://maps.google.com/maps'
AUTOCOMPLETE_URL = ('https://suggestqueries.google.com/' AUTOCOMPLETE_URL = ('https://suggestqueries.google.com/'
'complete/search?client=toolbar&') 'complete/search?client=toolbar&')
@ -151,6 +149,8 @@ class Request:
""" """
def __init__(self, normal_ua, root_path, config: Config): def __init__(self, normal_ua, root_path, config: Config):
self.search_url = 'https://www.google.com/search?gbv=1&num=' + str(
os.getenv('WHOOGLE_RESULTS_PER_PAGE', 10)) + '&q='
# Send heartbeat to Tor, used in determining if the user can or cannot # Send heartbeat to Tor, used in determining if the user can or cannot
# enable Tor for future requests # enable Tor for future requests
send_tor_signal(Signal.HEARTBEAT) send_tor_signal(Signal.HEARTBEAT)
@ -224,7 +224,7 @@ 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, def send(self, base_url='', query='', attempt=0,
force_mobile=False) -> Response: 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.
@ -285,7 +285,7 @@ class Request:
disable=True) disable=True)
response = requests.get( response = requests.get(
base_url + query, (base_url or self.search_url) + query,
proxies=self.proxies, proxies=self.proxies,
headers=headers, headers=headers,
cookies=cookies) cookies=cookies)
@ -295,6 +295,6 @@ class Request:
attempt += 1 attempt += 1
if attempt > 10: if attempt > 10:
raise TorError("Tor query failed -- max attempts exceeded 10") raise TorError("Tor query failed -- max attempts exceeded 10")
return self.send(base_url, query, attempt) return self.send((base_url or self.search_url), query, attempt)
return response return response

View File

@ -14,7 +14,7 @@ from app import app
from app.models.config import Config from app.models.config import Config
from app.request import Request, TorError from app.request import Request, TorError
from app.utils.bangs import resolve_bang from app.utils.bangs import resolve_bang
from app.utils.misc import read_config_bool from app.utils.misc import read_config_bool, get_client_ip
from app.utils.results import add_ip_card from app.utils.results import add_ip_card
from app.utils.results import bold_search_terms from app.utils.results import bold_search_terms
from app.utils.search import * from app.utils.search import *
@ -301,10 +301,11 @@ def search():
# Return 503 if temporarily blocked by captcha # Return 503 if temporarily blocked by captcha
resp_code = 503 if has_captcha(str(response)) else 200 resp_code = 503 if has_captcha(str(response)) else 200
response = bold_search_terms(response, query) response = bold_search_terms(response, query)
# Feature to display IP address # Feature to display IP address
if search_util.check_kw_ip(): if search_util.check_kw_ip():
html_soup = bsoup(response, "html.parser") html_soup = bsoup(str(response), 'html.parser')
response = add_ip_card(html_soup, request.remote_addr) response = add_ip_card(html_soup, get_client_ip(request))
return render_template( return render_template(
'display.html', 'display.html',
@ -322,7 +323,7 @@ def search():
is_translation=any( is_translation=any(
_ in query.lower() for _ in [translation['translate'], 'translate'] _ in query.lower() for _ in [translation['translate'], 'translate']
) and not search_util.search_type, # Standard search queries only ) and not search_util.search_type, # Standard search queries only
response=html.unescape(str(response)), response=response,
version_number=app.config['VERSION_NUMBER'], version_number=app.config['VERSION_NUMBER'],
search_header=(render_template( search_header=(render_template(
'header.html', 'header.html',
@ -434,7 +435,13 @@ def window():
for script in results('script'): for script in results('script'):
script.decompose() script.decompose()
return render_template('display.html', response=results) return render_template(
'display.html',
response=results,
translation=app.config['TRANSLATIONS'][
g.user_config.get_localization_lang()
]
)
def run_app() -> None: def run_app() -> None:

View File

@ -102,6 +102,7 @@ button::-moz-focus-inner {
} }
.open { .open {
overflow-y: scroll;
padding-bottom: 20px; padding-bottom: 20px;
} }

View File

@ -26,7 +26,7 @@ const setupConfigLayout = () => {
if (content.style.maxHeight) { if (content.style.maxHeight) {
content.style.maxHeight = null; content.style.maxHeight = null;
} else { } else {
content.style.maxHeight = content.scrollHeight + "px"; content.style.maxHeight = "400px";
} }
content.classList.toggle("open"); content.classList.toggle("open");

View File

@ -1,45 +1,45 @@
<html> <html>
<head> <head>
<link rel="shortcut icon" href="static/img/favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="static/img/favicon.ico" type="image/x-icon">
<link rel="icon" href="static/img/favicon.ico" type="image/x-icon"> <link rel="icon" href="static/img/favicon.ico" type="image/x-icon">
<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">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<link rel="stylesheet" href="{{ cb_url('input.css') }}"> <link rel="stylesheet" href="{{ cb_url('input.css') }}">
<link rel="stylesheet" href="{{ cb_url('search.css') }}"> <link rel="stylesheet" href="{{ cb_url('search.css') }}">
<link rel="stylesheet" href="{{ cb_url('header.css') }}"> <link rel="stylesheet" href="{{ cb_url('header.css') }}">
{% if config.theme %} {% if config.theme %}
{% if config.theme == 'system' %} {% if config.theme == 'system' %}
<style> <style>
@import "{{ cb_url('light-theme.css') }}" screen; @import "{{ cb_url('light-theme.css') }}" screen;
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark); @import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
</style> </style>
{% else %}
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
{% endif %}
{% else %} {% else %}
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/> <link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
{% endif %} {% endif %}
<style>{{ config.style }}</style> {% else %}
<title>{{ clean_query(query) }} - Whoogle Search</title> <link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
</head>
<body>
{{ search_header|safe }}
{% if is_translation %}
<iframe
id="lingva-iframe"
src="{{ lingva_url }}/auto/{{ translate_to }}/{{ translate_str }}">
</iframe>
{% endif %} {% endif %}
{{ response|safe }} <style>{{ config.style }}</style>
</body> <title>{{ clean_query(query) }} - Whoogle Search</title>
<footer> </head>
<p class="footer"> <body>
Whoogle Search v{{ version_number }} || {{ search_header|safe }}
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a> {% if is_translation %}
</p> <iframe
</footer> id="lingva-iframe"
<script src="{{ cb_url('autocomplete.js') }}"></script> src="{{ lingva_url }}/auto/{{ translate_to }}/{{ translate_str }}">
<script src="{{ cb_url('utils.js') }}"></script> </iframe>
<script src="{{ cb_url('keyboard.js') }}"></script> {% endif %}
{{ response|safe }}
</body>
<footer>
<p class="footer">
Whoogle Search v{{ version_number }} ||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
</p>
</footer>
<script src="{{ cb_url('autocomplete.js') }}"></script>
<script src="{{ cb_url('utils.js') }}"></script>
<script src="{{ cb_url('keyboard.js') }}"></script>
</html> </html>

View File

@ -2,8 +2,8 @@
<header> <header>
<div class="bz1lBb header-div"> <div class="bz1lBb header-div">
<form class="search-form Pg70bf" <form class="search-form Pg70bf"
id="search-form" id="search-form"
method="{{ 'GET' if config.get_only else 'POST' }}"> method="{{ 'GET' if config.get_only else 'POST' }}">
<a class="logo-link mobile-logo" href="home"> <a class="logo-link mobile-logo" href="home">
<div id="mobile-header-logo"> <div id="mobile-header-logo">
{{ logo|safe }} {{ logo|safe }}
@ -11,18 +11,18 @@
</a> </a>
<div class="H0PQec mobile-input-div"> <div class="H0PQec mobile-input-div">
<div class="sbc esbc autocomplete"> <div class="sbc esbc autocomplete">
<input <input
id="search-bar" id="search-bar"
class="mobile-search-bar" class="mobile-search-bar"
autocapitalize="none" autocapitalize="none"
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
class="noHIxc" class="noHIxc"
name="q" name="q"
type="text" type="text"
value="{{ clean_query(query) }}" value="{{ clean_query(query) }}"
dir="auto"> dir="auto">
<input id="search-reset" type="reset" value="x"> <input id="search-reset" type="reset" value="x">
<input name="tbm" value="{{ search_type }}" style="display: none"> <input name="tbm" value="{{ search_type }}" style="display: none">
<input type="submit" style="display: none;"> <input type="submit" style="display: none;">
@ -43,22 +43,22 @@
</div> </div>
<div class="search-div"> <div class="search-div">
<form id="search-form" <form id="search-form"
class="search-form" class="search-form"
id="sf" id="sf"
method="{{ 'GET' if config.get_only else 'POST' }}"> method="{{ 'GET' if config.get_only else 'POST' }}">
<div class="autocomplete header-autocomplete"> <div class="autocomplete header-autocomplete">
<div style="width: 100%; display: flex"> <div style="width: 100%; display: flex">
<input <input
id="search-bar" id="search-bar"
autocapitalize="none" autocapitalize="none"
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
class="search-bar-desktop noHIxc" class="search-bar-desktop noHIxc"
name="q" name="q"
spellcheck="false" spellcheck="false"
type="text" type="text"
value="{{ clean_query(query) }}" value="{{ clean_query(query) }}"
dir="auto"> dir="auto">
<input name="tbm" value="{{ search_type }}" style="display: none"> <input name="tbm" value="{{ search_type }}" style="display: none">
<input type="submit" style="display: none;"> <input type="submit" style="display: none;">
<div class="sc"></div> <div class="sc"></div>

View File

@ -1,221 +1,242 @@
<html style="background: #000;"> <html style="background: #000;">
<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="{{ cb_url('autocomplete.js') }}"></script> <script type="text/javascript" src="{{ cb_url('autocomplete.js') }}"></script>
<script type="text/javascript" src="{{ cb_url('controller.js') }}"></script> <script type="text/javascript" src="{{ cb_url('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">
{% if config.theme %} {% if config.theme %}
{% if config.theme == 'system' %} {% if config.theme == 'system' %}
<style> <style>
@import "{{ cb_url('light-theme.css') }}" screen; @import "{{ cb_url('light-theme.css') }}" screen;
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark); @import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
</style> </style>
{% else %}
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
{% endif %}
{% else %} {% else %}
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/> <link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
{% endif %} {% endif %}
<link rel="stylesheet" href="{{ cb_url('main.css') }}"> {% else %}
<noscript> <link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
<style> {% endif %}
#main { display: inherit !important; } <link rel="stylesheet" href="{{ cb_url('main.css') }}">
.content { max-height: 720px; padding: 18px; border-radius: 10px; } <noscript>
.collapsible { display: none; } <style>
</style> #main {
</noscript> display: inherit !important;
<style>{{ config.style }}</style> }
<title>Whoogle Search</title>
</head> .content {
<body id="main"> max-height: 400px;
<div class="search-container"> padding: 18px;
<div class="logo-container"> border-radius: 10px;
{{ logo|safe }} overflow-y: scroll;
</div> }
<form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
<div class="search-fields"> .collapsible {
<div class="autocomplete"> display: none;
<input }
type="text" </style>
name="q" </noscript>
id="search-bar" <style>{{ config.style }}</style>
class="home-search" <title>Whoogle Search</title>
autofocus="autofocus" </head>
autocapitalize="none" <body id="main">
spellcheck="false" <div class="search-container">
autocorrect="off" <div class="logo-container">
autocomplete="off" {{ logo|safe }}
dir="auto"> </div>
</div> <form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
<input type="submit" id="search-submit" value="{{ translation['search'] }}"> <div class="search-fields">
</div> <div class="autocomplete">
</form> <input
{% if not config_disabled %} type="text"
<br/> name="q"
<button id="config-collapsible" class="collapsible">{{ translation['config'] }}</button> id="search-bar"
<div class="content"> class="home-search"
<div class="config-fields"> autofocus="autofocus"
<form id="config-form" action="config" method="post"> autocapitalize="none"
<div class="config-div config-div-ctry"> spellcheck="false"
<label for="config-ctry">{{ translation['config-country'] }}: </label> autocorrect="off"
<select name="ctry" id="config-ctry"> autocomplete="off"
{% for ctry in countries %} dir="auto">
<option value="{{ ctry.value }}" </div>
{% if ctry.value in config.ctry %} <input type="submit" id="search-submit" value="{{ translation['search'] }}">
selected </div>
{% endif %}> </form>
{{ ctry.name }} {% if not config_disabled %}
</option> <br/>
{% endfor %} <button id="config-collapsible" class="collapsible">{{ translation['config'] }}</button>
</select> <div class="content">
<div><span class="info-text"> — {{ translation['config-country-help'] }}</span></div> <div class="config-fields">
</div> <form id="config-form" action="config" method="post">
<div class="config-div config-div-lang"> <div class="config-div config-div-ctry">
<label for="config-lang-interface">{{ translation['config-lang'] }}: </label> <label for="config-ctry">{{ translation['config-country'] }}: </label>
<select name="lang_interface" id="config-lang-interface"> <select name="ctry" id="config-ctry">
{% for lang in languages %} {% for ctry in countries %}
<option value="{{ lang.value }}" <option value="{{ ctry.value }}"
{% if lang.value in config.lang_interface %} {% if ctry.value in config.ctry %}
selected selected
{% endif %}> {% endif %}>
{{ lang.name }} {{ ctry.name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
</div> <div><span class="info-text"> — {{ translation['config-country-help'] }}</span></div>
<div class="config-div config-div-search-lang"> </div>
<label for="config-lang-search">{{ translation['config-lang-search'] }}: </label> <div class="config-div config-div-lang">
<select name="lang_search" id="config-lang-search"> <label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
{% for lang in languages %} <select name="lang_interface" id="config-lang-interface">
<option value="{{ lang.value }}" {% for lang in languages %}
{% if lang.value in config.lang_search %} <option value="{{ lang.value }}"
selected {% if lang.value in config.lang_interface %}
{% endif %}> selected
{{ lang.name }} {% endif %}>
</option> {{ lang.name }}
{% endfor %} </option>
</select> {% endfor %}
</div> </select>
<div class="config-div config-div-near"> </div>
<label for="config-near">{{ translation['config-near'] }}: </label> <div class="config-div config-div-search-lang">
<input type="text" name="near" id="config-near" placeholder="{{ translation['config-near-help'] }}" value="{{ config.near }}"> <label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
</div> <select name="lang_search" id="config-lang-search">
<div class="config-div config-div-block"> {% for lang in languages %}
<label for="config-block">{{ translation['config-block'] }}: </label> <option value="{{ lang.value }}"
<input type="text" name="block" id="config-block" placeholder="{{ translation['config-block-help'] }}" value="{{ config.block }}"> {% if lang.value in config.lang_search %}
</div> selected
<div class="config-div config-div-block"> {% endif %}>
<label for="config-block-title">{{ translation['config-block-title'] }}: </label> {{ lang.name }}
<input type="text" name="block_title" id="config-block" placeholder="{{ translation['config-block-title-help'] }}" value="{{ config.block_title }}"> </option>
</div> {% endfor %}
<div class="config-div config-div-block"> </select>
<label for="config-block-url">{{ translation['config-block-url'] }}: </label> </div>
<input type="text" name="block_url" id="config-block" placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}"> <div class="config-div config-div-near">
</div> <label for="config-near">{{ translation['config-near'] }}: </label>
<div class="config-div config-div-nojs"> <input type="text" name="near" id="config-near"
<label for="config-nojs">{{ translation['config-nojs'] }}: </label> placeholder="{{ translation['config-near-help'] }}" value="{{ config.near }}">
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}> </div>
</div> <div class="config-div config-div-block">
<div class="config-div config-div-theme"> <label for="config-block">{{ translation['config-block'] }}: </label>
<label for="config-theme">{{ translation['config-theme'] }}: </label> <input type="text" name="block" id="config-block"
<select name="theme" id="config-theme"> placeholder="{{ translation['config-block-help'] }}" value="{{ config.block }}">
{% for theme in themes %} </div>
<option value="{{ theme }}" <div class="config-div config-div-block">
{% if theme in config.theme %} <label for="config-block-title">{{ translation['config-block-title'] }}: </label>
selected <input type="text" name="block_title" id="config-block"
{% endif %}> placeholder="{{ translation['config-block-title-help'] }}"
{{ translation[theme].capitalize() }} value="{{ config.block_title }}">
</option> </div>
{% endfor %} <div class="config-div config-div-block">
</select> <label for="config-block-url">{{ translation['config-block-url'] }}: </label>
</div> <input type="text" name="block_url" id="config-block"
<!-- DEPRECATED --> placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
<!--<div class="config-div config-div-dark">--> </div>
<!--<label for="config-dark">{{ translation['config-dark'] }}: </label>--> <div class="config-div config-div-nojs">
<!--<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>--> <label for="config-nojs">{{ translation['config-nojs'] }}: </label>
<!--</div>--> <input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>
<div class="config-div config-div-safe"> </div>
<label for="config-safe">{{ translation['config-safe'] }}: </label> <div class="config-div config-div-theme">
<input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}> <label for="config-theme">{{ translation['config-theme'] }}: </label>
</div> <select name="theme" id="config-theme">
<div class="config-div config-div-alts"> {% for theme in themes %}
<label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label> <option value="{{ theme }}"
<input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}> {% if theme in config.theme %}
<div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div> selected
</div> {% endif %}>
<div class="config-div config-div-new-tab"> {{ translation[theme].capitalize() }}
<label for="config-new-tab">{{ translation['config-new-tab'] }}: </label> </option>
<input type="checkbox" name="new_tab" id="config-new-tab" {{ 'checked' if config.new_tab else '' }}> {% endfor %}
</div> </select>
<div class="config-div config-div-view-image"> </div>
<label for="config-view-image">{{ translation['config-images'] }}: </label> <!-- DEPRECATED -->
<input type="checkbox" name="view_image" id="config-view-image" {{ 'checked' if config.view_image else '' }}> <!--<div class="config-div config-div-dark">-->
<div><span class="info-text"> — {{ translation['config-images-help'] }}</span></div> <!--<label for="config-dark">{{ translation['config-dark'] }}: </label>-->
</div> <!--<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>-->
<div class="config-div config-div-tor"> <!--</div>-->
<label for="config-tor">{{ translation['config-tor'] }}: {{ '' if tor_available else 'Unavailable' }}</label> <div class="config-div config-div-safe">
<input type="checkbox" name="tor" id="config-tor" {{ '' if tor_available else 'hidden' }} {{ 'checked' if config.tor else '' }}> <label for="config-safe">{{ translation['config-safe'] }}: </label>
</div> <input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}>
<div class="config-div config-div-get-only"> </div>
<label for="config-get-only">{{ translation['config-get-only'] }}: </label> <div class="config-div config-div-alts">
<input type="checkbox" name="get_only" id="config-get-only" {{ 'checked' if config.get_only else '' }}> <label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label>
</div> <input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}>
<div class="config-div config-div-get-only"> <div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div>
<label for="config-accept-language">Set Accept-Language: </label> </div>
<input type="checkbox" name="accept_language" id="config-accept-language" {{ 'checked' if config.accept_language else '' }}> <div class="config-div config-div-new-tab">
</div> <label for="config-new-tab">{{ translation['config-new-tab'] }}: </label>
<div class="config-div config-div-root-url"> <input type="checkbox" name="new_tab"
<label for="config-url">{{ translation['config-url'] }}: </label> id="config-new-tab" {{ 'checked' if config.new_tab else '' }}>
<input type="text" name="url" id="config-url" value="{{ config.url }}"> </div>
</div> <div class="config-div config-div-view-image">
<div class="config-div config-div-custom-css"> <label for="config-view-image">{{ translation['config-images'] }}: </label>
<a id="css-link" <input type="checkbox" name="view_image"
href="https://github.com/benbusby/whoogle-search/wiki/User-Contributed-CSS-Themes"> id="config-view-image" {{ 'checked' if config.view_image else '' }}>
{{ translation['config-css'] }}: <div><span class="info-text"> — {{ translation['config-images-help'] }}</span></div>
</a> </div>
<textarea <div class="config-div config-div-tor">
name="style" <label for="config-tor">{{ translation['config-tor'] }}: {{ '' if tor_available else 'Unavailable' }}</label>
id="config-style" <input type="checkbox" name="tor"
autocapitalize="off" id="config-tor" {{ '' if tor_available else 'hidden' }} {{ 'checked' if config.tor else '' }}>
autocomplete="off" </div>
spellcheck="false" <div class="config-div config-div-get-only">
autocorrect="off" <label for="config-get-only">{{ translation['config-get-only'] }}: </label>
value=""> <input type="checkbox" name="get_only"
{{ config.style.replace('\t', '') }} id="config-get-only" {{ 'checked' if config.get_only else '' }}>
</textarea> </div>
</div> <div class="config-div config-div-get-only">
<div class="config-div"> <label for="config-accept-language">Set Accept-Language: </label>
<input type="submit" id="config-load" value="{{ translation['load'] }}">&nbsp; <input type="checkbox" name="accept_language"
<input type="submit" id="config-submit" value="{{ translation['apply'] }}">&nbsp; id="config-accept-language" {{ 'checked' if config.accept_language else '' }}>
<input type="submit" id="config-save" value="{{ translation['save-as'] }}"> </div>
</div> <div class="config-div config-div-root-url">
</form> <label for="config-url">{{ translation['config-url'] }}: </label>
</div> <input type="text" name="url" id="config-url" value="{{ config.url }}">
</div> </div>
{% endif %} <div class="config-div config-div-custom-css">
</div> <a id="css-link"
<footer> href="https://github.com/benbusby/whoogle-search/wiki/User-Contributed-CSS-Themes">
<p class="footer"> {{ translation['config-css'] }}:
Whoogle Search v{{ version_number }} || </a>
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a> <textarea
</p> name="style"
</footer> id="config-style"
</body> autocapitalize="off"
autocomplete="off"
spellcheck="false"
autocorrect="off"
value="">
{{ config.style.replace('\t', '') }}
</textarea>
</div>
<div class="config-div">
<input type="submit" id="config-load" value="{{ translation['load'] }}">&nbsp;
<input type="submit" id="config-submit" value="{{ translation['apply'] }}">&nbsp;
<input type="submit" id="config-save" value="{{ translation['save-as'] }}">
</div>
</form>
</div>
</div>
{% endif %}
</div>
<footer>
<p class="footer">
Whoogle Search v{{ version_number }} ||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
</p>
</footer>
</body>
</html> </html>

File diff suppressed because one or more lines are too long

View File

@ -55,9 +55,11 @@ def resolve_bang(query: str, bangs_dict: dict) -> str:
query = query.lower() query = query.lower()
split_query = query.split(' ') split_query = query.split(' ')
for operator in bangs_dict.keys(): for operator in bangs_dict.keys():
if operator not in split_query: if operator not in split_query \
and operator[1:] + operator[0] not in split_query:
continue continue
return bangs_dict[operator]['url'].replace(
return bangs_dict[operator]['url'].format( '{}',
query.replace(operator, '').strip()) query.replace(operator if operator in split_query
else operator[1:] + operator[0], '').strip(), 1)
return '' return ''

View File

@ -1,3 +1,4 @@
from flask import Request
import hashlib import hashlib
import os import os
@ -15,3 +16,10 @@ def read_config_bool(var: str) -> bool:
if val.isdigit(): if val.isdigit():
return bool(int(val)) return bool(int(val))
return False return False
def get_client_ip(r: Request) -> str:
if r.environ.get('HTTP_X_FORWARDED_FOR') is None:
return r.environ['REMOTE_ADDR']
else:
return r.environ['HTTP_X_FORWARDED_FOR']

View File

@ -1,4 +1,5 @@
from bs4 import BeautifulSoup, NavigableString from bs4 import BeautifulSoup, NavigableString
import html
import os import os
import urllib.parse as urlparse import urllib.parse as urlparse
from urllib.parse import parse_qs from urllib.parse import parse_qs
@ -52,11 +53,15 @@ def bold_search_terms(response: str, query: str) -> BeautifulSoup:
if len(element) == len(target_word): if len(element) == len(target_word):
return return
element.replace_with( if not re.match('.*[a-zA-Z0-9].*', target_word) or (
element.parent and element.parent.name == 'style'):
return
element.replace_with(BeautifulSoup(
re.sub(fr'\b((?![{{}}<>-]){target_word}(?![{{}}<>-]))\b', re.sub(fr'\b((?![{{}}<>-]){target_word}(?![{{}}<>-]))\b',
r'<b>\1</b>', r'<b>\1</b>',
element, html.escape(element),
flags=re.I) flags=re.I), 'html.parser')
) )
# Split all words out of query, grouping the ones wrapped in quotes # Split all words out of query, grouping the ones wrapped in quotes
@ -173,9 +178,7 @@ def append_nojs(result: BeautifulSoup) -> None:
""" """
nojs_link = BeautifulSoup(features='html.parser').new_tag('a') nojs_link = BeautifulSoup(features='html.parser').new_tag('a')
nojs_link['href'] = '/window?location=' + result['href'] nojs_link['href'] = '/window?location=' + result['href']
nojs_link['style'] = 'display:block;width:100%;' nojs_link.string = ' NoJS Link'
nojs_link.string = 'NoJS Link: ' + nojs_link['href']
result.append(BeautifulSoup('<br><hr><br>', 'html.parser'))
result.append(nojs_link) result.append(nojs_link)

4
run
View File

@ -12,6 +12,10 @@ SUBDIR="${1:-app}"
export APP_ROOT="$SCRIPT_DIR/$SUBDIR" export APP_ROOT="$SCRIPT_DIR/$SUBDIR"
export STATIC_FOLDER="$APP_ROOT/static" export STATIC_FOLDER="$APP_ROOT/static"
# Clear out build directory
rm -f "$SCRIPT_DIR"/app/static/build/*.js
rm -f "$SCRIPT_DIR"/app/static/build/*.css
# Check for regular vs test run # Check for regular vs test run
if [[ "$SUBDIR" == "test" ]]; then if [[ "$SUBDIR" == "test" ]]; then
# Set up static files for testing # Set up static files for testing

View File

@ -36,6 +36,11 @@ def test_ddg_bang(client):
assert rv._status_code == 302 assert rv._status_code == 302
assert rv.headers.get('Location').startswith('https://www.reddit.com') assert rv.headers.get('Location').startswith('https://www.reddit.com')
# Move '!' to end of the bang
rv = client.get('/search?q=gitlab%20w!')
assert rv._status_code == 302
assert rv.headers.get('Location').startswith('https://en.wikipedia.org')
# Ensure bang is case insensitive # Ensure bang is case insensitive
rv = client.get('/search?q=!GH%20whoogle') rv = client.get('/search?q=!GH%20whoogle')
assert rv._status_code == 302 assert rv._status_code == 302