Use consistent header for all result types (#535)

Introduces a header for switching between result types (i.e. "All", "News",
etc) that is consistent between the different result types. Previously, image
results had a tab header that was formatted in a drastically different manner,
which was jarring when switching from a different result page to the Images
page.

Created a G class enum to reference class names returned in search
results. As noted in the class doc, this should only be used/updated as
a last resort, as class names change frequently. For some instances,
such as replacing the tbm tab, it's a lot easier to just replace by
header name than attempting to replace it based on how the element is
structured.

Also updated a few styles to revert the latest styling changes being
applied by Google.

Co-authored-by: jacr13 <ramos.joao@protonmail.com>
Co-authored-by: Ben Busby <contact@benbusby.com>
pull/647/head
Joao A. Candido Ramos 2 years ago committed by GitHub
parent 4aa94a5d75
commit 11099f7b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -52,6 +52,9 @@ app.config['TRANSLATIONS'] = json.load(open(
app.config['THEMES'] = json.load(open(
os.path.join(app.config['STATIC_FOLDER'], 'settings/themes.json'),
encoding='utf-8'))
app.config['HEADER_TABS'] = json.load(open(
os.path.join(app.config['STATIC_FOLDER'], 'settings/header_tabs.json'),
encoding='utf-8'))
app.config['CONFIG_PATH'] = os.getenv(
'CONFIG_VOLUME',
os.path.join(app.config['STATIC_FOLDER'], 'config'))

@ -1,5 +1,6 @@
from app.models.config import Config
from app.models.endpoint import Endpoint
from app.models.g_classes import GClasses
from app.request import VALID_PARAMS, MAPS_URL
from app.utils.misc import read_config_bool
from app.utils.results import *
@ -13,6 +14,15 @@ from urllib.parse import parse_qs
import os
minimal_mode_sections = ['Top stories', 'Images', 'People also ask']
unsupported_g_pages = [
'support.google.com',
'accounts.google.com',
'policies.google.com',
'google.com/preferences',
'google.com/intl',
'advanced_search',
'tbm=shop'
]
def extract_q(q_str: str, href: str) -> str:
@ -80,6 +90,7 @@ class Filter:
self.remove_block_url()
self.collapse_sections()
self.update_styling(soup)
self.remove_block_tabs(soup)
for img in [_ for _ in soup.find_all('img') if 'src' in _.attrs]:
self.update_element_src(img, 'image/png')
@ -143,6 +154,21 @@ class Filter:
if block_url.search(_.attrs['href']) is not None]
_ = div.decompose() if len(block_divs) else None
def remove_block_tabs(self, soup) -> None:
if self.main_divs:
for div in self.main_divs.find_all(
'div',
attrs={'class': f'{GClasses.main_tbm_tab}'}
):
_ = div.decompose()
else:
# when in images tab
for div in soup.find_all(
'div',
attrs={'class': f'{GClasses.images_tbm_tab}'}
):
_ = div.decompose()
def collapse_sections(self) -> None:
"""Collapses long result sections ("people also asked", "related
searches", etc) into "details" elements
@ -273,6 +299,26 @@ class Filter:
except AttributeError:
pass
# Fix body max width on images tab
style = soup.find('style')
div = soup.find('div', attrs={'class': f'{GClasses.images_tbm_tab}'})
if style and div and not self.mobile:
css = style.string
css_html_tag = (
'html{'
'font-family: Roboto, Helvetica Neue, Arial, sans-serif;'
'font-size: 14px;'
'line-height: 20px;'
'text-size-adjust: 100%;'
'word-wrap: break-word;'
'}'
)
css = f"{css_html_tag}{css}"
css = re.sub('body{(.*?)}',
'body{padding:0 8px;margin:0 auto;max-width:736px;}',
css)
style.string = css
def update_link(self, link: Tag) -> None:
"""Update internal link paths with encrypted path, otherwise remove
unnecessary redirects and/or marketing params from the url
@ -284,14 +330,15 @@ class Filter:
None (the tag is updated directly)
"""
# Replace href with only the intended destination (no "utm" type tags)
href = link['href'].replace('https://www.google.com', '')
if 'advanced_search' in href or 'tbm=shop' in href:
# Remove any elements that direct to unsupported Google pages
if any(url in link['href'] for url in unsupported_g_pages):
# FIXME: The "Shopping" tab requires further filtering (see #136)
# Temporarily removing all links to that tab for now.
link.decompose()
return
# Replace href with only the intended destination (no "utm" type tags)
href = link['href'].replace('https://www.google.com', '')
result_link = urlparse.urlparse(href)
q = extract_q(result_link.query, href)
@ -362,11 +409,8 @@ class Filter:
"""
# 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
@ -404,12 +448,7 @@ class Filter:
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(
@ -419,7 +458,4 @@ class Filter:
# 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

@ -0,0 +1,16 @@
from enum import Enum
class GClasses(Enum):
"""A class for tracking obfuscated class names used in Google results that
are directly referenced in Whoogle's filtering code.
Note: Using these should be a last resort. It is always preferred to filter
results using structural cues instead of referencing class names, as these
are liable to change at any moment.
"""
main_tbm_tab = 'KP7LCb'
images_tbm_tab = 'n692Zd'
def __str__(self):
return self.value

@ -1,6 +1,7 @@
import argparse
import base64
import io
import os
import json
import os
import pickle
@ -17,14 +18,15 @@ from app.request import Request, TorError
from app.utils.bangs import resolve_bang
from app.utils.misc import read_config_bool, get_client_ip, get_request_url
from app.utils.results import add_ip_card, bold_search_terms,\
add_currency_card, check_currency
from app.utils.search import *
add_currency_card, check_currency, get_tabs_content
from app.utils.search import Search, needs_https, has_captcha
from app.utils.session import generate_user_key, valid_user_session
from bs4 import BeautifulSoup as bsoup
from flask import jsonify, make_response, request, redirect, render_template, \
send_file, session, url_for
send_file, session, url_for, g
from requests import exceptions, get
from requests.models import PreparedRequest
from cryptography.fernet import Fernet
# Load DDG bang json files only on init
bang_json = json.load(open(app.config['BANG_FILE'])) or {}
@ -347,6 +349,12 @@ def search():
html_soup = bsoup(str(response), 'html.parser')
response = add_ip_card(html_soup, get_client_ip(request))
# Update tabs content
tabs = get_tabs_content(app.config['HEADER_TABS'],
search_util.full_query,
search_util.search_type,
translation)
# Feature to display currency_card
conversion = check_currency(str(response))
if conversion:
@ -373,15 +381,14 @@ def search():
) and not search_util.search_type, # Standard search queries only
response=response,
version_number=app.config['VERSION_NUMBER'],
search_header=(render_template(
search_header=render_template(
'header.html',
config=g.user_config,
logo=render_template('logo.html', dark=g.user_config.dark),
query=urlparse.unquote(query),
search_type=search_util.search_type,
mobile=g.user_request.mobile)
if 'isch' not in
search_util.search_type else '')), 200
mobile=g.user_request.mobile,
tabs=tabs))
@app.route(f'/{Endpoint.config}', methods=['GET', 'POST', 'PUT'])

@ -74,6 +74,7 @@ select {
.ZINbbc.luh4tb {
background: var(--whoogle-dark-result-bg) !important;
margin-bottom: 24px !important;
}
.bRsWnc {
@ -204,3 +205,7 @@ path {
.cb:focus {
color: var(--whoogle-dark-contrast-text) !important;
}
.desktop-header, .mobile-header {
background-color: var(--whoogle-dark-result-bg) !important;
}

@ -98,6 +98,11 @@ header {
border: 0px !important;
}
.autocomplete-mobile{
display: -webkit-box;
width: 100%;
}
.desktop-header-logo {
height: 1.65em;
}
@ -106,3 +111,113 @@ header {
width: 100%;
flex: 1
}
a {
color: #1967D2;
text-decoration: none;
tap-highlight-color: rgba(0, 0, 0, .10);
}
.header-tab-div {
border-radius: 0 0 8px 8px;
box-shadow: 0 2px 3px rgba(32, 33, 36, 0.18);
margin-bottom: 20px;
overflow: hidden;
}
.header-tab-div-2 {
border-top: 1px solid #dadce0;
height: 39px;
overflow: hidden;
}
.header-tab-div-3 {
height: 51px;
overflow-x: auto;
overflow-y: hidden;
}
.desktop-header {
height: 39px;
display: box;
display: flex;
width: 100%;
}
.header-tab {
box-pack: justify;
font-size: 14px;
line-height: 37px;
justify-content: space-between;
}
.desktop-header a, .desktop-header span {
color: #70757a;
display: block;
flex: none;
padding: 0 16px;
text-align: center;
text-transform: uppercase;
}
span.header-tab-span {
border-bottom: 2px solid #4285f4;
color: #4285f4;
font-weight: bold;
}
.mobile-header {
height: 39px;
display: box;
display: flex;
overflow-x: scroll;
width: 100%;
padding-left: 12px;
}
.mobile-header a, .mobile-header span {
color: #70757a;
text-decoration: none;
display: inline-block;
/* padding: 8px 12px 8px 12px; */
}
span.mobile-tab-span {
border-bottom: 2px solid #202124;
color: #202124;
height: 26px;
/* margin: 0 12px; */
/* padding: 0; */
}
.desktop-header input {
margin: 2px 4px 2px 8px;
}
a.header-tab-a:visited {
color: #70757a;
}
.header-tab-div-end {
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
.search-bar-input {
display: block;
font-size: 16px;
padding: 0 0 0 8px;
flex: 1;
height: 35px;
outline: none;
border: none;
width: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
overflow: hidden;
}
@media (max-width: 801px) {
.header-tab-div {
margin-bottom: 10px !important
}
}

@ -43,6 +43,8 @@ select {
.ZINbbc.luh4tb {
background: var(--whoogle-result-bg) !important;
box-shadow: 0 1px 6px rgba(32,33,36,0.28) !important;
margin-bottom: 24px !important;
}
.bRsWnc {
@ -190,3 +192,7 @@ path {
.cb:focus {
color: var(--whoogle-text) !important;
}
.desktop-header, .mobile-header {
background-color: var(--whoogle-result-bg) !important;
}

@ -31,8 +31,9 @@ body {
}
details summary {
padding: 10px;
margin-bottom: 20px;
font-weight: bold;
padding-left: 10px;
}
details summary span {
@ -53,8 +54,18 @@ details summary span {
padding-top: 0 !important;
}
.footer {
text-align: center;
}
@media (min-width: 801px) {
body {
min-width: 736px !important;
}
}
@media (max-width: 801px) {
details summary {
margin-bottom: 10px !important
}
}

@ -0,0 +1,38 @@
{
"all": {
"tbm": null,
"href": "search?q={query}",
"name": "All",
"selected": true
},
"images": {
"tbm": "isch",
"href": "search?q={query}",
"name": "Images",
"selected": false
},
"maps": {
"tbm": null,
"href": "https://maps.google.com/maps?q={query}",
"name": "Maps",
"selected": false
},
"videos": {
"tbm": "vid",
"href": "search?q={query}",
"name": "Videos",
"selected": false
},
"news": {
"tbm": "nws",
"href": "search?q={query}",
"name": "News",
"selected": false
},
"books": {
"tbm": "bks",
"href": "search?q={query}",
"name": "Books",
"selected": false
}
}

@ -35,7 +35,13 @@
"dark": "dark",
"system": "system",
"ratelimit": "Instance has been ratelimited",
"continue-search": "Continue your search with Farside"
"continue-search": "Continue your search with Farside",
"all": "All",
"images": "Images",
"maps": "Maps",
"videos": "Videos",
"news": "News",
"books": "Books"
},
"lang_nl": {
"search": "Zoeken",
@ -73,7 +79,13 @@
"dark": "donker",
"system": "systeeminstellingen",
"ratelimit": "Instantie is beperkt in snelheid",
"continue-search": "Ga verder met zoeken met Farside"
"continue-search": "Ga verder met zoeken met Farside",
"all": "Alle",
"images": "Afbeeldingen",
"maps": "Maps",
"videos": "Videos",
"news": "Nieuws",
"books": "Boeken"
},
"lang_de": {
"search": "Suchen",
@ -111,7 +123,13 @@
"dark": "dunkel",
"system": "Systemeinstellung",
"ratelimit": "Instanz wurde ratenbegrenzt",
"continue-search": "Setzen Sie Ihre Suche fort mit Farside"
"continue-search": "Setzen Sie Ihre Suche fort mit Farside",
"all": "Alle",
"images": "Bilder",
"maps": "Maps",
"videos": "Videos",
"news": "Nieuws",
"books": "Bücher"
},
"lang_es": {
"search": "Buscar",
@ -149,7 +167,13 @@
"dark": "oscuro",
"system": "configuración del sistema",
"ratelimit": "La instancia ha sido ratelimited",
"continue-search": "Continúe su búsqueda con Farside"
"continue-search": "Continúe su búsqueda con Farside",
"all": "Todo",
"images": "Imágenes",
"maps": "Maps",
"videos": "Vídeos",
"news": "Noticias",
"books": "Libros"
},
"lang_it": {
"search": "Cerca",
@ -187,7 +211,13 @@
"dark": "notte",
"system": "impostazioni di sistema",
"ratelimit": "L'istanza è stata limitata alla velocità",
"continue-search": "Continua la tua ricerca con Farside"
"continue-search": "Continua la tua ricerca con Farside",
"all": "Tutti",
"images": "Immagini",
"maps": "Maps",
"videos": "Video",
"news": "Notizie",
"books": "Libri"
},
"lang_pt": {
"search": "Pesquisar",
@ -225,7 +255,13 @@
"dark": "escuro",
"system": "configuração de sistema",
"ratelimit": "A instância foi limitada pela taxa",
"continue-search": "Continue sua pesquisa com Farside"
"continue-search": "Continue sua pesquisa com Farside",
"all": "Todas",
"images": "Imagens",
"maps": "Maps",
"videos": "Vídeos",
"news": "Notícias",
"books": "Livros"
},
"lang_ru": {
"search": "Поиск",
@ -263,7 +299,13 @@
"dark": "темное",
"system": "системное",
"ratelimit": "Число экземпляров ограничено",
"continue-search": "Продолжайте поиск с Farside"
"continue-search": "Продолжайте поиск с Farside",
"all": "Все",
"images": "Картинки",
"maps": "Карты",
"videos": "Видео",
"news": "Новости",
"books": "Книги"
},
"lang_zh-CN": {
"search": "搜索",
@ -301,7 +343,13 @@
"dark": "黑暗的",
"system": "系统设置",
"ratelimit": "实例已被限速",
"continue-search": "继续搜索 Farside"
"continue-search": "继续搜索 Farside",
"all": "全部",
"images": "圖片",
"maps": "影片",
"videos": "地圖",
"news": "新聞",
"books": "書籍"
},
"lang_si": {
"search": "සොයන්න",
@ -339,7 +387,13 @@
"dark": "අඳුරු",
"system": "පද්ධතිය",
"ratelimit": "සේවාදායකය අනුපාතනය කර ඇත",
"continue-search": "Farside සමඟ ඔබගේ සෙවුම කරගෙන යන්න"
"continue-search": "Farside සමඟ ඔබගේ සෙවුම කරගෙන යන්න",
"all": "සියල්ල",
"images": "රූප",
"maps": "සිතියම්",
"videos": "වීඩියෝ",
"news": "අනුරූප",
"books": "පොත්"
},
"lang_fr": {
"search": "Chercher",
@ -377,7 +431,13 @@
"dark": "sombre",
"system": "système",
"ratelimit": "Le débit de l'instance a été limité",
"continue-search": "Continuez votre recherche avec Farside"
"continue-search": "Continuez votre recherche avec Farside",
"all": "Tous",
"images": "Images",
"maps": "Maps",
"videos": "Vidéos",
"news": "Actualités",
"books": "Livres"
},
"lang_fa": {
"search": "جستجو",
@ -415,7 +475,13 @@
"dark": "تیره",
"system": "سیستم",
"ratelimit": "نمونه با نرخ محدود شده است",
"continue-search": "Farside جستجوی خود را با "
"continue-search": "Farside جستجوی خود را با ",
"all": "همه",
"images": "تصاویر",
"maps": "نقشه‌ها",
"videos": "ویدئوها",
"news": "اخبار",
"books": "کتاب‌ها"
},
"lang_cs": {
"search": "Hledat",
@ -453,7 +519,13 @@
"dark": "Tmavý",
"system": "Systémový",
"ratelimit": "Instance byla omezena sazbou",
"continue-search": "Pokračujte ve vyhledávání pomocí Farside"
"continue-search": "Pokračujte ve vyhledávání pomocí Farside",
"all": "Vše",
"images": "Obrázky",
"maps": "Mapy",
"videos": "Videa",
"news": "Zprávy",
"books": "Knihy"
},
"lang_zh-TW": {
"search": "搜尋",
@ -491,7 +563,13 @@
"dark": "黑暗的",
"system": "依系統",
"ratelimit": "實例已被限速",
"continue-search": "繼續搜索 Farside"
"continue-search": "繼續搜索 Farside",
"all": "全部",
"images": "圖片",
"maps": "影片",
"videos": "地圖",
"news": "新聞",
"books": "書籍"
},
"lang_bg": {
"search": "Търсене",
@ -529,7 +607,13 @@
"dark": "тъмна",
"system": "системна",
"ratelimit": "Екземплярът е с ограничена скорост",
"continue-search": "Продължете търсенето си с Farside"
"continue-search": "Продължете търсенето си с Farside",
"all": "Всичкo",
"images": "Изображения",
"maps": "Видеоклипове",
"videos": "Новини",
"news": "Карти",
"books": "Книги"
},
"lang_hi": {
"search": "खोज",
@ -567,7 +651,13 @@
"dark": "अंधेरा",
"system": "प्रणाली",
"ratelimit": "इंस्टेंस को सीमित कर दिया गया है",
"continue-search": "के साथ अपनी खोज जारी रखें Farside"
"continue-search": "के साथ अपनी खोज जारी रखें Farside",
"all": "सभी",
"images": "इमेज",
"maps": "वीडियो",
"videos": "मैप",
"news": "समाचार",
"books": "किताबें"
},
"lang_ja": {
"search": "検索",
@ -605,6 +695,12 @@
"dark": "ダーク",
"system": "自動",
"ratelimit": "インスタンスはレート制限されています",
"continue-search": "で検索を続ける Farside"
"continue-search": "で検索を続ける Farside",
"all": "すべて",
"images": "画像",
"maps": "地図",
"videos": "動画",
"news": "ニュース",
"books": "書籍"
}
}

@ -1,7 +1,7 @@
{% if mobile %}
<header>
<div class="bz1lBb header-div">
<form class="search-form Pg70bf"
<div class="header-div">
<form class="search-form header"
id="search-form"
method="{{ 'GET' if config.get_only else 'POST' }}">
<a class="logo-link mobile-logo" href="home">
@ -10,7 +10,7 @@
</div>
</a>
<div class="H0PQec mobile-input-div">
<div class="sbc esbc autocomplete">
<div class="autocomplete-mobile esbc autocomplete">
<input
id="search-bar"
class="mobile-search-bar"
@ -18,7 +18,7 @@
autocomplete="off"
autocorrect="off"
spellcheck="false"
class="noHIxc"
class="search-bar-input"
name="q"
type="text"
value="{{ clean_query(query) }}"
@ -31,6 +31,25 @@
</div>
</form>
</div>
<div>
<div class="header-tab-div">
<div class="header-tab-div-2">
<div class="header-tab-div-3">
<div class="mobile-header header-tab">
{% for tab_id, tab_content in tabs.items() %}
{% if tab_content['selected'] %}
<span class="mobile-tab-span">{{ tab_content['name'] }}</span>
{% else %}
<a class="header-tab-a" href="{{ tab_content['href'] }}">{{ tab_content['name'] }}</a>
{% endif %}
{% endfor %}
<div class="header-tab-div-end"></div>
</div>
</div>
</div>
</div>
<div class="" id="s">
</div>
</header>
{% else %}
<header>
@ -53,7 +72,7 @@
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="search-bar-desktop noHIxc"
class="search-bar-desktop search-bar-input"
name="q"
spellcheck="false"
type="text"
@ -67,6 +86,25 @@
</form>
</div>
</header>
<div>
<div class="header-tab-div">
<div class="header-tab-div-2">
<div class="header-tab-div-3">
<div class="desktop-header header-tab">
{% for tab_id, tab_content in tabs.items() %}
{% if tab_content['selected'] %}
<span class="header-tab-span">{{ tab_content['name'] }}</span>
{% else %}
<a class="header-tab-a" href="{{ tab_content['href'] }}">{{ tab_content['name'] }}</a>
{% endif %}
{% endfor %}
<div class="header-tab-div-end"></div>
</div>
</div>
</div>
</div>
<div class="" id="s">
</div>
{% endif %}
<script type="text/javascript" src="{{ cb_url('header.js') }}"></script>

@ -1,116 +1,390 @@
<!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>
<div>
<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%}
html {
font-family: Roboto, Helvetica Neue, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
text-size-adjust: 100%;
color: #3c4043;
word-wrap: break-word;
background-color: #fff;
}
body {
padding: 0 8px;
margin: 0 auto;
max-width: 736px;
}
a {
text-decoration: none;
color: inherit;
}
a:hover {
text-decoration: underline;
}
a img {
border: 0;
}
.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%;
}
.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;
}
.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>
</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 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">
<td align="center" class="e3goi">
<div class="svla5d">
<div>
<div class="lIMUZd">
<div>
<table class="TxbwNb">
<tr>
<td>
<a href="{{ results[(i*4)+j].web_page }}">
<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].web_page }}">
<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 class="lIMUZd">
<div>
<table class="TxbwNb">
<tr>
<td>
<a href="{{ results[(i*4)+j].web_page }}">
<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].web_page }}">
<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>
</div>
</td>
{% endfor %}
</tr>
</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>
<br />
</div>

@ -1,5 +1,6 @@
from app.models.endpoint import Endpoint
from bs4 import BeautifulSoup, NavigableString
import copy
import html
import os
import urllib.parse as urlparse
@ -329,3 +330,39 @@ def add_currency_card(soup: BeautifulSoup,
element1.insert_before(conversion_box)
return soup
def get_tabs_content(tabs: dict,
full_query: str,
search_type: str,
translation: dict) -> dict:
"""Takes the default tabs content and updates it according to the query.
Args:
tabs: The default content for the tabs
full_query: The original search query
search_type: The current search_type
translation: The translation to get the names of the tabs
Returns:
dict: contains the name, the href and if the tab is selected or not
"""
tabs = copy.deepcopy(tabs)
for tab_id, tab_content in tabs.items():
# update name to desired language
if tab_id in translation:
tab_content['name'] = translation[tab_id]
# update href with query
query = full_query.replace(f'&tbm={search_type}', '')
if tab_content['tbm'] is not None:
query = f"{query}&tbm={tab_content['tbm']}"
tab_content['href'] = tab_content['href'].format(query=query)
# update if selected tab (default all tab is selected)
if tab_content['tbm'] == search_type:
tabs['all']['selected'] = False
tab_content['selected'] = True
return tabs

@ -120,6 +120,7 @@ class Search:
full_query = gen_query(self.query,
self.request_params,
self.config)
self.full_query = full_query
# force mobile search when view image is true and
# the request is not already made by a mobile

Loading…
Cancel
Save