Add "view image" functionality (#268)
* add view image option * prevent whoogle links from opening in a new tab. * remove view image template on mobile requests * change loop values to be more robust to the number of images * Update app/templates/imageresults.html * fix "Basically the .cvifge class needs width: 100%; in order to expand the search input to fit the form width." * Update app/templates/imageresults.html * remove hardcoded string from template * Add view image config var to app.json * Add view image config var to whoogle.env Co-authored-by: jacr13 <ramos.joao@protonmail.com> Co-authored-by: Ben Busby <benbusby@protonmail.com>main
parent
fcfa3783e3
commit
448efb8f2a
17
README.md
17
README.md
|
@ -103,17 +103,25 @@ Sandboxed temporary instance:
|
|||
|
||||
```bash
|
||||
$ whoogle-search --help
|
||||
usage: whoogle-search [-h] [--port <port number>] [--host <ip address>] [--debug]
|
||||
[--https-only]
|
||||
usage: whoogle-search [-h] [--port <port number>] [--host <ip address>] [--debug] [--https-only] [--userpass <username:password>]
|
||||
[--proxyauth <username:password>] [--proxytype <socks4|socks5|http>] [--proxyloc <location:port>]
|
||||
|
||||
Whoogle Search console runner
|
||||
|
||||
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)
|
||||
--host <ip address> Specifies the host address to use (default 127.0.0.1)
|
||||
--debug Activates debug mode for the server (default False)
|
||||
--https-only Enforces HTTPS redirects for all requests (default False)
|
||||
--https-only Enforces HTTPS redirects for all requests
|
||||
--userpass <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.
|
||||
|
||||
|
@ -286,6 +294,7 @@ These environment variables allow setting default config values, but can be over
|
|||
| WHOOGLE_CONFIG_ALTS | Use social media site alternatives (nitter, invidious, etc) |
|
||||
| WHOOGLE_CONFIG_TOR | Use Tor routing (if available) |
|
||||
| WHOOGLE_CONFIG_NEW_TAB | Always open results in new tab |
|
||||
| WHOOGLE_CONFIG_VIEW_IMAGE | Enable View Image option |
|
||||
| WHOOGLE_CONFIG_GET_ONLY | Search using GET requests only |
|
||||
| WHOOGLE_CONFIG_URL | The root url of the instance (`https://<your url>/`) |
|
||||
| WHOOGLE_CONFIG_STYLE | The custom CSS to use for styling (should be single line) |
|
||||
|
|
5
app.json
5
app.json
|
@ -114,6 +114,11 @@
|
|||
"description": "[CONFIG] Always open results in new tab (set to 1 or leave blank)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_CONFIG_VIEW_IMAGE": {
|
||||
"description": "[CONFIG] Enable View Image option (set to 1 or leave blank)",
|
||||
"value": "",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_CONFIG_GET_ONLY": {
|
||||
"description": "[CONFIG] Search using GET requests only (set to 1 or leave blank)",
|
||||
|
|
|
@ -254,3 +254,65 @@ class Filter:
|
|||
|
||||
# Replace link destination
|
||||
link_desc[0].replace_with(get_site_alt(link_desc[0]))
|
||||
|
||||
def view_image(self, soup) -> BeautifulSoup:
|
||||
"""Replaces the soup with a new one that handles mobile results and
|
||||
adds the link of the image full res to the results.
|
||||
|
||||
Args:
|
||||
soup: A BeautifulSoup object containing the image mobile results.
|
||||
|
||||
Returns:
|
||||
BeautifulSoup: The new BeautifulSoup object
|
||||
"""
|
||||
|
||||
# get some tags that are unchanged between mobile and pc versions
|
||||
search_input = soup.find_all('td', attrs={'class': "O4cRJf"})[0]
|
||||
search_options = soup.find_all('div', attrs={'class': "M7pB2"})[0]
|
||||
cor_suggested = soup.find_all('table', attrs={'class': "By0U9"})
|
||||
next_pages = soup.find_all('table', attrs={'class': "uZgmoc"})[0]
|
||||
information = soup.find_all('div', attrs={'class': "TuS8Ad"})[0]
|
||||
|
||||
results = []
|
||||
# find results div
|
||||
results_div = soup.find_all('div', attrs={'class': "nQvrDb"})[0]
|
||||
# find all the results
|
||||
results_all = results_div.find_all('div', attrs={'class': "lIMUZd"})
|
||||
|
||||
for item in results_all:
|
||||
urls = item.find('a')['href'].split('&imgrefurl=')
|
||||
|
||||
img_url = urlparse.unquote(urls[0].replace('/imgres?imgurl=', ''))
|
||||
webpage = urlparse.unquote(urls[1].split('&')[0])
|
||||
img_tbn = urlparse.unquote(item.find('a').find('img')['src'])
|
||||
results.append({
|
||||
'domain': urlparse.urlparse(webpage).netloc,
|
||||
'img_url': img_url,
|
||||
'webpage': webpage,
|
||||
'img_tbn': img_tbn
|
||||
})
|
||||
|
||||
soup = BeautifulSoup(render_template('imageresults.html',
|
||||
length=len(results),
|
||||
results=results,
|
||||
view_label="View Image"),
|
||||
features='html.parser')
|
||||
# replace search input object
|
||||
soup.find_all('td',
|
||||
attrs={'class': "O4cRJf"})[0].replaceWith(search_input)
|
||||
# replace search options object (All, Images, Videos, etc.)
|
||||
soup.find_all('div',
|
||||
attrs={'class': "M7pB2"})[0].replaceWith(search_options)
|
||||
# replace correction suggested by google object if exists
|
||||
if len(cor_suggested):
|
||||
soup.find_all(
|
||||
'table',
|
||||
attrs={'class': "By0U9"}
|
||||
)[0].replaceWith(cor_suggested[0])
|
||||
# replace next page object at the bottom of the page
|
||||
soup.find_all('table',
|
||||
attrs={'class': "uZgmoc"})[0].replaceWith(next_pages)
|
||||
# replace information about user connection at the bottom of the page
|
||||
soup.find_all('div',
|
||||
attrs={'class': "TuS8Ad"})[0].replaceWith(information)
|
||||
return soup
|
||||
|
|
|
@ -27,7 +27,9 @@ class Config:
|
|||
self.tor = read_config_bool('WHOOGLE_CONFIG_TOR')
|
||||
self.near = os.getenv('WHOOGLE_CONFIG_NEAR', '')
|
||||
self.new_tab = read_config_bool('WHOOGLE_CONFIG_NEW_TAB')
|
||||
self.view_image = read_config_bool('WHOOGLE_CONFIG_VIEW_IMAGE')
|
||||
self.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY')
|
||||
|
||||
self.safe_keys = [
|
||||
'lang_search',
|
||||
'lang_interface',
|
||||
|
|
|
@ -151,6 +151,8 @@ class Request:
|
|||
self.language = config.lang_search
|
||||
self.mobile = 'Android' in normal_ua or 'iPhone' in normal_ua
|
||||
self.modified_user_agent = gen_user_agent(self.mobile)
|
||||
if not self.mobile:
|
||||
self.modified_user_agent_mobile = gen_user_agent(True)
|
||||
|
||||
# Set up proxy, if previously configured
|
||||
if os.environ.get('WHOOGLE_PROXY_LOC'):
|
||||
|
@ -197,7 +199,8 @@ class Request:
|
|||
return [_.attrib['data'] for _ in
|
||||
root.findall('.//suggestion/[@data]')]
|
||||
|
||||
def send(self, base_url=SEARCH_URL, query='', attempt=0) -> Response:
|
||||
def send(self, base_url=SEARCH_URL, query='', attempt=0,
|
||||
force_mobile=False) -> Response:
|
||||
"""Sends an outbound request to a URL. Optionally sends the request
|
||||
using Tor, if enabled by the user.
|
||||
|
||||
|
@ -211,8 +214,13 @@ class Request:
|
|||
Response: The Response object returned by the requests call
|
||||
|
||||
"""
|
||||
if force_mobile and not self.mobile:
|
||||
modified_user_agent = self.modified_user_agent_mobile
|
||||
else:
|
||||
modified_user_agent = self.modified_user_agent
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.modified_user_agent
|
||||
'User-Agent': modified_user_agent
|
||||
}
|
||||
|
||||
# FIXME: Should investigate this further to ensure the consent
|
||||
|
|
|
@ -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&gbv=1&output=images&ie=UTF-8&tbm=isch&sa=X&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>
|
|
@ -132,6 +132,12 @@
|
|||
<label for="config-new-tab">Open Links in New Tab: </label>
|
||||
<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">Full Size Image Search: </label>
|
||||
<input type="checkbox" name="view_image" id="config-view-image" {{ 'checked' if config.view_image else '' }}>
|
||||
<div><span class="info-text"> — (Experimental) Adds the "View Image" option on desktop to view full size images in search results.
|
||||
This will cause image result thumbnails to be lower resolution.</span></div>
|
||||
</div>
|
||||
<div class="config-div config-div-tor">
|
||||
<label for="config-tor">Use 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 '' }}>
|
||||
|
|
|
@ -119,11 +119,23 @@ class Search:
|
|||
self.request_params,
|
||||
self.config,
|
||||
content_filter.near)
|
||||
get_body = g.user_request.send(query=full_query)
|
||||
|
||||
# force mobile search when view image is true and
|
||||
# the request is not already made by a mobile
|
||||
view_image = ('tbm=isch' in full_query
|
||||
and self.config.view_image
|
||||
and not g.user_request.mobile)
|
||||
|
||||
get_body = g.user_request.send(query=full_query,
|
||||
force_mobile=view_image)
|
||||
|
||||
# Produce cleanable html soup from response
|
||||
html_soup = bsoup(content_filter.reskin(get_body.text), 'html.parser')
|
||||
|
||||
# Replace current soup if view_image is active
|
||||
if view_image:
|
||||
html_soup = content_filter.view_image(html_soup)
|
||||
|
||||
# Indicate whether or not a Tor connection is active
|
||||
tor_banner = bsoup('', 'html.parser')
|
||||
if g.user_request.tor_valid:
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
# Open results in new tab
|
||||
#WHOOGLE_CONFIG_NEW_TAB=1
|
||||
|
||||
# Enable View Image option
|
||||
#WHOOGLE_CONFIG_VIEW_IMAGE=1
|
||||
|
||||
# Search using GET requests only (exposes query in logs)
|
||||
#WHOOGLE_CONFIG_GET_ONLY=1
|
||||
|
||||
|
|
Loading…
Reference in New Issue