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

|

|
||||||
|
|
28
app.json
28
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,11 +75,16 @@
|
||||||
"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
|
"required": false
|
||||||
},
|
},
|
||||||
|
"WHOOGLE_ALT_WIKI": {
|
||||||
|
"description": "The site to use as a replacement for wikipedia.com when site alternatives are enabled in the config.",
|
||||||
|
"value": "wikiless.org",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
"WHOOGLE_MINIMAL": {
|
"WHOOGLE_MINIMAL": {
|
||||||
"description": "Remove everything except basic result cards from all search queries (set to 1 or leave blank)",
|
"description": "Remove everything except basic result cards from all search queries (set to 1 or leave blank)",
|
||||||
"value": "",
|
"value": "",
|
||||||
|
|
|
@ -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 []
|
||||||
|
|
||||||
root = ET.fromstring(response)
|
try:
|
||||||
return [_.attrib['data'] for _ in
|
root = ET.fromstring(response)
|
||||||
root.findall('.//suggestion/[@data]')]
|
return [_.attrib['data'] for _ in
|
||||||
|
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:
|
||||||
tor_check = requests.get('https://check.torproject.org/',
|
try:
|
||||||
proxies=self.proxies, headers=headers)
|
tor_check = requests.get('https://check.torproject.org/',
|
||||||
self.tor_valid = 'Congratulations' in tor_check.text
|
proxies=self.proxies, headers=headers)
|
||||||
|
self.tor_valid = 'Congratulations' in tor_check.text
|
||||||
|
|
||||||
if not self.tor_valid:
|
if not self.tor_valid:
|
||||||
|
raise TorError(
|
||||||
|
"Tor connection succeeded, but the connection could "
|
||||||
|
"not be validated by torproject.org",
|
||||||
|
disable=True)
|
||||||
|
except ConnectionError:
|
||||||
raise TorError(
|
raise TorError(
|
||||||
"Tor connection succeeded, but the connection could not "
|
"Error raised during Tor connection validation",
|
||||||
"be validated by torproject.org",
|
|
||||||
disable=True)
|
disable=True)
|
||||||
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
|
|
225
app/routes.py
225
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
|
# Skip checking for session on any searches that don't
|
||||||
# unnecessary session directory expansion
|
# require a valid session
|
||||||
g.cookies_disabled = True
|
if (not Endpoint.autocomplete.in_path(request.path) and
|
||||||
|
not Endpoint.healthz.in_path(request.path) and
|
||||||
# Handle https upgrade
|
not Endpoint.opensearch.in_path(request.path)):
|
||||||
if needs_https(request.url):
|
return redirect(url_for(
|
||||||
return redirect(
|
'session_check',
|
||||||
request.url.replace('http://', 'https://', 1),
|
session_id=session['uuid'],
|
||||||
code=308)
|
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)
|
|
||||||
|
|
||||||
resp.headers['Content-Security-Policy'] = app.config['CSP']
|
if os.getenv('WHOOGLE_CSP', False):
|
||||||
if os.environ.get('HTTPS_ONLY', False):
|
resp.headers['Content-Security-Policy'] = app.config['CSP']
|
||||||
resp.headers['Content-Security-Policy'] += 'upgrade-insecure-requests'
|
if os.environ.get('HTTPS_ONLY', False):
|
||||||
|
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 }} ||
|
<script src="{{ cb_url('autocomplete.js') }}"></script>
|
||||||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
|
{% endif %}
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
<script src="{{ cb_url('autocomplete.js') }}"></script>
|
|
||||||
<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 @@
|
||||||
<h1>Error</h1>
|
{% if config.theme %}
|
||||||
<hr>
|
{% if config.theme == 'system' %}
|
||||||
<p>
|
<style>
|
||||||
Error: "{{ error_message|safe }}"
|
@import "{{ cb_url('light-theme.css') }}" screen;
|
||||||
</p>
|
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
|
||||||
<a href="/">Return Home</a>
|
</style>
|
||||||
|
{% else %}
|
||||||
|
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
|
||||||
|
{% endif %}
|
||||||
|
<link rel="stylesheet" href="{{ cb_url('main.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ cb_url('error.css') }}">
|
||||||
|
<style>{{ config.style }}</style>
|
||||||
|
<div>
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>
|
||||||
|
{{ error_message|safe }}
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
{% if blocked is defined %}
|
||||||
|
<h4>{{ translation['continue-search'] }} <a class="link" href="https://github.com/benbusby/farside">Farside</a>!</h4>
|
||||||
|
Whoogle:
|
||||||
|
<br>
|
||||||
|
<a class="link-color" href="{{farside}}/whoogle/search?q={{query}}{{params}}">
|
||||||
|
{{farside}}/whoogle/search?q={{query}}{{params}}
|
||||||
|
</a>
|
||||||
|
<br><br>
|
||||||
|
Searx:
|
||||||
|
<br>
|
||||||
|
<a class="link-color" href="{{farside}}/searx/search?q={{query}}">
|
||||||
|
{{farside}}/searx/search?q={{query}}
|
||||||
|
</a>
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<a class="link" href="home">Return Home</a>
|
||||||
|
</div>
|
||||||
|
|
9
app/templates/footer.html
Normal file
9
app/templates/footer.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<footer>
|
||||||
|
<p class="footer">
|
||||||
|
Whoogle Search v{{ version_number }} ||
|
||||||
|
<a class="link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
|
||||||
|
{% if newest_version %}
|
||||||
|
|| <span class="update_available">Update Available 🟢</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</footer>
|
|
@ -17,10 +17,13 @@
|
||||||
<meta name="referrer" content="no-referrer">
|
<meta name="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,145 +87,146 @@
|
||||||
<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 }}"
|
||||||
selected
|
{% if country.value in config.country %}
|
||||||
{% endif %}>
|
selected
|
||||||
{{ ctry.name }}
|
{% endif %}>
|
||||||
</option>
|
{{ country.name }}
|
||||||
{% endfor %}
|
</option>
|
||||||
</select>
|
{% endfor %}
|
||||||
<div><span class="info-text"> — {{ translation['config-country-help'] }}</span></div>
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-lang">
|
||||||
|
<label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
|
||||||
|
<select name="lang_interface" id="config-lang-interface">
|
||||||
|
{% for lang in languages %}
|
||||||
|
<option value="{{ lang.value }}"
|
||||||
|
{% if lang.value in config.lang_interface %}
|
||||||
|
selected
|
||||||
|
{% endif %}>
|
||||||
|
{{ lang.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-search-lang">
|
||||||
|
<label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
|
||||||
|
<select name="lang_search" id="config-lang-search">
|
||||||
|
{% for lang in languages %}
|
||||||
|
<option value="{{ lang.value }}"
|
||||||
|
{% if lang.value in config.lang_search %}
|
||||||
|
selected
|
||||||
|
{% endif %}>
|
||||||
|
{{ lang.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-near">
|
||||||
|
<label for="config-near">{{ translation['config-near'] }}: </label>
|
||||||
|
<input type="text" name="near" id="config-near"
|
||||||
|
placeholder="{{ translation['config-near-help'] }}" value="{{ config.near }}">
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-block">
|
||||||
|
<label for="config-block">{{ translation['config-block'] }}: </label>
|
||||||
|
<input type="text" name="block" id="config-block"
|
||||||
|
placeholder="{{ translation['config-block-help'] }}" value="{{ config.block }}">
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-block">
|
||||||
|
<label for="config-block-title">{{ translation['config-block-title'] }}: </label>
|
||||||
|
<input type="text" name="block_title" id="config-block"
|
||||||
|
placeholder="{{ translation['config-block-title-help'] }}"
|
||||||
|
value="{{ config.block_title }}">
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-block">
|
||||||
|
<label for="config-block-url">{{ translation['config-block-url'] }}: </label>
|
||||||
|
<input type="text" name="block_url" id="config-block"
|
||||||
|
placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-nojs">
|
||||||
|
<label for="config-nojs">{{ translation['config-nojs'] }}: </label>
|
||||||
|
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-theme">
|
||||||
|
<label for="config-theme">{{ translation['config-theme'] }}: </label>
|
||||||
|
<select name="theme" id="config-theme">
|
||||||
|
{% for theme in themes %}
|
||||||
|
<option value="{{ theme }}"
|
||||||
|
{% if theme in config.theme %}
|
||||||
|
selected
|
||||||
|
{% endif %}>
|
||||||
|
{{ translation[theme].capitalize() }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<!-- DEPRECATED -->
|
||||||
|
<!--<div class="config-div config-div-dark">-->
|
||||||
|
<!--<label for="config-dark">{{ translation['config-dark'] }}: </label>-->
|
||||||
|
<!--<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>-->
|
||||||
|
<!--</div>-->
|
||||||
|
<div class="config-div config-div-safe">
|
||||||
|
<label for="config-safe">{{ translation['config-safe'] }}: </label>
|
||||||
|
<input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-alts">
|
||||||
|
<label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label>
|
||||||
|
<input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}>
|
||||||
|
<div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-new-tab">
|
||||||
|
<label for="config-new-tab">{{ translation['config-new-tab'] }}: </label>
|
||||||
|
<input type="checkbox" name="new_tab"
|
||||||
|
id="config-new-tab" {{ 'checked' if config.new_tab else '' }}>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-view-image">
|
||||||
|
<label for="config-view-image">{{ translation['config-images'] }}: </label>
|
||||||
|
<input type="checkbox" name="view_image"
|
||||||
|
id="config-view-image" {{ 'checked' if config.view_image else '' }}>
|
||||||
|
<div><span class="info-text"> — {{ translation['config-images-help'] }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-tor">
|
||||||
|
<label for="config-tor">{{ translation['config-tor'] }}: {{ '' if tor_available else 'Unavailable' }}</label>
|
||||||
|
<input type="checkbox" name="tor"
|
||||||
|
id="config-tor" {{ '' if tor_available else 'hidden' }} {{ 'checked' if config.tor else '' }}>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-get-only">
|
||||||
|
<label for="config-get-only">{{ translation['config-get-only'] }}: </label>
|
||||||
|
<input type="checkbox" name="get_only"
|
||||||
|
id="config-get-only" {{ 'checked' if config.get_only else '' }}>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-get-only">
|
||||||
|
<label for="config-accept-language">Set Accept-Language: </label>
|
||||||
|
<input type="checkbox" name="accept_language"
|
||||||
|
id="config-accept-language" {{ 'checked' if config.accept_language else '' }}>
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-root-url">
|
||||||
|
<label for="config-url">{{ translation['config-url'] }}: </label>
|
||||||
|
<input type="text" name="url" id="config-url" value="{{ config.url }}">
|
||||||
|
</div>
|
||||||
|
<div class="config-div config-div-custom-css">
|
||||||
|
<a id="css-link"
|
||||||
|
href="https://github.com/benbusby/whoogle-search/wiki/User-Contributed-CSS-Themes">
|
||||||
|
{{ translation['config-css'] }}:
|
||||||
|
</a>
|
||||||
|
<textarea
|
||||||
|
name="style"
|
||||||
|
id="config-style"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
autocorrect="off"
|
||||||
|
value="">
|
||||||
|
{{ config.style.replace('\t', '') }}
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-div config-div-lang">
|
<div class="config-div config-buttons">
|
||||||
<label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
|
|
||||||
<select name="lang_interface" id="config-lang-interface">
|
|
||||||
{% for lang in languages %}
|
|
||||||
<option value="{{ lang.value }}"
|
|
||||||
{% if lang.value in config.lang_interface %}
|
|
||||||
selected
|
|
||||||
{% endif %}>
|
|
||||||
{{ lang.name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-search-lang">
|
|
||||||
<label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
|
|
||||||
<select name="lang_search" id="config-lang-search">
|
|
||||||
{% for lang in languages %}
|
|
||||||
<option value="{{ lang.value }}"
|
|
||||||
{% if lang.value in config.lang_search %}
|
|
||||||
selected
|
|
||||||
{% endif %}>
|
|
||||||
{{ lang.name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-near">
|
|
||||||
<label for="config-near">{{ translation['config-near'] }}: </label>
|
|
||||||
<input type="text" name="near" id="config-near"
|
|
||||||
placeholder="{{ translation['config-near-help'] }}" value="{{ config.near }}">
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-block">
|
|
||||||
<label for="config-block">{{ translation['config-block'] }}: </label>
|
|
||||||
<input type="text" name="block" id="config-block"
|
|
||||||
placeholder="{{ translation['config-block-help'] }}" value="{{ config.block }}">
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-block">
|
|
||||||
<label for="config-block-title">{{ translation['config-block-title'] }}: </label>
|
|
||||||
<input type="text" name="block_title" id="config-block"
|
|
||||||
placeholder="{{ translation['config-block-title-help'] }}"
|
|
||||||
value="{{ config.block_title }}">
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-block">
|
|
||||||
<label for="config-block-url">{{ translation['config-block-url'] }}: </label>
|
|
||||||
<input type="text" name="block_url" id="config-block"
|
|
||||||
placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-nojs">
|
|
||||||
<label for="config-nojs">{{ translation['config-nojs'] }}: </label>
|
|
||||||
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-theme">
|
|
||||||
<label for="config-theme">{{ translation['config-theme'] }}: </label>
|
|
||||||
<select name="theme" id="config-theme">
|
|
||||||
{% for theme in themes %}
|
|
||||||
<option value="{{ theme }}"
|
|
||||||
{% if theme in config.theme %}
|
|
||||||
selected
|
|
||||||
{% endif %}>
|
|
||||||
{{ translation[theme].capitalize() }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<!-- DEPRECATED -->
|
|
||||||
<!--<div class="config-div config-div-dark">-->
|
|
||||||
<!--<label for="config-dark">{{ translation['config-dark'] }}: </label>-->
|
|
||||||
<!--<input type="checkbox" name="dark" id="config-dark" {{ 'checked' if config.dark else '' }}>-->
|
|
||||||
<!--</div>-->
|
|
||||||
<div class="config-div config-div-safe">
|
|
||||||
<label for="config-safe">{{ translation['config-safe'] }}: </label>
|
|
||||||
<input type="checkbox" name="safe" id="config-safe" {{ 'checked' if config.safe else '' }}>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-alts">
|
|
||||||
<label class="tooltip" for="config-alts">{{ translation['config-alts'] }}: </label>
|
|
||||||
<input type="checkbox" name="alts" id="config-alts" {{ 'checked' if config.alts else '' }}>
|
|
||||||
<div><span class="info-text"> — {{ translation['config-alts-help'] }}</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-new-tab">
|
|
||||||
<label for="config-new-tab">{{ translation['config-new-tab'] }}: </label>
|
|
||||||
<input type="checkbox" name="new_tab"
|
|
||||||
id="config-new-tab" {{ 'checked' if config.new_tab else '' }}>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-view-image">
|
|
||||||
<label for="config-view-image">{{ translation['config-images'] }}: </label>
|
|
||||||
<input type="checkbox" name="view_image"
|
|
||||||
id="config-view-image" {{ 'checked' if config.view_image else '' }}>
|
|
||||||
<div><span class="info-text"> — {{ translation['config-images-help'] }}</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-tor">
|
|
||||||
<label for="config-tor">{{ translation['config-tor'] }}: {{ '' if tor_available else 'Unavailable' }}</label>
|
|
||||||
<input type="checkbox" name="tor"
|
|
||||||
id="config-tor" {{ '' if tor_available else 'hidden' }} {{ 'checked' if config.tor else '' }}>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-get-only">
|
|
||||||
<label for="config-get-only">{{ translation['config-get-only'] }}: </label>
|
|
||||||
<input type="checkbox" name="get_only"
|
|
||||||
id="config-get-only" {{ 'checked' if config.get_only else '' }}>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-get-only">
|
|
||||||
<label for="config-accept-language">Set Accept-Language: </label>
|
|
||||||
<input type="checkbox" name="accept_language"
|
|
||||||
id="config-accept-language" {{ 'checked' if config.accept_language else '' }}>
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-root-url">
|
|
||||||
<label for="config-url">{{ translation['config-url'] }}: </label>
|
|
||||||
<input type="text" name="url" id="config-url" value="{{ config.url }}">
|
|
||||||
</div>
|
|
||||||
<div class="config-div config-div-custom-css">
|
|
||||||
<a id="css-link"
|
|
||||||
href="https://github.com/benbusby/whoogle-search/wiki/User-Contributed-CSS-Themes">
|
|
||||||
{{ translation['config-css'] }}:
|
|
||||||
</a>
|
|
||||||
<textarea
|
|
||||||
name="style"
|
|
||||||
id="config-style"
|
|
||||||
autocapitalize="off"
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false"
|
|
||||||
autocorrect="off"
|
|
||||||
value="">
|
|
||||||
{{ config.style.replace('\t', '') }}
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="config-div">
|
|
||||||
<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: {}
|
|
@ -31,13 +31,15 @@ services:
|
||||||
#- WHOOGLE_PROXY_LOC=<proxy host/ip>
|
#- WHOOGLE_PROXY_LOC=<proxy host/ip>
|
||||||
# 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
|
|
@ -3,5 +3,9 @@
|
||||||
if [ "$(whoami)" != "root" ]; then
|
if [ "$(whoami)" != "root" ]; then
|
||||||
tor -f /etc/tor/torrc
|
tor -f /etc/tor/torrc
|
||||||
else
|
else
|
||||||
service tor start
|
if (grep alpine /etc/os-release >/dev/null); then
|
||||||
|
rc-service tor start
|
||||||
|
else
|
||||||
|
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
|
||||||
|
@ -55,11 +58,23 @@
|
||||||
#WHOOGLE_CONFIG_NEW_TAB=1
|
#WHOOGLE_CONFIG_NEW_TAB=1
|
||||||
|
|
||||||
# Enable View Image option
|
# Enable View Image option
|
||||||
#WHOOGLE_CONFIG_VIEW_IMAGE=1
|
#WHOOGLE_CONFIG_VIEW_IMAGE=1
|
||||||
|
|
||||||
# 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