Compare commits
No commits in common. 'main' and 'v0.5.4' have entirely different histories.
@ -1,38 +0,0 @@
|
||||
---
|
||||
name: New theme
|
||||
about: Create a new theme for Whoogle
|
||||
title: "[THEME] <your theme name>"
|
||||
labels: theme
|
||||
assignees: benbusby
|
||||
|
||||
---
|
||||
|
||||
Use the following template to design your theme, replacing the blank spaces with the colors of your choice.
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* LIGHT THEME COLORS */
|
||||
--whoogle-logo: #______;
|
||||
--whoogle-page-bg: #______;
|
||||
--whoogle-element-bg: #______;
|
||||
--whoogle-text: #______;
|
||||
--whoogle-contrast-text: #______;
|
||||
--whoogle-secondary-text: #______;
|
||||
--whoogle-result-bg: #______;
|
||||
--whoogle-result-title: #______;
|
||||
--whoogle-result-url: #______;
|
||||
--whoogle-result-visited: #______;
|
||||
|
||||
/* DARK THEME COLORS */
|
||||
--whoogle-dark-logo: #______;
|
||||
--whoogle-dark-page-bg: #______;
|
||||
--whoogle-dark-element-bg: #______;
|
||||
--whoogle-dark-text: #______;
|
||||
--whoogle-dark-contrast-text: #______;
|
||||
--whoogle-dark-secondary-text: #______;
|
||||
--whoogle-dark-result-bg: #______;
|
||||
--whoogle-dark-result-title: #______;
|
||||
--whoogle-dark-result-url: #______;
|
||||
--whoogle-dark-result-visited: #______;
|
||||
}
|
||||
```
|
@ -1,28 +0,0 @@
|
||||
name: docker_main
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["tests"]
|
||||
branches: [main]
|
||||
types:
|
||||
- completed
|
||||
|
||||
# TODO: Needs refactoring to use reusable workflows and share w/ docker_tests
|
||||
jobs:
|
||||
on-success:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: build and test (docker)
|
||||
run: |
|
||||
docker build --tag whoogle-search:test .
|
||||
docker run --publish 5000:5000 --detach --name whoogle-search-nocompose whoogle-search:test
|
||||
sleep 15
|
||||
docker exec whoogle-search-nocompose curl -f http://localhost:5000/healthz || exit 1
|
||||
- name: build and test (docker-compose)
|
||||
run: |
|
||||
docker rm -f whoogle-search-nocompose
|
||||
WHOOGLE_IMAGE="whoogle-search:test" docker-compose up --detach
|
||||
sleep 15
|
||||
docker exec whoogle-search curl -f http://localhost:5000/healthz || exit 1
|
@ -1,26 +0,0 @@
|
||||
name: docker_tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
pull_request:
|
||||
branches: main
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: build and test (docker)
|
||||
run: |
|
||||
docker build --tag whoogle-search:test .
|
||||
docker run --publish 5000:5000 --detach --name whoogle-search-nocompose whoogle-search:test
|
||||
sleep 15
|
||||
docker exec whoogle-search-nocompose curl -f http://localhost:5000/healthz || exit 1
|
||||
- name: build and test (docker-compose)
|
||||
run: |
|
||||
docker rm -f whoogle-search-nocompose
|
||||
WHOOGLE_IMAGE="whoogle-search:test" docker-compose up --detach
|
||||
sleep 15
|
||||
docker exec whoogle-search curl -f http://localhost:5000/healthz || exit 1
|
@ -0,0 +1,22 @@
|
||||
name: pep8
|
||||
|
||||
on:
|
||||
push
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pycodestyle
|
||||
- name: Run pycodestyle
|
||||
run: |
|
||||
pycodestyle --show-source --show-pep8 app/*
|
||||
pycodestyle --show-source --show-pep8 test/*
|
@ -1,68 +0,0 @@
|
||||
name: pypi
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
tags: v*
|
||||
|
||||
jobs:
|
||||
publish-test:
|
||||
name: Build and publish to TestPyPI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install pypa/build
|
||||
run: >-
|
||||
python -m
|
||||
pip install
|
||||
build
|
||||
setuptools
|
||||
--user
|
||||
- name: Set dev timestamp
|
||||
run: echo "DEV_BUILD=$(date +%s)" >> $GITHUB_ENV
|
||||
- name: Build binary wheel and source tarball
|
||||
run: >-
|
||||
python -m
|
||||
build
|
||||
--sdist
|
||||
--wheel
|
||||
--outdir dist/
|
||||
.
|
||||
- name: Publish distribution to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
publish:
|
||||
name: Build and publish to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install pypa/build
|
||||
run: >-
|
||||
python -m
|
||||
pip install
|
||||
build
|
||||
--user
|
||||
- name: Build binary wheel and source tarball
|
||||
run: >-
|
||||
python -m
|
||||
build
|
||||
--sdist
|
||||
--wheel
|
||||
--outdir dist/
|
||||
.
|
||||
- name: Publish distribution to PyPI
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
@ -1,19 +0,0 @@
|
||||
name: scan
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build the container image
|
||||
run: |
|
||||
docker build --tag whoogle-search:test .
|
||||
- name: Initiate grype scan
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b .
|
||||
chmod +x ./grype
|
||||
./grype whoogle-search:test --only-fixed
|
@ -1,17 +0,0 @@
|
||||
name: tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: pip install --upgrade pip && pip install -r requirements.txt
|
||||
- name: Run tests
|
||||
run: ./run test
|
@ -1 +1,3 @@
|
||||
entrypoint = "misc/replit.py"
|
||||
language = "python3"
|
||||
run = "./run"
|
||||
onBoot = "./run"
|
||||
|
@ -0,0 +1,15 @@
|
||||
language: python
|
||||
python: 3.6
|
||||
before_install:
|
||||
- sudo apt-get -y install libgnutls28-dev
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
script:
|
||||
- "./run test"
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: __token__
|
||||
password:
|
||||
secure: WNEH2Gg84MZF/AZEberFDGPPWb4cYyHAeD/XV8En94QRSI9Aznz6qiDKOvV4eVgjMAIEW5uB3TL1LHf6KU+Hrg6SmhF7JquqP1gsBOCDNFPTljO+k2Hc53uDdSnhi/HLgY7cnFNX4lc2nNrbyxZxMHuSA2oNz/tosyNGBEeyU+JA5va7uX0albGsLiNjimO4aeau83fsI0Hn2eN6ag68pewUMXNxzpyTeO2bRcCd5d5iILs07jMVwFoC2j7W11oNqrVuSWAs8CPe4+kwvNvXWxljUGiBGppNZ7RAsKNLwi6U6kGGUTWjQm09rY/2JBpJ2WEGmIWGIrno75iiFRbjnRp3mnXPvtVTyWhh+hQIUd7bJOVKM34i9eHotYTrkMJObgW1gnRzvI9VYldtgL/iP/Isn2Pv2EeMX8V+C9/8pxv0jkQkZMnFhE6gGlzpz37zTl04B2J7xyV5znM35Lx2Pn3zxdcmdCvD3yT8I4MuBbKqq2/v4emYCfPfOmfwnS0BEVSqr9lbx4xfUZV76tcvLcj4n86DJbx77pA2Ch8FRprpOOBcf0WuqTbZp8c3mb8prFp2EupUknXu7+C2VQ6sqrnzNuDeTGm/nyjjRQ81rlvlD4tqkwsEGEDDO44FF2eUTc5D2MvoHs4cnz095FWjy63gn5IxUjhMi31b5tGRz2Q=
|
||||
on:
|
||||
tags: true
|
@ -1,22 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Endpoint(Enum):
|
||||
autocomplete = 'autocomplete'
|
||||
home = 'home'
|
||||
healthz = 'healthz'
|
||||
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}')
|
@ -1,47 +0,0 @@
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
class GClasses:
|
||||
"""A class for tracking obfuscated class names used in Google results that
|
||||
are directly referenced in Whoogle's filtering code.
|
||||
|
||||
Note: Using these should be a last resort. It is always preferred to filter
|
||||
results using structural cues instead of referencing class names, as these
|
||||
are liable to change at any moment.
|
||||
"""
|
||||
main_tbm_tab = 'KP7LCb'
|
||||
images_tbm_tab = 'n692Zd'
|
||||
footer = 'TuS8Ad'
|
||||
result_class_a = 'ZINbbc'
|
||||
result_class_b = 'luh4td'
|
||||
scroller_class = 'idg8be'
|
||||
|
||||
result_classes = {
|
||||
result_class_a: ['Gx5Zad'],
|
||||
result_class_b: ['fP1Qef']
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def replace_css_classes(cls, soup: BeautifulSoup) -> BeautifulSoup:
|
||||
"""Replace updated Google classes with the original class names that
|
||||
Whoogle relies on for styling.
|
||||
|
||||
Args:
|
||||
soup: The result page as a BeautifulSoup object
|
||||
|
||||
Returns:
|
||||
BeautifulSoup: The new BeautifulSoup
|
||||
"""
|
||||
result_divs = soup.find_all('div', {
|
||||
'class': [_ for c in cls.result_classes.values() for _ in c]
|
||||
})
|
||||
|
||||
for div in result_divs:
|
||||
new_class = ' '.join(div['class'])
|
||||
for key, val in cls.result_classes.items():
|
||||
new_class = ' '.join(new_class.replace(_, key) for _ in val)
|
||||
div['class'] = new_class.split(' ')
|
||||
return soup
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"!i": {
|
||||
"url": "search?q={}&tbm=isch",
|
||||
"suggestion": "!i (Whoogle Images)"
|
||||
},
|
||||
"!v": {
|
||||
"url": "search?q={}&tbm=vid",
|
||||
"suggestion": "!v (Whoogle Videos)"
|
||||
},
|
||||
"!n": {
|
||||
"url": "search?q={}&tbm=nws",
|
||||
"suggestion": "!n (Whoogle News)"
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
@ -1,9 +0,0 @@
|
||||
html {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
html {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
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,67 +1,11 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const advSearchToggle = document.getElementById("adv-search-toggle");
|
||||
const advSearchDiv = document.getElementById("adv-search-div");
|
||||
const searchBar = document.getElementById("search-bar");
|
||||
const countrySelect = document.getElementById("result-country");
|
||||
const timePeriodSelect = document.getElementById("result-time-period");
|
||||
const arrowKeys = [37, 38, 39, 40];
|
||||
let searchValue = searchBar.value;
|
||||
|
||||
countrySelect.onchange = () => {
|
||||
let str = window.location.href;
|
||||
n = str.lastIndexOf("/search");
|
||||
if (n > 0) {
|
||||
str = str.substring(0, n) + `/search?q=${searchBar.value}`;
|
||||
str = tackOnParams(str);
|
||||
window.location.href = str;
|
||||
}
|
||||
}
|
||||
|
||||
timePeriodSelect.onchange = () => {
|
||||
let str = window.location.href;
|
||||
n = str.lastIndexOf("/search");
|
||||
if (n > 0) {
|
||||
str = str.substring(0, n) + `/search?q=${searchBar.value}`;
|
||||
str = tackOnParams(str);
|
||||
window.location.href = str;
|
||||
}
|
||||
}
|
||||
|
||||
function tackOnParams(str) {
|
||||
if (timePeriodSelect.value != "") {
|
||||
str = str + `&tbs=${timePeriodSelect.value}`;
|
||||
}
|
||||
if (countrySelect.value != "") {
|
||||
str = str + `&country=${countrySelect.value}`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
const toggleAdvancedSearch = on => {
|
||||
if (on) {
|
||||
advSearchDiv.style.maxHeight = "70px";
|
||||
searchBar.addEventListener("keyup", function (event) {
|
||||
if (event.keyCode !== 13) {
|
||||
handleUserInput(searchBar);
|
||||
} else {
|
||||
advSearchDiv.style.maxHeight = "0px";
|
||||
}
|
||||
localStorage.advSearchToggled = on;
|
||||
}
|
||||
|
||||
try {
|
||||
toggleAdvancedSearch(JSON.parse(localStorage.advSearchToggled));
|
||||
} catch (error) {
|
||||
console.warn("Did not recover advanced search toggle state");
|
||||
}
|
||||
|
||||
advSearchToggle.onclick = () => {
|
||||
toggleAdvancedSearch(advSearchToggle.checked);
|
||||
}
|
||||
|
||||
searchBar.addEventListener("keyup", function(event) {
|
||||
if (event.keyCode === 13) {
|
||||
document.getElementById("search-form").submit();
|
||||
} else if (searchBar.value !== searchValue && !arrowKeys.includes(event.keyCode)) {
|
||||
searchValue = searchBar.value;
|
||||
handleUserInput();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,62 +1,44 @@
|
||||
(function () {
|
||||
let searchBar, results;
|
||||
let shift = false;
|
||||
const keymap = {
|
||||
ArrowUp: goUp,
|
||||
ArrowDown: goDown,
|
||||
ShiftTab: goUp,
|
||||
Tab: goDown,
|
||||
k: goUp,
|
||||
j: goDown,
|
||||
'/': focusSearch,
|
||||
};
|
||||
let activeIdx = -1;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
searchBar = document.querySelector('#search-bar');
|
||||
results = document.querySelectorAll('#main>div>div>div>a');
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
shift = true;
|
||||
}
|
||||
|
||||
if (e.target.tagName === 'INPUT') return true;
|
||||
if (typeof keymap[e.key] === 'function') {
|
||||
e.preventDefault();
|
||||
|
||||
keymap[`${shift && e.key == 'Tab' ? 'Shift' : ''}${e.key}`]();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
shift = false;
|
||||
}
|
||||
});
|
||||
|
||||
function goUp () {
|
||||
if (activeIdx > 0) focusResult(activeIdx - 1);
|
||||
else focusSearch();
|
||||
}
|
||||
|
||||
function goDown () {
|
||||
if (activeIdx < results.length - 1) focusResult(activeIdx + 1);
|
||||
}
|
||||
|
||||
function focusResult (idx) {
|
||||
activeIdx = idx;
|
||||
results[activeIdx].scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
||||
results[activeIdx].focus();
|
||||
}
|
||||
|
||||
function focusSearch () {
|
||||
if (window.usingCalculator) {
|
||||
// if this function exists, it means the calculator widget has been displayed
|
||||
if (usingCalculator()) return;
|
||||
}
|
||||
activeIdx = -1;
|
||||
searchBar.focus();
|
||||
}
|
||||
let searchBar, results;
|
||||
const keymap = {
|
||||
ArrowUp: goUp,
|
||||
ArrowDown: goDown,
|
||||
k: goUp,
|
||||
j: goDown,
|
||||
'/': focusSearch,
|
||||
};
|
||||
let activeIdx = -1;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
searchBar = document.querySelector('#search-bar');
|
||||
results = document.querySelectorAll('#main>div>div>div>a');
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT') return true;
|
||||
if (typeof keymap[e.key] === 'function') {
|
||||
e.preventDefault();
|
||||
keymap[e.key]();
|
||||
}
|
||||
});
|
||||
|
||||
function goUp () {
|
||||
if (activeIdx > 0) focusResult(activeIdx - 1);
|
||||
else focusSearch();
|
||||
}
|
||||
|
||||
function goDown () {
|
||||
if (activeIdx < results.length - 1) focusResult(activeIdx + 1);
|
||||
}
|
||||
|
||||
function focusResult (idx) {
|
||||
activeIdx = idx;
|
||||
results[activeIdx].scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
||||
results[activeIdx].focus();
|
||||
}
|
||||
|
||||
function focusSearch () {
|
||||
activeIdx = -1;
|
||||
searchBar.focus();
|
||||
}
|
||||
}());
|
||||
|
@ -1,247 +1,248 @@
|
||||
[
|
||||
{"name": "-------", "value": ""},
|
||||
{"name": "Afghanistan", "value": "AF"},
|
||||
{"name": "Albania", "value": "AL"},
|
||||
{"name": "Algeria", "value": "DZ"},
|
||||
{"name": "American Samoa", "value": "AS"},
|
||||
{"name": "Andorra", "value": "AD"},
|
||||
{"name": "Angola", "value": "AO"},
|
||||
{"name": "Anguilla", "value": "AI"},
|
||||
{"name": "Antarctica", "value": "AQ"},
|
||||
{"name": "Antigua and Barbuda", "value": "AG"},
|
||||
{"name": "Argentina", "value": "AR"},
|
||||
{"name": "Armenia", "value": "AM"},
|
||||
{"name": "Aruba", "value": "AW"},
|
||||
{"name": "Australia", "value": "AU"},
|
||||
{"name": "Austria", "value": "AT"},
|
||||
{"name": "Azerbaijan", "value": "AZ"},
|
||||
{"name": "Bahamas", "value": "BS"},
|
||||
{"name": "Bahrain", "value": "BH"},
|
||||
{"name": "Bangladesh", "value": "BD"},
|
||||
{"name": "Barbados", "value": "BB"},
|
||||
{"name": "Belarus", "value": "BY"},
|
||||
{"name": "Belgium", "value": "BE"},
|
||||
{"name": "Belize", "value": "BZ"},
|
||||
{"name": "Benin", "value": "BJ"},
|
||||
{"name": "Bermuda", "value": "BM"},
|
||||
{"name": "Bhutan", "value": "BT"},
|
||||
{"name": "Bolivia", "value": "BO"},
|
||||
{"name": "Bosnia and Herzegovina", "value": "BA"},
|
||||
{"name": "Botswana", "value": "BW"},
|
||||
{"name": "Bouvet Island", "value": "BV"},
|
||||
{"name": "Brazil", "value": "BR"},
|
||||
{"name": "British Indian Ocean Territory", "value": "IO"},
|
||||
{"name": "Brunei Darussalam", "value": "BN"},
|
||||
{"name": "Bulgaria", "value": "BG"},
|
||||
{"name": "Burkina Faso", "value": "BF"},
|
||||
{"name": "Burundi", "value": "BI"},
|
||||
{"name": "Cambodia", "value": "KH"},
|
||||
{"name": "Cameroon", "value": "CM"},
|
||||
{"name": "Canada", "value": "CA"},
|
||||
{"name": "Cape Verde", "value": "CV"},
|
||||
{"name": "Cayman Islands", "value": "KY"},
|
||||
{"name": "Central African Republic", "value": "CF"},
|
||||
{"name": "Chad", "value": "TD"},
|
||||
{"name": "Chile", "value": "CL"},
|
||||
{"name": "China", "value": "CN"},
|
||||
{"name": "Christmas Island", "value": "CX"},
|
||||
{"name": "Cocos (Keeling) Islands", "value": "CC"},
|
||||
{"name": "Colombia", "value": "CO"},
|
||||
{"name": "Comoros", "value": "KM"},
|
||||
{"name": "Congo", "value": "CG"},
|
||||
{"name": "Congo, Democratic Republic of the", "value": "CD"},
|
||||
{"name": "Cook Islands", "value": "CK"},
|
||||
{"name": "Costa Rica", "value": "CR"},
|
||||
{"name": "Cote D'ivoire", "value": "CI"},
|
||||
{"name": "Croatia (Hrvatska)", "value": "HR"},
|
||||
{"name": "Cuba", "value": "CU"},
|
||||
{"name": "Cyprus", "value": "CY"},
|
||||
{"name": "Czech Republic", "value": "CZ"},
|
||||
{"name": "Denmark", "value": "DK"},
|
||||
{"name": "Djibouti", "value": "DJ"},
|
||||
{"name": "Dominica", "value": "DM"},
|
||||
{"name": "Dominican Republic", "value": "DO"},
|
||||
{"name": "East Timor", "value": "TP"},
|
||||
{"name": "Ecuador", "value": "EC"},
|
||||
{"name": "Egypt", "value": "EG"},
|
||||
{"name": "El Salvador", "value": "SV"},
|
||||
{"name": "Equatorial Guinea", "value": "GQ"},
|
||||
{"name": "Eritrea", "value": "ER"},
|
||||
{"name": "Estonia", "value": "EE"},
|
||||
{"name": "Ethiopia", "value": "ET"},
|
||||
{"name": "European Union", "value": "EU"},
|
||||
{"name": "Falkland Islands (Malvinas)", "value": "FK"},
|
||||
{"name": "Faroe Islands", "value": "FO"},
|
||||
{"name": "Fiji", "value": "FJ"},
|
||||
{"name": "Finland", "value": "FI"},
|
||||
{"name": "France", "value": "FR"},
|
||||
{"name": "France, Metropolitan", "value": "FX"},
|
||||
{"name": "French Guiana", "value": "GF"},
|
||||
{"name": "French Polynesia", "value": "PF"},
|
||||
{"name": "French Southern Territories", "value": "TF"},
|
||||
{"name": "Gabon", "value": "GA"},
|
||||
{"name": "Gambia", "value": "GM"},
|
||||
{"name": "Georgia", "value": "GE"},
|
||||
{"name": "Germany", "value": "DE"},
|
||||
{"name": "Ghana", "value": "GH"},
|
||||
{"name": "Gibraltar", "value": "GI"},
|
||||
{"name": "Greece", "value": "GR"},
|
||||
{"name": "Greenland", "value": "GL"},
|
||||
{"name": "Grenada", "value": "GD"},
|
||||
{"name": "Guadeloupe", "value": "GP"},
|
||||
{"name": "Guam", "value": "GU"},
|
||||
{"name": "Guatemala", "value": "GT"},
|
||||
{"name": "Guinea", "value": "GN"},
|
||||
{"name": "Guinea-Bissau", "value": "GW"},
|
||||
{"name": "Guyana", "value": "GY"},
|
||||
{"name": "Haiti", "value": "HT"},
|
||||
{"name": "Heard Island and Mcdonald Islands", "value": "HM"},
|
||||
{"name": "Holy See (Vatican City State)", "value": "VA"},
|
||||
{"name": "Honduras", "value": "HN"},
|
||||
{"name": "Hong Kong", "value": "HK"},
|
||||
{"name": "Hungary", "value": "HU"},
|
||||
{"name": "Iceland", "value": "IS"},
|
||||
{"name": "India", "value": "IN"},
|
||||
{"name": "Indonesia", "value": "ID"},
|
||||
{"name": "Iran, Islamic Republic of", "value": "IR"},
|
||||
{"name": "Iraq", "value": "IQ"},
|
||||
{"name": "Ireland", "value": "IE"},
|
||||
{"name": "Israel", "value": "IL"},
|
||||
{"name": "Italy", "value": "IT"},
|
||||
{"name": "Jamaica", "value": "JM"},
|
||||
{"name": "Japan", "value": "JP"},
|
||||
{"name": "Jordan", "value": "JO"},
|
||||
{"name": "Kazakhstan", "value": "KZ"},
|
||||
{"name": "Kenya", "value": "KE"},
|
||||
{"name": "Kiribati", "value": "KI"},
|
||||
{"name": "Korea, Democratic People's Republic of", "value": "KP"},
|
||||
{"name": "Korea, Republic of", "value": "KR"},
|
||||
{"name": "Kuwait", "value": "KW"},
|
||||
{"name": "Kyrgyzstan", "value": "KG"},
|
||||
{"name": "Lao People's Democratic Republic", "value": "LA"},
|
||||
{"name": "Latvia", "value": "LV"},
|
||||
{"name": "Lebanon", "value": "LB"},
|
||||
{"name": "Lesotho", "value": "LS"},
|
||||
{"name": "Liberia", "value": "LR"},
|
||||
{"name": "Libyan Arab Jamahiriya", "value": "LY"},
|
||||
{"name": "Liechtenstein", "value": "LI"},
|
||||
{"name": "Lithuania", "value": "LT"},
|
||||
{"name": "Luxembourg", "value": "LU"},
|
||||
{"name": "Macao", "value": "MO"},
|
||||
{"name": "Afghanistan", "value": "countryAF"},
|
||||
{"name": "Albania", "value": "countryAL"},
|
||||
{"name": "Algeria", "value": "countryDZ"},
|
||||
{"name": "American Samoa", "value": "countryAS"},
|
||||
{"name": "Andorra", "value": "countryAD"},
|
||||
{"name": "Angola", "value": "countryAO"},
|
||||
{"name": "Anguilla", "value": "countryAI"},
|
||||
{"name": "Antarctica", "value": "countryAQ"},
|
||||
{"name": "Antigua and Barbuda", "value": "countryAG"},
|
||||
{"name": "Argentina", "value": "countryAR"},
|
||||
{"name": "Armenia", "value": "countryAM"},
|
||||
{"name": "Aruba", "value": "countryAW"},
|
||||
{"name": "Australia", "value": "countryAU"},
|
||||
{"name": "Austria", "value": "countryAT"},
|
||||
{"name": "Azerbaijan", "value": "countryAZ"},
|
||||
{"name": "Bahamas", "value": "countryBS"},
|
||||
{"name": "Bahrain", "value": "countryBH"},
|
||||
{"name": "Bangladesh", "value": "countryBD"},
|
||||
{"name": "Barbados", "value": "countryBB"},
|
||||
{"name": "Belarus", "value": "countryBY"},
|
||||
{"name": "Belgium", "value": "countryBE"},
|
||||
{"name": "Belize", "value": "countryBZ"},
|
||||
{"name": "Benin", "value": "countryBJ"},
|
||||
{"name": "Bermuda", "value": "countryBM"},
|
||||
{"name": "Bhutan", "value": "countryBT"},
|
||||
{"name": "Bolivia", "value": "countryBO"},
|
||||
{"name": "Bosnia and Herzegovina", "value": "countryBA"},
|
||||
{"name": "Botswana", "value": "countryBW"},
|
||||
{"name": "Bouvet Island", "value": "countryBV"},
|
||||
{"name": "Brazil", "value": "countryBR"},
|
||||
{"name": "British Indian Ocean Territory", "value": "countryIO"},
|
||||
{"name": "Brunei Darussalam", "value": "countryBN"},
|
||||
{"name": "Bulgaria", "value": "countryBG"},
|
||||
{"name": "Burkina Faso", "value": "countryBF"},
|
||||
{"name": "Burundi", "value": "countryBI"},
|
||||
{"name": "Cambodia", "value": "countryKH"},
|
||||
{"name": "Cameroon", "value": "countryCM"},
|
||||
{"name": "Canada", "value": "countryCA"},
|
||||
{"name": "Cape Verde", "value": "countryCV"},
|
||||
{"name": "Cayman Islands", "value": "countryKY"},
|
||||
{"name": "Central African Republic", "value": "countryCF"},
|
||||
{"name": "Chad", "value": "countryTD"},
|
||||
{"name": "Chile", "value": "countryCL"},
|
||||
{"name": "China", "value": "countryCN"},
|
||||
{"name": "Christmas Island", "value": "countryCX"},
|
||||
{"name": "Cocos (Keeling) Islands", "value": "countryCC"},
|
||||
{"name": "Colombia", "value": "countryCO"},
|
||||
{"name": "Comoros", "value": "countryKM"},
|
||||
{"name": "Congo", "value": "countryCG"},
|
||||
{"name": "Congo, Democratic Republic of the", "value": "countryCD"},
|
||||
{"name": "Cook Islands", "value": "countryCK"},
|
||||
{"name": "Costa Rica", "value": "countryCR"},
|
||||
{"name": "Cote D\"ivoire", "value": "countryCI"},
|
||||
{"name": "Croatia (Hrvatska)", "value": "countryHR"},
|
||||
{"name": "Cuba", "value": "countryCU"},
|
||||
{"name": "Cyprus", "value": "countryCY"},
|
||||
{"name": "Czech Republic", "value": "countryCZ"},
|
||||
{"name": "Denmark", "value": "countryDK"},
|
||||
{"name": "Djibouti", "value": "countryDJ"},
|
||||
{"name": "Dominica", "value": "countryDM"},
|
||||
{"name": "Dominican Republic", "value": "countryDO"},
|
||||
{"name": "East Timor", "value": "countryTP"},
|
||||
{"name": "Ecuador", "value": "countryEC"},
|
||||
{"name": "Egypt", "value": "countryEG"},
|
||||
{"name": "El Salvador", "value": "countrySV"},
|
||||
{"name": "Equatorial Guinea", "value": "countryGQ"},
|
||||
{"name": "Eritrea", "value": "countryER"},
|
||||
{"name": "Estonia", "value": "countryEE"},
|
||||
{"name": "Ethiopia", "value": "countryET"},
|
||||
{"name": "European Union", "value": "countryEU"},
|
||||
{"name": "Falkland Islands (Malvinas)", "value": "countryFK"},
|
||||
{"name": "Faroe Islands", "value": "countryFO"},
|
||||
{"name": "Fiji", "value": "countryFJ"},
|
||||
{"name": "Finland", "value": "countryFI"},
|
||||
{"name": "France", "value": "countryFR"},
|
||||
{"name": "France, Metropolitan", "value": "countryFX"},
|
||||
{"name": "French Guiana", "value": "countryGF"},
|
||||
{"name": "French Polynesia", "value": "countryPF"},
|
||||
{"name": "French Southern Territories", "value": "countryTF"},
|
||||
{"name": "Gabon", "value": "countryGA"},
|
||||
{"name": "Gambia", "value": "countryGM"},
|
||||
{"name": "Georgia", "value": "countryGE"},
|
||||
{"name": "Germany", "value": "countryDE"},
|
||||
{"name": "Ghana", "value": "countryGH"},
|
||||
{"name": "Gibraltar", "value": "countryGI"},
|
||||
{"name": "Greece", "value": "countryGR"},
|
||||
{"name": "Greenland", "value": "countryGL"},
|
||||
{"name": "Grenada", "value": "countryGD"},
|
||||
{"name": "Guadeloupe", "value": "countryGP"},
|
||||
{"name": "Guam", "value": "countryGU"},
|
||||
{"name": "Guatemala", "value": "countryGT"},
|
||||
{"name": "Guinea", "value": "countryGN"},
|
||||
{"name": "Guinea-Bissau", "value": "countryGW"},
|
||||
{"name": "Guyana", "value": "countryGY"},
|
||||
{"name": "Haiti", "value": "countryHT"},
|
||||
{"name": "Heard Island and Mcdonald Islands", "value": "countryHM"},
|
||||
{"name": "Holy See (Vatican City State)", "value": "countryVA"},
|
||||
{"name": "Honduras", "value": "countryHN"},
|
||||
{"name": "Hong Kong", "value": "countryHK"},
|
||||
{"name": "Hungary", "value": "countryHU"},
|
||||
{"name": "Iceland", "value": "countryIS"},
|
||||
{"name": "India", "value": "countryIN"},
|
||||
{"name": "Indonesia", "value": "countryID"},
|
||||
{"name": "Iran, Islamic Republic of", "value": "countryIR"},
|
||||
{"name": "Iraq", "value": "countryIQ"},
|
||||
{"name": "Ireland", "value": "countryIE"},
|
||||
{"name": "Israel", "value": "countryIL"},
|
||||
{"name": "Italy", "value": "countryIT"},
|
||||
{"name": "Jamaica", "value": "countryJM"},
|
||||
{"name": "Japan", "value": "countryJP"},
|
||||
{"name": "Jordan", "value": "countryJO"},
|
||||
{"name": "Kazakhstan", "value": "countryKZ"},
|
||||
{"name": "Kenya", "value": "countryKE"},
|
||||
{"name": "Kiribati", "value": "countryKI"},
|
||||
{"name": "Korea, Democratic People\"s Republic of",
|
||||
"value": "countryKP"},
|
||||
{"name": "Korea, Republic of", "value": "countryKR"},
|
||||
{"name": "Kuwait", "value": "countryKW"},
|
||||
{"name": "Kyrgyzstan", "value": "countryKG"},
|
||||
{"name": "Lao People\"s Democratic Republic", "value": "countryLA"},
|
||||
{"name": "Latvia", "value": "countryLV"},
|
||||
{"name": "Lebanon", "value": "countryLB"},
|
||||
{"name": "Lesotho", "value": "countryLS"},
|
||||
{"name": "Liberia", "value": "countryLR"},
|
||||
{"name": "Libyan Arab Jamahiriya", "value": "countryLY"},
|
||||
{"name": "Liechtenstein", "value": "countryLI"},
|
||||
{"name": "Lithuania", "value": "countryLT"},
|
||||
{"name": "Luxembourg", "value": "countryLU"},
|
||||
{"name": "Macao", "value": "countryMO"},
|
||||
{"name": "Macedonia, the Former Yugosalv Republic of",
|
||||
"value": "MK"},
|
||||
{"name": "Madagascar", "value": "MG"},
|
||||
{"name": "Malawi", "value": "MW"},
|
||||
{"name": "Malaysia", "value": "MY"},
|
||||
{"name": "Maldives", "value": "MV"},
|
||||
{"name": "Mali", "value": "ML"},
|
||||
{"name": "Malta", "value": "MT"},
|
||||
{"name": "Marshall Islands", "value": "MH"},
|
||||
{"name": "Martinique", "value": "MQ"},
|
||||
{"name": "Mauritania", "value": "MR"},
|
||||
{"name": "Mauritius", "value": "MU"},
|
||||
{"name": "Mayotte", "value": "YT"},
|
||||
{"name": "Mexico", "value": "MX"},
|
||||
{"name": "Micronesia, Federated States of", "value": "FM"},
|
||||
{"name": "Moldova, Republic of", "value": "MD"},
|
||||
{"name": "Monaco", "value": "MC"},
|
||||
{"name": "Mongolia", "value": "MN"},
|
||||
{"name": "Montserrat", "value": "MS"},
|
||||
{"name": "Morocco", "value": "MA"},
|
||||
{"name": "Mozambique", "value": "MZ"},
|
||||
{"name": "Myanmar", "value": "MM"},
|
||||
{"name": "Namibia", "value": "NA"},
|
||||
{"name": "Nauru", "value": "NR"},
|
||||
{"name": "Nepal", "value": "NP"},
|
||||
{"name": "Netherlands", "value": "NL"},
|
||||
{"name": "Netherlands Antilles", "value": "AN"},
|
||||
{"name": "New Caledonia", "value": "NC"},
|
||||
{"name": "New Zealand", "value": "NZ"},
|
||||
{"name": "Nicaragua", "value": "NI"},
|
||||
{"name": "Niger", "value": "NE"},
|
||||
{"name": "Nigeria", "value": "NG"},
|
||||
{"name": "Niue", "value": "NU"},
|
||||
{"name": "Norfolk Island", "value": "NF"},
|
||||
{"name": "Northern Mariana Islands", "value": "MP"},
|
||||
{"name": "Norway", "value": "NO"},
|
||||
{"name": "Oman", "value": "OM"},
|
||||
{"name": "Pakistan", "value": "PK"},
|
||||
{"name": "Palau", "value": "PW"},
|
||||
{"name": "Palestinian Territory", "value": "PS"},
|
||||
{"name": "Panama", "value": "PA"},
|
||||
{"name": "Papua New Guinea", "value": "PG"},
|
||||
{"name": "Paraguay", "value": "PY"},
|
||||
{"name": "Peru", "value": "PE"},
|
||||
{"name": "Philippines", "value": "PH"},
|
||||
{"name": "Pitcairn", "value": "PN"},
|
||||
{"name": "Poland", "value": "PL"},
|
||||
{"name": "Portugal", "value": "PT"},
|
||||
{"name": "Puerto Rico", "value": "PR"},
|
||||
{"name": "Qatar", "value": "QA"},
|
||||
{"name": "Reunion", "value": "RE"},
|
||||
{"name": "Romania", "value": "RO"},
|
||||
{"name": "Russian Federation", "value": "RU"},
|
||||
{"name": "Rwanda", "value": "RW"},
|
||||
{"name": "Saint Helena", "value": "SH"},
|
||||
{"name": "Saint Kitts and Nevis", "value": "KN"},
|
||||
{"name": "Saint Lucia", "value": "LC"},
|
||||
{"name": "Saint Pierre and Miquelon", "value": "PM"},
|
||||
{"name": "Saint Vincent and the Grenadines", "value": "VC"},
|
||||
{"name": "Samoa", "value": "WS"},
|
||||
{"name": "San Marino", "value": "SM"},
|
||||
{"name": "Sao Tome and Principe", "value": "ST"},
|
||||
{"name": "Saudi Arabia", "value": "SA"},
|
||||
{"name": "Senegal", "value": "SN"},
|
||||
{"name": "Serbia and Montenegro", "value": "CS"},
|
||||
{"name": "Seychelles", "value": "SC"},
|
||||
{"name": "Sierra Leone", "value": "SL"},
|
||||
{"name": "Singapore", "value": "SG"},
|
||||
{"name": "Slovakia", "value": "SK"},
|
||||
{"name": "Slovenia", "value": "SI"},
|
||||
{"name": "Solomon Islands", "value": "SB"},
|
||||
{"name": "Somalia", "value": "SO"},
|
||||
{"name": "South Africa", "value": "ZA"},
|
||||
"value": "countryMK"},
|
||||
{"name": "Madagascar", "value": "countryMG"},
|
||||
{"name": "Malawi", "value": "countryMW"},
|
||||
{"name": "Malaysia", "value": "countryMY"},
|
||||
{"name": "Maldives", "value": "countryMV"},
|
||||
{"name": "Mali", "value": "countryML"},
|
||||
{"name": "Malta", "value": "countryMT"},
|
||||
{"name": "Marshall Islands", "value": "countryMH"},
|
||||
{"name": "Martinique", "value": "countryMQ"},
|
||||
{"name": "Mauritania", "value": "countryMR"},
|
||||
{"name": "Mauritius", "value": "countryMU"},
|
||||
{"name": "Mayotte", "value": "countryYT"},
|
||||
{"name": "Mexico", "value": "countryMX"},
|
||||
{"name": "Micronesia, Federated States of", "value": "countryFM"},
|
||||
{"name": "Moldova, Republic of", "value": "countryMD"},
|
||||
{"name": "Monaco", "value": "countryMC"},
|
||||
{"name": "Mongolia", "value": "countryMN"},
|
||||
{"name": "Montserrat", "value": "countryMS"},
|
||||
{"name": "Morocco", "value": "countryMA"},
|
||||
{"name": "Mozambique", "value": "countryMZ"},
|
||||
{"name": "Myanmar", "value": "countryMM"},
|
||||
{"name": "Namibia", "value": "countryNA"},
|
||||
{"name": "Nauru", "value": "countryNR"},
|
||||
{"name": "Nepal", "value": "countryNP"},
|
||||
{"name": "Netherlands", "value": "countryNL"},
|
||||
{"name": "Netherlands Antilles", "value": "countryAN"},
|
||||
{"name": "New Caledonia", "value": "countryNC"},
|
||||
{"name": "New Zealand", "value": "countryNZ"},
|
||||
{"name": "Nicaragua", "value": "countryNI"},
|
||||
{"name": "Niger", "value": "countryNE"},
|
||||
{"name": "Nigeria", "value": "countryNG"},
|
||||
{"name": "Niue", "value": "countryNU"},
|
||||
{"name": "Norfolk Island", "value": "countryNF"},
|
||||
{"name": "Northern Mariana Islands", "value": "countryMP"},
|
||||
{"name": "Norway", "value": "countryNO"},
|
||||
{"name": "Oman", "value": "countryOM"},
|
||||
{"name": "Pakistan", "value": "countryPK"},
|
||||
{"name": "Palau", "value": "countryPW"},
|
||||
{"name": "Palestinian Territory", "value": "countryPS"},
|
||||
{"name": "Panama", "value": "countryPA"},
|
||||
{"name": "Papua New Guinea", "value": "countryPG"},
|
||||
{"name": "Paraguay", "value": "countryPY"},
|
||||
{"name": "Peru", "value": "countryPE"},
|
||||
{"name": "Philippines", "value": "countryPH"},
|
||||
{"name": "Pitcairn", "value": "countryPN"},
|
||||
{"name": "Poland", "value": "countryPL"},
|
||||
{"name": "Portugal", "value": "countryPT"},
|
||||
{"name": "Puerto Rico", "value": "countryPR"},
|
||||
{"name": "Qatar", "value": "countryQA"},
|
||||
{"name": "Reunion", "value": "countryRE"},
|
||||
{"name": "Romania", "value": "countryRO"},
|
||||
{"name": "Russian Federation", "value": "countryRU"},
|
||||
{"name": "Rwanda", "value": "countryRW"},
|
||||
{"name": "Saint Helena", "value": "countrySH"},
|
||||
{"name": "Saint Kitts and Nevis", "value": "countryKN"},
|
||||
{"name": "Saint Lucia", "value": "countryLC"},
|
||||
{"name": "Saint Pierre and Miquelon", "value": "countryPM"},
|
||||
{"name": "Saint Vincent and the Grenadines", "value": "countryVC"},
|
||||
{"name": "Samoa", "value": "countryWS"},
|
||||
{"name": "San Marino", "value": "countrySM"},
|
||||
{"name": "Sao Tome and Principe", "value": "countryST"},
|
||||
{"name": "Saudi Arabia", "value": "countrySA"},
|
||||
{"name": "Senegal", "value": "countrySN"},
|
||||
{"name": "Serbia and Montenegro", "value": "countryCS"},
|
||||
{"name": "Seychelles", "value": "countrySC"},
|
||||
{"name": "Sierra Leone", "value": "countrySL"},
|
||||
{"name": "Singapore", "value": "countrySG"},
|
||||
{"name": "Slovakia", "value": "countrySK"},
|
||||
{"name": "Slovenia", "value": "countrySI"},
|
||||
{"name": "Solomon Islands", "value": "countrySB"},
|
||||
{"name": "Somalia", "value": "countrySO"},
|
||||
{"name": "South Africa", "value": "countryZA"},
|
||||
{"name": "South Georgia and the South Sandwich Islands",
|
||||
"value": "GS"},
|
||||
{"name": "Spain", "value": "ES"},
|
||||
{"name": "Sri Lanka", "value": "LK"},
|
||||
{"name": "Sudan", "value": "SD"},
|
||||
{"name": "Suriname", "value": "SR"},
|
||||
{"name": "Svalbard and Jan Mayen", "value": "SJ"},
|
||||
{"name": "Swaziland", "value": "SZ"},
|
||||
{"name": "Sweden", "value": "SE"},
|
||||
{"name": "Switzerland", "value": "CH"},
|
||||
{"name": "Syrian Arab Republic", "value": "SY"},
|
||||
{"name": "Taiwan", "value": "TW"},
|
||||
{"name": "Tajikistan", "value": "TJ"},
|
||||
{"name": "Tanzania, United Republic of", "value": "TZ"},
|
||||
{"name": "Thailand", "value": "TH"},
|
||||
{"name": "Togo", "value": "TG"},
|
||||
{"name": "Tokelau", "value": "TK"},
|
||||
{"name": "Tonga", "value": "TO"},
|
||||
{"name": "Trinidad and Tobago", "value": "TT"},
|
||||
{"name": "Tunisia", "value": "TN"},
|
||||
{"name": "Turkey", "value": "TR"},
|
||||
{"name": "Turkmenistan", "value": "TM"},
|
||||
{"name": "Turks and Caicos Islands", "value": "TC"},
|
||||
{"name": "Tuvalu", "value": "TV"},
|
||||
{"name": "Uganda", "value": "UG"},
|
||||
{"name": "Ukraine", "value": "UA"},
|
||||
{"name": "United Arab Emirates", "value": "AE"},
|
||||
{"name": "United Kingdom", "value": "UK"},
|
||||
{"name": "United States", "value": "US"},
|
||||
{"name": "United States Minor Outlying Islands", "value": "UM"},
|
||||
{"name": "Uruguay", "value": "UY"},
|
||||
{"name": "Uzbekistan", "value": "UZ"},
|
||||
{"name": "Vanuatu", "value": "VU"},
|
||||
{"name": "Venezuela", "value": "VE"},
|
||||
{"name": "Vietnam", "value": "VN"},
|
||||
{"name": "Virgin Islands, British", "value": "VG"},
|
||||
{"name": "Virgin Islands, U.S.", "value": "VI"},
|
||||
{"name": "Wallis and Futuna", "value": "WF"},
|
||||
{"name": "Western Sahara", "value": "EH"},
|
||||
{"name": "Yemen", "value": "YE"},
|
||||
{"name": "Yugoslavia", "value": "YU"},
|
||||
{"name": "Zambia", "value": "ZM"},
|
||||
{"name": "Zimbabwe", "value": "ZW"}
|
||||
"value": "countryGS"},
|
||||
{"name": "Spain", "value": "countryES"},
|
||||
{"name": "Sri Lanka", "value": "countryLK"},
|
||||
{"name": "Sudan", "value": "countrySD"},
|
||||
{"name": "Suriname", "value": "countrySR"},
|
||||
{"name": "Svalbard and Jan Mayen", "value": "countrySJ"},
|
||||
{"name": "Swaziland", "value": "countrySZ"},
|
||||
{"name": "Sweden", "value": "countrySE"},
|
||||
{"name": "Switzerland", "value": "countryCH"},
|
||||
{"name": "Syrian Arab Republic", "value": "countrySY"},
|
||||
{"name": "Taiwan, Province of China", "value": "countryTW"},
|
||||
{"name": "Tajikistan", "value": "countryTJ"},
|
||||
{"name": "Tanzania, United Republic of", "value": "countryTZ"},
|
||||
{"name": "Thailand", "value": "countryTH"},
|
||||
{"name": "Togo", "value": "countryTG"},
|
||||
{"name": "Tokelau", "value": "countryTK"},
|
||||
{"name": "Tonga", "value": "countryTO"},
|
||||
{"name": "Trinidad and Tobago", "value": "countryTT"},
|
||||
{"name": "Tunisia", "value": "countryTN"},
|
||||
{"name": "Turkey", "value": "countryTR"},
|
||||
{"name": "Turkmenistan", "value": "countryTM"},
|
||||
{"name": "Turks and Caicos Islands", "value": "countryTC"},
|
||||
{"name": "Tuvalu", "value": "countryTV"},
|
||||
{"name": "Uganda", "value": "countryUG"},
|
||||
{"name": "Ukraine", "value": "countryUA"},
|
||||
{"name": "United Arab Emirates", "value": "countryAE"},
|
||||
{"name": "United Kingdom", "value": "countryUK"},
|
||||
{"name": "United States", "value": "countryUS"},
|
||||
{"name": "United States Minor Outlying Islands", "value": "countryUM"},
|
||||
{"name": "Uruguay", "value": "countryUY"},
|
||||
{"name": "Uzbekistan", "value": "countryUZ"},
|
||||
{"name": "Vanuatu", "value": "countryVU"},
|
||||
{"name": "Venezuela", "value": "countryVE"},
|
||||
{"name": "Vietnam", "value": "countryVN"},
|
||||
{"name": "Virgin Islands, British", "value": "countryVG"},
|
||||
{"name": "Virgin Islands, U.S.", "value": "countryVI"},
|
||||
{"name": "Wallis and Futuna", "value": "countryWF"},
|
||||
{"name": "Western Sahara", "value": "countryEH"},
|
||||
{"name": "Yemen", "value": "countryYE"},
|
||||
{"name": "Yugoslavia", "value": "countryYU"},
|
||||
{"name": "Zambia", "value": "countryZM"},
|
||||
{"name": "Zimbabwe", "value": "countryZW"}
|
||||
]
|
||||
|
@ -1,32 +0,0 @@
|
||||
{
|
||||
"all": {
|
||||
"tbm": null,
|
||||
"href": "search?q={query}",
|
||||
"name": "All",
|
||||
"selected": true
|
||||
},
|
||||
"images": {
|
||||
"tbm": "isch",
|
||||
"href": "search?q={query}",
|
||||
"name": "Images",
|
||||
"selected": false
|
||||
},
|
||||
"maps": {
|
||||
"tbm": null,
|
||||
"href": "https://maps.google.com/maps?q={map_query}",
|
||||
"name": "Maps",
|
||||
"selected": false
|
||||
},
|
||||
"videos": {
|
||||
"tbm": "vid",
|
||||
"href": "search?q={query}",
|
||||
"name": "Videos",
|
||||
"selected": false
|
||||
},
|
||||
"news": {
|
||||
"tbm": "nws",
|
||||
"href": "search?q={query}",
|
||||
"name": "News",
|
||||
"selected": false
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
[
|
||||
"light",
|
||||
"dark",
|
||||
"system"
|
||||
]
|
@ -1,8 +0,0 @@
|
||||
[
|
||||
{"name": "Any time", "value": ""},
|
||||
{"name": "Past hour", "value": "qdr:h"},
|
||||
{"name": "Past 24 hours", "value": "qdr:d"},
|
||||
{"name": "Past week", "value": "qdr:w"},
|
||||
{"name": "Past month", "value": "qdr:m"},
|
||||
{"name": "Past year", "value": "qdr:y"}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -1,260 +0,0 @@
|
||||
<!--
|
||||
Calculator widget.
|
||||
This file should contain all required
|
||||
CSS, HTML, and JS for it.
|
||||
-->
|
||||
|
||||
<style>
|
||||
#calc-text {
|
||||
background: var(--whoogle-dark-page-bg);
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
color: var(--whoogle-dark-text);
|
||||
}
|
||||
#prev-equation {
|
||||
text-align: right;
|
||||
}
|
||||
.error-border {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
#calc-btns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
grid-template-rows: repeat(5, 1fr);
|
||||
gap: 5px;
|
||||
}
|
||||
#calc-btns button {
|
||||
background: #313141;
|
||||
color: var(--whoogle-dark-text);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#calc-btns button:hover {
|
||||
background: #414151;
|
||||
}
|
||||
#calc-btns .common {
|
||||
background: #51516a;
|
||||
}
|
||||
#calc-btns .common:hover {
|
||||
background: #61617a;
|
||||
}
|
||||
#calc-btn-0 { grid-row: 5; grid-column: 3; }
|
||||
#calc-btn-1 { grid-row: 4; grid-column: 3; }
|
||||
#calc-btn-2 { grid-row: 4; grid-column: 4; }
|
||||
#calc-btn-3 { grid-row: 4; grid-column: 5; }
|
||||
#calc-btn-4 { grid-row: 3; grid-column: 3; }
|
||||
#calc-btn-5 { grid-row: 3; grid-column: 4; }
|
||||
#calc-btn-6 { grid-row: 3; grid-column: 5; }
|
||||
#calc-btn-7 { grid-row: 2; grid-column: 3; }
|
||||
#calc-btn-8 { grid-row: 2; grid-column: 4; }
|
||||
#calc-btn-9 { grid-row: 2; grid-column: 5; }
|
||||
#calc-btn-EQ { grid-row: 5; grid-column: 5; }
|
||||
#calc-btn-PT { grid-row: 5; grid-column: 4; }
|
||||
#calc-btn-BCK { grid-row: 5; grid-column: 6; }
|
||||
#calc-btn-ADD { grid-row: 4; grid-column: 6; }
|
||||
#calc-btn-SUB { grid-row: 3; grid-column: 6; }
|
||||
#calc-btn-MLT { grid-row: 2; grid-column: 6; }
|
||||
#calc-btn-DIV { grid-row: 1; grid-column: 6; }
|
||||
#calc-btn-CLR { grid-row: 1; grid-column: 5; }
|
||||
#calc-btn-PRC{ grid-row: 1; grid-column: 4; }
|
||||
#calc-btn-RP { grid-row: 1; grid-column: 3; }
|
||||
#calc-btn-LP { grid-row: 1; grid-column: 2; }
|
||||
#calc-btn-ABS { grid-row: 1; grid-column: 1; }
|
||||
#calc-btn-SIN { grid-row: 2; grid-column: 2; }
|
||||
#calc-btn-COS { grid-row: 3; grid-column: 2; }
|
||||
#calc-btn-TAN { grid-row: 4; grid-column: 2; }
|
||||
#calc-btn-SQR { grid-row: 5; grid-column: 2; }
|
||||
#calc-btn-EXP { grid-row: 2; grid-column: 1; }
|
||||
#calc-btn-E { grid-row: 3; grid-column: 1; }
|
||||
#calc-btn-PI { grid-row: 4; grid-column: 1; }
|
||||
#calc-btn-LOG { grid-row: 5; grid-column: 1; }
|
||||
</style>
|
||||
<p id="prev-equation"></p>
|
||||
<div id="calculator-widget">
|
||||
<p id="calc-text">0</p>
|
||||
<div id="calc-btns">
|
||||
<button id="calc-btn-0" class="common">0</button>
|
||||
<button id="calc-btn-1" class="common">1</button>
|
||||
<button id="calc-btn-2" class="common">2</button>
|
||||
<button id="calc-btn-3" class="common">3</button>
|
||||
<button id="calc-btn-4" class="common">4</button>
|
||||
<button id="calc-btn-5" class="common">5</button>
|
||||
<button id="calc-btn-6" class="common">6</button>
|
||||
<button id="calc-btn-7" class="common">7</button>
|
||||
<button id="calc-btn-8" class="common">8</button>
|
||||
<button id="calc-btn-9" class="common">9</button>
|
||||
<button id="calc-btn-EQ" class="common">=</button>
|
||||
<button id="calc-btn-PT" class="common">.</button>
|
||||
<button id="calc-btn-BCK">⬅</button>
|
||||
<button id="calc-btn-ADD">+</button>
|
||||
<button id="calc-btn-SUB">-</button>
|
||||
<button id="calc-btn-MLT">x</button>
|
||||
<button id="calc-btn-DIV">/</button>
|
||||
<button id="calc-btn-CLR">C</button>
|
||||
<button id="calc-btn-PRC">%</button>
|
||||
<button id="calc-btn-RP">)</button>
|
||||
<button id="calc-btn-LP">(</button>
|
||||
<button id="calc-btn-ABS">|x|</button>
|
||||
<button id="calc-btn-SIN">sin</button>
|
||||
<button id="calc-btn-COS">cos</button>
|
||||
<button id="calc-btn-TAN">tan</button>
|
||||
<button id="calc-btn-SQR">√</button>
|
||||
<button id="calc-btn-EXP">^</button>
|
||||
<button id="calc-btn-E">ℇ</button>
|
||||
<button id="calc-btn-PI">π</button>
|
||||
<button id="calc-btn-LOG">log</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// JS does not have this by default.
|
||||
// from https://www.freecodecamp.org/news/how-to-factorialize-a-number-in-javascript-9263c89a4b38/
|
||||
function factorial(num) {
|
||||
if (num < 0)
|
||||
return -1;
|
||||
else if (num === 0)
|
||||
return 1;
|
||||
else {
|
||||
return (num * factorial(num - 1));
|
||||
}
|
||||
}
|
||||
// returns true if the user is currently focused on the calculator widget
|
||||
function usingCalculator() {
|
||||
let activeElement = document.activeElement;
|
||||
while (true) {
|
||||
if (!activeElement) return false;
|
||||
if (activeElement.id === "calculator-wrapper") return true;
|
||||
activeElement = activeElement.parentElement;
|
||||
}
|
||||
}
|
||||
const $ = q => document.querySelectorAll(q);
|
||||
// key bindings for commonly used buttons
|
||||
const keybindings = {
|
||||
"0": "0",
|
||||
"1": "1",
|
||||
"2": "2",
|
||||
"3": "3",
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6",
|
||||
"7": "7",
|
||||
"8": "8",
|
||||
"9": "9",
|
||||
"Enter": "EQ",
|
||||
".": "PT",
|
||||
"+": "ADD",
|
||||
"-": "SUB",
|
||||
"*": "MLT",
|
||||
"/": "DIV",
|
||||
"%": "PRC",
|
||||
"c": "CLR",
|
||||
"(": "LP",
|
||||
")": "RP",
|
||||
"Backspace": "BCK",
|
||||
}
|
||||
window.addEventListener("keydown", event => {
|
||||
if (!usingCalculator()) return;
|
||||
if (event.key === "Enter" && document.activeElement.id !== "search-bar")
|
||||
event.preventDefault();
|
||||
if (keybindings[event.key])
|
||||
document.getElementById("calc-btn-" + keybindings[event.key]).click();
|
||||
})
|
||||
// calculates the string
|
||||
const calc = () => {
|
||||
var mathtext = document.getElementById("calc-text");
|
||||
var statement = mathtext.innerHTML
|
||||
// remove empty ()
|
||||
.replace("()", "")
|
||||
// special constants
|
||||
.replace("π", "(Math.PI)")
|
||||
.replace("ℇ", "(Math.E)")
|
||||
// turns 3(1+2) into 3*(1+2) (for example)
|
||||
.replace(/(?<=[0-9\)])(?<=[^+\-x*\/%^])\(/, "x(")
|
||||
// same except reversed
|
||||
.replace(/\)(?=[0-9\(])(?=[^+\-x*\/%^])/, ")x")
|
||||
// replace human friendly x with JS *
|
||||
.replace("x", "*")
|
||||
// trig & misc functions
|
||||
.replace("sin", "Math.sin")
|
||||
.replace("cos", "Math.cos")
|
||||
.replace("tan", "Math.tan")
|
||||
.replace("√", "Math.sqrt")
|
||||
.replace("^", "**")
|
||||
.replace("abs", "Math.abs")
|
||||
.replace("log", "Math.log")
|
||||
;
|
||||
// add any missing )s to the end
|
||||
while(true) if (
|
||||
(statement.match(/\(/g) || []).length >
|
||||
(statement.match(/\)/g) || []).length
|
||||
) statement += ")"; else break;
|
||||
// evaluate the expression.
|
||||
console.log("calculating [" + statement + "]");
|
||||
try {
|
||||
var result = eval(statement);
|
||||
document.getElementById("prev-equation").innerHTML = mathtext.innerHTML + " = ";
|
||||
mathtext.innerHTML = result;
|
||||
mathtext.classList.remove("error-border");
|
||||
} catch (e) {
|
||||
mathtext.classList.add("error-border");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
const updateCalc = (e) => {
|
||||
// character(s) recieved from button
|
||||
var c = event.target.innerHTML;
|
||||
var mathtext = document.getElementById("calc-text");
|
||||
if (mathtext.innerHTML === "0") mathtext.innerHTML = "";
|
||||
// special cases
|
||||
switch (c) {
|
||||
case "C":
|
||||
// Clear
|
||||
mathtext.innerHTML = "0";
|
||||
break;
|
||||
case "⬅":
|
||||
// Delete
|
||||
mathtext.innerHTML = mathtext.innerHTML.slice(0, -1);
|
||||
if (mathtext.innerHTML.length === 0) {
|
||||
mathtext.innerHTML = "0";
|
||||
}
|
||||
break;
|
||||
case "=":
|
||||
calc()
|
||||
break;
|
||||
case "sin":
|
||||
case "cos":
|
||||
case "tan":
|
||||
case "log":
|
||||
case "√":
|
||||
mathtext.innerHTML += `${c}(`;
|
||||
break;
|
||||
case "|x|":
|
||||
mathtext.innerHTML += "abs("
|
||||
break;
|
||||
case "+":
|
||||
case "-":
|
||||
case "x":
|
||||
case "/":
|
||||
case "%":
|
||||
case "^":
|
||||
if (mathtext.innerHTML.length === 0) mathtext.innerHTML = "0";
|
||||
// prevent typing 2 operators in a row
|
||||
if (mathtext.innerHTML.match(/[+\-x\/%^] $/))
|
||||
mathtext.innerHTML = mathtext.innerHTML.slice(0, -3);
|
||||
mathtext.innerHTML += ` ${c} `;
|
||||
break;
|
||||
default:
|
||||
mathtext.innerHTML += c;
|
||||
}
|
||||
}
|
||||
for (let i of $("#calc-btns button")) {
|
||||
i.addEventListener('click', event => {
|
||||
updateCalc(event);
|
||||
})
|
||||
}
|
||||
</script>
|
@ -1,48 +1,29 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="shortcut icon" href="static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" href="static/img/favicon.ico" type="image/x-icon">
|
||||
{% if not search_type %}
|
||||
<head>
|
||||
<link rel="shortcut icon" href="static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" href="static/img/favicon.ico" type="image/x-icon">
|
||||
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
|
||||
{% else %}
|
||||
<link rel="search" href="opensearch.xml?tbm={{ search_type }}" type="application/opensearchdescription+xml" title="Whoogle Search ({{ search_name }})">
|
||||
{% endif %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<link rel="stylesheet" href="{{ cb_url('logo.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('input.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('search.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('header.css') }}">
|
||||
{% if config.theme %}
|
||||
{% if config.theme == 'system' %}
|
||||
<style>
|
||||
@import "{{ cb_url('light-theme.css') }}" screen;
|
||||
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
|
||||
</style>
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
<style>{{ config.style }}</style>
|
||||
<title>{{ clean_query(query) }} - Whoogle Search</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ search_header|safe }}
|
||||
{% if is_translation %}
|
||||
<iframe
|
||||
id="lingva-iframe"
|
||||
src="{{ lingva_url }}/auto/{{ translate_to }}/{{ translate_str }}">
|
||||
</iframe>
|
||||
{% endif %}
|
||||
{{ response|safe }}
|
||||
</body>
|
||||
{% include 'footer.html' %}
|
||||
{% if autocomplete_enabled == '1' %}
|
||||
<script src="{{ cb_url('autocomplete.js') }}"></script>
|
||||
{% endif %}
|
||||
<script src="{{ cb_url('utils.js') }}"></script>
|
||||
<script src="{{ cb_url('keyboard.js') }}"></script>
|
||||
<script src="{{ cb_url('currency.js') }}"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<link rel="stylesheet" href="static/css/input.css">
|
||||
<link rel="stylesheet" href="static/css/search.css">
|
||||
<link rel="stylesheet" href="static/css/variables.css">
|
||||
<link rel="stylesheet" href="static/css/header.css">
|
||||
<link rel="stylesheet" href="static/css/{{ 'dark' if config.dark else 'light' }}-theme.css"/>
|
||||
<style>{{ config.style }}</style>
|
||||
<title>{{ clean_query(query) }} - Whoogle Search</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ search_header|safe }}
|
||||
{{ response|safe }}
|
||||
</body>
|
||||
<footer>
|
||||
<p style="color: {{ 'var(--whoogle-dark-text)' if config.dark else 'var(--whoogle-text)' }};">
|
||||
Whoogle Search v{{ version_number }} ||
|
||||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
|
||||
</p>
|
||||
</footer>
|
||||
<script src="static/js/autocomplete.js"></script>
|
||||
<script src="static/js/utils.js"></script>
|
||||
<script src="static/js/keyboard.js"></script>
|
||||
</html>
|
||||
|
@ -1,106 +1,6 @@
|
||||
{% if config.theme %}
|
||||
{% if config.theme == 'system' %}
|
||||
<style>
|
||||
@import "{{ cb_url('light-theme.css') }}" screen;
|
||||
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
|
||||
</style>
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ cb_url('main.css') }}">
|
||||
<link rel="stylesheet" href="{{ cb_url('error.css') }}">
|
||||
<style>{{ config.style }}</style>
|
||||
<div>
|
||||
<h1>Error</h1>
|
||||
<p>
|
||||
{{ error_message }}
|
||||
</p>
|
||||
<hr>
|
||||
{% if query and translation %}
|
||||
<p>
|
||||
<h4><a class="link" href="https://farside.link">{{ translation['continue-search'] }}</a></h4>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/benbusby/whoogle-search">Whoogle</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-color" href="{{farside}}/whoogle/search?q={{query}}{{params}}">
|
||||
{{farside}}/whoogle/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/searxng/searxng">SearXNG</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-color" href="{{farside}}/searxng/search?q={{query}}">
|
||||
{{farside}}/searxng/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h4>Other options:</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://kagi.com">Kagi</a>
|
||||
<ul>
|
||||
<li>Requires account</li>
|
||||
<li>
|
||||
<a class="link-color" href="https://kagi.com/search?q={{query}}">
|
||||
kagi.com/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://duckduckgo.com">DuckDuckGo</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-color" href="https://duckduckgo.com/search?q={{query}}">
|
||||
duckduckgo.com/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://search.brave.com">Brave Search</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-color" href="https://search.brave.com/search?q={{query}}">
|
||||
search.brave.com/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://ecosia.com">Ecosia</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-color" href="https://ecosia.com/search?q={{query}}">
|
||||
ecosia.com/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://google.com">Google</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="link-color" href="https://google.com/search?q={{query}}">
|
||||
google.com/search?q={{query}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
</p>
|
||||
{% endif %}
|
||||
<a class="link" href="home">Return Home</a>
|
||||
</div>
|
||||
<h1>Error</h1>
|
||||
<hr>
|
||||
<p>
|
||||
Error: "{{ error_message|safe }}"
|
||||
</p>
|
||||
<a href="/">Return Home</a>
|
||||
|
@ -1,9 +0,0 @@
|
||||
<footer>
|
||||
<p class="footer">
|
||||
Whoogle Search v{{ version_number }} ||
|
||||
<a class="link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
|
||||
{% if has_update %}
|
||||
|| <span class="update_available">Update Available 🟢</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</footer>
|
@ -1,390 +1,116 @@
|
||||
<div>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="application/xhtml+xml; charset=utf-8" http-equiv="Content-Type"/>
|
||||
<meta content="no-cache" name="Cache-Control"/>
|
||||
<title>
|
||||
</title>
|
||||
<style>
|
||||
html {
|
||||
font-family: Roboto, Helvetica Neue, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-size-adjust: 100%;
|
||||
color: #3c4043;
|
||||
word-wrap: break-word;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
padding: 0 8px;
|
||||
margin: 0 auto;
|
||||
max-width: 736px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.FbhRzb {
|
||||
border-left: thin solid #dadce0;
|
||||
border-right: thin solid #dadce0;
|
||||
border-top: thin solid #dadce0;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.n692Zd {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.cvifge {
|
||||
height: 40px;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.QvGUP {
|
||||
height: 40px;
|
||||
padding: 0 8px 0 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.O4cRJf {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
padding-right: 16px;
|
||||
}
|
||||
.O1ePr {
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.kgJEQe {
|
||||
height: 36px;
|
||||
width: 98px;
|
||||
vertical-align: top;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.lXLRf {
|
||||
vertical-align: top;
|
||||
}
|
||||
.MhzMZd {
|
||||
border: 0;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.xB0fq {
|
||||
height: 40px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
background-color: #4285f4;
|
||||
color: #fff;
|
||||
padding: 0 16px;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
}
|
||||
.xB0fq:focus {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
.M7pB2 {
|
||||
border: thin solid #dadce0;
|
||||
margin: 0 0 3px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
height: 40px;
|
||||
}
|
||||
.euZec {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
border-spacing: 0;
|
||||
}
|
||||
table.euZec td {
|
||||
padding: 0;
|
||||
width: 25%;
|
||||
}
|
||||
.QIqI7 {
|
||||
display: inline-block;
|
||||
padding-top: 4px;
|
||||
font-weight: bold;
|
||||
color: #4285f4;
|
||||
}
|
||||
.EY24We {
|
||||
border-bottom: 2px solid #4285f4;
|
||||
}
|
||||
.CsQyDc {
|
||||
display: inline-block;
|
||||
color: #70757a;
|
||||
}
|
||||
.TuS8Ad {
|
||||
font-size: 14px;
|
||||
}
|
||||
.HddGcc {
|
||||
padding: 8px;
|
||||
color: #70757a;
|
||||
}
|
||||
.dzp8ae {
|
||||
font-weight: bold;
|
||||
color: #3c4043;
|
||||
}
|
||||
.rEM8G {
|
||||
color: #70757a;
|
||||
}
|
||||
.bookcf {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
}
|
||||
.InWNIe {
|
||||
text-align: center;
|
||||
}
|
||||
.uZgmoc {
|
||||
border: thin solid #dadce0;
|
||||
color: #70757a;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
.frGj1b {
|
||||
display: block;
|
||||
padding: 12px 0 12px 0;
|
||||
width: 100%;
|
||||
}
|
||||
.BnJWBc {
|
||||
text-align: center;
|
||||
padding: 6px 0 13px 0;
|
||||
height: 35px;
|
||||
}
|
||||
.e3goi {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
height: 180px;
|
||||
}
|
||||
.GpQGbf {
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.X6ZCif {
|
||||
color: #202124;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
display: inline-block;
|
||||
padding-top: 2px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
.TwVfHd {
|
||||
border-radius: 16px;
|
||||
border: thin solid #dadce0;
|
||||
display: inline-block;
|
||||
padding: 8px 8px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.yekiAe {
|
||||
background-color: #dadce0;
|
||||
}
|
||||
.svla5d {
|
||||
width: 100%;
|
||||
}
|
||||
.ezO2md {
|
||||
border: thin solid #dadce0;
|
||||
padding: 12px 16px 12px 16px;
|
||||
margin-bottom: 10px;
|
||||
font-family: Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.TxbwNb {
|
||||
border-spacing: 0;
|
||||
}
|
||||
.K35ahc {
|
||||
width: 100%;
|
||||
}
|
||||
.owohpf {
|
||||
text-align: center;
|
||||
}
|
||||
.RAyV4b {
|
||||
width: 162px;
|
||||
height: 140px;
|
||||
line-height: 140px;
|
||||
overflow: "hidden";
|
||||
text-align: center;
|
||||
}
|
||||
.t0fcAb {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.Tor4Ec {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.fYyStc {
|
||||
word-break: break-word;
|
||||
}
|
||||
.ynsChf {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.Fj3V3b {
|
||||
color: #1967d2;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.FrIlee {
|
||||
color: #202124;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.F9iS2e {
|
||||
color: #70757a;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.WMQ2Le {
|
||||
color: #70757a;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.x3G5ab {
|
||||
color: #202124;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.fuLhoc {
|
||||
color: #1967d2;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.epoveb {
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: 400;
|
||||
color: #202124;
|
||||
}
|
||||
.dXDvrc {
|
||||
color: #0d652d;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.dloBPe {
|
||||
font-weight: bold;
|
||||
}
|
||||
.YVIcad {
|
||||
color: #70757a;
|
||||
}
|
||||
.JkVVdd {
|
||||
color: #ea4335;
|
||||
}
|
||||
.oXZRFd {
|
||||
color: #ea4335;
|
||||
}
|
||||
.MQHtg {
|
||||
color: #fbbc04;
|
||||
}
|
||||
.pyMRrb {
|
||||
color: #1e8e3e;
|
||||
}
|
||||
.EtTZid {
|
||||
color: #1e8e3e;
|
||||
}
|
||||
.M3vVJe {
|
||||
color: #1967d2;
|
||||
}
|
||||
.qXLe6d {
|
||||
display: block;
|
||||
}
|
||||
.NHQNef {
|
||||
font-style: italic;
|
||||
}
|
||||
.Cb8Z7c {
|
||||
white-space: pre;
|
||||
}
|
||||
a.ZWRArf {
|
||||
text-decoration: none;
|
||||
}
|
||||
a .CVA68e:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a{text-decoration:none;color:inherit}a:hover{text-decoration:underline}a img{border:0}body{font-family:Roboto,Helvetica,Arial,sans-serif;padding:8px;margin:0 auto;max-width:700px;min-width:240px;}.FbhRzb{border-left:thin solid #dadce0;border-right:thin solid #dadce0;border-top:thin solid #dadce0;height:40px;overflow:hidden}.n692Zd{margin-bottom:10px}.cvifge{height:40px;border-spacing:0;width:100%;}.QvGUP{height:40px;padding:0 8px 0 8px;vertical-align:top}.O4cRJf{height:40px;width:100%;padding:0;padding-right:16px}.O1ePr{height:40px;padding:0;vertical-align:top}.kgJEQe{height:36px;width:98px;vertical-align:top;margin-top:4px}.lXLRf{vertical-align:top}.MhzMZd{border:0;vertical-align:middle;font-size:14px;height:40px;padding:0;width:100%;padding-left:16px}.xB0fq{height:40px;border:none;font-size:14px;background-color:#4285f4;color:#fff;padding:0 16px;margin:0;vertical-align:top;cursor:pointer}.xB0fq:focus{border:1px solid #000}.M7pB2{border:thin solid #dadce0;margin:0 0 3px 0;font-size:13px;font-weight:500;height:40px}.euZec{width:100%;height:40px;text-align:center;border-spacing:0}table.euZec td{padding:0;width:25%}.QIqI7{display:inline-block;padding-top:4px;font-weight:bold;color:#4285f4}.EY24We{border-bottom:2px solid #4285f4}.CsQyDc{display:inline-block;color:#70757a}.TuS8Ad{font-size:14px}.HddGcc{padding:8px;color:#70757a}.dzp8ae{font-weight:bold;color:#3c4043}.rEM8G{color:#70757a}.bookcf{table-layout:fixed;width:100%;border-spacing:0}.InWNIe{text-align:center}.uZgmoc{border:thin solid #dadce0;color:#70757a;font-size:14px;text-align:center;table-layout:fixed;width:100%}.frGj1b{display:block;padding:12px 0 12px 0;width:100%}.BnJWBc{text-align:center;padding:6px 0 13px 0;height:35px}.e3goi{vertical-align:top;padding:0;height:180px}.GpQGbf{margin:auto;border-collapse:collapse;border-spacing:0;width:100%}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
.X6ZCif{color:#202124;font-size:11px;line-height:16px;display:inline-block;padding-top:2px;overflow:hidden;padding-bottom:4px;width:100%}.TwVfHd{border-radius:16px;border:thin solid #dadce0;display:inline-block;padding:8px 8px;margin-right:8px;margin-bottom:4px}.yekiAe{background-color:#dadce0}.svla5d{width:100%}.ezO2md{border:thin solid #dadce0;padding:12px 16px 12px 16px;margin-bottom:10px;font-family:Roboto,Helvetica,Arial,sans-serif}.lIMUZd{font-family:Roboto,Helvetica,Arial,sans-serif}.TxbwNb{border-spacing:0}.K35ahc{width:100%}.owohpf{text-align:center}.RAyV4b{width:162px;height:140px;line-height:140px;overflow:'hidden';text-align:center;}.t0fcAb{text-align:center;margin:auto;vertical-align:middle;width:100%;height:100%;object-fit: contain}.Tor4Ec{padding-top:2px;padding-bottom:8px;}.fYyStc{word-break:break-word}.ynsChf{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.Fj3V3b{color:#1967D2;font-size:14px;line-height:20px}.FrIlee{color:#202124;font-size:11px;line-height:16px}.F9iS2e{color:#70757a;font-size:11px;line-height:16px}.WMQ2Le{color:#70757a;font-size:12px;line-height:16px}.x3G5ab{color:#202124;font-size:12px;line-height:16px}.fuLhoc{color:#1967D2;font-size:18px;line-height:24px}.epoveb{font-size:32px;line-height:40px;font-weight:400;color:#202124}.dXDvrc{color:#0d652d;font-size:14px;line-height:20px;word-wrap:break-word}.dloBPe{font-weight:bold}.YVIcad{color:#70757a}.JkVVdd{color:#ea4335}.oXZRFd{color:#ea4335}.MQHtg{color:#fbbc04}.pyMRrb{color:#1e8e3e}.EtTZid{color:#1e8e3e}.M3vVJe{color:#1967D2}.qXLe6d{display:block}.NHQNef{font-style:italic}.Cb8Z7c{white-space:pre}a.ZWRArf{text-decoration:none}a .CVA68e:hover{text-decoration:underline}
|
||||
</style>
|
||||
<div class="n692Zd">
|
||||
<div class="BnJWBc">
|
||||
<a class="lXLRf" href="/?safe=off&gbv=1&output=images&ie=UTF-8&tbm=isch&sa=X&ved=0ahUKEwjhh7TZyd_vAhWShf0HHeYzCmsQPAgC">
|
||||
<img alt="Google" class="kgJEQe" src="/images/branding/searchlogo/1x/googlelogo_desk_heirloom_color_150x55dp.gif"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="FbhRzb">
|
||||
<form action="/search">
|
||||
<input name="safe" type="hidden" value="off"/>
|
||||
<input name="gbv" type="hidden" value="1"/>
|
||||
<input name="ie" type="hidden" value="ISO-8859-1"/>
|
||||
<input name="tbm" type="hidden" value="isch"/>
|
||||
<input name="oq" type="hidden"/>
|
||||
<input name="aqs" type="hidden"/>
|
||||
<table class="cvifge">
|
||||
<tr>
|
||||
<td class="O4cRJf">
|
||||
<!-- search input -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<div class="M7pB2">
|
||||
<!-- search options -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="X6ZCif"> Not present in mobile
|
||||
</div> -->
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div class="lIMUZd">
|
||||
<table class="By0U9">
|
||||
<!-- correction suggested -->
|
||||
</table>
|
||||
</div>
|
||||
<div class="lIMUZd">
|
||||
<table class="By0U9">
|
||||
<!-- correction suggested -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="GpQGbf">
|
||||
{% for i in range((length // 4) + 1) %}
|
||||
<tr>
|
||||
{% for j in range([length - (i*4), 4]|min) %}
|
||||
<td align="center" class="e3goi">
|
||||
<td align="center" class="e3goi">
|
||||
<div class="svla5d">
|
||||
<div>
|
||||
<div class="lIMUZd">
|
||||
<div>
|
||||
<table class="TxbwNb">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ results[(i*4)+j].web_page }}">
|
||||
<div class="RAyV4b">
|
||||
<img
|
||||
alt=""
|
||||
class="t0fcAb"
|
||||
src="{{ results[(i*4)+j].img_tbn }}"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ results[(i*4)+j].web_page }}">
|
||||
<div class="Tor4Ec">
|
||||
<span class="qXLe6d x3G5ab">
|
||||
<span class="fYyStc">
|
||||
{{ results[(i*4)+j].domain }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ results[(i*4)+j].img_url }}">
|
||||
<div class="Tor4Ec">
|
||||
<span class="qXLe6d F9iS2e">
|
||||
<span class="fYyStc"> {{ view_label }} </span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="lIMUZd">
|
||||
<div>
|
||||
<table class="TxbwNb">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ results[(i*4)+j].webpage }}">
|
||||
<div class="RAyV4b">
|
||||
<img alt="" class="t0fcAb" src="{{ results[(i*4)+j].img_tbn }}"/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ results[(i*4)+j].webpage }}">
|
||||
<div class="Tor4Ec">
|
||||
<span class="qXLe6d x3G5ab">
|
||||
<span class="fYyStc">
|
||||
{{ results[(i*4)+j].domain }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ results[(i*4)+j].img_url }}">
|
||||
<div class="Tor4Ec">
|
||||
<span class="qXLe6d F9iS2e">
|
||||
<span class="fYyStc">
|
||||
{{ view_label }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<table class="uZgmoc">
|
||||
<!-- next page object -->
|
||||
</table>
|
||||
<br />
|
||||
</div>
|
||||
<br/>
|
||||
<div class="TuS8Ad">
|
||||
<!-- information about user connection -->
|
||||
<div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,275 +1,181 @@
|
||||
<html style="background: #000;">
|
||||
<head>
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="static/img/favicon/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="static/img/favicon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="static/img/favicon/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="static/img/favicon/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="static/img/favicon/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="static/img/favicon/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="static/img/favicon/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="static/img/favicon/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="static/img/favicon/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="static/img/favicon/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="static/img/favicon/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="static/img/favicon/manifest.json">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png">
|
||||
{% if autocomplete_enabled == '1' %}
|
||||
<script src="{{ cb_url('autocomplete.js') }}"></script>
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="{{ cb_url('controller.js') }}"></script>
|
||||
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="{{ cb_url('logo.css') }}">
|
||||
{% if config.theme %}
|
||||
{% if config.theme == 'system' %}
|
||||
<style>
|
||||
@import "{{ cb_url('light-theme.css') }}" screen;
|
||||
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
|
||||
</style>
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="{{ cb_url('main.css') }}">
|
||||
<noscript>
|
||||
<style>
|
||||
#main {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-height: 400px;
|
||||
padding: 18px;
|
||||
border-radius: 10px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.collapsible {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<style>{{ config.style }}</style>
|
||||
<title>Whoogle Search</title>
|
||||
</head>
|
||||
<body id="main">
|
||||
<div class="search-container">
|
||||
<div class="logo-container">
|
||||
{{ logo|safe }}
|
||||
</div>
|
||||
<form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
|
||||
<div class="search-fields">
|
||||
<div class="autocomplete">
|
||||
{% if config.preferences %}
|
||||
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
|
||||
{% endif %}
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
id="search-bar"
|
||||
class="home-search"
|
||||
autofocus="autofocus"
|
||||
autocapitalize="none"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
dir="auto">
|
||||
</div>
|
||||
<input type="submit" id="search-submit" value="{{ translation['search'] }}">
|
||||
</div>
|
||||
</form>
|
||||
{% if not config_disabled %}
|
||||
<br/>
|
||||
<button id="config-collapsible" class="collapsible">{{ translation['config'] }}</button>
|
||||
<div class="content">
|
||||
<div class="config-fields">
|
||||
<form id="config-form" action="config" method="post">
|
||||
<div class="config-options">
|
||||
<div class="config-div config-div-country">
|
||||
<label for="config-country">{{ translation['config-country'] }}: </label>
|
||||
<select name="country" id="config-country">
|
||||
{% for country in countries %}
|
||||
<option value="{{ country.value }}"
|
||||
{% if (
|
||||
config.country != '' and config.country in country.value
|
||||
) or (
|
||||
config.country == '' and country.value == '')
|
||||
%}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ country.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div">
|
||||
<label for="config-time-period">{{ translation['config-time-period'] }}</label>
|
||||
<select name="tbs" id="config-time-period">
|
||||
{% for time_period in time_periods %}
|
||||
<option value="{{ time_period.value }}"
|
||||
{% if (
|
||||
config.tbs != '' and config.tbs in time_period.value
|
||||
) or (
|
||||
config.tbs == '' and time_period.value == '')
|
||||
%}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ translation[time_period.value] }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-lang">
|
||||
<label for="config-lang-interface">{{ translation['config-lang'] }}: </label>
|
||||
<select name="lang_interface" id="config-lang-interface">
|
||||
{% for lang in languages %}
|
||||
<option value="{{ lang.value }}"
|
||||
{% if lang.value in config.lang_interface %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-search-lang">
|
||||
<label for="config-lang-search">{{ translation['config-lang-search'] }}: </label>
|
||||
<select name="lang_search" id="config-lang-search">
|
||||
{% for lang in languages %}
|
||||
<option value="{{ lang.value }}"
|
||||
{% if lang.value in config.lang_search %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-div config-div-near">
|
||||
<label for="config-near">{{ translation['config-near'] }}: </label>
|
||||
<input type="text" name="near" id="config-near"
|
||||
placeholder="{{ translation['config-near-help'] }}" value="{{ config.near }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block">{{ translation['config-block'] }}: </label>
|
||||
<input type="text" name="block" id="config-block"
|
||||
placeholder="{{ translation['config-block-help'] }}" value="{{ config.block }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block-title">{{ translation['config-block-title'] }}: </label>
|
||||
<input type="text" name="block_title" id="config-block"
|
||||
placeholder="{{ translation['config-block-title-help'] }}"
|
||||
value="{{ config.block_title }}">
|
||||
</div>
|
||||
<div class="config-div config-div-block">
|
||||
<label for="config-block-url">{{ translation['config-block-url'] }}: </label>
|
||||
<input type="text" name="block_url" id="config-block"
|
||||
placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
|
||||
</div>
|
||||
<div class="config-div config-div-anon-view">
|
||||
<label for="config-anon-view">{{ translation['config-anon-view'] }}: </label>
|
||||
<input type="checkbox" name="anon_view" id="config-anon-view" {{ 'checked' if config.anon_view else '' }}>
|
||||
</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-accept-language">
|
||||
<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_modified"
|
||||
id="config-style"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
value="">{{ config.style_modified.replace('\t', '') }}</textarea>
|
||||
</div>
|
||||
<div class="config-div config-div-pref-url">
|
||||
<label for="config-pref-encryption">{{ translation['config-pref-encryption'] }}: </label>
|
||||
<input type="checkbox" name="preferences_encrypted"
|
||||
id="config-pref-encryption" {{ 'checked' if config.preferences_encrypted and config.preferences_key else '' }}>
|
||||
<div><span class="info-text"> — {{ translation['config-pref-help'] }}</span></div>
|
||||
<label for="config-pref-url">{{ translation['config-pref-url'] }}: </label>
|
||||
<input type="text" name="pref-url" id="config-pref-url" value="{{ config.url }}?preferences={{ config.preferences }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-div config-buttons">
|
||||
<input type="submit" id="config-load" value="{{ translation['load'] }}">
|
||||
<input type="submit" id="config-submit" value="{{ translation['apply'] }}">
|
||||
<input type="submit" id="config-save" value="{{ translation['save-as'] }}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="static/img/favicon/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="static/img/favicon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="static/img/favicon/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="static/img/favicon/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="static/img/favicon/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="static/img/favicon/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="static/img/favicon/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="static/img/favicon/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="static/img/favicon/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="static/img/favicon/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="static/img/favicon/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="static/img/favicon/manifest.json">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png">
|
||||
<script type="text/javascript" src="static/js/autocomplete.js"></script>
|
||||
<script type="text/javascript" src="static/js/controller.js"></script>
|
||||
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="static/css/variables.css">
|
||||
<link rel="stylesheet" href="static/css/main.css">
|
||||
<link rel="stylesheet" href="static/css/{{ 'dark' if config.dark else 'light' }}-theme.css"/>
|
||||
<noscript>
|
||||
<style>
|
||||
#main { display: inherit !important; }
|
||||
.content { max-height: 720px; padding: 18px; border-radius: 10px; }
|
||||
.collapsible { display: none; }
|
||||
</style>
|
||||
</noscript>
|
||||
<style>{{ config.style }}</style>
|
||||
<title>Whoogle Search</title>
|
||||
</head>
|
||||
<body id="main" style="display: none; background-color: {{ '#000' if config.dark else '#fff' }}">
|
||||
<div class="search-container">
|
||||
<div class="logo-container">
|
||||
{{ logo|safe }}
|
||||
</div>
|
||||
<form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
|
||||
<div class="search-fields">
|
||||
<div class="autocomplete">
|
||||
<input
|
||||
type="text"
|
||||
name="q"
|
||||
id="search-bar"
|
||||
class="home-search"
|
||||
autofocus="autofocus"
|
||||
autocapitalize="none"
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
<input type="submit" id="search-submit" value="{{ translation['search'] }}">
|
||||
</div>
|
||||
</form>
|
||||
{% if not config_disabled %}
|
||||
<br/>
|
||||
<button id="config-collapsible" class="collapsible">{{ translation['config'] }}</button>
|
||||
<div class="content">
|
||||
<div class="config-fields">
|
||||
<form id="config-form" action="config" method="post">
|
||||
<div class="config-div config-div-ctry">
|
||||
<label for="config-ctry">{{ translation['config-country'] }}: </label>
|
||||
<select name="ctry" id="config-ctry">
|
||||
{% for ctry in countries %}
|
||||
<option value="{{ ctry.value }}"
|
||||
{% if ctry.value in config.ctry %}
|
||||
selected
|
||||
{% endif %}>
|
||||
{{ ctry.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div><span class="info-text"> — {{ translation['config-country-help'] }}</span></div>
|
||||
</div>
|
||||
<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="City Name" 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="Comma-separated site list" value="{{ config.block }}">
|
||||
</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-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-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">
|
||||
<label for="config-style">{{ translation['config-css'] }}:</label>
|
||||
<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-submit" value="{{ translation['apply'] }}">
|
||||
<input type="submit" id="config-save" value="{{ translation['save-as'] }}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<footer>
|
||||
<p style="color: {{ 'var(--whoogle-dark-text)' if config.dark else 'var(--whoogle-text)' }};">
|
||||
Whoogle Search v{{ version_number }} ||
|
||||
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.6 KiB |
File diff suppressed because one or more lines are too long
@ -1,139 +0,0 @@
|
||||
import base64
|
||||
from bs4 import BeautifulSoup as bsoup
|
||||
from cryptography.fernet import Fernet
|
||||
from flask import Request
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from requests import exceptions, get
|
||||
from urllib.parse import urlparse
|
||||
|
||||
ddg_favicon_site = 'http://icons.duckduckgo.com/ip2'
|
||||
|
||||
empty_gif = base64.b64decode(
|
||||
'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==')
|
||||
|
||||
placeholder_img = base64.b64decode(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAABF0lEQVRIS8XWPw9EMBQA8Eok' \
|
||||
'JBKrMFqMBt//GzAYLTZ/VomExPDu6uLiaPteqVynBn0/75W2Vp7nEIYhe6p1XcespmmAd7Is' \
|
||||
'M+4URcGiKPogvMMvmIS2eN9MOMKbKWgf54SYgI4vKkTuQKJKSJErkKzUSkQHUs0lilAg7GMh' \
|
||||
'ISoIA/hYMiKCKIA2soeowCWEMkfHtUmrXLcyGYYBfN9HF8djiaglWzNZlgVs21YisoAUaEXG' \
|
||||
'cQTP86QIFgi7vyLzPIPjOEIEC7ANQv/4aZrAdd0TUtc1i+MYnSsMWjPp+x6CIPgJVlUVS5KE' \
|
||||
'DKig/+wnVzM4pnzaGeHd+ENlWbI0TbVLJBtw2uMfP63wc9d2kDCWxi5Q27bsBerSJ9afJbeL' \
|
||||
'AAAAAElFTkSuQmCC'
|
||||
)
|
||||
|
||||
|
||||
def fetch_favicon(url: str) -> bytes:
|
||||
"""Fetches a favicon using DuckDuckGo's favicon retriever
|
||||
|
||||
Args:
|
||||
url: The url to fetch the favicon from
|
||||
Returns:
|
||||
bytes - the favicon bytes, or a placeholder image if one
|
||||
was not returned
|
||||
"""
|
||||
domain = urlparse(url).netloc
|
||||
|
||||
response = get(f'{ddg_favicon_site}/{domain}.ico')
|
||||
|
||||
if response.status_code == 200 and len(response.content) > 0:
|
||||
tmp_mem = io.BytesIO()
|
||||
tmp_mem.write(response.content)
|
||||
tmp_mem.seek(0)
|
||||
|
||||
return tmp_mem.read()
|
||||
else:
|
||||
return placeholder_img
|
||||
|
||||
|
||||
def gen_file_hash(path: str, static_file: str) -> str:
|
||||
file_contents = open(os.path.join(path, static_file), 'rb').read()
|
||||
file_hash = hashlib.md5(file_contents).hexdigest()[:8]
|
||||
filename_split = os.path.splitext(static_file)
|
||||
|
||||
return filename_split[0] + '.' + file_hash + filename_split[-1]
|
||||
|
||||
|
||||
def read_config_bool(var: str, default: bool=False) -> bool:
|
||||
val = os.getenv(var, '1' if default else '0')
|
||||
# user can specify one of the following values as 'true' inputs (all
|
||||
# variants with upper case letters will also work):
|
||||
# ('true', 't', '1', 'yes', 'y')
|
||||
val = val.lower() in ('true', 't', '1', 'yes', 'y')
|
||||
return val
|
||||
|
||||
|
||||
def get_client_ip(r: Request) -> str:
|
||||
if r.environ.get('HTTP_X_FORWARDED_FOR') is None:
|
||||
return r.environ['REMOTE_ADDR']
|
||||
else:
|
||||
return r.environ['HTTP_X_FORWARDED_FOR']
|
||||
|
||||
|
||||
def get_request_url(url: str) -> str:
|
||||
if os.getenv('HTTPS_ONLY', False):
|
||||
return url.replace('http://', 'https://', 1)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def get_proxy_host_url(r: Request, default: str, root=False) -> str:
|
||||
scheme = r.headers.get('X-Forwarded-Proto', 'https')
|
||||
http_host = r.headers.get('X-Forwarded-Host')
|
||||
|
||||
full_path = r.full_path if not root else ''
|
||||
if full_path.startswith('/'):
|
||||
full_path = f'/{full_path}'
|
||||
|
||||
if http_host:
|
||||
prefix = os.environ.get('WHOOGLE_URL_PREFIX', '')
|
||||
if prefix:
|
||||
prefix = f'/{re.sub("[^0-9a-zA-Z]+", "", prefix)}'
|
||||
return f'{scheme}://{http_host}{prefix}{full_path}'
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def check_for_update(version_url: str, current: str) -> int:
|
||||
# Check for the latest version of Whoogle
|
||||
try:
|
||||
update = bsoup(get(version_url).text, 'html.parser')
|
||||
latest = update.select_one('[class="Link--primary"]').string[1:]
|
||||
current = int(''.join(filter(str.isdigit, current)))
|
||||
latest = int(''.join(filter(str.isdigit, latest)))
|
||||
has_update = '' if current >= latest else latest
|
||||
except (exceptions.ConnectionError, AttributeError):
|
||||
# Ignore failures, assume current version is up to date
|
||||
has_update = ''
|
||||
|
||||
return has_update
|
||||
|
||||
|
||||
def get_abs_url(url, page_url):
|
||||
# Creates a valid absolute URL using a partial or relative URL
|
||||
if url.startswith('//'):
|
||||
return f'https:{url}'
|
||||
elif url.startswith('/'):
|
||||
return f'{urlparse(page_url).netloc}{url}'
|
||||
elif url.startswith('./'):
|
||||
return f'{page_url}{url[2:]}'
|
||||
return url
|
||||
|
||||
|
||||
def list_to_dict(lst: list) -> dict:
|
||||
if len(lst) < 2:
|
||||
return {}
|
||||
return {lst[i].replace(' ', ''): lst[i+1].replace(' ', '')
|
||||
for i in range(0, len(lst), 2)}
|
||||
|
||||
|
||||
def encrypt_string(key: bytes, string: str) -> str:
|
||||
cipher_suite = Fernet(key)
|
||||
return cipher_suite.encrypt(string.encode()).decode()
|
||||
|
||||
|
||||
def decrypt_string(key: bytes, string: str) -> str:
|
||||
cipher_suite = Fernet(g.session_key)
|
||||
return cipher_suite.decrypt(string.encode()).decode()
|
@ -1,71 +0,0 @@
|
||||
from pathlib import Path
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
# root
|
||||
BASE_DIR = Path(__file__).parent.parent.parent
|
||||
|
||||
def add_ip_card(html_soup: BeautifulSoup, ip: str) -> BeautifulSoup:
|
||||
"""Adds the client's IP address to the search results
|
||||
if query contains keywords
|
||||
|
||||
Args:
|
||||
html_soup: The parsed search result containing the keywords
|
||||
ip: ip address of the client
|
||||
|
||||
Returns:
|
||||
BeautifulSoup
|
||||
|
||||
"""
|
||||
main_div = html_soup.select_one('#main')
|
||||
if main_div:
|
||||
# HTML IP card tag
|
||||
ip_tag = html_soup.new_tag('div')
|
||||
ip_tag['class'] = 'ZINbbc xpd O9g5cc uUPGi'
|
||||
|
||||
# For IP Address html tag
|
||||
ip_address = html_soup.new_tag('div')
|
||||
ip_address['class'] = 'kCrYT ip-address-div'
|
||||
ip_address.string = ip
|
||||
|
||||
# Text below the IP address
|
||||
ip_text = html_soup.new_tag('div')
|
||||
ip_text.string = 'Your public IP address'
|
||||
ip_text['class'] = 'kCrYT ip-text-div'
|
||||
|
||||
# Adding all the above html tags to the IP card
|
||||
ip_tag.append(ip_address)
|
||||
ip_tag.append(ip_text)
|
||||
|
||||
# Insert the element at the top of the result list
|
||||
main_div.insert_before(ip_tag)
|
||||
return html_soup
|
||||
|
||||
def add_calculator_card(html_soup: BeautifulSoup) -> BeautifulSoup:
|
||||
"""Adds the a calculator widget to the search results
|
||||
if query contains keywords
|
||||
|
||||
Args:
|
||||
html_soup: The parsed search result containing the keywords
|
||||
|
||||
Returns:
|
||||
BeautifulSoup
|
||||
"""
|
||||
main_div = html_soup.select_one('#main')
|
||||
if main_div:
|
||||
# absolute path
|
||||
widget_file = open(BASE_DIR / 'app/static/widgets/calculator.html', encoding="utf8")
|
||||
widget_tag = html_soup.new_tag('div')
|
||||
widget_tag['class'] = 'ZINbbc xpd O9g5cc uUPGi'
|
||||
widget_tag['id'] = 'calculator-wrapper'
|
||||
calculator_text = html_soup.new_tag('div')
|
||||
calculator_text['class'] = 'kCrYT ip-address-div'
|
||||
calculator_text.string = 'Calculator'
|
||||
calculator_widget = html_soup.new_tag('div')
|
||||
calculator_widget.append(BeautifulSoup(widget_file, 'html.parser'))
|
||||
calculator_widget['class'] = 'kCrYT ip-text-div'
|
||||
widget_tag.append(calculator_text)
|
||||
widget_tag.append(calculator_widget)
|
||||
main_div.insert_before(widget_tag)
|
||||
widget_file.close()
|
||||
return html_soup
|
@ -1,7 +0,0 @@
|
||||
import os
|
||||
|
||||
optional_dev_tag = ''
|
||||
if os.getenv('DEV_BUILD'):
|
||||
optional_dev_tag = '.dev' + os.getenv('DEV_BUILD')
|
||||
|
||||
__version__ = '0.8.4' + optional_dev_tag
|
@ -1,23 +0,0 @@
|
||||
# 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/
|
@ -1,23 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: whoogle
|
||||
description: A self hosted search engine on Kubernetes
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: 0.8.4
|
||||
|
||||
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
|
@ -1,22 +0,0 @@
|
||||
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 }}
|
@ -1,62 +0,0 @@
|
||||
{{/*
|
||||
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 }}
|
@ -1,82 +0,0 @@
|
||||
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
|
||||
{{- if and .Values.conf.WHOOGLE_USER .Values.conf.WHOOGLE_PASS }}
|
||||
httpHeaders:
|
||||
- name: Authorization
|
||||
value: Basic {{ b64enc (printf "%s:%s" .Values.conf.WHOOGLE_USER .Values.conf.WHOOGLE_PASS) }}
|
||||
{{- end }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
{{- if and .Values.conf.WHOOGLE_USER .Values.conf.WHOOGLE_PASS }}
|
||||
httpHeaders:
|
||||
- name: Authorization
|
||||
value: Basic {{ b64enc (printf "%s:%s" .Values.conf.WHOOGLE_USER .Values.conf.WHOOGLE_PASS) }}
|
||||
{{- end }}
|
||||
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 }}
|
@ -1,28 +0,0 @@
|
||||
{{- 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 }}
|
@ -1,61 +0,0 @@
|
||||
{{- 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 }}
|
@ -1,15 +0,0 @@
|
||||
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 }}
|
@ -1,12 +0,0 @@
|
||||
{{- 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 }}
|
@ -1,15 +0,0 @@
|
||||
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
|
@ -1,114 +0,0 @@
|
||||
# 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_URL_PREFIX: "" # The URL prefix to use for the whoogle instance (i.e. "/whoogle")
|
||||
# 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_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_ALT_IMDB: "" # The imdb.com alternative to use. Set to "" to continue using imdb.com when site alternatives are enabled.
|
||||
# WHOOGLE_ALT_QUORA: "" # The quora.com alternative to use. Set to "" to continue using quora.com when site alternatives are enabled.
|
||||
# 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)
|
||||
# WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED: "" # Encrypt preferences token, requires key
|
||||
# WHOOGLE_CONFIG_PREFERENCES_KEY: "" # Key to encrypt preferences in URL (REQUIRED to show url)
|
||||
|
||||
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: {}
|
@ -1,80 +0,0 @@
|
||||
# can't use mem_limit in a 3.x docker-compose file in non swarm mode
|
||||
# see https://github.com/docker/compose/issues/4513
|
||||
version: "2.4"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.7"
|
||||
container_name: "traefik"
|
||||
command:
|
||||
#- "--log.level=DEBUG"
|
||||
- "--api.insecure=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
|
||||
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
- "--certificatesresolvers.myresolver.acme.email=change@domain.name"
|
||||
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
|
||||
ports:
|
||||
- "443:443"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- "./letsencrypt:/letsencrypt"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
|
||||
whoogle-search:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.whoami.rule=Host(`change.host.name`)"
|
||||
- "traefik.http.routers.whoami.entrypoints=websecure"
|
||||
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.whoogle-search.loadbalancer.server.port=5000"
|
||||
image: ${WHOOGLE_IMAGE:-benbusby/whoogle-search}
|
||||
container_name: whoogle-search
|
||||
restart: unless-stopped
|
||||
pids_limit: 50
|
||||
mem_limit: 256mb
|
||||
memswap_limit: 256mb
|
||||
# user debian-tor from tor package
|
||||
user: whoogle
|
||||
security_opt:
|
||||
- no-new-privileges
|
||||
cap_drop:
|
||||
- ALL
|
||||
tmpfs:
|
||||
- /config/:size=10M,uid=927,gid=927,mode=1700
|
||||
- /var/lib/tor/:size=15M,uid=927,gid=927,mode=1700
|
||||
- /run/tor/:size=1M,uid=927,gid=927,mode=1700
|
||||
environment: # Uncomment to configure environment variables
|
||||
# Basic auth configuration, uncomment to enable
|
||||
#- WHOOGLE_USER=<auth username>
|
||||
#- WHOOGLE_PASS=<auth password>
|
||||
# Proxy configuration, uncomment to enable
|
||||
#- WHOOGLE_PROXY_USER=<proxy username>
|
||||
#- WHOOGLE_PROXY_PASS=<proxy password>
|
||||
#- WHOOGLE_PROXY_TYPE=<proxy type (http|https|socks4|socks5)
|
||||
#- WHOOGLE_PROXY_LOC=<proxy host/ip>
|
||||
# Site alternative configurations, uncomment to enable
|
||||
# Note: If not set, the feature will still be available
|
||||
# with default values.
|
||||
#- WHOOGLE_ALT_TW=farside.link/nitter
|
||||
#- WHOOGLE_ALT_YT=farside.link/invidious
|
||||
#- WHOOGLE_ALT_IG=farside.link/bibliogram/u
|
||||
#- WHOOGLE_ALT_RD=farside.link/libreddit
|
||||
#- WHOOGLE_ALT_MD=farside.link/scribe
|
||||
#- WHOOGLE_ALT_TL=farside.link/lingva
|
||||
#- WHOOGLE_ALT_IMG=farside.link/rimgo
|
||||
#- WHOOGLE_ALT_WIKI=farside.link/wikiless
|
||||
#- WHOOGLE_ALT_IMDB=farside.link/libremdb
|
||||
#- WHOOGLE_ALT_QUORA=farside.link/quetre
|
||||
# - WHOOGLE_CONFIG_DISABLE=1
|
||||
# - WHOOGLE_CONFIG_SEARCH_LANGUAGE=lang_en
|
||||
# - WHOOGLE_CONFIG_GET_ONLY=1
|
||||
# - WHOOGLE_CONFIG_COUNTRY=FR
|
||||
# - WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED=1
|
||||
# - WHOOGLE_CONFIG_PREFERENCES_KEY="NEEDS_TO_BE_MODIFIED"
|
||||
#env_file: # Alternatively, load variables from whoogle.env
|
||||
#- whoogle.env
|
||||
ports:
|
||||
- 8000:5000
|
Binary file not shown.
After Width: | Height: | Size: 215 KiB |
Binary file not shown.
Before Width: | Height: | Size: 214 KiB |
Binary file not shown.
After Width: | Height: | Size: 139 KiB |
Binary file not shown.
Before Width: | Height: | Size: 61 KiB |
@ -1,24 +0,0 @@
|
||||
https://search.albony.xyz
|
||||
https://search.garudalinux.org
|
||||
https://search.dr460nf1r3.org
|
||||
https://search.nezumi.party
|
||||
https://s.tokhmi.xyz
|
||||
https://search.sethforprivacy.com
|
||||
https://whoogle.dcs0.hu
|
||||
https://whoogle.lunar.icu
|
||||
https://gowogle.voring.me
|
||||
https://whoogle.privacydev.net
|
||||
https://whoogle.hostux.net
|
||||
https://wg.vern.cc
|
||||
https://whoogle.hxvy0.gq
|
||||
https://whoogle.ungovernable.men
|
||||
https://whoogle2.ungovernable.men
|
||||
https://whoogle3.ungovernable.men
|
||||
https://wgl.frail.duckdns.org
|
||||
https://whoogle.no-logs.com
|
||||
https://whoogle.ftw.lol
|
||||
https://whoogle-search--replitcomreside.repl.co
|
||||
https://search.notrustverify.ch
|
||||
https://whoogle.datura.network
|
||||
https://whoogle.yepserver.xyz
|
||||
https://search.snine.nl
|
@ -1,5 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
# A plague upon Replit and all who have built it
|
||||
replit_cmd = "killall -q python3 > /dev/null 2>&1; pip install -r requirements.txt && ./run"
|
||||
subprocess.run(replit_cmd, shell=True)
|
@ -1 +0,0 @@
|
||||
# Place password here. Keep this safe.
|
@ -1,33 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
FF_STRING="FascistFirewall 1"
|
||||
|
||||
if [ "$WHOOGLE_TOR_SERVICE" == "0" ]; then
|
||||
echo "Skipping Tor startup..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$WHOOGLE_TOR_FF" == "1" ]; then
|
||||
if (grep -q "$FF_STRING" /etc/tor/torrc); then
|
||||
echo "FascistFirewall feature already enabled."
|
||||
else
|
||||
echo "$FF_STRING" >> /etc/tor/torrc
|
||||
|
||||
if [ "$?" -eq 0 ]; then
|
||||
echo "FascistFirewall added to /etc/tor/torrc"
|
||||
else
|
||||
echo "ERROR: Unable to modify /etc/tor/torrc with $FF_STRING."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$(whoami)" != "root" ]; then
|
||||
tor -f /etc/tor/torrc
|
||||
else
|
||||
if (grep alpine /etc/os-release >/dev/null); then
|
||||
rc-service tor start
|
||||
else
|
||||
service tor start
|
||||
fi
|
||||
service tor start
|
||||
fi
|
||||
|
@ -1,67 +0,0 @@
|
||||
import json
|
||||
import pathlib
|
||||
import requests
|
||||
|
||||
lingva = 'https://lingva.ml/api/v1/en'
|
||||
|
||||
|
||||
def format_lang(lang: str) -> str:
|
||||
# Chinese (traditional and simplified) require
|
||||
# a different format for lingva translations
|
||||
if 'zh-' in lang:
|
||||
if lang == 'zh-TW':
|
||||
return 'zh_HANT'
|
||||
return 'zh'
|
||||
|
||||
# Strip lang prefix to leave only the actual
|
||||
# language code (i.e. 'en', 'fr', etc)
|
||||
return lang.replace('lang_', '')
|
||||
|
||||
|
||||
def translate(v: str, lang: str) -> str:
|
||||
# Strip lang prefix to leave only the actual
|
||||
#language code (i.e. "es", "fr", etc)
|
||||
lang = format_lang(lang)
|
||||
|
||||
lingva_req = f'{lingva}/{lang}/{v}'
|
||||
|
||||
response = requests.get(lingva_req).json()
|
||||
|
||||
if 'translation' in response:
|
||||
return response['translation']
|
||||
return ''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
file_path = pathlib.Path(__file__).parent.resolve()
|
||||
tl_path = 'app/static/settings/translations.json'
|
||||
|
||||
with open(f'{file_path}/../{tl_path}', 'r+', encoding='utf-8') as tl_file:
|
||||
tl_data = json.load(tl_file)
|
||||
|
||||
# If there are any english translations that don't
|
||||
# exist for other languages, extract them and translate
|
||||
# them now
|
||||
en_tl = tl_data['lang_en']
|
||||
for k, v in en_tl.items():
|
||||
for lang in tl_data:
|
||||
if lang == 'lang_en' or k in tl_data[lang]:
|
||||
continue
|
||||
|
||||
translation = ''
|
||||
if len(k) == 0:
|
||||
# Special case for placeholder text that gets used
|
||||
# for translations without any key present
|
||||
translation = v
|
||||
else:
|
||||
# Translate the string using lingva
|
||||
translation = translate(v, lang)
|
||||
|
||||
if len(translation) == 0:
|
||||
print(f'! Unable to translate {lang}[{k}]')
|
||||
continue
|
||||
print(f'{lang}[{k}] = {translation}')
|
||||
tl_data[lang][k] = translation
|
||||
|
||||
# Write out updated translations json
|
||||
print(json.dumps(tl_data, indent=4, ensure_ascii=False))
|
@ -1,3 +0,0 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
@ -1,37 +1,34 @@
|
||||
attrs==22.2.0
|
||||
beautifulsoup4==4.11.2
|
||||
brotli==1.0.9
|
||||
cachelib==0.10.2
|
||||
certifi==2023.7.22
|
||||
cffi==1.15.1
|
||||
chardet==5.1.0
|
||||
click==8.1.3
|
||||
cryptography==3.3.2; platform_machine == 'armv7l'
|
||||
cryptography==42.0.4; platform_machine != 'armv7l'
|
||||
cssutils==2.6.0
|
||||
defusedxml==0.7.1
|
||||
Flask==2.3.2
|
||||
idna==3.4
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.3
|
||||
MarkupSafe==2.1.2
|
||||
more-itertools==9.0.0
|
||||
packaging==23.0
|
||||
pluggy==1.0.0
|
||||
pycodestyle==2.10.0
|
||||
pycparser==2.21
|
||||
pyOpenSSL==19.1.0; platform_machine == 'armv7l'
|
||||
pyOpenSSL==24.0.0; platform_machine != 'armv7l'
|
||||
pyparsing==3.0.9
|
||||
attrs==19.3.0
|
||||
beautifulsoup4==4.8.2
|
||||
bs4==0.0.1
|
||||
cachelib==0.1
|
||||
certifi==2020.4.5.1
|
||||
cffi==1.13.2
|
||||
chardet==3.0.4
|
||||
Click==7.0
|
||||
cryptography==3.3.2
|
||||
Flask==1.1.1
|
||||
Flask-Session==0.3.2
|
||||
idna==2.9
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.3
|
||||
MarkupSafe==1.1.1
|
||||
more-itertools==8.3.0
|
||||
packaging==20.4
|
||||
pluggy==0.13.1
|
||||
py==1.10.0
|
||||
pycodestyle==2.6.0
|
||||
pycparser==2.19
|
||||
pyOpenSSL==19.1.0
|
||||
pyparsing==2.4.7
|
||||
PySocks==1.7.1
|
||||
pytest==7.2.1
|
||||
python-dateutil==2.8.2
|
||||
requests==2.31.0
|
||||
soupsieve==2.4
|
||||
stem==1.8.1
|
||||
urllib3==1.26.18
|
||||
validators==0.22.0
|
||||
waitress==2.1.2
|
||||
wcwidth==0.2.6
|
||||
Werkzeug==3.0.1
|
||||
python-dotenv==0.21.1
|
||||
pytest==5.4.1
|
||||
python-dateutil==2.8.1
|
||||
requests==2.25.1
|
||||
soupsieve==1.9.5
|
||||
stem==1.8.0
|
||||
urllib3==1.26.5
|
||||
waitress==1.4.3
|
||||
wcwidth==0.1.9
|
||||
Werkzeug==0.16.0
|
||||
python-dotenv==0.16.0
|
||||
|
@ -1,37 +1,26 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# Usage:
|
||||
# ./run # Runs the full web app
|
||||
# ./run test # Runs the testing suite
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= command cd -- "$(dirname -- "$0")" && pwd -P)"
|
||||
SCRIPT_DIR="$(builtin cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
|
||||
# Set directory to serve static content from
|
||||
SUBDIR="${1:-app}"
|
||||
export APP_ROOT="$SCRIPT_DIR/$SUBDIR"
|
||||
export STATIC_FOLDER="$APP_ROOT/static"
|
||||
|
||||
# Clear out build directory
|
||||
rm -f "$SCRIPT_DIR"/app/static/build/*.js
|
||||
rm -f "$SCRIPT_DIR"/app/static/build/*.css
|
||||
|
||||
# Check for regular vs test run
|
||||
if [ "$SUBDIR" = "test" ]; then
|
||||
if [[ "$SUBDIR" == "test" ]]; then
|
||||
# Set up static files for testing
|
||||
rm -rf "$STATIC_FOLDER"
|
||||
ln -s "$SCRIPT_DIR/app/static" "$STATIC_FOLDER"
|
||||
pytest -sv
|
||||
else
|
||||
mkdir -p "$STATIC_FOLDER"
|
||||
|
||||
if [ ! -z "$UNIX_SOCKET" ]; then
|
||||
python3 -um app \
|
||||
--unix-socket "$UNIX_SOCKET"
|
||||
else
|
||||
echo "Running on http://${ADDRESS:-0.0.0.0}:${PORT:-"${EXPOSE_PORT:-5000}"}"
|
||||
python3 -um app \
|
||||
--host "${ADDRESS:-0.0.0.0}" \
|
||||
--port "${PORT:-"${EXPOSE_PORT:-5000}"}"
|
||||
fi
|
||||
python3 -um app \
|
||||
--host "${ADDRESS:-0.0.0.0}" \
|
||||
--port "${PORT:-"${EXPOSE_PORT:-5000}"}"
|
||||
fi
|
||||
|
@ -1,45 +0,0 @@
|
||||
[metadata]
|
||||
name = whoogle-search
|
||||
version = attr: app.version.__version__
|
||||
url = https://github.com/benbusby/whoogle-search
|
||||
description = Self-hosted, ad-free, privacy-respecting metasearch engine
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
keywords = search, metasearch, flask, adblock, degoogle, privacy
|
||||
author = Ben Busby
|
||||
author_email = contact@benbusby.com
|
||||
license = MIT
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
include_package_data = True
|
||||
install_requires=
|
||||
beautifulsoup4
|
||||
brotli
|
||||
cssutils
|
||||
cryptography
|
||||
defusedxml
|
||||
Flask
|
||||
python-dotenv
|
||||
requests
|
||||
stem
|
||||
validators
|
||||
waitress
|
||||
|
||||
[options.extras_require]
|
||||
test =
|
||||
pytest
|
||||
python-dateutil
|
||||
dev = pycodestyle
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
test*
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
whoogle-search = app.routes:run_app
|
@ -0,0 +1,29 @@
|
||||
import setuptools
|
||||
|
||||
long_description = open('README.md', 'r').read()
|
||||
|
||||
requirements = list(open('requirements.txt', 'r'))
|
||||
|
||||
setuptools.setup(
|
||||
author='Ben Busby',
|
||||
author_email='benbusby@protonmail.com',
|
||||
name='whoogle-search',
|
||||
version='0.5.4',
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
description='Self-hosted, ad-free, privacy-respecting metasearch engine',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/benbusby/whoogle-search',
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'whoogle-search=app.routes:run_app',
|
||||
]
|
||||
},
|
||||
packages=setuptools.find_packages(),
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
],
|
||||
)
|
@ -1,16 +1,12 @@
|
||||
from app.models.endpoint import Endpoint
|
||||
|
||||
|
||||
def test_autocomplete_get(client):
|
||||
rv = client.get(f'/{Endpoint.autocomplete}?q=green+eggs+and')
|
||||
rv = client.get('/autocomplete?q=green+eggs+and')
|
||||
assert rv._status_code == 200
|
||||
assert len(rv.data) >= 1
|
||||
assert b'green eggs and ham' in rv.data
|
||||
|
||||
|
||||
def test_autocomplete_post(client):
|
||||
rv = client.post(f'/{Endpoint.autocomplete}',
|
||||
data=dict(q='the+cat+in+the'))
|
||||
rv = client.post('/autocomplete', data=dict(q='the+cat+in+the'))
|
||||
assert rv._status_code == 200
|
||||
assert len(rv.data) >= 1
|
||||
assert b'the cat in the hat' in rv.data
|
||||
|
Loading…
Reference in New Issue