Merge branch 'main' into dev-header-tabs
This commit is contained in:
commit
1b6d4fe243
2
.github/workflows/buildx.yml
vendored
2
.github/workflows/buildx.yml
vendored
|
@ -2,7 +2,7 @@ name: buildx
|
|||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["tests"]
|
||||
workflows: ["docker_tests"]
|
||||
branches: [main]
|
||||
types:
|
||||
- completed
|
||||
|
|
21
.github/workflows/docker_tests.yml
vendored
Normal file
21
.github/workflows/docker_tests.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: docker_tests
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["tests"]
|
||||
branches: [main]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
on-success:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: build and test
|
||||
run: |
|
||||
docker build --tag whoogle-search:test .
|
||||
docker run --publish 5000:5000 --detach --name whoogle-search whoogle-search:test
|
||||
sleep 15
|
||||
docker exec whoogle-search curl -f http://localhost:5000/healthz || exit 1
|
4
.replit
4
.replit
|
@ -1,3 +1,3 @@
|
|||
language = "bash"
|
||||
run = "pip install -r requirements.txt && ./run"
|
||||
onBoot = "pip install -r requirements.txt && ./run"
|
||||
run = "killall -q python3 > /dev/null 2>&1; pip install -r requirements.txt && ./run"
|
||||
onBoot = "killall -q python3 > /dev/null 2>&1; pip install -r requirements.txt && ./run"
|
||||
|
|
81
Dockerfile
81
Dockerfile
|
@ -1,64 +1,64 @@
|
|||
FROM python:3.8-slim as builder
|
||||
FROM python:3.8-alpine as builder
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
RUN apk --update add \
|
||||
build-base \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
libssl-dev \
|
||||
openssl-dev \
|
||||
libffi-dev
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install --prefix /install --no-warn-script-location --no-cache-dir -r requirements.txt
|
||||
|
||||
FROM python:3.8-slim
|
||||
FROM python:3.8-alpine
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libcurl4-openssl-dev \
|
||||
tor \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --update --no-cache tor curl bash openrc
|
||||
# libcurl4-openssl-dev
|
||||
|
||||
ARG DOCKER_USER=whoogle
|
||||
ARG DOCKER_USERID=927
|
||||
ARG config_dir=/config
|
||||
RUN mkdir -p $config_dir
|
||||
RUN mkdir -p -m 777 $config_dir
|
||||
VOLUME $config_dir
|
||||
ENV CONFIG_VOLUME=$config_dir
|
||||
|
||||
ARG username=''
|
||||
ENV WHOOGLE_USER=$username
|
||||
ARG password=''
|
||||
ENV WHOOGLE_PASS=$password
|
||||
|
||||
ARG proxyuser=''
|
||||
ENV WHOOGLE_PROXY_USER=$proxyuser
|
||||
ARG proxypass=''
|
||||
ENV WHOOGLE_PROXY_PASS=$proxypass
|
||||
ARG proxytype=''
|
||||
ENV WHOOGLE_PROXY_TYPE=$proxytype
|
||||
ARG proxyloc=''
|
||||
ENV WHOOGLE_PROXY_LOC=$proxyloc
|
||||
|
||||
ARG whoogle_dotenv=''
|
||||
ENV WHOOGLE_DOTENV=$whoogle_dotenv
|
||||
|
||||
ARG use_https=''
|
||||
ENV HTTPS_ONLY=$use_https
|
||||
|
||||
ARG whoogle_port=5000
|
||||
ENV EXPOSE_PORT=$whoogle_port
|
||||
|
||||
ARG twitter_alt='nitter.net'
|
||||
ENV WHOOGLE_ALT_TW=$twitter_alt
|
||||
ARG youtube_alt='invidious.snopyta.org'
|
||||
ENV WHOOGLE_ALT_YT=$youtube_alt
|
||||
ARG instagram_alt='bibliogram.art/u'
|
||||
ENV WHOOGLE_ALT_IG=$instagram_alt
|
||||
ARG reddit_alt='libredd.it'
|
||||
ENV WHOOGLE_ALT_RD=$reddit_alt
|
||||
ARG twitter_alt='farside.link/nitter'
|
||||
ARG youtube_alt='farside.link/invidious'
|
||||
ARG instagram_alt='farside.link/bibliogram'
|
||||
ARG reddit_alt='farside.link/libreddit'
|
||||
ARG medium_alt='farside.link/scribe'
|
||||
ARG translate_alt='lingva.ml'
|
||||
ENV WHOOGLE_ALT_TL=$translate_alt
|
||||
ARG medium_alt='scribe.rip'
|
||||
ENV WHOOGLE_ALT_MD=$medium_alt
|
||||
ARG imgur_alt='imgin.voidnet.tech'
|
||||
ARG wikipedia_alt='wikiless.org'
|
||||
|
||||
ENV CONFIG_VOLUME=$config_dir \
|
||||
WHOOGLE_USER=$username \
|
||||
WHOOGLE_PASS=$password \
|
||||
WHOOGLE_PROXY_USER=$proxyuser \
|
||||
WHOOGLE_PROXY_PASS=$proxypass \
|
||||
WHOOGLE_PROXY_TYPE=$proxytype \
|
||||
WHOOGLE_PROXY_LOC=$proxyloc \
|
||||
WHOOGLE_DOTENV=$whoogle_dotenv \
|
||||
HTTPS_ONLY=$use_https \
|
||||
EXPOSE_PORT=$whoogle_port \
|
||||
WHOOGLE_ALT_TW=$twitter_alt \
|
||||
WHOOGLE_ALT_YT=$youtube_alt \
|
||||
WHOOGLE_ALT_IG=$instagram_alt \
|
||||
WHOOGLE_ALT_RD=$reddit_alt \
|
||||
WHOOGLE_ALT_MD=$medium_alt \
|
||||
WHOOGLE_ALT_TL=$translate_alt \
|
||||
WHOOGLE_ALT_IMG=$imgur_alt \
|
||||
WHOOGLE_ALT_WIKI=$wikipedia_alt
|
||||
|
||||
WORKDIR /whoogle
|
||||
|
||||
|
@ -72,6 +72,13 @@ COPY run .
|
|||
# Allow writing symlinks to build dir
|
||||
RUN chown 102:102 app/static/build
|
||||
|
||||
# Create user/group to run as
|
||||
RUN adduser -D -g $DOCKER_USERID -u $DOCKER_USERID $DOCKER_USER
|
||||
# Fix ownership / permissions
|
||||
RUN chown -R ${DOCKER_USER}:${DOCKER_USER} /whoogle /var/lib/tor
|
||||
|
||||
USER $DOCKER_USER:$DOCKER_USER
|
||||
|
||||
EXPOSE $EXPOSE_PORT
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
|
|
64
README.md
64
README.md
|
@ -22,6 +22,7 @@ Contents
|
|||
6. [Manual](#f-manual)
|
||||
7. [Docker](#g-manual-docker)
|
||||
8. [Arch/AUR](#arch-linux--arch-based-distributions)
|
||||
9. [Helm/Kubernetes](#helm-chart-for-kubernetes)
|
||||
4. [Environment Variables and Configuration](#environment-variables)
|
||||
5. [Usage](#usage)
|
||||
6. [Extra Steps](#extra-steps)
|
||||
|
@ -163,7 +164,7 @@ See the [available environment variables](#environment-variables) for additional
|
|||
|
||||
### F) Manual
|
||||
|
||||
*Note: `Content-Security-Policy` headers are already sent by Whoogle -- you don't/shouldn't need to apply a CSP header yourself*
|
||||
*Note: `Content-Security-Policy` headers can be sent by Whoogle if you set `WHOOGLE_CSP`.*
|
||||
|
||||
Clone the repo and run the following commands to start the app in a local-only environment:
|
||||
|
||||
|
@ -178,7 +179,7 @@ pip install -r requirements.txt
|
|||
See the [available environment variables](#environment-variables) for additional configuration.
|
||||
|
||||
#### systemd Configuration
|
||||
After building the virtual environment, you can add the following to `/lib/systemd/system/whoogle.service` to set up a Whoogle Search systemd service:
|
||||
After building the virtual environment, you can add something like the following to `/lib/systemd/system/whoogle.service` to set up a Whoogle Search systemd service:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
|
@ -196,18 +197,26 @@ Description=Whoogle
|
|||
# Site alternative configurations, uncomment to enable
|
||||
# Note: If not set, the feature will still be available
|
||||
# with default values.
|
||||
#Environment=WHOOGLE_ALT_TW=nitter.net
|
||||
#Environment=WHOOGLE_ALT_YT=invidious.snopyta.org
|
||||
#Environment=WHOOGLE_ALT_IG=bibliogram.art/u
|
||||
#Environment=WHOOGLE_ALT_RD=libredd.it
|
||||
#Environment=WHOOGLE_ALT_TW=farside.link/nitter
|
||||
#Environment=WHOOGLE_ALT_YT=farside.link/invidious
|
||||
#Environment=WHOOGLE_ALT_IG=farside.link/bibliogram/u
|
||||
#Environment=WHOOGLE_ALT_RD=farside.link/libreddit
|
||||
#Environment=WHOOGLE_ALT_MD=farside.link/scribe
|
||||
#Environment=WHOOGLE_ALT_TL=lingva.ml
|
||||
#Environment=WHOOGLE_ALT_MD=scribe.rip
|
||||
#Environment=WHOOGLE_ALT_IMG=imgin.voidnet.tech
|
||||
#Environment=WHOOGLE_ALT_WIKI=wikiless.org
|
||||
# Load values from dotenv only
|
||||
#Environment=WHOOGLE_DOTENV=1
|
||||
Type=simple
|
||||
User=<username>
|
||||
WorkingDirectory=<whoogle_directory>
|
||||
ExecStart=<whoogle_directory>/venv/bin/python3 -um app --host 0.0.0.0 --port 5000
|
||||
# If installed as a package, add:
|
||||
ExecStart=<python_install_dir>/python3 <whoogle_install_dir>/whoogle-search --host 127.0.0.1 --port 5000
|
||||
# For example:
|
||||
# ExecStart=/usr/bin/python3 /home/my_username/.local/bin/whoogle-search --host 127.0.0.1 --port 5000
|
||||
# Otherwise if running the app from source, add:
|
||||
ExecStart=<whoogle_repo_dir>/run
|
||||
# For example:
|
||||
# ExecStart=/var/www/whoogle-search/run
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
@ -287,6 +296,13 @@ You may also edit environment variables from your app’s Settings tab in the He
|
|||
#### Arch Linux & Arch-based Distributions
|
||||
There is an [AUR package available](https://aur.archlinux.org/packages/whoogle-git/), as well as a pre-built and daily updated package available at [Chaotic-AUR](https://chaotic.cx).
|
||||
|
||||
#### Helm chart for Kubernetes
|
||||
To use the Kubernetes Helm Chart:
|
||||
1. Ensure you have [Helm](https://helm.sh/docs/intro/install/) `>=3.0.0` installed
|
||||
2. Clone this repository
|
||||
3. Update [charts/whoogle/values.yaml](./charts/whoogle/values.yaml) as desired
|
||||
4. Run `helm install whoogle ./charts/whoogle`
|
||||
|
||||
#### Using your own server, or alternative container deployment
|
||||
There are other methods for deploying docker containers that are well outlined in [this article](https://rollout.io/blog/the-shortlist-of-docker-hosting/), but there are too many to describe set up for each here. Generally it should be about the same amount of effort as the Heroku deployment.
|
||||
|
||||
|
@ -320,8 +336,12 @@ There are a few optional environment variables available for customizing a Whoog
|
|||
| WHOOGLE_ALT_RD | The reddit.com alternative to use when site alternatives are enabled in the config. |
|
||||
| WHOOGLE_ALT_TL | The Google Translate alternative to use. This is used for all "translate ____" searches. |
|
||||
| WHOOGLE_ALT_MD | The medium.com alternative to use when site alternatives are enabled in the config. |
|
||||
| WHOOGLE_ALT_IMG | The imgur.com alternative to use when site alternatives are enabled in the config. |
|
||||
| WHOOGLE_ALT_WIKI | The wikipedia.com alternative to use when site alternatives are enabled in the config. |
|
||||
| WHOOGLE_AUTOCOMPLETE | Controls visibility of autocomplete/search suggestions. Default on -- use '0' to disable |
|
||||
| WHOOGLE_MINIMAL | Remove everything except basic result cards from all search queries. |
|
||||
| WHOOGLE_CSP | Sets a default set of 'Content-Security-Policy' headers |
|
||||
| WHOOGLE_RESULTS_PER_PAGE | Set the number of results per page |
|
||||
|
||||
### Config Environment Variables
|
||||
These environment variables allow setting default config values, but can be overwritten manually by using the home page config menu. These allow a shortcut for destroying/rebuilding an instance to the same config state every time.
|
||||
|
@ -488,25 +508,33 @@ A lot of the app currently piggybacks on Google's existing support for fetching
|
|||
|
||||
## Public Instances
|
||||
|
||||
*Note: Use public instances at your own discretion. Maintainers of Whoogle do not personally validate the integrity of these instances, and popular public instances are more likely to be rate-limited or blocked.*
|
||||
*Note: Use public instances at your own discretion. The maintainers of Whoogle are only responsible for https://whoogle.fossho.st, and do not personally validate the integrity of any other instances. Popular public instances are more likely to be rate-limited or blocked.*
|
||||
|
||||
| Website | Country | Language | Cloudflare |
|
||||
|-|-|-|-|
|
||||
| [https://whoogle.fossho.st](https://whoogle.fossho.st) | 🇺🇸 US | Multi-choice | |
|
||||
| [https://search.albony.xyz](https://search.albony.xyz/) | 🇮🇳 IN | Multi-choice | |
|
||||
| [https://whoogle.sdf.org](https://whoogle.sdf.org) | 🇺🇸 US | Multi-choice |
|
||||
| [https://whoogle.kavin.rocks](https://whoogle.kavin.rocks) | 🇮🇳 IN | Unknown | ✅ |
|
||||
| [https://search.garudalinux.org](https://search.garudalinux.org) | 🇩🇪 DE | Multi-choice | |
|
||||
| [https://whooglesearch.net](https://whooglesearch.net) | 🇩🇪 DE | Spanish | |
|
||||
| [https://search.flawcra.cc](https://search.flawcra.cc) |🇩🇪 DE | Unknown | ✅ |
|
||||
| [https://search.exonip.de](https://search.exonip.de) | 🇳🇱 NL | Multi-choice | |
|
||||
| [https://s.alefvanoon.xyz](https://s.alefvanoon.xyz) | 🇺🇸 US | English | ✅ |
|
||||
| [https://search.flux.industries](https://search.flux.industries) | 🇩🇪 DE | German | ✅ |
|
||||
| [http://whoogledq5f5wly5p4i2ohnvjwlihnlg4oajjum2oeddfwqdwupbuhqd.onion](http://whoogledq5f5wly5p4i2ohnvjwlihnlg4oajjum2oeddfwqdwupbuhqd.onion) | 🇮🇳 IN | Unknown | |
|
||||
| [https://s.alefvanoon.xyz](https://s.alefvanoon.xyz) | 🇺🇸 US | Multi-choice | ✅ |
|
||||
| [https://www.whooglesearch.ml](https://www.whooglesearch.ml) | 🇺🇸 US | English | |
|
||||
| [https://search.sethforprivacy.com](https://search.sethforprivacy.com) | 🇩🇪 DE | English | |
|
||||
| [https://whoogle.dcs0.hu](https://whoogle.dcs0.hu) | 🇭🇺 HU | Multi-choice | ✅ |
|
||||
|
||||
* A checkmark in the "Cloudflare" category here refers to the use of the reverse proxy, [Cloudflare](https://cloudflare). The checkmark will not be listed for a site which uses Cloudflare DNS but rather the proxying service which grants Cloudflare the ability to monitor traffic to the website.
|
||||
* A checkmark in the "Cloudflare" category here refers to the use of the reverse proxy, [Cloudflare](https://cloudflare.com). The checkmark will not be listed for a site which uses Cloudflare DNS but rather the proxying service which grants Cloudflare the ability to monitor traffic to the website.
|
||||
|
||||
#### Onion Instances
|
||||
|
||||
| Website | Country | Language |
|
||||
|-|-|-|
|
||||
| [http://whoglqjdkgt2an4tdepberwqz3hk7tjo4kqgdnuj77rt7nshw2xqhqad.onion](http://whoglqjdkgt2an4tdepberwqz3hk7tjo4kqgdnuj77rt7nshw2xqhqad.onion) | 🇺🇸 US | Multi-choice
|
||||
| [http://nuifgsnbb2mcyza74o7illtqmuaqbwu4flam3cdmsrnudwcmkqur37qd.onion](http://nuifgsnbb2mcyza74o7illtqmuaqbwu4flam3cdmsrnudwcmkqur37qd.onion) | 🇩🇪 DE | English
|
||||
|
||||
## Screenshots
|
||||
#### Desktop
|
||||

|
||||

|
||||
|
||||
#### Mobile
|
||||

|
||||

|
||||
|
|
28
app.json
28
app.json
|
@ -47,22 +47,27 @@
|
|||
},
|
||||
"WHOOGLE_ALT_TW": {
|
||||
"description": "The site to use as a replacement for twitter.com when site alternatives are enabled in the config.",
|
||||
"value": "nitter.net",
|
||||
"value": "farside.link/nitter",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_YT": {
|
||||
"description": "The site to use as a replacement for youtube.com when site alternatives are enabled in the config.",
|
||||
"value": "invidious.snopyta.org",
|
||||
"value": "farside.link/invidious",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_IG": {
|
||||
"description": "The site to use as a replacement for instagram.com when site alternatives are enabled in the config.",
|
||||
"value": "bibliogram.art/u",
|
||||
"value": "farside.link/bibliogram/u",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_RD": {
|
||||
"description": "The site to use as a replacement for reddit.com when site alternatives are enabled in the config.",
|
||||
"value": "libredd.it",
|
||||
"value": "farside.link/libreddit",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_MD": {
|
||||
"description": "The site to use as a replacement for medium.com when site alternatives are enabled in the config.",
|
||||
"value": "farside.link/scribe",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_TL": {
|
||||
|
@ -70,11 +75,16 @@
|
|||
"value": "lingva.ml",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_MD": {
|
||||
"description": "The site to use as a replacement for medium.com when site alternatives are enabled in the config.",
|
||||
"value": "scribe.rip",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_IMG": {
|
||||
"description": "The site to use as a replacement for imgur.com when site alternatives are enabled in the config.",
|
||||
"value": "imgin.voidnet.tech",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_ALT_WIKI": {
|
||||
"description": "The site to use as a replacement for wikipedia.com when site alternatives are enabled in the config.",
|
||||
"value": "wikiless.org",
|
||||
"required": false
|
||||
},
|
||||
"WHOOGLE_MINIMAL": {
|
||||
"description": "Remove everything except basic result cards from all search queries (set to 1 or leave blank)",
|
||||
"value": "",
|
||||
|
|
|
@ -15,16 +15,21 @@ app = Flask(__name__, static_folder=os.path.dirname(
|
|||
os.path.abspath(__file__)) + '/static')
|
||||
|
||||
# Load .env file if enabled
|
||||
if os.getenv("WHOOGLE_DOTENV", ''):
|
||||
if os.getenv('WHOOGLE_DOTENV', ''):
|
||||
dotenv_path = '../whoogle.env'
|
||||
load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
dotenv_path))
|
||||
|
||||
app.default_key = generate_user_key()
|
||||
app.no_cookie_ips = []
|
||||
app.config['SECRET_KEY'] = os.urandom(32)
|
||||
app.config['SESSION_TYPE'] = 'filesystem'
|
||||
app.config['VERSION_NUMBER'] = '0.6.0'
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = 'strict'
|
||||
|
||||
if os.getenv('HTTPS_ONLY'):
|
||||
app.config['SESSION_COOKIE_NAME'] = '__Secure-session'
|
||||
app.config['SESSION_COOKIE_SECURE'] = True
|
||||
|
||||
app.config['VERSION_NUMBER'] = '0.7.0'
|
||||
app.config['APP_ROOT'] = os.getenv(
|
||||
'APP_ROOT',
|
||||
os.path.dirname(os.path.abspath(__file__)))
|
||||
|
@ -38,9 +43,11 @@ app.config['LANGUAGES'] = json.load(open(
|
|||
os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json'),
|
||||
encoding='utf-8'))
|
||||
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'),
|
||||
encoding='utf-8'))
|
||||
app.config['TRANSLATIONS'] = json.load(open(
|
||||
os.path.join(app.config['STATIC_FOLDER'], 'settings/translations.json')))
|
||||
os.path.join(app.config['STATIC_FOLDER'], 'settings/translations.json'),
|
||||
encoding='utf-8'))
|
||||
app.config['THEMES'] = json.load(open(
|
||||
os.path.join(app.config['STATIC_FOLDER'], 'settings/themes.json')))
|
||||
app.config['HEADER_TABS'] = json.load(open(
|
||||
|
@ -61,6 +68,8 @@ app.config['BANG_PATH'] = os.getenv(
|
|||
app.config['BANG_FILE'] = os.path.join(
|
||||
app.config['BANG_PATH'],
|
||||
'bangs.json')
|
||||
app.config['RELEASES_URL'] = 'https://github.com/' \
|
||||
'benbusby/whoogle-search/releases'
|
||||
|
||||
# The alternative to Google Translate is treated a bit differently than other
|
||||
# social media site alternatives, in that it is used for any translation
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from app.models.config import Config
|
||||
from app.models.endpoint import Endpoint
|
||||
from app.request import VALID_PARAMS, MAPS_URL
|
||||
from app.utils.misc import read_config_bool
|
||||
from app.utils.results import *
|
||||
|
@ -44,18 +46,8 @@ class Filter:
|
|||
# type result (such as "people also asked", "related searches", etc)
|
||||
RESULT_CHILD_LIMIT = 7
|
||||
|
||||
def __init__(self, user_key: str, mobile=False, config=None) -> None:
|
||||
if config is None:
|
||||
config = {}
|
||||
self.near = config['near'] if 'near' in config else ''
|
||||
self.dark = config['dark'] if 'dark' in config else False
|
||||
self.nojs = config['nojs'] if 'nojs' in config else False
|
||||
self.new_tab = config['new_tab'] if 'new_tab' in config else False
|
||||
self.alt_redirect = config['alts'] if 'alts' in config else False
|
||||
self.block_title = (
|
||||
config['block_title'] if 'block_title' in config else '')
|
||||
self.block_url = (
|
||||
config['block_url'] if 'block_url' in config else '')
|
||||
def __init__(self, user_key: str, config: Config, mobile=False) -> None:
|
||||
self.config = config
|
||||
self.mobile = mobile
|
||||
self.user_key = user_key
|
||||
self.main_divs = ResultSet('')
|
||||
|
@ -68,16 +60,6 @@ class Filter:
|
|||
def elements(self):
|
||||
return self._elements
|
||||
|
||||
def reskin(self, page: str) -> str:
|
||||
# Aesthetic only re-skinning
|
||||
if self.dark:
|
||||
page = page.replace(
|
||||
'fff', '000').replace(
|
||||
'202124', 'ddd').replace(
|
||||
'1967D2', '3b85ea')
|
||||
|
||||
return page
|
||||
|
||||
def encrypt_path(self, path, is_element=False) -> str:
|
||||
# Encrypts path to avoid plaintext results in logs
|
||||
if is_element:
|
||||
|
@ -109,7 +91,7 @@ class Filter:
|
|||
|
||||
input_form = soup.find('form')
|
||||
if input_form is not None:
|
||||
input_form['method'] = 'POST'
|
||||
input_form['method'] = 'GET' if self.config.get_only else 'POST'
|
||||
|
||||
# Ensure no extra scripts passed through
|
||||
for script in soup('script'):
|
||||
|
@ -143,9 +125,7 @@ class Filter:
|
|||
_ = div.decompose() if len(div_ads) else None
|
||||
|
||||
def remove_block_titles(self) -> None:
|
||||
if not self.main_divs:
|
||||
return
|
||||
if self.block_title == '':
|
||||
if not self.main_divs or not self.config.block_title:
|
||||
return
|
||||
block_title = re.compile(self.block_title)
|
||||
for div in [_ for _ in self.main_divs.find_all('div', recursive=True)]:
|
||||
|
@ -154,9 +134,7 @@ class Filter:
|
|||
_ = div.decompose() if len(block_divs) else None
|
||||
|
||||
def remove_block_url(self) -> None:
|
||||
if not self.main_divs:
|
||||
return
|
||||
if self.block_url == '':
|
||||
if not self.main_divs or not self.config.block_url:
|
||||
return
|
||||
block_url = re.compile(self.block_url)
|
||||
for div in [_ for _ in self.main_divs.find_all('div', recursive=True)]:
|
||||
|
@ -211,10 +189,17 @@ class Filter:
|
|||
# Find and decompose the first element with an inner HTML text val.
|
||||
# This typically extracts the title of the section (i.e. "Related
|
||||
# Searches", "People also ask", etc)
|
||||
# If there are more than one child tags with text
|
||||
# parenthesize the rest except the first
|
||||
label = 'Collapsed Results'
|
||||
subtitle = None
|
||||
for elem in result_children:
|
||||
if elem.text:
|
||||
label = elem.text
|
||||
content = list(elem.strings)
|
||||
label = content[0]
|
||||
if len(content) > 1:
|
||||
subtitle = '<span> (' + \
|
||||
''.join(content[1:]) + ')</span>'
|
||||
elem.decompose()
|
||||
break
|
||||
|
||||
|
@ -229,6 +214,11 @@ class Filter:
|
|||
details = BeautifulSoup(features='html.parser').new_tag('details')
|
||||
summary = BeautifulSoup(features='html.parser').new_tag('summary')
|
||||
summary.string = label
|
||||
|
||||
if subtitle:
|
||||
soup = BeautifulSoup(subtitle, 'html.parser')
|
||||
summary.append(soup)
|
||||
|
||||
details.append(summary)
|
||||
|
||||
if parent and not minimal_mode:
|
||||
|
@ -254,14 +244,14 @@ class Filter:
|
|||
if src.startswith(LOGO_URL):
|
||||
# Re-brand with Whoogle logo
|
||||
element.replace_with(BeautifulSoup(
|
||||
render_template('logo.html', dark=self.dark),
|
||||
render_template('logo.html'),
|
||||
features='html.parser'))
|
||||
return
|
||||
elif src.startswith(GOOG_IMG) or GOOG_STATIC in src:
|
||||
element['src'] = BLANK_B64
|
||||
return
|
||||
|
||||
element['src'] = 'element?url=' + self.encrypt_path(
|
||||
element['src'] = f'{Endpoint.element}?url=' + self.encrypt_path(
|
||||
src,
|
||||
is_element=True) + '&type=' + urlparse.quote(mime)
|
||||
|
||||
|
@ -353,10 +343,10 @@ class Filter:
|
|||
link['href'] = filter_link_args(q)
|
||||
|
||||
# Add no-js option
|
||||
if self.nojs:
|
||||
if self.config.nojs:
|
||||
append_nojs(link)
|
||||
|
||||
if self.new_tab:
|
||||
if self.config.new_tab:
|
||||
link['target'] = '_blank'
|
||||
else:
|
||||
if href.startswith(MAPS_URL):
|
||||
|
@ -366,7 +356,7 @@ class Filter:
|
|||
link['href'] = href
|
||||
|
||||
# Replace link location if "alts" config is enabled
|
||||
if self.alt_redirect:
|
||||
if self.config.alts:
|
||||
# Search and replace all link descriptions
|
||||
# with alternative location
|
||||
link['href'] = get_site_alt(link['href'])
|
||||
|
@ -409,7 +399,12 @@ class Filter:
|
|||
for item in results_all:
|
||||
urls = item.find('a')['href'].split('&imgrefurl=')
|
||||
|
||||
img_url = urlparse.unquote(urls[0].replace('/imgres?imgurl=', ''))
|
||||
# Skip urls that are not two-element lists
|
||||
if len(urls) != 2:
|
||||
continue
|
||||
|
||||
img_url = urlparse.unquote(urls[0].replace(
|
||||
f'/{Endpoint.imgres}?imgurl=', ''))
|
||||
|
||||
try:
|
||||
# Try to strip out only the necessary part of the web page link
|
||||
|
|
|
@ -17,8 +17,8 @@ class Config:
|
|||
self.block = os.getenv('WHOOGLE_CONFIG_BLOCK', '')
|
||||
self.block_title = os.getenv('WHOOGLE_CONFIG_BLOCK_TITLE', '')
|
||||
self.block_url = os.getenv('WHOOGLE_CONFIG_BLOCK_URL', '')
|
||||
self.ctry = os.getenv('WHOOGLE_CONFIG_COUNTRY', '')
|
||||
self.theme = os.getenv('WHOOGLE_CONFIG_THEME', '')
|
||||
self.country = os.getenv('WHOOGLE_CONFIG_COUNTRY', '')
|
||||
self.theme = os.getenv('WHOOGLE_CONFIG_THEME', 'system')
|
||||
self.safe = read_config_bool('WHOOGLE_CONFIG_SAFE')
|
||||
self.dark = read_config_bool('WHOOGLE_CONFIG_DARK') # deprecated
|
||||
self.alts = read_config_bool('WHOOGLE_CONFIG_ALTS')
|
||||
|
@ -33,9 +33,13 @@ class Config:
|
|||
self.safe_keys = [
|
||||
'lang_search',
|
||||
'lang_interface',
|
||||
'ctry',
|
||||
'dark',
|
||||
'theme'
|
||||
'country',
|
||||
'theme',
|
||||
'alts',
|
||||
'new_tab',
|
||||
'view_image',
|
||||
'block',
|
||||
'safe'
|
||||
]
|
||||
|
||||
# Skip setting custom config if there isn't one
|
||||
|
@ -105,5 +109,26 @@ class Config:
|
|||
for param_key in params.keys():
|
||||
if not self.is_safe_key(param_key):
|
||||
continue
|
||||
self[param_key] = params.get(param_key)
|
||||
param_val = params.get(param_key)
|
||||
|
||||
if param_val == 'off':
|
||||
param_val = False
|
||||
elif param_val.isdigit():
|
||||
param_val = int(param_val)
|
||||
|
||||
self[param_key] = param_val
|
||||
return self
|
||||
|
||||
def to_params(self) -> str:
|
||||
"""Generates a set of safe params for using in Whoogle URLs
|
||||
|
||||
Returns:
|
||||
str -- a set of URL parameters
|
||||
"""
|
||||
param_str = ''
|
||||
for safe_key in self.safe_keys:
|
||||
if not self[safe_key]:
|
||||
continue
|
||||
param_str = param_str + f'&{safe_key}={self[safe_key]}'
|
||||
|
||||
return param_str
|
||||
|
|
23
app/models/endpoint.py
Normal file
23
app/models/endpoint.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class Endpoint(Enum):
|
||||
autocomplete = 'autocomplete'
|
||||
home = 'home'
|
||||
healthz = 'healthz'
|
||||
session = 'session'
|
||||
config = 'config'
|
||||
opensearch = 'opensearch.xml'
|
||||
search = 'search'
|
||||
search_html = 'search.html'
|
||||
url = 'url'
|
||||
imgres = 'imgres'
|
||||
element = 'element'
|
||||
window = 'window'
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def in_path(self, path: str) -> bool:
|
||||
return path.startswith(self.value) or \
|
||||
path.startswith(f'/{self.value}')
|
|
@ -59,7 +59,7 @@ def gen_user_agent(is_mobile) -> str:
|
|||
return DESKTOP_UA.format("Mozilla", linux, firefox)
|
||||
|
||||
|
||||
def gen_query(query, args, config, near_city=None) -> str:
|
||||
def gen_query(query, args, config) -> str:
|
||||
param_dict = {key: '' for key in VALID_PARAMS}
|
||||
|
||||
# Use :past(hour/day/week/month/year) if available
|
||||
|
@ -96,8 +96,8 @@ def gen_query(query, args, config, near_city=None) -> str:
|
|||
param_dict['start'] = '&start=' + args.get('start')
|
||||
|
||||
# Search for results near a particular city, if available
|
||||
if near_city:
|
||||
param_dict['near'] = '&near=' + urlparse.quote(near_city)
|
||||
if config.near:
|
||||
param_dict['near'] = '&near=' + urlparse.quote(config.near)
|
||||
|
||||
# Set language for results (lr) if source isn't set, otherwise use the
|
||||
# result language param provided in the results
|
||||
|
@ -107,19 +107,25 @@ def gen_query(query, args, config, near_city=None) -> str:
|
|||
[_ for _ in lang if not _.isdigit()]
|
||||
)) if lang else ''
|
||||
else:
|
||||
param_dict['lr'] = '&lr=' + (
|
||||
config.lang_search if config.lang_search else ''
|
||||
)
|
||||
param_dict['lr'] = (
|
||||
'&lr=' + config.lang_search
|
||||
) if config.lang_search else ''
|
||||
|
||||
# 'nfpr' defines the exclusion of results from an auto-corrected query
|
||||
if 'nfpr' in args:
|
||||
param_dict['nfpr'] = '&nfpr=' + args.get('nfpr')
|
||||
|
||||
param_dict['cr'] = ('&cr=' + config.ctry) if config.ctry else ''
|
||||
param_dict['hl'] = '&hl=' + (
|
||||
config.lang_interface.replace('lang_', '')
|
||||
if config.lang_interface else ''
|
||||
)
|
||||
# 'chips' is used in image tabs to pass the optional 'filter' to add to the
|
||||
# given search term
|
||||
if 'chips' in args:
|
||||
param_dict['chips'] = '&chips=' + args.get('chips')
|
||||
|
||||
param_dict['gl'] = (
|
||||
'&gl=' + config.country
|
||||
) if config.country else ''
|
||||
param_dict['hl'] = (
|
||||
'&hl=' + config.lang_interface.replace('lang_', '')
|
||||
) if config.lang_interface else ''
|
||||
param_dict['safe'] = '&safe=' + ('active' if config.safe else 'off')
|
||||
|
||||
# Block all sites specified in the user config
|
||||
|
@ -213,16 +219,23 @@ class Request:
|
|||
list: The list of matches for possible search suggestions
|
||||
|
||||
"""
|
||||
ac_query = dict(hl=self.language, q=query)
|
||||
ac_query = dict(q=query)
|
||||
if self.language:
|
||||
ac_query['hl'] = self.language
|
||||
|
||||
response = self.send(base_url=AUTOCOMPLETE_URL,
|
||||
query=urlparse.urlencode(ac_query)).text
|
||||
|
||||
if not response:
|
||||
return []
|
||||
|
||||
root = ET.fromstring(response)
|
||||
return [_.attrib['data'] for _ in
|
||||
root.findall('.//suggestion/[@data]')]
|
||||
try:
|
||||
root = ET.fromstring(response)
|
||||
return [_.attrib['data'] for _ in
|
||||
root.findall('.//suggestion/[@data]')]
|
||||
except ET.ParseError:
|
||||
# Malformed XML response
|
||||
return []
|
||||
|
||||
def send(self, base_url='', query='', attempt=0,
|
||||
force_mobile=False) -> Response:
|
||||
|
@ -274,14 +287,19 @@ class Request:
|
|||
|
||||
# Make sure that the tor connection is valid, if enabled
|
||||
if self.tor:
|
||||
tor_check = requests.get('https://check.torproject.org/',
|
||||
proxies=self.proxies, headers=headers)
|
||||
self.tor_valid = 'Congratulations' in tor_check.text
|
||||
try:
|
||||
tor_check = requests.get('https://check.torproject.org/',
|
||||
proxies=self.proxies, headers=headers)
|
||||
self.tor_valid = 'Congratulations' in tor_check.text
|
||||
|
||||
if not self.tor_valid:
|
||||
if not self.tor_valid:
|
||||
raise TorError(
|
||||
"Tor connection succeeded, but the connection could "
|
||||
"not be validated by torproject.org",
|
||||
disable=True)
|
||||
except ConnectionError:
|
||||
raise TorError(
|
||||
"Tor connection succeeded, but the connection could not "
|
||||
"be validated by torproject.org",
|
||||
"Error raised during Tor connection validation",
|
||||
disable=True)
|
||||
|
||||
response = requests.get(
|
||||
|
|
225
app/routes.py
225
app/routes.py
|
@ -1,30 +1,45 @@
|
|||
import argparse
|
||||
import base64
|
||||
import html
|
||||
import io
|
||||
import json
|
||||
import pickle
|
||||
import urllib.parse as urlparse
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from functools import wraps
|
||||
|
||||
import waitress
|
||||
from app import app
|
||||
from app.models.config import Config
|
||||
from app.models.endpoint import Endpoint
|
||||
from app.request import Request, TorError
|
||||
from app.utils.bangs import resolve_bang
|
||||
from app.utils.misc import read_config_bool, get_client_ip
|
||||
from app.utils.results import add_ip_card, bold_search_terms, get_tabs_content
|
||||
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, get_tabs_content
|
||||
from app.utils.search import *
|
||||
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
|
||||
from requests import exceptions
|
||||
from requests import exceptions, get
|
||||
from requests.models import PreparedRequest
|
||||
|
||||
# Load DDG bang json files only on init
|
||||
bang_json = json.load(open(app.config['BANG_FILE']))
|
||||
|
||||
# Check the newest version of WHOOGLE
|
||||
update = bsoup(get(app.config['RELEASES_URL']).text, 'html.parser')
|
||||
newest_version = update.select_one('[class="Link--primary"]').string[1:]
|
||||
current_version = int(''.join(filter(str.isdigit,
|
||||
app.config['VERSION_NUMBER'])))
|
||||
newest_version = int(''.join(filter(str.isdigit, newest_version)))
|
||||
newest_version = '' if current_version >= newest_version \
|
||||
else newest_version
|
||||
|
||||
ac_var = 'WHOOGLE_AUTOCOMPLETE'
|
||||
autocomplete_enabled = os.getenv(ac_var, '1')
|
||||
|
||||
|
||||
def auth_required(f):
|
||||
@wraps(f)
|
||||
|
@ -46,40 +61,91 @@ def auth_required(f):
|
|||
return decorated
|
||||
|
||||
|
||||
def session_required(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
if (valid_user_session(session) and
|
||||
'cookies_disabled' not in request.args):
|
||||
g.session_key = session['key']
|
||||
else:
|
||||
session.pop('_permanent', None)
|
||||
g.session_key = app.default_key
|
||||
|
||||
# Clear out old sessions
|
||||
invalid_sessions = []
|
||||
for user_session in os.listdir(app.config['SESSION_FILE_DIR']):
|
||||
session_path = os.path.join(
|
||||
app.config['SESSION_FILE_DIR'],
|
||||
user_session)
|
||||
try:
|
||||
with open(session_path, 'rb') as session_file:
|
||||
_ = pickle.load(session_file)
|
||||
data = pickle.load(session_file)
|
||||
if isinstance(data, dict) and 'valid' in data:
|
||||
continue
|
||||
invalid_sessions.append(session_path)
|
||||
except (EOFError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
for invalid_session in invalid_sessions:
|
||||
try:
|
||||
os.remove(invalid_session)
|
||||
except FileNotFoundError:
|
||||
# Don't throw error if the invalid session has been removed
|
||||
pass
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request_func():
|
||||
g.request_params = (
|
||||
request.args if request.method == 'GET' else request.form
|
||||
)
|
||||
g.cookies_disabled = False
|
||||
|
||||
# Skip pre-request actions if verifying session
|
||||
if '/session' in request.path and not valid_user_session(session):
|
||||
return
|
||||
|
||||
default_config = json.load(open(app.config['DEFAULT_CONFIG'])) \
|
||||
if os.path.exists(app.config['DEFAULT_CONFIG']) else {}
|
||||
|
||||
# Generate session values for user if unavailable
|
||||
if not valid_user_session(session):
|
||||
session['config'] = json.load(open(app.config['DEFAULT_CONFIG'])) \
|
||||
if os.path.exists(app.config['DEFAULT_CONFIG']) else {}
|
||||
if (not valid_user_session(session) and
|
||||
'cookies_disabled' not in request.args):
|
||||
session['config'] = default_config
|
||||
session['uuid'] = str(uuid.uuid4())
|
||||
session['key'] = generate_user_key(True)
|
||||
session['key'] = generate_user_key()
|
||||
|
||||
# Flag cookies as possibly disabled in order to prevent against
|
||||
# unnecessary session directory expansion
|
||||
g.cookies_disabled = True
|
||||
|
||||
# Handle https upgrade
|
||||
if needs_https(request.url):
|
||||
return redirect(
|
||||
request.url.replace('http://', 'https://', 1),
|
||||
code=308)
|
||||
|
||||
g.user_config = Config(**session['config'])
|
||||
# Skip checking for session on any searches that don't
|
||||
# require a valid session
|
||||
if (not Endpoint.autocomplete.in_path(request.path) and
|
||||
not Endpoint.healthz.in_path(request.path) and
|
||||
not Endpoint.opensearch.in_path(request.path)):
|
||||
return redirect(url_for(
|
||||
'session_check',
|
||||
session_id=session['uuid'],
|
||||
follow=get_request_url(request.url)), code=307)
|
||||
else:
|
||||
g.user_config = Config(**session['config'])
|
||||
elif 'cookies_disabled' not in request.args:
|
||||
# Set session as permanent
|
||||
session.permanent = True
|
||||
app.permanent_session_lifetime = timedelta(days=365)
|
||||
g.user_config = Config(**session['config'])
|
||||
else:
|
||||
# User has cookies disabled, fall back to immutable default config
|
||||
session.pop('_permanent', None)
|
||||
g.user_config = Config(**default_config)
|
||||
|
||||
if not g.user_config.url:
|
||||
g.user_config.url = request.url_root.replace(
|
||||
'http://',
|
||||
'https://') if os.getenv('HTTPS_ONLY', False) else request.url_root
|
||||
g.user_config.url = get_request_url(request.url_root)
|
||||
|
||||
g.user_request = Request(
|
||||
request.headers.get('User-Agent'),
|
||||
request.url_root,
|
||||
get_request_url(request.url_root),
|
||||
config=g.user_config)
|
||||
|
||||
g.app_location = g.user_config.url
|
||||
|
@ -87,22 +153,14 @@ def before_request_func():
|
|||
|
||||
@app.after_request
|
||||
def after_request_func(resp):
|
||||
# Check if address consistently has cookies blocked,
|
||||
# in which case start removing session files after creation.
|
||||
#
|
||||
# Note: This is primarily done to prevent overpopulation of session
|
||||
# directories, since browsers that block cookies will still trigger
|
||||
# Flask's session creation routine with every request.
|
||||
if g.cookies_disabled and request.remote_addr not in app.no_cookie_ips:
|
||||
app.no_cookie_ips.append(request.remote_addr)
|
||||
elif g.cookies_disabled and request.remote_addr in app.no_cookie_ips:
|
||||
session_list = list(session.keys())
|
||||
for key in session_list:
|
||||
session.pop(key)
|
||||
resp.headers['X-Content-Type-Options'] = 'nosniff'
|
||||
resp.headers['X-Frame-Options'] = 'DENY'
|
||||
|
||||
resp.headers['Content-Security-Policy'] = app.config['CSP']
|
||||
if os.environ.get('HTTPS_ONLY', False):
|
||||
resp.headers['Content-Security-Policy'] += 'upgrade-insecure-requests'
|
||||
if os.getenv('WHOOGLE_CSP', False):
|
||||
resp.headers['Content-Security-Policy'] = app.config['CSP']
|
||||
if os.environ.get('HTTPS_ONLY', False):
|
||||
resp.headers['Content-Security-Policy'] += \
|
||||
'upgrade-insecure-requests'
|
||||
|
||||
return resp
|
||||
|
||||
|
@ -113,22 +171,28 @@ def unknown_page(e):
|
|||
return redirect(g.app_location)
|
||||
|
||||
|
||||
@app.route('/healthz', methods=['GET'])
|
||||
@app.route(f'/{Endpoint.healthz}', methods=['GET'])
|
||||
def healthz():
|
||||
return ''
|
||||
|
||||
|
||||
@app.route('/home', methods=['GET'])
|
||||
def home():
|
||||
return redirect(url_for('.index'))
|
||||
@app.route(f'/{Endpoint.session}/<session_id>', methods=['GET', 'PUT', 'POST'])
|
||||
def session_check(session_id):
|
||||
if 'uuid' in session and session['uuid'] == session_id:
|
||||
session['valid'] = True
|
||||
return redirect(request.args.get('follow'), code=307)
|
||||
else:
|
||||
follow_url = request.args.get('follow')
|
||||
req = PreparedRequest()
|
||||
req.prepare_url(follow_url, {'cookies_disabled': 1})
|
||||
session.pop('_permanent', None)
|
||||
return redirect(req.url, code=307)
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
@app.route(f'/{Endpoint.home}', methods=['GET'])
|
||||
@auth_required
|
||||
def index():
|
||||
# Reset keys
|
||||
session['key'] = generate_user_key(g.cookies_disabled)
|
||||
|
||||
# Redirect if an error was raised
|
||||
if 'error_message' in session and session['error_message']:
|
||||
error_message = session['error_message']
|
||||
|
@ -136,22 +200,27 @@ def index():
|
|||
return render_template('error.html', error_message=error_message)
|
||||
|
||||
return render_template('index.html',
|
||||
newest_version=newest_version,
|
||||
languages=app.config['LANGUAGES'],
|
||||
countries=app.config['COUNTRIES'],
|
||||
themes=app.config['THEMES'],
|
||||
autocomplete_enabled=autocomplete_enabled,
|
||||
translation=app.config['TRANSLATIONS'][
|
||||
g.user_config.get_localization_lang()
|
||||
],
|
||||
logo=render_template(
|
||||
'logo.html',
|
||||
dark=g.user_config.dark),
|
||||
config_disabled=app.config['CONFIG_DISABLE'],
|
||||
config_disabled=(
|
||||
app.config['CONFIG_DISABLE'] or
|
||||
not valid_user_session(session) or
|
||||
'cookies_disabled' in request.args),
|
||||
config=g.user_config,
|
||||
tor_available=int(os.environ.get('TOR_AVAILABLE')),
|
||||
version_number=app.config['VERSION_NUMBER'])
|
||||
|
||||
|
||||
@app.route('/opensearch.xml', methods=['GET'])
|
||||
@app.route(f'/{Endpoint.opensearch}', methods=['GET'])
|
||||
def opensearch():
|
||||
opensearch_url = g.app_location
|
||||
if opensearch_url.endswith('/'):
|
||||
|
@ -171,7 +240,7 @@ def opensearch():
|
|||
), 200, {'Content-Disposition': 'attachment; filename="opensearch.xml"'}
|
||||
|
||||
|
||||
@app.route('/search.html', methods=['GET'])
|
||||
@app.route(f'/{Endpoint.search_html}', methods=['GET'])
|
||||
def search_html():
|
||||
search_url = g.app_location
|
||||
if search_url.endswith('/'):
|
||||
|
@ -179,9 +248,8 @@ def search_html():
|
|||
return render_template('search.html', url=search_url)
|
||||
|
||||
|
||||
@app.route('/autocomplete', methods=['GET', 'POST'])
|
||||
@app.route(f'/{Endpoint.autocomplete}', methods=['GET', 'POST'])
|
||||
def autocomplete():
|
||||
ac_var = 'WHOOGLE_AUTOCOMPLETE'
|
||||
if os.getenv(ac_var) and not read_config_bool(ac_var):
|
||||
return jsonify({})
|
||||
|
||||
|
@ -212,14 +280,14 @@ def autocomplete():
|
|||
])
|
||||
|
||||
|
||||
@app.route('/search', methods=['GET', 'POST'])
|
||||
@app.route(f'/{Endpoint.search}', methods=['GET', 'POST'])
|
||||
@session_required
|
||||
@auth_required
|
||||
def search():
|
||||
# Update user config if specified in search args
|
||||
g.user_config = g.user_config.from_params(g.request_params)
|
||||
|
||||
search_util = Search(request, g.user_config, session,
|
||||
cookies_disabled=g.cookies_disabled)
|
||||
search_util = Search(request, g.user_config, g.session_key)
|
||||
query = search_util.new_search_query()
|
||||
|
||||
bang = resolve_bang(query=query, bangs_dict=bang_json)
|
||||
|
@ -228,7 +296,7 @@ def search():
|
|||
|
||||
# Redirect to home if invalid/blank search
|
||||
if not query:
|
||||
return redirect('/')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
# Generate response and number of external elements from the page
|
||||
try:
|
||||
|
@ -250,7 +318,16 @@ def search():
|
|||
translate_to = localization_lang.replace('lang_', '')
|
||||
|
||||
# Return 503 if temporarily blocked by captcha
|
||||
resp_code = 503 if has_captcha(str(response)) else 200
|
||||
if has_captcha(str(response)):
|
||||
return render_template(
|
||||
'error.html',
|
||||
blocked=True,
|
||||
error_message=translation['ratelimit'],
|
||||
translation=translation,
|
||||
farside='https://farside.link',
|
||||
config=g.user_config,
|
||||
query=urlparse.unquote(query),
|
||||
params=g.user_config.to_params()), 503
|
||||
response = bold_search_terms(response, query)
|
||||
|
||||
# Feature to display IP address
|
||||
|
@ -264,11 +341,19 @@ def search():
|
|||
search_util.search_type,
|
||||
translation)
|
||||
|
||||
# Feature to display currency_card
|
||||
conversion = check_currency(str(response))
|
||||
if conversion:
|
||||
html_soup = bsoup(str(response), 'html.parser')
|
||||
response = add_currency_card(html_soup, conversion)
|
||||
|
||||
return render_template(
|
||||
'display.html',
|
||||
newest_version=newest_version,
|
||||
query=urlparse.unquote(query),
|
||||
search_type=search_util.search_type,
|
||||
config=g.user_config,
|
||||
autocomplete_enabled=autocomplete_enabled,
|
||||
lingva_url=app.config['TRANSLATE_URL'],
|
||||
translation=translation,
|
||||
translate_to=translate_to,
|
||||
|
@ -292,10 +377,13 @@ def search():
|
|||
tabs=tabs)), resp_code
|
||||
|
||||
|
||||
@app.route('/config', methods=['GET', 'POST', 'PUT'])
|
||||
@app.route(f'/{Endpoint.config}', methods=['GET', 'POST', 'PUT'])
|
||||
@session_required
|
||||
@auth_required
|
||||
def config():
|
||||
config_disabled = app.config['CONFIG_DISABLE']
|
||||
config_disabled = (
|
||||
app.config['CONFIG_DISABLE'] or
|
||||
not valid_user_session(session))
|
||||
if request.method == 'GET':
|
||||
return json.dumps(g.user_config.__dict__)
|
||||
elif request.method == 'PUT' and not config_disabled:
|
||||
|
@ -322,18 +410,14 @@ def config():
|
|||
app.config['CONFIG_PATH'],
|
||||
request.args.get('name')), 'wb'))
|
||||
|
||||
# Overwrite default config if user has cookies disabled
|
||||
if g.cookies_disabled:
|
||||
open(app.config['DEFAULT_CONFIG'], 'w').write(
|
||||
json.dumps(config_data, indent=4))
|
||||
|
||||
session['config'] = config_data
|
||||
return redirect(config_data['url'])
|
||||
else:
|
||||
return redirect(url_for('.index'), code=403)
|
||||
|
||||
|
||||
@app.route('/url', methods=['GET'])
|
||||
@app.route(f'/{Endpoint.url}', methods=['GET'])
|
||||
@session_required
|
||||
@auth_required
|
||||
def url():
|
||||
if 'url' in request.args:
|
||||
|
@ -348,16 +432,18 @@ def url():
|
|||
error_message='Unable to resolve query: ' + q)
|
||||
|
||||
|
||||
@app.route('/imgres')
|
||||
@app.route(f'/{Endpoint.imgres}')
|
||||
@session_required
|
||||
@auth_required
|
||||
def imgres():
|
||||
return redirect(request.args.get('imgurl'))
|
||||
|
||||
|
||||
@app.route('/element')
|
||||
@app.route(f'/{Endpoint.element}')
|
||||
@session_required
|
||||
@auth_required
|
||||
def element():
|
||||
cipher_suite = Fernet(session['key'])
|
||||
cipher_suite = Fernet(g.session_key)
|
||||
src_url = cipher_suite.decrypt(request.args.get('url').encode()).decode()
|
||||
src_type = request.args.get('type')
|
||||
|
||||
|
@ -376,7 +462,7 @@ def element():
|
|||
return send_file(io.BytesIO(empty_gif), mimetype='image/gif')
|
||||
|
||||
|
||||
@app.route('/window')
|
||||
@app.route(f'/{Endpoint.window}')
|
||||
@auth_required
|
||||
def window():
|
||||
get_body = g.user_request.send(base_url=request.args.get('location')).text
|
||||
|
@ -457,7 +543,8 @@ def run_app() -> None:
|
|||
os.environ['WHOOGLE_PROXY_TYPE'] = args.proxytype
|
||||
os.environ['WHOOGLE_PROXY_LOC'] = args.proxyloc
|
||||
|
||||
os.environ['HTTPS_ONLY'] = '1' if args.https_only else ''
|
||||
if args.https_only:
|
||||
os.environ['HTTPS_ONLY'] = '1'
|
||||
|
||||
if args.debug:
|
||||
app.run(host=args.host, port=args.port, debug=args.debug)
|
||||
|
|
|
@ -138,10 +138,14 @@ select {
|
|||
color: var(--whoogle-dark-contrast-text) !important;
|
||||
}
|
||||
|
||||
#gh-link {
|
||||
.link {
|
||||
color: var(--whoogle-dark-contrast-text);
|
||||
}
|
||||
|
||||
.link-color {
|
||||
color: var(--whoogle-dark-result-url) !important;
|
||||
}
|
||||
|
||||
.autocomplete-items {
|
||||
border: 1px solid var(--whoogle-dark-element-bg);
|
||||
}
|
||||
|
@ -187,6 +191,10 @@ path {
|
|||
color: var(--whoogle-dark-text) !important;
|
||||
}
|
||||
|
||||
.ip-text-div{
|
||||
.ip-text-div, .update_available, .cb_label, .cb {
|
||||
color: var(--whoogle-dark-secondary-text) !important;
|
||||
}
|
||||
|
||||
.cb:focus {
|
||||
color: var(--whoogle-dark-contrast-text) !important;
|
||||
}
|
||||
|
|
9
app/static/css/error.css
Normal file
9
app/static/css/error.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
html {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
html {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
|
@ -12,3 +12,30 @@
|
|||
height: 40px;
|
||||
width: 50px;
|
||||
}
|
||||
.ZINbbc.xpd.O9g5cc.uUPGi input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cb {
|
||||
width: 40%;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
line-height: 28px;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #5f6368;
|
||||
font-size: 14px !important;
|
||||
height: 36px;
|
||||
padding: 0 0 0 12px;
|
||||
margin: 10px 10px 10px 0;
|
||||
}
|
||||
|
||||
.conversion_box {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.ZINbbc.xpd.O9g5cc.uUPGi input:focus-visible {
|
||||
outline: 0;
|
||||
}
|
||||
|
|
|
@ -125,10 +125,14 @@ input {
|
|||
color: var(--whoogle-contrast-text);
|
||||
}
|
||||
|
||||
#gh-link {
|
||||
.link {
|
||||
color: var(--whoogle-element-bg);
|
||||
}
|
||||
|
||||
.link-color {
|
||||
color: var(--whoogle-result-url) !important;
|
||||
}
|
||||
|
||||
.autocomplete-items {
|
||||
border: 1px solid var(--whoogle-element-bg);
|
||||
}
|
||||
|
@ -175,6 +179,10 @@ path {
|
|||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.ip-text-div{
|
||||
.ip-text-div, .update_available, .cb_label, .cb {
|
||||
color: var(--whoogle-secondary-text) !important;
|
||||
}
|
||||
|
||||
.cb:focus {
|
||||
color: var(--whoogle-text) !important;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ a {
|
|||
|
||||
@media (max-width: 1000px) {
|
||||
svg {
|
||||
margin-top: .7em;
|
||||
margin-top: .3em;
|
||||
height: 70%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,15 @@ body {
|
|||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.config-options {
|
||||
max-height: 370px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.config-buttons {
|
||||
max-height: 30px;
|
||||
}
|
||||
|
||||
.config-div {
|
||||
padding: 5px;
|
||||
}
|
||||
|
@ -102,7 +111,6 @@ button::-moz-focus-inner {
|
|||
}
|
||||
|
||||
.open {
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
|
@ -136,6 +144,7 @@ footer {
|
|||
|
||||
.whoogle-svg {
|
||||
width: 80%;
|
||||
height: initial;
|
||||
display: block;
|
||||
margin: auto;
|
||||
padding-bottom: 10px;
|
||||
|
@ -168,3 +177,10 @@ details summary {
|
|||
padding: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
@media (max-width: 1000px) {
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ details summary {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
details summary span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#lingva-iframe {
|
||||
width: 100%;
|
||||
height: 650px;
|
||||
|
|
9
app/static/js/currency.js
Normal file
9
app/static/js/currency.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const convert = (n1, n2, conversionFactor) => {
|
||||
// id's for currency input boxes
|
||||
let id1 = "cb" + n1;
|
||||
let id2 = "cb" + n2;
|
||||
// getting the value of the input box that just got filled
|
||||
let inputBox = document.getElementById(id1).value;
|
||||
// updating the other input box after conversion
|
||||
document.getElementById(id2).value = ((inputBox * conversionFactor).toFixed(2));
|
||||
}
|
|
@ -1,248 +1,247 @@
|
|||
[
|
||||
{"name": "-------", "value": ""},
|
||||
{"name": "Afghanistan", "value": "countryAF"},
|
||||
{"name": "Albania", "value": "countryAL"},
|
||||
{"name": "Algeria", "value": "countryDZ"},
|
||||
{"name": "American Samoa", "value": "countryAS"},
|
||||
{"name": "Andorra", "value": "countryAD"},
|
||||
{"name": "Angola", "value": "countryAO"},
|
||||
{"name": "Anguilla", "value": "countryAI"},
|
||||
{"name": "Antarctica", "value": "countryAQ"},
|
||||
{"name": "Antigua and Barbuda", "value": "countryAG"},
|
||||
{"name": "Argentina", "value": "countryAR"},
|
||||
{"name": "Armenia", "value": "countryAM"},
|
||||
{"name": "Aruba", "value": "countryAW"},
|
||||
{"name": "Australia", "value": "countryAU"},
|
||||
{"name": "Austria", "value": "countryAT"},
|
||||
{"name": "Azerbaijan", "value": "countryAZ"},
|
||||
{"name": "Bahamas", "value": "countryBS"},
|
||||
{"name": "Bahrain", "value": "countryBH"},
|
||||
{"name": "Bangladesh", "value": "countryBD"},
|
||||
{"name": "Barbados", "value": "countryBB"},
|
||||
{"name": "Belarus", "value": "countryBY"},
|
||||
{"name": "Belgium", "value": "countryBE"},
|
||||
{"name": "Belize", "value": "countryBZ"},
|
||||
{"name": "Benin", "value": "countryBJ"},
|
||||
{"name": "Bermuda", "value": "countryBM"},
|
||||
{"name": "Bhutan", "value": "countryBT"},
|
||||
{"name": "Bolivia", "value": "countryBO"},
|
||||
{"name": "Bosnia and Herzegovina", "value": "countryBA"},
|
||||
{"name": "Botswana", "value": "countryBW"},
|
||||
{"name": "Bouvet Island", "value": "countryBV"},
|
||||
{"name": "Brazil", "value": "countryBR"},
|
||||
{"name": "British Indian Ocean Territory", "value": "countryIO"},
|
||||
{"name": "Brunei Darussalam", "value": "countryBN"},
|
||||
{"name": "Bulgaria", "value": "countryBG"},
|
||||
{"name": "Burkina Faso", "value": "countryBF"},
|
||||
{"name": "Burundi", "value": "countryBI"},
|
||||
{"name": "Cambodia", "value": "countryKH"},
|
||||
{"name": "Cameroon", "value": "countryCM"},
|
||||
{"name": "Canada", "value": "countryCA"},
|
||||
{"name": "Cape Verde", "value": "countryCV"},
|
||||
{"name": "Cayman Islands", "value": "countryKY"},
|
||||
{"name": "Central African Republic", "value": "countryCF"},
|
||||
{"name": "Chad", "value": "countryTD"},
|
||||
{"name": "Chile", "value": "countryCL"},
|
||||
{"name": "China", "value": "countryCN"},
|
||||
{"name": "Christmas Island", "value": "countryCX"},
|
||||
{"name": "Cocos (Keeling) Islands", "value": "countryCC"},
|
||||
{"name": "Colombia", "value": "countryCO"},
|
||||
{"name": "Comoros", "value": "countryKM"},
|
||||
{"name": "Congo", "value": "countryCG"},
|
||||
{"name": "Congo, Democratic Republic of the", "value": "countryCD"},
|
||||
{"name": "Cook Islands", "value": "countryCK"},
|
||||
{"name": "Costa Rica", "value": "countryCR"},
|
||||
{"name": "Cote D\"ivoire", "value": "countryCI"},
|
||||
{"name": "Croatia (Hrvatska)", "value": "countryHR"},
|
||||
{"name": "Cuba", "value": "countryCU"},
|
||||
{"name": "Cyprus", "value": "countryCY"},
|
||||
{"name": "Czech Republic", "value": "countryCZ"},
|
||||
{"name": "Denmark", "value": "countryDK"},
|
||||
{"name": "Djibouti", "value": "countryDJ"},
|
||||
{"name": "Dominica", "value": "countryDM"},
|
||||
{"name": "Dominican Republic", "value": "countryDO"},
|
||||
{"name": "East Timor", "value": "countryTP"},
|
||||
{"name": "Ecuador", "value": "countryEC"},
|
||||
{"name": "Egypt", "value": "countryEG"},
|
||||
{"name": "El Salvador", "value": "countrySV"},
|
||||
{"name": "Equatorial Guinea", "value": "countryGQ"},
|
||||
{"name": "Eritrea", "value": "countryER"},
|
||||
{"name": "Estonia", "value": "countryEE"},
|
||||
{"name": "Ethiopia", "value": "countryET"},
|
||||
{"name": "European Union", "value": "countryEU"},
|
||||
{"name": "Falkland Islands (Malvinas)", "value": "countryFK"},
|
||||
{"name": "Faroe Islands", "value": "countryFO"},
|
||||
{"name": "Fiji", "value": "countryFJ"},
|
||||
{"name": "Finland", "value": "countryFI"},
|
||||
{"name": "France", "value": "countryFR"},
|
||||
{"name": "France, Metropolitan", "value": "countryFX"},
|
||||
{"name": "French Guiana", "value": "countryGF"},
|
||||
{"name": "French Polynesia", "value": "countryPF"},
|
||||
{"name": "French Southern Territories", "value": "countryTF"},
|
||||
{"name": "Gabon", "value": "countryGA"},
|
||||
{"name": "Gambia", "value": "countryGM"},
|
||||
{"name": "Georgia", "value": "countryGE"},
|
||||
{"name": "Germany", "value": "countryDE"},
|
||||
{"name": "Ghana", "value": "countryGH"},
|
||||
{"name": "Gibraltar", "value": "countryGI"},
|
||||
{"name": "Greece", "value": "countryGR"},
|
||||
{"name": "Greenland", "value": "countryGL"},
|
||||
{"name": "Grenada", "value": "countryGD"},
|
||||
{"name": "Guadeloupe", "value": "countryGP"},
|
||||
{"name": "Guam", "value": "countryGU"},
|
||||
{"name": "Guatemala", "value": "countryGT"},
|
||||
{"name": "Guinea", "value": "countryGN"},
|
||||
{"name": "Guinea-Bissau", "value": "countryGW"},
|
||||
{"name": "Guyana", "value": "countryGY"},
|
||||
{"name": "Haiti", "value": "countryHT"},
|
||||
{"name": "Heard Island and Mcdonald Islands", "value": "countryHM"},
|
||||
{"name": "Holy See (Vatican City State)", "value": "countryVA"},
|
||||
{"name": "Honduras", "value": "countryHN"},
|
||||
{"name": "Hong Kong", "value": "countryHK"},
|
||||
{"name": "Hungary", "value": "countryHU"},
|
||||
{"name": "Iceland", "value": "countryIS"},
|
||||
{"name": "India", "value": "countryIN"},
|
||||
{"name": "Indonesia", "value": "countryID"},
|
||||
{"name": "Iran, Islamic Republic of", "value": "countryIR"},
|
||||
{"name": "Iraq", "value": "countryIQ"},
|
||||
{"name": "Ireland", "value": "countryIE"},
|
||||
{"name": "Israel", "value": "countryIL"},
|
||||
{"name": "Italy", "value": "countryIT"},
|
||||
{"name": "Jamaica", "value": "countryJM"},
|
||||
{"name": "Japan", "value": "countryJP"},
|
||||
{"name": "Jordan", "value": "countryJO"},
|
||||
{"name": "Kazakhstan", "value": "countryKZ"},
|
||||
{"name": "Kenya", "value": "countryKE"},
|
||||
{"name": "Kiribati", "value": "countryKI"},
|
||||
{"name": "Korea, Democratic People\"s Republic of",
|
||||
"value": "countryKP"},
|
||||
{"name": "Korea, Republic of", "value": "countryKR"},
|
||||
{"name": "Kuwait", "value": "countryKW"},
|
||||
{"name": "Kyrgyzstan", "value": "countryKG"},
|
||||
{"name": "Lao People\"s Democratic Republic", "value": "countryLA"},
|
||||
{"name": "Latvia", "value": "countryLV"},
|
||||
{"name": "Lebanon", "value": "countryLB"},
|
||||
{"name": "Lesotho", "value": "countryLS"},
|
||||
{"name": "Liberia", "value": "countryLR"},
|
||||
{"name": "Libyan Arab Jamahiriya", "value": "countryLY"},
|
||||
{"name": "Liechtenstein", "value": "countryLI"},
|
||||
{"name": "Lithuania", "value": "countryLT"},
|
||||
{"name": "Luxembourg", "value": "countryLU"},
|
||||
{"name": "Macao", "value": "countryMO"},
|
||||
{"name": "Afghanistan", "value": "AF"},
|
||||
{"name": "Albania", "value": "AL"},
|
||||
{"name": "Algeria", "value": "DZ"},
|
||||
{"name": "American Samoa", "value": "AS"},
|
||||
{"name": "Andorra", "value": "AD"},
|
||||
{"name": "Angola", "value": "AO"},
|
||||
{"name": "Anguilla", "value": "AI"},
|
||||
{"name": "Antarctica", "value": "AQ"},
|
||||
{"name": "Antigua and Barbuda", "value": "AG"},
|
||||
{"name": "Argentina", "value": "AR"},
|
||||
{"name": "Armenia", "value": "AM"},
|
||||
{"name": "Aruba", "value": "AW"},
|
||||
{"name": "Australia", "value": "AU"},
|
||||
{"name": "Austria", "value": "AT"},
|
||||
{"name": "Azerbaijan", "value": "AZ"},
|
||||
{"name": "Bahamas", "value": "BS"},
|
||||
{"name": "Bahrain", "value": "BH"},
|
||||
{"name": "Bangladesh", "value": "BD"},
|
||||
{"name": "Barbados", "value": "BB"},
|
||||
{"name": "Belarus", "value": "BY"},
|
||||
{"name": "Belgium", "value": "BE"},
|
||||
{"name": "Belize", "value": "BZ"},
|
||||
{"name": "Benin", "value": "BJ"},
|
||||
{"name": "Bermuda", "value": "BM"},
|
||||
{"name": "Bhutan", "value": "BT"},
|
||||
{"name": "Bolivia", "value": "BO"},
|
||||
{"name": "Bosnia and Herzegovina", "value": "BA"},
|
||||
{"name": "Botswana", "value": "BW"},
|
||||
{"name": "Bouvet Island", "value": "BV"},
|
||||
{"name": "Brazil", "value": "BR"},
|
||||
{"name": "British Indian Ocean Territory", "value": "IO"},
|
||||
{"name": "Brunei Darussalam", "value": "BN"},
|
||||
{"name": "Bulgaria", "value": "BG"},
|
||||
{"name": "Burkina Faso", "value": "BF"},
|
||||
{"name": "Burundi", "value": "BI"},
|
||||
{"name": "Cambodia", "value": "KH"},
|
||||
{"name": "Cameroon", "value": "CM"},
|
||||
{"name": "Canada", "value": "CA"},
|
||||
{"name": "Cape Verde", "value": "CV"},
|
||||
{"name": "Cayman Islands", "value": "KY"},
|
||||
{"name": "Central African Republic", "value": "CF"},
|
||||
{"name": "Chad", "value": "TD"},
|
||||
{"name": "Chile", "value": "CL"},
|
||||
{"name": "China", "value": "CN"},
|
||||
{"name": "Christmas Island", "value": "CX"},
|
||||
{"name": "Cocos (Keeling) Islands", "value": "CC"},
|
||||
{"name": "Colombia", "value": "CO"},
|
||||
{"name": "Comoros", "value": "KM"},
|
||||
{"name": "Congo", "value": "CG"},
|
||||
{"name": "Congo, Democratic Republic of the", "value": "CD"},
|
||||
{"name": "Cook Islands", "value": "CK"},
|
||||
{"name": "Costa Rica", "value": "CR"},
|
||||
{"name": "Cote D'ivoire", "value": "CI"},
|
||||
{"name": "Croatia (Hrvatska)", "value": "HR"},
|
||||
{"name": "Cuba", "value": "CU"},
|
||||
{"name": "Cyprus", "value": "CY"},
|
||||
{"name": "Czech Republic", "value": "CZ"},
|
||||
{"name": "Denmark", "value": "DK"},
|
||||
{"name": "Djibouti", "value": "DJ"},
|
||||
{"name": "Dominica", "value": "DM"},
|
||||
{"name": "Dominican Republic", "value": "DO"},
|
||||
{"name": "East Timor", "value": "TP"},
|
||||
{"name": "Ecuador", "value": "EC"},
|
||||
{"name": "Egypt", "value": "EG"},
|
||||
{"name": "El Salvador", "value": "SV"},
|
||||
{"name": "Equatorial Guinea", "value": "GQ"},
|
||||
{"name": "Eritrea", "value": "ER"},
|
||||
{"name": "Estonia", "value": "EE"},
|
||||
{"name": "Ethiopia", "value": "ET"},
|
||||
{"name": "European Union", "value": "EU"},
|
||||
{"name": "Falkland Islands (Malvinas)", "value": "FK"},
|
||||
{"name": "Faroe Islands", "value": "FO"},
|
||||
{"name": "Fiji", "value": "FJ"},
|
||||
{"name": "Finland", "value": "FI"},
|
||||
{"name": "France", "value": "FR"},
|
||||
{"name": "France, Metropolitan", "value": "FX"},
|
||||
{"name": "French Guiana", "value": "GF"},
|
||||
{"name": "French Polynesia", "value": "PF"},
|
||||
{"name": "French Southern Territories", "value": "TF"},
|
||||
{"name": "Gabon", "value": "GA"},
|
||||
{"name": "Gambia", "value": "GM"},
|
||||
{"name": "Georgia", "value": "GE"},
|
||||
{"name": "Germany", "value": "DE"},
|
||||
{"name": "Ghana", "value": "GH"},
|
||||
{"name": "Gibraltar", "value": "GI"},
|
||||
{"name": "Greece", "value": "GR"},
|
||||
{"name": "Greenland", "value": "GL"},
|
||||
{"name": "Grenada", "value": "GD"},
|
||||
{"name": "Guadeloupe", "value": "GP"},
|
||||
{"name": "Guam", "value": "GU"},
|
||||
{"name": "Guatemala", "value": "GT"},
|
||||
{"name": "Guinea", "value": "GN"},
|
||||
{"name": "Guinea-Bissau", "value": "GW"},
|
||||
{"name": "Guyana", "value": "GY"},
|
||||
{"name": "Haiti", "value": "HT"},
|
||||
{"name": "Heard Island and Mcdonald Islands", "value": "HM"},
|
||||
{"name": "Holy See (Vatican City State)", "value": "VA"},
|
||||
{"name": "Honduras", "value": "HN"},
|
||||
{"name": "Hong Kong", "value": "HK"},
|
||||
{"name": "Hungary", "value": "HU"},
|
||||
{"name": "Iceland", "value": "IS"},
|
||||
{"name": "India", "value": "IN"},
|
||||
{"name": "Indonesia", "value": "ID"},
|
||||
{"name": "Iran, Islamic Republic of", "value": "IR"},
|
||||
{"name": "Iraq", "value": "IQ"},
|
||||
{"name": "Ireland", "value": "IE"},
|
||||
{"name": "Israel", "value": "IL"},
|
||||
{"name": "Italy", "value": "IT"},
|
||||
{"name": "Jamaica", "value": "JM"},
|
||||
{"name": "Japan", "value": "JP"},
|
||||
{"name": "Jordan", "value": "JO"},
|
||||
{"name": "Kazakhstan", "value": "KZ"},
|
||||
{"name": "Kenya", "value": "KE"},
|
||||
{"name": "Kiribati", "value": "KI"},
|
||||
{"name": "Korea, Democratic People's Republic of", "value": "KP"},
|
||||
{"name": "Korea, Republic of", "value": "KR"},
|
||||
{"name": "Kuwait", "value": "KW"},
|
||||
{"name": "Kyrgyzstan", "value": "KG"},
|
||||
{"name": "Lao People's Democratic Republic", "value": "LA"},
|
||||
{"name": "Latvia", "value": "LV"},
|
||||
{"name": "Lebanon", "value": "LB"},
|
||||
{"name": "Lesotho", "value": "LS"},
|
||||
{"name": "Liberia", "value": "LR"},
|
||||
{"name": "Libyan Arab Jamahiriya", "value": "LY"},
|
||||
{"name": "Liechtenstein", "value": "LI"},
|
||||
{"name": "Lithuania", "value": "LT"},
|
||||
{"name": "Luxembourg", "value": "LU"},
|
||||
{"name": "Macao", "value": "MO"},
|
||||
{"name": "Macedonia, the Former Yugosalv Republic of",
|
||||
"value": "countryMK"},
|
||||
{"name": "Madagascar", "value": "countryMG"},
|
||||
{"name": "Malawi", "value": "countryMW"},
|
||||
{"name": "Malaysia", "value": "countryMY"},
|
||||
{"name": "Maldives", "value": "countryMV"},
|
||||
{"name": "Mali", "value": "countryML"},
|
||||
{"name": "Malta", "value": "countryMT"},
|
||||
{"name": "Marshall Islands", "value": "countryMH"},
|
||||
{"name": "Martinique", "value": "countryMQ"},
|
||||
{"name": "Mauritania", "value": "countryMR"},
|
||||
{"name": "Mauritius", "value": "countryMU"},
|
||||
{"name": "Mayotte", "value": "countryYT"},
|
||||
{"name": "Mexico", "value": "countryMX"},
|
||||
{"name": "Micronesia, Federated States of", "value": "countryFM"},
|
||||
{"name": "Moldova, Republic of", "value": "countryMD"},
|
||||
{"name": "Monaco", "value": "countryMC"},
|
||||
{"name": "Mongolia", "value": "countryMN"},
|
||||
{"name": "Montserrat", "value": "countryMS"},
|
||||
{"name": "Morocco", "value": "countryMA"},
|
||||
{"name": "Mozambique", "value": "countryMZ"},
|
||||
{"name": "Myanmar", "value": "countryMM"},
|
||||
{"name": "Namibia", "value": "countryNA"},
|
||||
{"name": "Nauru", "value": "countryNR"},
|
||||
{"name": "Nepal", "value": "countryNP"},
|
||||
{"name": "Netherlands", "value": "countryNL"},
|
||||
{"name": "Netherlands Antilles", "value": "countryAN"},
|
||||
{"name": "New Caledonia", "value": "countryNC"},
|
||||
{"name": "New Zealand", "value": "countryNZ"},
|
||||
{"name": "Nicaragua", "value": "countryNI"},
|
||||
{"name": "Niger", "value": "countryNE"},
|
||||
{"name": "Nigeria", "value": "countryNG"},
|
||||
{"name": "Niue", "value": "countryNU"},
|
||||
{"name": "Norfolk Island", "value": "countryNF"},
|
||||
{"name": "Northern Mariana Islands", "value": "countryMP"},
|
||||
{"name": "Norway", "value": "countryNO"},
|
||||
{"name": "Oman", "value": "countryOM"},
|
||||
{"name": "Pakistan", "value": "countryPK"},
|
||||
{"name": "Palau", "value": "countryPW"},
|
||||
{"name": "Palestinian Territory", "value": "countryPS"},
|
||||
{"name": "Panama", "value": "countryPA"},
|
||||
{"name": "Papua New Guinea", "value": "countryPG"},
|
||||
{"name": "Paraguay", "value": "countryPY"},
|
||||
{"name": "Peru", "value": "countryPE"},
|
||||
{"name": "Philippines", "value": "countryPH"},
|
||||
{"name": "Pitcairn", "value": "countryPN"},
|
||||
{"name": "Poland", "value": "countryPL"},
|
||||
{"name": "Portugal", "value": "countryPT"},
|
||||
{"name": "Puerto Rico", "value": "countryPR"},
|
||||
{"name": "Qatar", "value": "countryQA"},
|
||||
{"name": "Reunion", "value": "countryRE"},
|
||||
{"name": "Romania", "value": "countryRO"},
|
||||
{"name": "Russian Federation", "value": "countryRU"},
|
||||
{"name": "Rwanda", "value": "countryRW"},
|
||||
{"name": "Saint Helena", "value": "countrySH"},
|
||||
{"name": "Saint Kitts and Nevis", "value": "countryKN"},
|
||||
{"name": "Saint Lucia", "value": "countryLC"},
|
||||
{"name": "Saint Pierre and Miquelon", "value": "countryPM"},
|
||||
{"name": "Saint Vincent and the Grenadines", "value": "countryVC"},
|
||||
{"name": "Samoa", "value": "countryWS"},
|
||||
{"name": "San Marino", "value": "countrySM"},
|
||||
{"name": "Sao Tome and Principe", "value": "countryST"},
|
||||
{"name": "Saudi Arabia", "value": "countrySA"},
|
||||
{"name": "Senegal", "value": "countrySN"},
|
||||
{"name": "Serbia and Montenegro", "value": "countryCS"},
|
||||
{"name": "Seychelles", "value": "countrySC"},
|
||||
{"name": "Sierra Leone", "value": "countrySL"},
|
||||
{"name": "Singapore", "value": "countrySG"},
|
||||
{"name": "Slovakia", "value": "countrySK"},
|
||||
{"name": "Slovenia", "value": "countrySI"},
|
||||
{"name": "Solomon Islands", "value": "countrySB"},
|
||||
{"name": "Somalia", "value": "countrySO"},
|
||||
{"name": "South Africa", "value": "countryZA"},
|
||||
"value": "MK"},
|
||||
{"name": "Madagascar", "value": "MG"},
|
||||
{"name": "Malawi", "value": "MW"},
|
||||
{"name": "Malaysia", "value": "MY"},
|
||||
{"name": "Maldives", "value": "MV"},
|
||||
{"name": "Mali", "value": "ML"},
|
||||
{"name": "Malta", "value": "MT"},
|
||||
{"name": "Marshall Islands", "value": "MH"},
|
||||
{"name": "Martinique", "value": "MQ"},
|
||||
{"name": "Mauritania", "value": "MR"},
|
||||
{"name": "Mauritius", "value": "MU"},
|
||||
{"name": "Mayotte", "value": "YT"},
|
||||
{"name": "Mexico", "value": "MX"},
|
||||
{"name": "Micronesia, Federated States of", "value": "FM"},
|
||||
{"name": "Moldova, Republic of", "value": "MD"},
|
||||
{"name": "Monaco", "value": "MC"},
|
||||
{"name": "Mongolia", "value": "MN"},
|
||||
{"name": "Montserrat", "value": "MS"},
|
||||
{"name": "Morocco", "value": "MA"},
|
||||
{"name": "Mozambique", "value": "MZ"},
|
||||
{"name": "Myanmar", "value": "MM"},
|
||||
{"name": "Namibia", "value": "NA"},
|
||||
{"name": "Nauru", "value": "NR"},
|
||||
{"name": "Nepal", "value": "NP"},
|
||||
{"name": "Netherlands", "value": "NL"},
|
||||
{"name": "Netherlands Antilles", "value": "AN"},
|
||||
{"name": "New Caledonia", "value": "NC"},
|
||||
{"name": "New Zealand", "value": "NZ"},
|
||||
{"name": "Nicaragua", "value": "NI"},
|
||||
{"name": "Niger", "value": "NE"},
|
||||
{"name": "Nigeria", "value": "NG"},
|
||||
{"name": "Niue", "value": "NU"},
|
||||
{"name": "Norfolk Island", "value": "NF"},
|
||||
{"name": "Northern Mariana Islands", "value": "MP"},
|
||||
{"name": "Norway", "value": "NO"},
|
||||
{"name": "Oman", "value": "OM"},
|
||||
{"name": "Pakistan", "value": "PK"},
|
||||
{"name": "Palau", "value": "PW"},
|
||||
{"name": "Palestinian Territory", "value": "PS"},
|
||||
{"name": "Panama", "value": "PA"},
|
||||
{"name": "Papua New Guinea", "value": "PG"},
|
||||
{"name": "Paraguay", "value": "PY"},
|
||||
{"name": "Peru", "value": "PE"},
|
||||
{"name": "Philippines", "value": "PH"},
|
||||
{"name": "Pitcairn", "value": "PN"},
|
||||
{"name": "Poland", "value": "PL"},
|
||||
{"name": "Portugal", "value": "PT"},
|
||||
{"name": "Puerto Rico", "value": "PR"},
|
||||
{"name": "Qatar", "value": "QA"},
|
||||
{"name": "Reunion", "value": "RE"},
|
||||
{"name": "Romania", "value": "RO"},
|
||||
{"name": "Russian Federation", "value": "RU"},
|
||||
{"name": "Rwanda", "value": "RW"},
|
||||
{"name": "Saint Helena", "value": "SH"},
|
||||
{"name": "Saint Kitts and Nevis", "value": "KN"},
|
||||
{"name": "Saint Lucia", "value": "LC"},
|
||||
{"name": "Saint Pierre and Miquelon", "value": "PM"},
|
||||
{"name": "Saint Vincent and the Grenadines", "value": "VC"},
|
||||
{"name": "Samoa", "value": "WS"},
|
||||
{"name": "San Marino", "value": "SM"},
|
||||
{"name": "Sao Tome and Principe", "value": "ST"},
|
||||
{"name": "Saudi Arabia", "value": "SA"},
|
||||
{"name": "Senegal", "value": "SN"},
|
||||
{"name": "Serbia and Montenegro", "value": "CS"},
|
||||
{"name": "Seychelles", "value": "SC"},
|
||||
{"name": "Sierra Leone", "value": "SL"},
|
||||
{"name": "Singapore", "value": "SG"},
|
||||
{"name": "Slovakia", "value": "SK"},
|
||||
{"name": "Slovenia", "value": "SI"},
|
||||
{"name": "Solomon Islands", "value": "SB"},
|
||||
{"name": "Somalia", "value": "SO"},
|
||||
{"name": "South Africa", "value": "ZA"},
|
||||
{"name": "South Georgia and the South Sandwich Islands",
|
||||
"value": "countryGS"},
|
||||
{"name": "Spain", "value": "countryES"},
|
||||
{"name": "Sri Lanka", "value": "countryLK"},
|
||||
{"name": "Sudan", "value": "countrySD"},
|
||||
{"name": "Suriname", "value": "countrySR"},
|
||||
{"name": "Svalbard and Jan Mayen", "value": "countrySJ"},
|
||||
{"name": "Swaziland", "value": "countrySZ"},
|
||||
{"name": "Sweden", "value": "countrySE"},
|
||||
{"name": "Switzerland", "value": "countryCH"},
|
||||
{"name": "Syrian Arab Republic", "value": "countrySY"},
|
||||
{"name": "Taiwan", "value": "countryTW"},
|
||||
{"name": "Tajikistan", "value": "countryTJ"},
|
||||
{"name": "Tanzania, United Republic of", "value": "countryTZ"},
|
||||
{"name": "Thailand", "value": "countryTH"},
|
||||
{"name": "Togo", "value": "countryTG"},
|
||||
{"name": "Tokelau", "value": "countryTK"},
|
||||
{"name": "Tonga", "value": "countryTO"},
|
||||
{"name": "Trinidad and Tobago", "value": "countryTT"},
|
||||
{"name": "Tunisia", "value": "countryTN"},
|
||||
{"name": "Turkey", "value": "countryTR"},
|
||||
{"name": "Turkmenistan", "value": "countryTM"},
|
||||
{"name": "Turks and Caicos Islands", "value": "countryTC"},
|
||||
{"name": "Tuvalu", "value": "countryTV"},
|
||||
{"name": "Uganda", "value": "countryUG"},
|
||||
{"name": "Ukraine", "value": "countryUA"},
|
||||
{"name": "United Arab Emirates", "value": "countryAE"},
|
||||
{"name": "United Kingdom", "value": "countryUK"},
|
||||
{"name": "United States", "value": "countryUS"},
|
||||
{"name": "United States Minor Outlying Islands", "value": "countryUM"},
|
||||
{"name": "Uruguay", "value": "countryUY"},
|
||||
{"name": "Uzbekistan", "value": "countryUZ"},
|
||||
{"name": "Vanuatu", "value": "countryVU"},
|
||||
{"name": "Venezuela", "value": "countryVE"},
|
||||
{"name": "Vietnam", "value": "countryVN"},
|
||||
{"name": "Virgin Islands, British", "value": "countryVG"},
|
||||
{"name": "Virgin Islands, U.S.", "value": "countryVI"},
|
||||
{"name": "Wallis and Futuna", "value": "countryWF"},
|
||||
{"name": "Western Sahara", "value": "countryEH"},
|
||||
{"name": "Yemen", "value": "countryYE"},
|
||||
{"name": "Yugoslavia", "value": "countryYU"},
|
||||
{"name": "Zambia", "value": "countryZM"},
|
||||
{"name": "Zimbabwe", "value": "countryZW"}
|
||||
"value": "GS"},
|
||||
{"name": "Spain", "value": "ES"},
|
||||
{"name": "Sri Lanka", "value": "LK"},
|
||||
{"name": "Sudan", "value": "SD"},
|
||||
{"name": "Suriname", "value": "SR"},
|
||||
{"name": "Svalbard and Jan Mayen", "value": "SJ"},
|
||||
{"name": "Swaziland", "value": "SZ"},
|
||||
{"name": "Sweden", "value": "SE"},
|
||||
{"name": "Switzerland", "value": "CH"},
|
||||
{"name": "Syrian Arab Republic", "value": "SY"},
|
||||
{"name": "Taiwan", "value": "TW"},
|
||||
{"name": "Tajikistan", "value": "TJ"},
|
||||
{"name": "Tanzania, United Republic of", "value": "TZ"},
|
||||
{"name": "Thailand", "value": "TH"},
|
||||
{"name": "Togo", "value": "TG"},
|
||||
{"name": "Tokelau", "value": "TK"},
|
||||
{"name": "Tonga", "value": "TO"},
|
||||
{"name": "Trinidad and Tobago", "value": "TT"},
|
||||
{"name": "Tunisia", "value": "TN"},
|
||||
{"name": "Turkey", "value": "TR"},
|
||||
{"name": "Turkmenistan", "value": "TM"},
|
||||
{"name": "Turks and Caicos Islands", "value": "TC"},
|
||||
{"name": "Tuvalu", "value": "TV"},
|
||||
{"name": "Uganda", "value": "UG"},
|
||||
{"name": "Ukraine", "value": "UA"},
|
||||
{"name": "United Arab Emirates", "value": "AE"},
|
||||
{"name": "United Kingdom", "value": "UK"},
|
||||
{"name": "United States", "value": "US"},
|
||||
{"name": "United States Minor Outlying Islands", "value": "UM"},
|
||||
{"name": "Uruguay", "value": "UY"},
|
||||
{"name": "Uzbekistan", "value": "UZ"},
|
||||
{"name": "Vanuatu", "value": "VU"},
|
||||
{"name": "Venezuela", "value": "VE"},
|
||||
{"name": "Vietnam", "value": "VN"},
|
||||
{"name": "Virgin Islands, British", "value": "VG"},
|
||||
{"name": "Virgin Islands, U.S.", "value": "VI"},
|
||||
{"name": "Wallis and Futuna", "value": "WF"},
|
||||
{"name": "Western Sahara", "value": "EH"},
|
||||
{"name": "Yemen", "value": "YE"},
|
||||
{"name": "Yugoslavia", "value": "YU"},
|
||||
{"name": "Zambia", "value": "ZM"},
|
||||
{"name": "Zimbabwe", "value": "ZW"}
|
||||
]
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
"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-country": "Set Country",
|
||||
"config-lang": "Interface Language",
|
||||
"config-lang-search": "Search Language",
|
||||
"config-near": "Near",
|
||||
|
@ -35,6 +34,8 @@
|
|||
"light": "light",
|
||||
"dark": "dark",
|
||||
"system": "system",
|
||||
"ratelimit": "Instance has been ratelimited",
|
||||
"continue-search": "Continue your search with ",
|
||||
"all": "All",
|
||||
"images": "Images",
|
||||
"maps": "Maps",
|
||||
|
@ -45,8 +46,7 @@
|
|||
"lang_nl": {
|
||||
"search": "Zoeken",
|
||||
"config": "Instellingen",
|
||||
"config-country": "Filter zoek resultaten bij land",
|
||||
"config-country-help": "Let op: Als je dit aanzet zal alleen website die gehost worden in het land weergegeven worden.",
|
||||
"config-country": "Land instellen",
|
||||
"config-lang": "Taal instellingen",
|
||||
"config-lang-search": "Zoek taal",
|
||||
"config-near": "Dichtbij",
|
||||
|
@ -77,13 +77,14 @@
|
|||
"translate": "vertalen",
|
||||
"light": "helder",
|
||||
"dark": "donker",
|
||||
"system": "systeeminstellingen"
|
||||
"system": "systeeminstellingen",
|
||||
"ratelimit": "Instantie is beperkt in snelheid",
|
||||
"continue-search": "Ga verder met zoeken met "
|
||||
},
|
||||
"lang_de": {
|
||||
"search": "Suchen",
|
||||
"config": "Einstellungen",
|
||||
"config-country": "Ergebnisse nach Land filtern",
|
||||
"config-country-help": "Hinweis: Wenn aktiv, wird eine Webseite nur angezeigt, wenn sie auch in dem jeweiligen Land *gehosted* wird.",
|
||||
"config-country": "Land einstellen",
|
||||
"config-lang": "Oberflächen-Sprache",
|
||||
"config-lang-search": "Such-Sprache",
|
||||
"config-near": "In der Nähe von",
|
||||
|
@ -114,13 +115,14 @@
|
|||
"translate": "Übersetzen",
|
||||
"light": "hell",
|
||||
"dark": "dunkel",
|
||||
"system": "Systemeinstellung"
|
||||
"system": "Systemeinstellung",
|
||||
"ratelimit": "Instanz wurde ratenbegrenzt",
|
||||
"continue-search": "Setzen Sie Ihre Suche fort mit "
|
||||
},
|
||||
"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-country": "Establecer País",
|
||||
"config-lang": "Idioma de Interfaz",
|
||||
"config-lang-search": "Idioma de Búsqueda",
|
||||
"config-near": "Cerca",
|
||||
|
@ -151,13 +153,14 @@
|
|||
"translate": "traducir",
|
||||
"light": "brillante",
|
||||
"dark": "oscuro",
|
||||
"system": "configuración del sistema"
|
||||
"system": "configuración del sistema",
|
||||
"ratelimit": "La instancia ha sido ratelimited",
|
||||
"continue-search": "Continúe su búsqueda con "
|
||||
},
|
||||
"lang_it": {
|
||||
"search": "Cerca",
|
||||
"config": "Impostazioni",
|
||||
"config-country": "Filtra risultati per paese",
|
||||
"config-country-help": "Nota: se abilitato, il sito sarà presente tra i risultati se e soltanto se il server risiede nel paese selezionato",
|
||||
"config-country": "Imposta Paese",
|
||||
"config-lang": "Lingua dell'interfaccia",
|
||||
"config-lang-search": "Lingua della ricerca",
|
||||
"config-near": "Vicino",
|
||||
|
@ -188,13 +191,14 @@
|
|||
"translate": "tradurre",
|
||||
"light": "luminoso",
|
||||
"dark": "notte",
|
||||
"system": "impostazioni di sistema"
|
||||
"system": "impostazioni di sistema",
|
||||
"ratelimit": "L'istanza è stata limitata alla velocità",
|
||||
"continue-search": "Continua la tua ricerca con "
|
||||
},
|
||||
"lang_pt": {
|
||||
"search": "Pesquisar",
|
||||
"config": "Configuração",
|
||||
"config-country": "Filtrar Resultados por País",
|
||||
"config-country-help": "Observação: Se ativado, um site só aparecerá nos resultados da pesquisa se estiver *hospedado* no país selecionado.",
|
||||
"config-country": "Definir País",
|
||||
"config-lang": "Idioma da Interface",
|
||||
"config-lang-search": "Idioma da Pesquisa",
|
||||
"config-near": "Perto",
|
||||
|
@ -225,13 +229,52 @@
|
|||
"translate": "traduzir",
|
||||
"light": "brilhante",
|
||||
"dark": "escuro",
|
||||
"system": "configuração de sistema"
|
||||
"system": "configuração de sistema",
|
||||
"ratelimit": "A instância foi limitada pela taxa",
|
||||
"continue-search": "Continue sua pesquisa com "
|
||||
},
|
||||
"lang_ru": {
|
||||
"search": "Поиск",
|
||||
"config": "Настройка",
|
||||
"config-country": "Установить страну",
|
||||
"config-lang": "Язык интерфейса",
|
||||
"config-lang-search": "Язык поиска",
|
||||
"config-near": "Около",
|
||||
"config-near-help": "Название города",
|
||||
"config-block": "Блокировать",
|
||||
"config-block-help": "Список сайтов, разделенный запятыми",
|
||||
"config-block-title": "Блокировать по названию",
|
||||
"config-block-title-help": "Используйте regex",
|
||||
"config-block-url": "Блокировать по URL-адресу",
|
||||
"config-block-url-help": "Используйте regex",
|
||||
"config-theme": "Оформление",
|
||||
"config-nojs": "Показывать ссылки NoJS",
|
||||
"config-dark": "Темный режим",
|
||||
"config-safe": "Безопасный поиск",
|
||||
"config-alts": "Заменить ссылки на социальные сети",
|
||||
"config-alts-help": "Замена ссылкок Twitter, YouTube, Instagram и т.д. на альтернативы, уважающие конфиденциальность.",
|
||||
"config-new-tab": "Открывать ссылки в новой вкладке",
|
||||
"config-images": "Поиск полноразмерных изображений",
|
||||
"config-images-help": "(Экспериментально) Добавляет опцию 'Просмотр изображения' к поиску изображений в ПК-режиме. Это приведет к тому, что миниатюры изображений будут иметь более низкое разрешение.",
|
||||
"config-tor": "Использовать Tor",
|
||||
"config-get-only": "Только GET-запросы",
|
||||
"config-url": "Корневой URL-адрес",
|
||||
"config-css": "Пользовательский CSS",
|
||||
"load": "Загрузить",
|
||||
"apply": "Применить",
|
||||
"save-as": "Сохранить как...",
|
||||
"github-link": "Посмотреть в GitHub",
|
||||
"translate": "перевести",
|
||||
"light": "светлое",
|
||||
"dark": "темное",
|
||||
"system": "системное",
|
||||
"ratelimit": "Число экземпляров ограничено",
|
||||
"continue-search": "Продолжайте поиск с "
|
||||
},
|
||||
"lang_zh-CN": {
|
||||
"search": "搜索",
|
||||
"config": "配置",
|
||||
"config-country": "按国家过滤搜索结果",
|
||||
"config-country-help": "注意:启用后,只有在所选国家*部署*的网站会出现在搜索结果中。",
|
||||
"config-country": "设置国家",
|
||||
"config-lang": "界面语言",
|
||||
"config-lang-search": "搜索语言",
|
||||
"config-near": "接近",
|
||||
|
@ -262,13 +305,14 @@
|
|||
"translate": "翻译",
|
||||
"light": "明亮的",
|
||||
"dark": "黑暗的",
|
||||
"system": "系统设置"
|
||||
"system": "系统设置",
|
||||
"ratelimit": "实例已被限速",
|
||||
"continue-search": "继续搜索 "
|
||||
},
|
||||
"lang_si": {
|
||||
"search": "සොයන්න",
|
||||
"config": "වින්යාසය",
|
||||
"config-country": "රට අනුව ප්රතිඵල පෙරන්න",
|
||||
"config-country-help": "සටහන: සබල කර ඇත්නම්, වියමන අඩවියක් සෙවුම් ප්රතිඵලවල දිස්වන්නේ එය තෝරාගත් රටෙහි සිට *සත්කාරකත්වය* දරන්නේ නම් පමණි.",
|
||||
"config-country": "රට සකසන්න",
|
||||
"config-lang": "අතුරු මුහුණතෙහි භාෂාව",
|
||||
"config-lang-search": "සෙවුම් භාෂාව",
|
||||
"config-near": "ආසන්න",
|
||||
|
@ -299,13 +343,14 @@
|
|||
"translate": "පරිවර්තනය කරන්න",
|
||||
"light": "දීප්තිමත්",
|
||||
"dark": "අඳුරු",
|
||||
"system": "පද්ධතිය"
|
||||
"system": "පද්ධතිය",
|
||||
"ratelimit": "උදාහරණය අනුපාත කර ඇත",
|
||||
"continue-search": "සමඟ ඔබේ සෙවීම දිගටම කරගෙන යන්න"
|
||||
},
|
||||
"lang_fr": {
|
||||
"search": "Chercher",
|
||||
"config": "Configuration",
|
||||
"config-country": "Filter les Résultats par Pays",
|
||||
"config-country-help": "Note : Si activé, un site web va uniquement apparaitre dans les résultat de la recherche si il est *hébérgé* dans le pays sélectionné.",
|
||||
"config-country": "Définir le pays",
|
||||
"config-lang": "Langage de l'Interface",
|
||||
"config-lang-search": "Langage de Recherche",
|
||||
"config-near": "Proche",
|
||||
|
@ -336,13 +381,14 @@
|
|||
"translate": "Traduire",
|
||||
"light": "clair",
|
||||
"dark": "sombre",
|
||||
"system": "système"
|
||||
"system": "système",
|
||||
"ratelimit": "Le débit de l'instance a été limité",
|
||||
"continue-search": "Continuez votre recherche avec "
|
||||
},
|
||||
"lang_fa": {
|
||||
"search": "جستجو",
|
||||
"config": "پیکربندی",
|
||||
"config-country": "فیلتر نتایج بر اساس کشور",
|
||||
"config-country-help": "توجه: در صورت فعال بودن، وبسایت تنها در صورتی نمایش داده میشود که *در کشور انتخابی میزبانی شده باشد*.",
|
||||
"config-country": "کشور را تنظیم کنید",
|
||||
"config-lang": "زبان رابط کاربری",
|
||||
"config-lang-search": "زبان جستجو",
|
||||
"config-near": "نزدیک",
|
||||
|
@ -373,13 +419,14 @@
|
|||
"translate": "ترجمه",
|
||||
"light": "روشن",
|
||||
"dark": "تیره",
|
||||
"system": "سیستم"
|
||||
"system": "سیستم",
|
||||
"ratelimit": "نمونه با نرخ محدود شده است",
|
||||
"continue-search": "جستجوی خود را با "
|
||||
},
|
||||
"lang_cs": {
|
||||
"search": "Hledat",
|
||||
"config": "Konfigurace",
|
||||
"config-country": "Filtrovat výsledky podle země",
|
||||
"config-country-help": "Poznámka: Pokud je povoleno, webová stránka se objeví ve výsledcích vyhledávání, pouze pokud je *hostována* ve vybrané zemi.",
|
||||
"config-country": "Nastavte zemi",
|
||||
"config-lang": "Jazyk rozhraní",
|
||||
"config-lang-search": "Jazyk vyhledávání",
|
||||
"config-near": "Poblíž",
|
||||
|
@ -410,13 +457,14 @@
|
|||
"translate": "Přeložit",
|
||||
"light": "Světlý",
|
||||
"dark": "Tmavý",
|
||||
"system": "Systémový"
|
||||
"system": "Systémový",
|
||||
"ratelimit": "Instance byla omezena sazbou",
|
||||
"continue-search": "Pokračujte ve vyhledávání pomocí "
|
||||
},
|
||||
"lang_zh-TW": {
|
||||
"search": "搜尋",
|
||||
"config": "設定",
|
||||
"config-country": "依國家過濾結果",
|
||||
"config-country-help": "注意:一經套用,只有在部署在指定國家內的網站會出現在搜尋結果中。",
|
||||
"config-country": "設置國家",
|
||||
"config-lang": "界面語言",
|
||||
"config-lang-search": "搜尋語言",
|
||||
"config-near": "接近",
|
||||
|
@ -447,13 +495,14 @@
|
|||
"translate": "翻譯",
|
||||
"light": "明亮的",
|
||||
"dark": "黑暗的",
|
||||
"system": "依系統"
|
||||
"system": "依系統",
|
||||
"ratelimit": "實例已被限速",
|
||||
"continue-search": "繼續搜索 "
|
||||
},
|
||||
"lang_bg": {
|
||||
"search": "Търсене",
|
||||
"config": "Конфигурация",
|
||||
"config-country": "Филтрирай резултатите по държави",
|
||||
"config-country-help": "Забележка: Ако това е разрешено, уебсайтoвете ще се показват в резултатите от търсенето, само ако са * хоствани * в избраната държава.",
|
||||
"config-country": "Задайте държава",
|
||||
"config-lang": "Език на интерфейса",
|
||||
"config-lang-search": "Език за търсене",
|
||||
"config-near": "Близо до",
|
||||
|
@ -484,13 +533,14 @@
|
|||
"translate": "превод",
|
||||
"light": "светла",
|
||||
"dark": "тъмна",
|
||||
"system": "системна"
|
||||
"system": "системна",
|
||||
"ratelimit": "Екземплярът е с ограничена скорост",
|
||||
"continue-search": "Продължете търсенето си с "
|
||||
},
|
||||
"lang_hi": {
|
||||
"search": "खोज",
|
||||
"config": "कॉन्फ़िगरेशन",
|
||||
"config-country": "देश के अनुसार परिणाम फ़िल्टर करें",
|
||||
"config-country-help": "नोट: यदि सक्षम है, तो कोई वेबसाइट खोज परिणामों में केवल तभी दिखाई देगी जब वह चयनित देश में *होस्ट* हो।",
|
||||
"config-country": "देश सेट करें",
|
||||
"config-lang": "इंटरफ़ेस भाषा",
|
||||
"config-lang-search": "खोज की भाषा",
|
||||
"config-near": "पास",
|
||||
|
@ -521,6 +571,46 @@
|
|||
"translate": "अनुवाद करना",
|
||||
"light": "रोशनी",
|
||||
"dark": "अंधेरा",
|
||||
"system": "प्रणाली"
|
||||
"system": "प्रणाली",
|
||||
"ratelimit": "इंस्टेंस को सीमित कर दिया गया है",
|
||||
"continue-search": "के साथ अपनी खोज जारी रखें "
|
||||
},
|
||||
"lang_ja": {
|
||||
"search": "検索",
|
||||
"config": "設定",
|
||||
"config-country": "国を設定する",
|
||||
"config-lang": "インタフェースの言語",
|
||||
"config-lang-search": "検索する言語",
|
||||
"config-near": "場所",
|
||||
"config-near-help": "街の名前",
|
||||
"config-block": "ブロック",
|
||||
"config-block-help": "サイトのリストをコンマ区切りで入力",
|
||||
"config-block-title": "タイトルでブロック",
|
||||
"config-block-title-help": "正規表現を使用します",
|
||||
"config-block-url": "でブロック",
|
||||
"config-block-url-help": "正規表現を使用",
|
||||
"config-theme": "テーマ",
|
||||
"config-nojs": "非JSリンクを表示",
|
||||
"config-dark": "ダークモード",
|
||||
"config-safe": "セーフサーチ",
|
||||
"config-alts": "ソーシャルメディアのリンクを置き換え",
|
||||
"config-alts-help": "Twitter/YouTube/Instagramなどのリンクを、プライバシーを尊重した代替サイトに置き換えます。",
|
||||
"config-new-tab": "新しいタブでリンクを開く",
|
||||
"config-images": "フルサイズの画像を検索",
|
||||
"config-images-help": "(実験的) デスクトップの画像検索に「画像を表示」オプションを追加します。これにより、画像検索結果のサムネイルの解像度が低くなります。",
|
||||
"config-tor": "Torを使用",
|
||||
"config-get-only": "GETリクエストのみ",
|
||||
"config-url": "ルートURL",
|
||||
"config-css": "カスタムCSS",
|
||||
"load": "読み込み",
|
||||
"apply": "反映",
|
||||
"save-as": "名前を付けて保存",
|
||||
"github-link": "Githubで確認",
|
||||
"translate": "翻訳",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"system": "自動",
|
||||
"ratelimit": "インスタンスはレート制限されています",
|
||||
"continue-search": "で検索を続ける "
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<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="referrer" content="no-referrer">
|
||||
<link rel="stylesheet" href="{{ cb_url('logo.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('input.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('search.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('header.css') }}">
|
||||
|
@ -33,13 +34,11 @@
|
|||
{% 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>
|
||||
{% include 'footer.html' %}
|
||||
{% if autocomplete_enabled == '1' %}
|
||||
<script src="{{ cb_url('autocomplete.js') }}"></script>
|
||||
{% endif %}
|
||||
<script src="{{ cb_url('utils.js') }}"></script>
|
||||
<script src="{{ cb_url('keyboard.js') }}"></script>
|
||||
<script src="{{ cb_url('currency.js') }}"></script>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,40 @@
|
|||
<h1>Error</h1>
|
||||
<hr>
|
||||
<p>
|
||||
Error: "{{ error_message|safe }}"
|
||||
</p>
|
||||
<a href="/">Return Home</a>
|
||||
{% if config.theme %}
|
||||
{% if config.theme == 'system' %}
|
||||
<style>
|
||||
@import "{{ cb_url('light-theme.css') }}" screen;
|
||||
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
|
||||
</style>
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ cb_url('main.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('error.css') }}">
|
||||
<style>{{ config.style }}</style>
|
||||
<div>
|
||||
<h1>Error</h1>
|
||||
<p>
|
||||
{{ error_message|safe }}
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
{% if blocked is defined %}
|
||||
<h4>{{ translation['continue-search'] }} <a class="link" href="https://github.com/benbusby/farside">Farside</a>!</h4>
|
||||
Whoogle:
|
||||
<br>
|
||||
<a class="link-color" href="{{farside}}/whoogle/search?q={{query}}{{params}}">
|
||||
{{farside}}/whoogle/search?q={{query}}{{params}}
|
||||
</a>
|
||||
<br><br>
|
||||
Searx:
|
||||
<br>
|
||||
<a class="link-color" href="{{farside}}/searx/search?q={{query}}">
|
||||
{{farside}}/searx/search?q={{query}}
|
||||
</a>
|
||||
<hr>
|
||||
{% endif %}
|
||||
</p>
|
||||
<a class="link" href="home">Return Home</a>
|
||||
</div>
|
||||
|
|
9
app/templates/footer.html
Normal file
9
app/templates/footer.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<footer>
|
||||
<p class="footer">
|
||||
Whoogle Search v{{ version_number }} ||
|
||||
<a class="link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
|
||||
{% if newest_version %}
|
||||
|| <span class="update_available">Update Available 🟢</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</footer>
|
|
@ -17,10 +17,13 @@
|
|||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png">
|
||||
<script type="text/javascript" src="{{ cb_url('autocomplete.js') }}"></script>
|
||||
{% if autocomplete_enabled == '1' %}
|
||||
<script src="{{ cb_url('autocomplete.js') }}"></script>
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="{{ cb_url('controller.js') }}"></script>
|
||||
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="{{ cb_url('logo.css') }}">
|
||||
{% if config.theme %}
|
||||
{% if config.theme == 'system' %}
|
||||
<style>
|
||||
|
@ -84,145 +87,146 @@
|
|||
<div class="content">
|
||||
<div class="config-fields">
|
||||
<form id="config-form" action="config" method="post">
|
||||
<div class="config-div config-div-ctry">
|
||||
<label for="config-ctry">{{ translation['config-country'] }}: </label>
|
||||
<select name="ctry" id="config-ctry">
|
||||
{% for ctry in countries %}
|
||||
<option value="{{ ctry.value }}"
|
||||
{% if ctry.value in config.ctry %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ ctry.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div><span class="info-text"> — {{ translation['config-country-help'] }}</span></div>
|
||||
<div class="config-options">
|
||||
<div class="config-div config-div-country">
|
||||
<label for="config-country">{{ translation['config-country'] }}: </label>
|
||||
<select name="country" id="config-country">
|
||||
{% for country in countries %}
|
||||
<option value="{{ country.value }}"
|
||||
{% if country.value in config.country %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ country.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-lang">
|
||||
<label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
|
||||
<select name="lang_interface" id="config-lang-interface">
|
||||
{% for lang in languages %}
|
||||
<option value="{{ lang.value }}"
|
||||
{% if lang.value in config.lang_interface %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-search-lang">
|
||||
<label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
|
||||
<select name="lang_search" id="config-lang-search">
|
||||
{% for lang in languages %}
|
||||
<option value="{{ lang.value }}"
|
||||
{% if lang.value in config.lang_search %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-near">
|
||||
<label for="config-near">{{ translation['config-near'] }}: </label>
|
||||
<input type="text" name="near" id="config-near"
|
||||
placeholder="{{ translation['config-near-help'] }}" 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="{{ translation['config-block-help'] }}" value="{{ config.block }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block-title">{{ translation['config-block-title'] }}: </label>
|
||||
<input type="text" name="block_title" id="config-block"
|
||||
placeholder="{{ translation['config-block-title-help'] }}"
|
||||
value="{{ config.block_title }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block-url">{{ translation['config-block-url'] }}: </label>
|
||||
<input type="text" name="block_url" id="config-block"
|
||||
placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
|
||||
</div>
|
||||
<div class="config-div config-div-nojs">
|
||||
<label for="config-nojs">{{ translation['config-nojs'] }}: </label>
|
||||
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-theme">
|
||||
<label for="config-theme">{{ translation['config-theme'] }}: </label>
|
||||
<select name="theme" id="config-theme">
|
||||
{% for theme in themes %}
|
||||
<option value="{{ theme }}"
|
||||
{% if theme in config.theme %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ translation[theme].capitalize() }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<!-- DEPRECATED -->
|
||||
<!--<div class="config-div config-div-dark">-->
|
||||
<!--<label for="config-dark">{{ translation['config-dark'] }}: </label>-->
|
||||
<!--<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>-->
|
||||
<!--</div>-->
|
||||
<div class="config-div config-div-safe">
|
||||
<label for="config-safe">{{ translation['config-safe'] }}: </label>
|
||||
<input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-alts">
|
||||
<label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label>
|
||||
<input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}>
|
||||
<div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div>
|
||||
</div>
|
||||
<div class="config-div config-div-new-tab">
|
||||
<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 '' }}>
|
||||
</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 class="config-div config-div-tor">
|
||||
<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 '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-get-only">
|
||||
<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 '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-get-only">
|
||||
<label for="config-accept-language">Set Accept-Language: </label>
|
||||
<input type="checkbox" name="accept_language"
|
||||
id="config-accept-language" {{ 'checked' if config.accept_language else '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-root-url">
|
||||
<label for="config-url">{{ translation['config-url'] }}: </label>
|
||||
<input type="text" name="url" id="config-url" value="{{ config.url }}">
|
||||
</div>
|
||||
<div class="config-div config-div-custom-css">
|
||||
<a id="css-link"
|
||||
href="https://github.com/benbusby/whoogle-search/wiki/User-Contributed-CSS-Themes">
|
||||
{{ translation['config-css'] }}:
|
||||
</a>
|
||||
<textarea
|
||||
name="style"
|
||||
id="config-style"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
value="">
|
||||
{{ config.style.replace('\t', '') }}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-div config-div-lang">
|
||||
<label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
|
||||
<select name="lang_interface" id="config-lang-interface">
|
||||
{% for lang in languages %}
|
||||
<option value="{{ lang.value }}"
|
||||
{% if lang.value in config.lang_interface %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-search-lang">
|
||||
<label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
|
||||
<select name="lang_search" id="config-lang-search">
|
||||
{% for lang in languages %}
|
||||
<option value="{{ lang.value }}"
|
||||
{% if lang.value in config.lang_search %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-near">
|
||||
<label for="config-near">{{ translation['config-near'] }}: </label>
|
||||
<input type="text" name="near" id="config-near"
|
||||
placeholder="{{ translation['config-near-help'] }}" 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="{{ translation['config-block-help'] }}" value="{{ config.block }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block-title">{{ translation['config-block-title'] }}: </label>
|
||||
<input type="text" name="block_title" id="config-block"
|
||||
placeholder="{{ translation['config-block-title-help'] }}"
|
||||
value="{{ config.block_title }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block-url">{{ translation['config-block-url'] }}: </label>
|
||||
<input type="text" name="block_url" id="config-block"
|
||||
placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
|
||||
</div>
|
||||
<div class="config-div config-div-nojs">
|
||||
<label for="config-nojs">{{ translation['config-nojs'] }}: </label>
|
||||
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-theme">
|
||||
<label for="config-theme">{{ translation['config-theme'] }}: </label>
|
||||
<select name="theme" id="config-theme">
|
||||
{% for theme in themes %}
|
||||
<option value="{{ theme }}"
|
||||
{% if theme in config.theme %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ translation[theme].capitalize() }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<!-- DEPRECATED -->
|
||||
<!--<div class="config-div config-div-dark">-->
|
||||
<!--<label for="config-dark">{{ translation['config-dark'] }}: </label>-->
|
||||
<!--<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>-->
|
||||
<!--</div>-->
|
||||
<div class="config-div config-div-safe">
|
||||
<label for="config-safe">{{ translation['config-safe'] }}: </label>
|
||||
<input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-alts">
|
||||
<label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label>
|
||||
<input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}>
|
||||
<div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div>
|
||||
</div>
|
||||
<div class="config-div config-div-new-tab">
|
||||
<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 '' }}>
|
||||
</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 class="config-div config-div-tor">
|
||||
<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 '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-get-only">
|
||||
<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 '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-get-only">
|
||||
<label for="config-accept-language">Set Accept-Language: </label>
|
||||
<input type="checkbox" name="accept_language"
|
||||
id="config-accept-language" {{ 'checked' if config.accept_language else '' }}>
|
||||
</div>
|
||||
<div class="config-div config-div-root-url">
|
||||
<label for="config-url">{{ translation['config-url'] }}: </label>
|
||||
<input type="text" name="url" id="config-url" value="{{ config.url }}">
|
||||
</div>
|
||||
<div class="config-div config-div-custom-css">
|
||||
<a id="css-link"
|
||||
href="https://github.com/benbusby/whoogle-search/wiki/User-Contributed-CSS-Themes">
|
||||
{{ translation['config-css'] }}:
|
||||
</a>
|
||||
<textarea
|
||||
name="style"
|
||||
id="config-style"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
value="">
|
||||
{{ config.style.replace('\t', '') }}
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="config-div">
|
||||
<div class="config-div config-buttons">
|
||||
<input type="submit" id="config-load" value="{{ translation['load'] }}">
|
||||
<input type="submit" id="config-submit" value="{{ translation['apply'] }}">
|
||||
<input type="submit" id="config-save" value="{{ translation['save-as'] }}">
|
||||
|
@ -232,11 +236,6 @@
|
|||
</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>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<link rel="stylesheet" href="{{ cb_url('logo.css') }}">
|
||||
<svg id="Layer_1" class="whoogle-svg" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1028 254">
|
||||
<defs>
|
||||
<style>
|
||||
|
@ -17,4 +16,3 @@
|
|||
<path class="cls-1" d="M950.51,539.43c-.31,20.82-10.91,37.89-28,44.71-25.32,10.11-53.89-7-57.87-34.41-1.51-10.43-1.06-20.59,2.68-30.44,7.08-18.66,25.09-29.59,45-27.58,17.76,1.79,33.92,17.68,36.86,36.35C949.79,531.82,950.08,535.64,950.51,539.43Z" transform="translate(-446 -413)"></path>
|
||||
<path class="cls-1" d="M1099.71,539.39c-.39,22.14-11.74,39.51-30.16,45.6-25.8,8.54-53.64-10.27-55.87-37.67-.78-9.54-.55-18.93,3-28,7.25-18.72,24.95-29.59,45-27.62,17.2,1.68,33.14,16.78,36.57,34.84C1099,530.77,1099.23,535.1,1099.71,539.39Z" transform="translate(-446 -413)"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
@ -23,3 +23,10 @@ def get_client_ip(r: Request) -> str:
|
|||
return r.environ['REMOTE_ADDR']
|
||||
else:
|
||||
return r.environ['HTTP_X_FORWARDED_FOR']
|
||||
|
||||
|
||||
def get_request_url(url: str) -> str:
|
||||
if os.getenv('HTTPS_ONLY', False):
|
||||
return url.replace('http://', 'https://', 1)
|
||||
|
||||
return url
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from app.models.endpoint import Endpoint
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
import copy
|
||||
import html
|
||||
|
@ -24,14 +25,16 @@ BLACKLIST = [
|
|||
]
|
||||
|
||||
SITE_ALTS = {
|
||||
'twitter.com': os.getenv('WHOOGLE_ALT_TW', 'nitter.net'),
|
||||
'youtube.com': os.getenv('WHOOGLE_ALT_YT', 'invidious.snopyta.org'),
|
||||
'instagram.com': os.getenv('WHOOGLE_ALT_IG', 'bibliogram.art/u'),
|
||||
'reddit.com': os.getenv('WHOOGLE_ALT_RD', 'libredd.it'),
|
||||
'twitter.com': os.getenv('WHOOGLE_ALT_TW', 'farside.link/nitter'),
|
||||
'youtube.com': os.getenv('WHOOGLE_ALT_YT', 'farside.link/invidious'),
|
||||
'instagram.com': os.getenv('WHOOGLE_ALT_IG', 'farside.link/bibliogram/u'),
|
||||
'reddit.com': os.getenv('WHOOGLE_ALT_RD', 'farside.link/libreddit'),
|
||||
**dict.fromkeys([
|
||||
'medium.com',
|
||||
'levelup.gitconnected.com'
|
||||
], os.getenv('WHOOGLE_ALT_MD', 'scribe.rip'))
|
||||
], os.getenv('WHOOGLE_ALT_MD', 'farside.link/scribe')),
|
||||
'imgur.com': os.getenv('WHOOGLE_ALT_IMG', 'imgin.voidnet.tech'),
|
||||
'wikipedia.com': os.getenv('WHOOGLE_ALT_WIKI', 'wikiless.org')
|
||||
}
|
||||
|
||||
|
||||
|
@ -178,7 +181,7 @@ def append_nojs(result: BeautifulSoup) -> None:
|
|||
|
||||
"""
|
||||
nojs_link = BeautifulSoup(features='html.parser').new_tag('a')
|
||||
nojs_link['href'] = '/window?location=' + result['href']
|
||||
nojs_link['href'] = f'/{Endpoint.window}?location=' + result['href']
|
||||
nojs_link.string = ' NoJS Link'
|
||||
result.append(nojs_link)
|
||||
|
||||
|
@ -225,6 +228,110 @@ def add_ip_card(html_soup: BeautifulSoup, ip: str) -> BeautifulSoup:
|
|||
return html_soup
|
||||
|
||||
|
||||
def check_currency(response: str) -> dict:
|
||||
"""Check whether the results have currency conversion
|
||||
|
||||
Args:
|
||||
response: Search query Result
|
||||
|
||||
Returns:
|
||||
dict: Consists of currency names and values
|
||||
|
||||
"""
|
||||
soup = BeautifulSoup(response, 'html.parser')
|
||||
currency_link = soup.find('a', {'href': 'https://g.co/gfd'})
|
||||
if currency_link:
|
||||
while 'class' not in currency_link.attrs or \
|
||||
'ZINbbc' not in currency_link.attrs['class']:
|
||||
currency_link = currency_link.parent
|
||||
currency_link = currency_link.find_all(class_='BNeawe')
|
||||
currency1 = currency_link[0].text
|
||||
currency2 = currency_link[1].text
|
||||
currency1 = currency1.rstrip('=').split(' ', 1)
|
||||
currency2 = currency2.split(' ', 1)
|
||||
if currency2[0][-3] == ',':
|
||||
currency1[0] = currency1[0].replace('.', '')
|
||||
currency1[0] = currency1[0].replace(',', '.')
|
||||
currency2[0] = currency2[0].replace('.', '')
|
||||
currency2[0] = currency2[0].replace(',', '.')
|
||||
else:
|
||||
currency1[0] = currency1[0].replace(',', '')
|
||||
currency2[0] = currency2[0].replace(',', '')
|
||||
return {'currencyValue1': float(currency1[0]),
|
||||
'currencyLabel1': currency1[1],
|
||||
'currencyValue2': float(currency2[0]),
|
||||
'currencyLabel2': currency2[1]
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
def add_currency_card(soup: BeautifulSoup,
|
||||
conversion_details: dict) -> BeautifulSoup:
|
||||
"""Adds the currency conversion boxes
|
||||
to response of the search query
|
||||
|
||||
Args:
|
||||
soup: Parsed search result
|
||||
conversion_details: Dictionary of currency
|
||||
related information
|
||||
|
||||
Returns:
|
||||
BeautifulSoup
|
||||
"""
|
||||
# Element before which the code will be changed
|
||||
# (This is the 'disclaimer' link)
|
||||
element1 = soup.find('a', {'href': 'https://g.co/gfd'})
|
||||
|
||||
while 'class' not in element1.attrs or \
|
||||
'nXE3Ob' not in element1.attrs['class']:
|
||||
element1 = element1.parent
|
||||
|
||||
# Creating the conversion factor
|
||||
conversion_factor = (conversion_details['currencyValue1'] /
|
||||
conversion_details['currencyValue2'])
|
||||
|
||||
# Creating a new div for the input boxes
|
||||
conversion_box = soup.new_tag('div')
|
||||
conversion_box['class'] = 'conversion_box'
|
||||
|
||||
# Currency to be converted from
|
||||
input_box1 = soup.new_tag('input')
|
||||
input_box1['id'] = 'cb1'
|
||||
input_box1['type'] = 'number'
|
||||
input_box1['class'] = 'cb'
|
||||
input_box1['value'] = conversion_details['currencyValue1']
|
||||
input_box1['oninput'] = f'convert(1, 2, {1 / conversion_factor})'
|
||||
|
||||
label_box1 = soup.new_tag('label')
|
||||
label_box1['for'] = 'cb1'
|
||||
label_box1['class'] = 'cb_label'
|
||||
label_box1.append(conversion_details['currencyLabel1'])
|
||||
|
||||
br = soup.new_tag('br')
|
||||
|
||||
# Currency to be converted to
|
||||
input_box2 = soup.new_tag('input')
|
||||
input_box2['id'] = 'cb2'
|
||||
input_box2['type'] = 'number'
|
||||
input_box2['class'] = 'cb'
|
||||
input_box2['value'] = conversion_details['currencyValue2']
|
||||
input_box2['oninput'] = f'convert(2, 1, {conversion_factor})'
|
||||
|
||||
label_box2 = soup.new_tag('label')
|
||||
label_box2['for'] = 'cb2'
|
||||
label_box2['class'] = 'cb_label'
|
||||
label_box2.append(conversion_details['currencyLabel2'])
|
||||
|
||||
conversion_box.append(input_box1)
|
||||
conversion_box.append(label_box1)
|
||||
conversion_box.append(br)
|
||||
conversion_box.append(input_box2)
|
||||
conversion_box.append(label_box2)
|
||||
|
||||
element1.insert_before(conversion_box)
|
||||
return soup
|
||||
|
||||
|
||||
def get_tabs_content(tabs: dict,
|
||||
full_query: str,
|
||||
search_type: str,
|
||||
|
|
|
@ -52,16 +52,15 @@ class Search:
|
|||
Attributes:
|
||||
request: the incoming flask request
|
||||
config: the current user config settings
|
||||
session: the flask user session
|
||||
session_key: the flask user fernet key
|
||||
"""
|
||||
|
||||
def __init__(self, request, config, session, cookies_disabled=False):
|
||||
def __init__(self, request, config, session_key, cookies_disabled=False):
|
||||
method = request.method
|
||||
self.request_params = request.args if method == 'GET' else request.form
|
||||
self.user_agent = request.headers.get('User-Agent')
|
||||
self.feeling_lucky = False
|
||||
self.config = config
|
||||
self.session = session
|
||||
self.session_key = session_key
|
||||
self.query = ''
|
||||
self.cookies_disabled = cookies_disabled
|
||||
self.search_type = self.request_params.get(
|
||||
|
@ -96,7 +95,7 @@ class Search:
|
|||
else:
|
||||
# Attempt to decrypt if this is an internal link
|
||||
try:
|
||||
q = Fernet(self.session['key']).decrypt(q.encode()).decode()
|
||||
q = Fernet(self.session_key).decrypt(q.encode()).decode()
|
||||
except InvalidToken:
|
||||
pass
|
||||
|
||||
|
@ -115,7 +114,7 @@ class Search:
|
|||
"""
|
||||
mobile = 'Android' in self.user_agent or 'iPhone' in self.user_agent
|
||||
|
||||
content_filter = Filter(self.session['key'],
|
||||
content_filter = Filter(self.session_key,
|
||||
mobile=mobile,
|
||||
config=self.config)
|
||||
full_query = gen_query(self.query,
|
||||
|
@ -134,17 +133,15 @@ class Search:
|
|||
force_mobile=view_image)
|
||||
|
||||
# Produce cleanable html soup from response
|
||||
html_soup = bsoup(content_filter.reskin(get_body.text), 'html.parser')
|
||||
html_soup = bsoup(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:
|
||||
tor_banner = bsoup(TOR_BANNER, 'html.parser')
|
||||
html_soup.insert(0, tor_banner)
|
||||
html_soup.insert(0, bsoup(TOR_BANNER, 'html.parser'))
|
||||
|
||||
if self.feeling_lucky:
|
||||
return get_first_link(html_soup)
|
||||
|
|
|
@ -4,7 +4,7 @@ from flask import current_app as app
|
|||
REQUIRED_SESSION_VALUES = ['uuid', 'config', 'key']
|
||||
|
||||
|
||||
def generate_user_key(cookies_disabled=False) -> bytes:
|
||||
def generate_user_key() -> bytes:
|
||||
"""Generates a key for encrypting searches and element URLs
|
||||
|
||||
Args:
|
||||
|
@ -16,9 +16,6 @@ def generate_user_key(cookies_disabled=False) -> bytes:
|
|||
str: A unique Fernet key
|
||||
|
||||
"""
|
||||
if cookies_disabled:
|
||||
return app.default_key
|
||||
|
||||
# Generate/regenerate unique key per user
|
||||
return Fernet.generate_key()
|
||||
|
||||
|
|
23
charts/whoogle/.helmignore
Normal file
23
charts/whoogle/.helmignore
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
23
charts/whoogle/Chart.yaml
Normal file
23
charts/whoogle/Chart.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
apiVersion: v2
|
||||
name: whoogle
|
||||
description: A self hosted search engine on Kubernetes
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: 0.7.0
|
||||
|
||||
icon: https://github.com/benbusby/whoogle-search/raw/main/app/static/img/favicon/favicon-96x96.png
|
||||
|
||||
sources:
|
||||
- https://github.com/benbusby/whoogle-search
|
||||
- https://gitlab.com/benbusby/whoogle-search
|
||||
- https://gogs.benbusby.com/benbusby/whoogle-search
|
||||
|
||||
keywords:
|
||||
- whoogle
|
||||
- degoogle
|
||||
- search
|
||||
- google
|
||||
- search-engine
|
||||
- privacy
|
||||
- tor
|
||||
- python
|
22
charts/whoogle/templates/NOTES.txt
Normal file
22
charts/whoogle/templates/NOTES.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "whoogle.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "whoogle.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "whoogle.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "whoogle.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
62
charts/whoogle/templates/_helpers.tpl
Normal file
62
charts/whoogle/templates/_helpers.tpl
Normal file
|
@ -0,0 +1,62 @@
|
|||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "whoogle.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "whoogle.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "whoogle.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "whoogle.labels" -}}
|
||||
helm.sh/chart: {{ include "whoogle.chart" . }}
|
||||
{{ include "whoogle.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "whoogle.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "whoogle.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "whoogle.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "whoogle.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
72
charts/whoogle/templates/deployment.yaml
Normal file
72
charts/whoogle/templates/deployment.yaml
Normal file
|
@ -0,0 +1,72 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "whoogle.fullname" . }}
|
||||
labels:
|
||||
{{- include "whoogle.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "whoogle.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "whoogle.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- range .}}
|
||||
- name: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "whoogle.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: whoogle
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.conf }}
|
||||
env:
|
||||
{{- range $k,$v := . }}
|
||||
{{- if $v }}
|
||||
- name: {{ $k }}
|
||||
value: {{ tpl (toString $v) $ | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ default 5000 .Values.conf.EXPOSE_PORT }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
28
charts/whoogle/templates/hpa.yaml
Normal file
28
charts/whoogle/templates/hpa.yaml
Normal file
|
@ -0,0 +1,28 @@
|
|||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "whoogle.fullname" . }}
|
||||
labels:
|
||||
{{- include "whoogle.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "whoogle.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
61
charts/whoogle/templates/ingress.yaml
Normal file
61
charts/whoogle/templates/ingress.yaml
Normal file
|
@ -0,0 +1,61 @@
|
|||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "whoogle.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "whoogle.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
15
charts/whoogle/templates/service.yaml
Normal file
15
charts/whoogle/templates/service.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "whoogle.fullname" . }}
|
||||
labels:
|
||||
{{- include "whoogle.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "whoogle.selectorLabels" . | nindent 4 }}
|
12
charts/whoogle/templates/serviceaccount.yaml
Normal file
12
charts/whoogle/templates/serviceaccount.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "whoogle.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "whoogle.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
15
charts/whoogle/templates/tests/test-connection.yaml
Normal file
15
charts/whoogle/templates/tests/test-connection.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "whoogle.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "whoogle.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "whoogle.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
110
charts/whoogle/values.yaml
Normal file
110
charts/whoogle/values.yaml
Normal file
|
@ -0,0 +1,110 @@
|
|||
# Default values for whoogle.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: benbusby/whoogle-search
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
pullSecrets: []
|
||||
# - my-image-pull-secret
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
conf: {}
|
||||
# WHOOGLE_DOTENV: "" # Load environment variables in whoogle.env
|
||||
# WHOOGLE_USER: "" # The username for basic auth. WHOOGLE_PASS must also be set if used.
|
||||
# WHOOGLE_PASS: "" # The password for basic auth. WHOOGLE_USER must also be set if used.
|
||||
# WHOOGLE_PROXY_USER: "" # The username of the proxy server.
|
||||
# WHOOGLE_PROXY_PASS: "" # The password of the proxy server.
|
||||
# WHOOGLE_PROXY_TYPE: "" # The type of the proxy server. Can be "socks5", "socks4", or "http".
|
||||
# WHOOGLE_PROXY_LOC: "" # The location of the proxy server (host or ip).
|
||||
# EXPOSE_PORT: "" # The port where Whoogle will be exposed. (default 5000)
|
||||
# HTTPS_ONLY: "" # Enforce HTTPS. (See https://github.com/benbusby/whoogle-search#https-enforcement)
|
||||
# WHOOGLE_ALT_TW: "" # The twitter.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_ALT_YT: "" # The youtube.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_ALT_IG: "" # The instagram.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_ALT_RD: "" # The reddit.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_ALT_TL: "" # The Google Translate alternative to use. This is used for all "translate ____" searches.
|
||||
# WHOOGLE_ALT_MD: "" # The medium.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_ALT_IMG: "" # The imgur.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_ALT_WIKI: "" # The wikipedia.com alternative to use when site alternatives are enabled in the config.
|
||||
# WHOOGLE_AUTOCOMPLETE: "" # Controls visibility of autocomplete/search suggestions. Default on -- use '0' to disable
|
||||
# WHOOGLE_MINIMAL: "" # Remove everything except basic result cards from all search queries.
|
||||
|
||||
# WHOOGLE_CONFIG_DISABLE: "" # Hide config from UI and disallow changes to config by client
|
||||
# WHOOGLE_CONFIG_COUNTRY: "" # Filter results by hosting country
|
||||
# WHOOGLE_CONFIG_LANGUAGE: "" # Set interface language
|
||||
# WHOOGLE_CONFIG_SEARCH_LANGUAGE: "" # Set search result language
|
||||
# WHOOGLE_CONFIG_BLOCK: "" # Block websites from search results (use comma-separated list)
|
||||
# WHOOGLE_CONFIG_THEME: "" # Set theme mode (light, dark, or system)
|
||||
# WHOOGLE_CONFIG_SAFE: "" # Enable safe searches
|
||||
# WHOOGLE_CONFIG_ALTS: "" # Use social media site alternatives (nitter, invidious, etc)
|
||||
# WHOOGLE_CONFIG_NEAR: "" # Restrict results to only those near a particular city
|
||||
# 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)
|
||||
|
||||
podAnnotations: {}
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 5000
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: whoogle.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - whoogle.example.com
|
||||
|
||||
resources: {}
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
|
@ -32,12 +32,14 @@ services:
|
|||
# Site alternative configurations, uncomment to enable
|
||||
# Note: If not set, the feature will still be available
|
||||
# with default values.
|
||||
#- WHOOGLE_ALT_TW=nitter.net
|
||||
#- WHOOGLE_ALT_YT=invidious.snopyta.org
|
||||
#- WHOOGLE_ALT_IG=bibliogram.art/u
|
||||
#- WHOOGLE_ALT_RD=libredd.it
|
||||
#- WHOOGLE_ALT_TW=farside.link/nitter
|
||||
#- WHOOGLE_ALT_YT=farside.link/invidious
|
||||
#- WHOOGLE_ALT_IG=farside.link/bibliogram/u
|
||||
#- WHOOGLE_ALT_RD=farside.link/libreddit
|
||||
#- WHOOGLE_ALT_MD=farside.link/scribe
|
||||
#- WHOOGLE_ALT_TL=lingva.ml
|
||||
#- WHOOGLE_ALT_MD=scribe.rip
|
||||
#- WHOOGLE_ALT_IMG=imgin.voidnet.tech
|
||||
#- WHOOGLE_ALT_WIKI=wikiless.org
|
||||
#env_file: # Alternatively, load variables from whoogle.env
|
||||
#- whoogle.env
|
||||
ports:
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 215 KiB |
BIN
docs/screenshot_desktop.png
Normal file
BIN
docs/screenshot_desktop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 214 KiB |
Binary file not shown.
Before Width: | Height: | Size: 139 KiB |
BIN
docs/screenshot_mobile.png
Normal file
BIN
docs/screenshot_mobile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
9
misc/instances.txt
Normal file
9
misc/instances.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
https://s.alefvanoon.xyz
|
||||
https://search.albony.xyz
|
||||
https://search.exonip.de
|
||||
https://search.garudalinux.org
|
||||
https://search.sethforprivacy.com
|
||||
https://whoogle.fossho.st
|
||||
https://whooglesearch.net
|
||||
https://www.whooglesearch.ml
|
||||
https://whoogle.dcs0.hu
|
|
@ -3,5 +3,9 @@
|
|||
if [ "$(whoami)" != "root" ]; then
|
||||
tor -f /etc/tor/torrc
|
||||
else
|
||||
service tor start
|
||||
if (grep alpine /etc/os-release >/dev/null); then
|
||||
rc-service tor start
|
||||
else
|
||||
service tor start
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -7,7 +7,7 @@ chardet==3.0.4
|
|||
click==8.0.3
|
||||
cryptography==3.3.2
|
||||
Flask==1.1.1
|
||||
Flask-Session==0.3.2
|
||||
Flask-Session==0.4.0
|
||||
idna==2.9
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.3
|
||||
|
@ -17,7 +17,7 @@ packaging==20.4
|
|||
pluggy==0.13.1
|
||||
py==1.10.0
|
||||
pycodestyle==2.6.0
|
||||
pycparser==2.19
|
||||
pycparser==2.21
|
||||
pyOpenSSL==19.1.0
|
||||
pyparsing==2.4.7
|
||||
PySocks==1.7.1
|
||||
|
|
2
setup.py
2
setup.py
|
@ -13,7 +13,7 @@ setuptools.setup(
|
|||
author='Ben Busby',
|
||||
author_email='contact@benbusby.com',
|
||||
name='whoogle-search',
|
||||
version='0.6.0' + optional_dev_tag,
|
||||
version='0.7.0' + optional_dev_tag,
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
description='Self-hosted, ad-free, privacy-respecting metasearch engine',
|
||||
|
|
|
@ -9,7 +9,7 @@ demo_config = {
|
|||
'nojs': str(random.getrandbits(1)),
|
||||
'lang_interface': random.choice(app.config['LANGUAGES'])['value'],
|
||||
'lang_search': random.choice(app.config['LANGUAGES'])['value'],
|
||||
'ctry': random.choice(app.config['COUNTRIES'])['value']
|
||||
'country': random.choice(app.config['COUNTRIES'])['value']
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
from app.models.endpoint import Endpoint
|
||||
|
||||
|
||||
def test_autocomplete_get(client):
|
||||
rv = client.get('/autocomplete?q=green+eggs+and')
|
||||
rv = client.get(f'/{Endpoint.autocomplete}?q=green+eggs+and')
|
||||
assert rv._status_code == 200
|
||||
assert len(rv.data) >= 1
|
||||
assert b'green eggs and ham' in rv.data
|
||||
|
||||
|
||||
def test_autocomplete_post(client):
|
||||
rv = client.post('/autocomplete', data=dict(q='the+cat+in+the'))
|
||||
rv = client.post(f'/{Endpoint.autocomplete}',
|
||||
data=dict(q='the+cat+in+the'))
|
||||
assert rv._status_code == 200
|
||||
assert len(rv.data) >= 1
|
||||
assert b'the cat in the hat' in rv.data
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from cryptography.fernet import Fernet
|
||||
|
||||
from app import app
|
||||
from app.models.endpoint import Endpoint
|
||||
from app.utils.session import generate_user_key, valid_user_session
|
||||
|
||||
|
||||
|
@ -37,13 +38,13 @@ def test_query_decryption(client):
|
|||
rv = client.get('/')
|
||||
cookie = rv.headers['Set-Cookie']
|
||||
|
||||
rv = client.get('/search?q=test+1', headers={'Cookie': cookie})
|
||||
rv = client.get(f'/{Endpoint.search}?q=test+1', headers={'Cookie': cookie})
|
||||
assert rv._status_code == 200
|
||||
|
||||
with client.session_transaction() as session:
|
||||
assert valid_user_session(session)
|
||||
|
||||
rv = client.get('/search?q=test+2', headers={'Cookie': cookie})
|
||||
rv = client.get(f'/{Endpoint.search}?q=test+2', headers={'Cookie': cookie})
|
||||
assert rv._status_code == 200
|
||||
|
||||
with client.session_transaction() as session:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from bs4 import BeautifulSoup
|
||||
from app.filter import Filter
|
||||
from app.models.config import Config
|
||||
from app.models.endpoint import Endpoint
|
||||
from app.utils.session import generate_user_key
|
||||
from datetime import datetime
|
||||
from dateutil.parser import *
|
||||
|
@ -10,7 +12,7 @@ from test.conftest import demo_config
|
|||
|
||||
def get_search_results(data):
|
||||
secret_key = generate_user_key()
|
||||
soup = Filter(user_key=secret_key).clean(
|
||||
soup = Filter(user_key=secret_key, config=Config(**demo_config)).clean(
|
||||
BeautifulSoup(data, 'html.parser'))
|
||||
|
||||
main_divs = soup.find('div', {'id': 'main'})
|
||||
|
@ -30,7 +32,7 @@ def get_search_results(data):
|
|||
|
||||
|
||||
def test_get_results(client):
|
||||
rv = client.get('/search?q=test')
|
||||
rv = client.get(f'/{Endpoint.search}?q=test')
|
||||
assert rv._status_code == 200
|
||||
|
||||
# Depending on the search, there can be more
|
||||
|
@ -41,7 +43,7 @@ def test_get_results(client):
|
|||
|
||||
|
||||
def test_post_results(client):
|
||||
rv = client.post('/search', data=dict(q='test'))
|
||||
rv = client.post(f'/{Endpoint.search}', data=dict(q='test'))
|
||||
assert rv._status_code == 200
|
||||
|
||||
# Depending on the search, there can be more
|
||||
|
@ -52,7 +54,7 @@ def test_post_results(client):
|
|||
|
||||
|
||||
def test_translate_search(client):
|
||||
rv = client.post('/search', data=dict(q='translate hola'))
|
||||
rv = client.post(f'/{Endpoint.search}', data=dict(q='translate hola'))
|
||||
assert rv._status_code == 200
|
||||
|
||||
# Pretty weak test, but better than nothing
|
||||
|
@ -62,7 +64,7 @@ def test_translate_search(client):
|
|||
|
||||
|
||||
def test_block_results(client):
|
||||
rv = client.post('/search', data=dict(q='pinterest'))
|
||||
rv = client.post(f'/{Endpoint.search}', data=dict(q='pinterest'))
|
||||
assert rv._status_code == 200
|
||||
|
||||
has_pinterest = False
|
||||
|
@ -74,28 +76,17 @@ def test_block_results(client):
|
|||
assert has_pinterest
|
||||
|
||||
demo_config['block'] = 'pinterest.com'
|
||||
rv = client.post('/config', data=demo_config)
|
||||
rv = client.post(f'/{Endpoint.config}', data=demo_config)
|
||||
assert rv._status_code == 302
|
||||
|
||||
rv = client.post('/search', data=dict(q='pinterest'))
|
||||
rv = client.post(f'/{Endpoint.search}', data=dict(q='pinterest'))
|
||||
assert rv._status_code == 200
|
||||
|
||||
for link in BeautifulSoup(rv.data, 'html.parser').find_all('a', href=True):
|
||||
assert 'pinterest.com' not in urlparse(link['href']).netloc
|
||||
|
||||
|
||||
# TODO: Unit test the site alt method instead -- the results returned
|
||||
# are too unreliable for this test in particular.
|
||||
# def test_site_alts(client):
|
||||
# rv = client.post('/search', data=dict(q='twitter official account'))
|
||||
# assert b'twitter.com/Twitter' in rv.data
|
||||
|
||||
# client.post('/config', data=dict(alts=True))
|
||||
# assert json.loads(client.get('/config').data)['alts']
|
||||
|
||||
# rv = client.post('/search', data=dict(q='twitter official account'))
|
||||
# assert b'twitter.com/Twitter' not in rv.data
|
||||
# assert b'nitter.net/Twitter' in rv.data
|
||||
result_site = urlparse(link['href']).netloc
|
||||
if not result_site:
|
||||
continue
|
||||
assert result_site not in 'pinterest.com'
|
||||
|
||||
|
||||
def test_recent_results(client):
|
||||
|
@ -106,7 +97,7 @@ def test_recent_results(client):
|
|||
}
|
||||
|
||||
for time, num_days in times.items():
|
||||
rv = client.post('/search', data=dict(q='test :' + time))
|
||||
rv = client.post(f'/{Endpoint.search}', data=dict(q='test :' + time))
|
||||
result_divs = get_search_results(rv.data)
|
||||
|
||||
current_date = datetime.now()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from app import app
|
||||
from app.models.endpoint import Endpoint
|
||||
|
||||
import json
|
||||
|
||||
|
@ -11,47 +12,47 @@ def test_main(client):
|
|||
|
||||
|
||||
def test_search(client):
|
||||
rv = client.get('/search?q=test')
|
||||
rv = client.get(f'/{Endpoint.search}?q=test')
|
||||
assert rv._status_code == 200
|
||||
|
||||
|
||||
def test_feeling_lucky(client):
|
||||
rv = client.get('/search?q=!%20test')
|
||||
rv = client.get(f'/{Endpoint.search}?q=!%20test')
|
||||
assert rv._status_code == 303
|
||||
|
||||
|
||||
def test_ddg_bang(client):
|
||||
# Bang at beginning of query
|
||||
rv = client.get('/search?q=!gh%20whoogle')
|
||||
rv = client.get(f'/{Endpoint.search}?q=!gh%20whoogle')
|
||||
assert rv._status_code == 302
|
||||
assert rv.headers.get('Location').startswith('https://github.com')
|
||||
|
||||
# Move bang to end of query
|
||||
rv = client.get('/search?q=github%20!w')
|
||||
rv = client.get(f'/{Endpoint.search}?q=github%20!w')
|
||||
assert rv._status_code == 302
|
||||
assert rv.headers.get('Location').startswith('https://en.wikipedia.org')
|
||||
|
||||
# Move bang to middle of query
|
||||
rv = client.get('/search?q=big%20!r%20chungus')
|
||||
rv = client.get(f'/{Endpoint.search}?q=big%20!r%20chungus')
|
||||
assert rv._status_code == 302
|
||||
assert rv.headers.get('Location').startswith('https://www.reddit.com')
|
||||
|
||||
# Move '!' to end of the bang
|
||||
rv = client.get('/search?q=gitlab%20w!')
|
||||
rv = client.get(f'/{Endpoint.search}?q=gitlab%20w!')
|
||||
assert rv._status_code == 302
|
||||
assert rv.headers.get('Location').startswith('https://en.wikipedia.org')
|
||||
|
||||
# Ensure bang is case insensitive
|
||||
rv = client.get('/search?q=!GH%20whoogle')
|
||||
rv = client.get(f'/{Endpoint.search}?q=!GH%20whoogle')
|
||||
assert rv._status_code == 302
|
||||
assert rv.headers.get('Location').startswith('https://github.com')
|
||||
|
||||
|
||||
def test_config(client):
|
||||
rv = client.post('/config', data=demo_config)
|
||||
rv = client.post(f'/{Endpoint.config}', data=demo_config)
|
||||
assert rv._status_code == 302
|
||||
|
||||
rv = client.get('/config')
|
||||
rv = client.get(f'/{Endpoint.config}')
|
||||
assert rv._status_code == 200
|
||||
|
||||
config = json.loads(rv.data)
|
||||
|
@ -62,15 +63,15 @@ def test_config(client):
|
|||
app.config['CONFIG_DISABLE'] = 1
|
||||
dark_mod = not demo_config['dark']
|
||||
demo_config['dark'] = dark_mod
|
||||
rv = client.post('/config', data=demo_config)
|
||||
rv = client.post(f'/{Endpoint.config}', data=demo_config)
|
||||
assert rv._status_code == 403
|
||||
|
||||
rv = client.get('/config')
|
||||
rv = client.get(f'/{Endpoint.config}')
|
||||
config = json.loads(rv.data)
|
||||
assert config['dark'] != dark_mod
|
||||
|
||||
|
||||
def test_opensearch(client):
|
||||
rv = client.get('/opensearch.xml')
|
||||
rv = client.get(f'/{Endpoint.opensearch}')
|
||||
assert rv._status_code == 200
|
||||
assert '<ShortName>Whoogle</ShortName>' in str(rv.data)
|
||||
|
|
|
@ -7,25 +7,28 @@
|
|||
# - docker-compose: Uncomment the env_file option
|
||||
# - docker: Add "--env-file ./whoogle.env" to your build command
|
||||
|
||||
#WHOOGLE_ALT_TW=nitter.net
|
||||
#WHOOGLE_ALT_YT=invidious.snopyta.org
|
||||
#WHOOGLE_ALT_IG=bibliogram.art/u
|
||||
#WHOOGLE_ALT_RD=libredd.it
|
||||
#WHOOGLE_ALT_TW=farside.link/nitter
|
||||
#WHOOGLE_ALT_YT=farside.link/invidious
|
||||
#WHOOGLE_ALT_IG=farside.link/bibliogram/u
|
||||
#WHOOGLE_ALT_RD=farside.link/libreddit
|
||||
#WHOOGLE_ALT_MD=farside.link/scribe
|
||||
#WHOOGLE_ALT_TL=lingva.ml
|
||||
#WHOOGLE_ALT_MD=scribe.rip
|
||||
#WHOOGLE_ALT_IMG=imgin.voidnet.tech
|
||||
#WHOOGLE_ALT_WIKI=wikiless.org
|
||||
#WHOOGLE_USER=""
|
||||
#WHOOGLE_PASS=""
|
||||
#WHOOGLE_PROXY_USER=""
|
||||
#WHOOGLE_PROXY_PASS=""
|
||||
#WHOOGLE_PROXY_TYPE=""
|
||||
#WHOOGLE_PROXY_LOC=""
|
||||
#WHOOGLE_CSP=1
|
||||
#HTTPS_ONLY=1
|
||||
|
||||
# Restrict results to only those near a particular city
|
||||
#WHOOGLE_CONFIG_NEAR=denver
|
||||
|
||||
# See app/static/settings/countries.json for values
|
||||
#WHOOGLE_CONFIG_COUNTRY=countryUK
|
||||
#WHOOGLE_CONFIG_COUNTRY=US
|
||||
|
||||
# See app/static/settings/languages.json for values
|
||||
#WHOOGLE_CONFIG_LANGUAGE=lang_en
|
||||
|
@ -60,6 +63,18 @@
|
|||
# Search using GET requests only (exposes query in logs)
|
||||
#WHOOGLE_CONFIG_GET_ONLY=1
|
||||
|
||||
# Remove everything except basic result cards from all search queries
|
||||
#WHOOGLE_MINIMAL=0
|
||||
|
||||
# Set the number of results per page
|
||||
#WHOOGLE_RESULTS_PER_PAGE=10
|
||||
|
||||
# Controls visibility of autocomplete/search suggestions
|
||||
#WHOOGLE_AUTOCOMPLETE=1
|
||||
|
||||
# The port where Whoogle will be exposed
|
||||
#EXPOSE_PORT=5000
|
||||
|
||||
# Set instance URL
|
||||
#WHOOGLE_CONFIG_URL=https://<whoogle url>/
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user