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:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: ["tests"]
|
workflows: ["docker_tests"]
|
||||||
branches: [main]
|
branches: [main]
|
||||||
types:
|
types:
|
||||||
- completed
|
- 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"
|
language = "bash"
|
||||||
run = "pip install -r requirements.txt && ./run"
|
run = "killall -q python3 > /dev/null 2>&1; pip install -r requirements.txt && ./run"
|
||||||
onBoot = "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 \
|
RUN apk --update add \
|
||||||
build-essential \
|
build-base \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libxslt-dev \
|
libxslt-dev \
|
||||||
libssl-dev \
|
openssl-dev \
|
||||||
libffi-dev
|
libffi-dev
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN pip install --upgrade pip
|
||||||
RUN pip install --prefix /install --no-warn-script-location --no-cache-dir -r requirements.txt
|
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 \
|
RUN apk add --update --no-cache tor curl bash openrc
|
||||||
libcurl4-openssl-dev \
|
# libcurl4-openssl-dev
|
||||||
tor \
|
|
||||||
curl \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
|
ARG DOCKER_USER=whoogle
|
||||||
|
ARG DOCKER_USERID=927
|
||||||
ARG config_dir=/config
|
ARG config_dir=/config
|
||||||
RUN mkdir -p $config_dir
|
RUN mkdir -p -m 777 $config_dir
|
||||||
VOLUME $config_dir
|
VOLUME $config_dir
|
||||||
ENV CONFIG_VOLUME=$config_dir
|
|
||||||
|
|
||||||
ARG username=''
|
ARG username=''
|
||||||
ENV WHOOGLE_USER=$username
|
|
||||||
ARG password=''
|
ARG password=''
|
||||||
ENV WHOOGLE_PASS=$password
|
|
||||||
|
|
||||||
ARG proxyuser=''
|
ARG proxyuser=''
|
||||||
ENV WHOOGLE_PROXY_USER=$proxyuser
|
|
||||||
ARG proxypass=''
|
ARG proxypass=''
|
||||||
ENV WHOOGLE_PROXY_PASS=$proxypass
|
|
||||||
ARG proxytype=''
|
ARG proxytype=''
|
||||||
ENV WHOOGLE_PROXY_TYPE=$proxytype
|
|
||||||
ARG proxyloc=''
|
ARG proxyloc=''
|
||||||
ENV WHOOGLE_PROXY_LOC=$proxyloc
|
|
||||||
|
|
||||||
ARG whoogle_dotenv=''
|
ARG whoogle_dotenv=''
|
||||||
ENV WHOOGLE_DOTENV=$whoogle_dotenv
|
|
||||||
|
|
||||||
ARG use_https=''
|
ARG use_https=''
|
||||||
ENV HTTPS_ONLY=$use_https
|
|
||||||
|
|
||||||
ARG whoogle_port=5000
|
ARG whoogle_port=5000
|
||||||
ENV EXPOSE_PORT=$whoogle_port
|
ARG twitter_alt='farside.link/nitter'
|
||||||
|
ARG youtube_alt='farside.link/invidious'
|
||||||
ARG twitter_alt='nitter.net'
|
ARG instagram_alt='farside.link/bibliogram'
|
||||||
ENV WHOOGLE_ALT_TW=$twitter_alt
|
ARG reddit_alt='farside.link/libreddit'
|
||||||
ARG youtube_alt='invidious.snopyta.org'
|
ARG medium_alt='farside.link/scribe'
|
||||||
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 translate_alt='lingva.ml'
|
ARG translate_alt='lingva.ml'
|
||||||
ENV WHOOGLE_ALT_TL=$translate_alt
|
ARG imgur_alt='imgin.voidnet.tech'
|
||||||
ARG medium_alt='scribe.rip'
|
ARG wikipedia_alt='wikiless.org'
|
||||||
ENV WHOOGLE_ALT_MD=$medium_alt
|
|
||||||
|
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
|
WORKDIR /whoogle
|
||||||
|
|
||||||
|
@ -72,6 +72,13 @@ COPY run .
|
||||||
# Allow writing symlinks to build dir
|
# Allow writing symlinks to build dir
|
||||||
RUN chown 102:102 app/static/build
|
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
|
EXPOSE $EXPOSE_PORT
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s \
|
HEALTHCHECK --interval=30s --timeout=5s \
|
||||||
|
|
64
README.md
64
README.md
|
@ -22,6 +22,7 @@ Contents
|
||||||
6. [Manual](#f-manual)
|
6. [Manual](#f-manual)
|
||||||
7. [Docker](#g-manual-docker)
|
7. [Docker](#g-manual-docker)
|
||||||
8. [Arch/AUR](#arch-linux--arch-based-distributions)
|
8. [Arch/AUR](#arch-linux--arch-based-distributions)
|
||||||
|
9. [Helm/Kubernetes](#helm-chart-for-kubernetes)
|
||||||
4. [Environment Variables and Configuration](#environment-variables)
|
4. [Environment Variables and Configuration](#environment-variables)
|
||||||
5. [Usage](#usage)
|
5. [Usage](#usage)
|
||||||
6. [Extra Steps](#extra-steps)
|
6. [Extra Steps](#extra-steps)
|
||||||
|
@ -163,7 +164,7 @@ See the [available environment variables](#environment-variables) for additional
|
||||||
|
|
||||||
### F) Manual
|
### 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:
|
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.
|
See the [available environment variables](#environment-variables) for additional configuration.
|
||||||
|
|
||||||
#### systemd 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
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
|
@ -196,18 +197,26 @@ Description=Whoogle
|
||||||
# Site alternative configurations, uncomment to enable
|
# Site alternative configurations, uncomment to enable
|
||||||
# Note: If not set, the feature will still be available
|
# Note: If not set, the feature will still be available
|
||||||
# with default values.
|
# with default values.
|
||||||
#Environment=WHOOGLE_ALT_TW=nitter.net
|
#Environment=WHOOGLE_ALT_TW=farside.link/nitter
|
||||||
#Environment=WHOOGLE_ALT_YT=invidious.snopyta.org
|
#Environment=WHOOGLE_ALT_YT=farside.link/invidious
|
||||||
#Environment=WHOOGLE_ALT_IG=bibliogram.art/u
|
#Environment=WHOOGLE_ALT_IG=farside.link/bibliogram/u
|
||||||
#Environment=WHOOGLE_ALT_RD=libredd.it
|
#Environment=WHOOGLE_ALT_RD=farside.link/libreddit
|
||||||
|
#Environment=WHOOGLE_ALT_MD=farside.link/scribe
|
||||||
#Environment=WHOOGLE_ALT_TL=lingva.ml
|
#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
|
# Load values from dotenv only
|
||||||
#Environment=WHOOGLE_DOTENV=1
|
#Environment=WHOOGLE_DOTENV=1
|
||||||
Type=simple
|
Type=simple
|
||||||
User=<username>
|
User=<username>
|
||||||
WorkingDirectory=<whoogle_directory>
|
# If installed as a package, add:
|
||||||
ExecStart=<whoogle_directory>/venv/bin/python3 -um app --host 0.0.0.0 --port 5000
|
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
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=3
|
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
|
#### 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).
|
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
|
#### 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.
|
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_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_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_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_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_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
|
### 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.
|
These environment variables allow setting default config values, but can be overwritten manually by using the home page config menu. These allow a shortcut for destroying/rebuilding an instance to the same config state every time.
|
||||||
|
@ -488,25 +508,33 @@ A lot of the app currently piggybacks on Google's existing support for fetching
|
||||||
|
|
||||||
## Public Instances
|
## 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 |
|
| 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.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://search.garudalinux.org](https://search.garudalinux.org) | 🇩🇪 DE | Multi-choice | |
|
||||||
| [https://whooglesearch.net](https://whooglesearch.net) | 🇩🇪 DE | Spanish | |
|
| [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://search.exonip.de](https://search.exonip.de) | 🇳🇱 NL | Multi-choice | |
|
||||||
| [https://s.alefvanoon.xyz](https://s.alefvanoon.xyz) | 🇺🇸 US | English | ✅ |
|
| [https://s.alefvanoon.xyz](https://s.alefvanoon.xyz) | 🇺🇸 US | Multi-choice | ✅ |
|
||||||
| [https://search.flux.industries](https://search.flux.industries) | 🇩🇪 DE | German | ✅ |
|
| [https://www.whooglesearch.ml](https://www.whooglesearch.ml) | 🇺🇸 US | English | |
|
||||||
| [http://whoogledq5f5wly5p4i2ohnvjwlihnlg4oajjum2oeddfwqdwupbuhqd.onion](http://whoogledq5f5wly5p4i2ohnvjwlihnlg4oajjum2oeddfwqdwupbuhqd.onion) | 🇮🇳 IN | Unknown | |
|
| [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
|
## Screenshots
|
||||||
#### Desktop
|
#### Desktop
|
||||||

|

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

|

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