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 \
|
||||
|
|
76
README.md
76
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)
|
||||
|
@ -84,7 +85,7 @@ Provides:
|
|||
- Free HTTPS url (https://\<your app name\>.herokuapp.com)
|
||||
- Downtime after periods of inactivity \([solution](https://github.com/benbusby/whoogle-search#prevent-downtime-heroku-only)\)
|
||||
|
||||
Notes:
|
||||
Notes:
|
||||
- Requires a (free) Heroku account
|
||||
- Sometimes has issues with auto-redirecting to `https`. Make sure to navigate to the `https` version of your app before adding as a default search engine.
|
||||
|
||||
|
@ -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]
|
||||
|
@ -195,19 +196,27 @@ Description=Whoogle
|
|||
#Environment=WHOOGLE_PROXY_LOC=<proxy host/ip>
|
||||
# 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
|
||||
# with default values.
|
||||
#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.
|
||||
|
@ -413,7 +433,7 @@ Note: You should have your own domain name and [an https certificate](https://le
|
|||
- Docker image: Set the environment variable HTTPS_ONLY=1
|
||||
- Pip/Pipx: Add the `--https-only` flag to the end of the `whoogle-search` command
|
||||
- Default `run` script: Modify the script locally to include the `--https-only` flag at the end of the python run command
|
||||
|
||||
|
||||
### Using with Firefox Containers
|
||||
Unfortunately, Firefox Containers do not currently pass through `POST` requests (the default) to the engine, and Firefox caches the opensearch template on initial page load. To get around this, you can take the following steps to get it working as expected:
|
||||
|
||||
|
@ -448,7 +468,7 @@ Under the hood, Whoogle is a basic Flask app with the following structure:
|
|||
- CSS/Javascript files, should be self-explanatory
|
||||
- `static/settings`
|
||||
- Key-value JSON files for establishing valid configuration values
|
||||
|
||||
|
||||
|
||||
If you're new to the project, the easiest way to get started would be to try fixing [an open bug report](https://github.com/benbusby/whoogle-search/issues?q=is%3Aissue+is%3Aopen+label%3Abug). If there aren't any open, or if the open ones are too stale, try taking on a [feature request](https://github.com/benbusby/whoogle-search/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement). Generally speaking, if you can write something that has any potential of breaking down in the future, you should write a test for it.
|
||||
|
||||
|
@ -467,7 +487,7 @@ def contains(x: list, y: int) -> bool:
|
|||
"""
|
||||
|
||||
return y in x
|
||||
```
|
||||
```
|
||||
|
||||
#### Translating
|
||||
|
||||
|
@ -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: {}
|
|
@ -31,13 +31,15 @@ services:
|
|||
#- WHOOGLE_PROXY_LOC=<proxy host/ip>
|
||||
# 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
|
||||
# with default values.
|
||||
#- 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
|
||||
|
@ -55,11 +58,23 @@
|
|||
#WHOOGLE_CONFIG_NEW_TAB=1
|
||||
|
||||
# Enable View Image option
|
||||
#WHOOGLE_CONFIG_VIEW_IMAGE=1
|
||||
#WHOOGLE_CONFIG_VIEW_IMAGE=1
|
||||
|
||||
# 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