Compare commits
113 Commits
2287a6826a
...
testing
| Author | SHA1 | Date | |
|---|---|---|---|
| ac3dd7b804 | |||
| 3075003825 | |||
| c6f1156bc6 | |||
| cf5dbbbc77 | |||
| 8477fb4846 | |||
| 8204f4ee72 | |||
| 9d9a0fe95e | |||
| 427a84d823 | |||
| e971a3d7ab | |||
| 89cb563793 | |||
| ac5c83b87d | |||
| 34437e8491 | |||
| 3d30ed1caa | |||
| 2ee495b383 | |||
| c27fd585ce | |||
| 89425caaa6 | |||
| 52347c1843 | |||
| c5b2bff2c5 | |||
| 61059eff9c | |||
| d159f8384f | |||
|
|
1b787ed35e | ||
|
|
8e2e7774d7 | ||
|
|
e982b9f732 | ||
|
|
48456caeb3 | ||
|
|
ef158ce1f4 | ||
|
|
cbf9ec7bf4 | ||
|
|
409ede1530 | ||
|
|
1326ec7429 | ||
|
|
50406d4b46 | ||
|
|
5ce3aa3acf | ||
|
|
76ebad0b21 | ||
|
|
d76f030cb3 | ||
|
|
b3b15ecc72 | ||
|
|
1319b250af | ||
|
|
198928de05 | ||
|
|
11d9c830b8 | ||
|
|
743f90514b | ||
|
|
48801dbc9a | ||
|
|
5451ab243a | ||
|
|
7ca24eee45 | ||
|
|
c6a70782b2 | ||
|
|
01a07f34b2 | ||
|
|
f32fcb1243 | ||
|
|
bc06b1aece | ||
|
|
ff60fe635f | ||
|
|
6e7119fa4e | ||
|
|
f52cd3f008 | ||
|
|
a2fa7de880 | ||
|
|
0315988f5a | ||
|
|
2e74d86321 | ||
|
|
19b116f1d7 | ||
|
|
fe08bb1d90 | ||
|
|
8ef5fbca4e | ||
|
|
7351c38e6c | ||
|
|
bdfe1c2a15 | ||
|
|
4cbfba9d7b | ||
|
|
4a594f1b53 | ||
|
|
590b211652 | ||
|
|
41e3a0baa7 | ||
|
|
11204459da | ||
|
|
03787b2e5a | ||
|
|
72294a9ffa | ||
|
|
d47cf9db24 | ||
|
|
705ebe64b5 | ||
|
|
b072b0f66b | ||
|
|
4beb5523f5 | ||
|
|
54e6e2a96d | ||
|
|
abcf8ca073 | ||
|
|
1a16281490 | ||
|
|
fd33559cfb | ||
|
|
60e31eacfc | ||
|
|
c45b970546 | ||
|
|
a4251be397 | ||
|
|
c20038e7c3 | ||
|
|
20e40ded6d | ||
|
|
4de0766b76 | ||
|
|
6de83ca47f | ||
|
|
a32bcd54c5 | ||
|
|
c733aa83e8 | ||
|
|
9dfc6f38d5 | ||
|
|
e1076f5c35 | ||
|
|
8489817561 | ||
|
|
38ff1e4a7d | ||
|
|
21cdb0332b | ||
|
|
30330617d9 | ||
|
|
33729439c5 | ||
|
|
20ce88a274 | ||
|
|
8595e467ce | ||
|
|
3392beb914 | ||
|
|
c6c6d3027c | ||
|
|
d19eb3903e | ||
|
|
f45d4145e6 | ||
|
|
937eb907d3 | ||
|
|
851c0e5cc0 | ||
|
|
07a94d4d2e | ||
|
|
e9157b3c1a | ||
|
|
5ae3b3f17e | ||
|
|
50ac76ce94 | ||
|
|
5aea044eb5 | ||
|
|
808dcaf1e2 | ||
|
|
777ba6ddf8 | ||
|
|
8ca57748ff | ||
|
|
30399c50e2 | ||
|
|
e6467bce7c | ||
|
|
8ca2bbc4e9 | ||
|
|
a74593419f | ||
|
|
f94802f2d2 | ||
|
|
d1c584b961 | ||
|
|
81f3d15665 | ||
|
|
99ad69450d | ||
|
|
087da66565 | ||
|
|
b84ae39978 | ||
|
|
391bb1268d |
18
.github/dependabot.yml
vendored
@@ -20,3 +20,21 @@ updates:
|
||||
target-branch: "master"
|
||||
commit-message:
|
||||
prefix: "[upd] web-client (simple):"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "friday"
|
||||
target-branch: "master"
|
||||
commit-message:
|
||||
prefix: "[upd] docker:"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "friday"
|
||||
target-branch: "master"
|
||||
commit-message:
|
||||
prefix: "[upd] github-actions:"
|
||||
|
||||
57
.github/workflows/checker.yml
vendored
@@ -1,31 +1,46 @@
|
||||
name: "Checker"
|
||||
on: # yamllint disable-line rule:truthy
|
||||
---
|
||||
name: Checker
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 4 * * 5"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.13"
|
||||
|
||||
jobs:
|
||||
checker:
|
||||
name: Checker
|
||||
runs-on: ubuntu-24.04
|
||||
search:
|
||||
name: Search
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ubuntu packages
|
||||
run: |
|
||||
sudo ./utils/searxng.sh install packages
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
architecture: 'x64'
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
make V=1 install
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Checker
|
||||
run: |
|
||||
make search.checker
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-"
|
||||
path: "./local"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Search checker
|
||||
run: make search.checker
|
||||
|
||||
88
.github/workflows/data-update.yml
vendored
@@ -1,14 +1,27 @@
|
||||
name: "Update searx.data"
|
||||
on: # yamllint disable-line rule:truthy
|
||||
---
|
||||
name: Update searx.data
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "59 23 28 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.13"
|
||||
|
||||
jobs:
|
||||
updateData:
|
||||
name: Update data - ${{ matrix.fetch }}
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.repository_owner == 'searxng'}}
|
||||
data:
|
||||
if: github.repository_owner == 'searxng'
|
||||
name: ${{ matrix.fetch }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -20,48 +33,51 @@ jobs:
|
||||
- update_engine_traits.py
|
||||
- update_wikidata_units.py
|
||||
- update_engine_descriptions.py
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ubuntu packages
|
||||
run: |
|
||||
sudo ./utils/searxng.sh install packages
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
architecture: 'x64'
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
make V=1 install
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-"
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Fetch data
|
||||
env:
|
||||
FETCH_SCRIPT: ./searxng_extra/update/${{ matrix.fetch }}
|
||||
run: |
|
||||
V=1 ./manage pyenv.cmd python "$FETCH_SCRIPT"
|
||||
run: V=1 ./manage pyenv.cmd python "./searxng_extra/update/${{ matrix.fetch }}"
|
||||
|
||||
- name: Create Pull Request
|
||||
- name: Create PR
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: '[data] update searx.data - ${{ matrix.fetch }}'
|
||||
committer: searxng-bot <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: update_data_${{ matrix.fetch }}
|
||||
delete-branch: true
|
||||
draft: false
|
||||
title: '[data] update searx.data - ${{ matrix.fetch }}'
|
||||
body: |
|
||||
update searx.data - ${{ matrix.fetch }}
|
||||
author: "${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>"
|
||||
committer: "searxng-bot <searxng-bot@users.noreply.github.com>"
|
||||
title: "[data] update searx.data - ${{ matrix.fetch }}"
|
||||
commit-message: "[data] update searx.data - ${{ matrix.fetch }}"
|
||||
branch: "update_data_${{ matrix.fetch }}"
|
||||
delete-branch: "true"
|
||||
draft: "false"
|
||||
signoff: "false"
|
||||
labels: |
|
||||
data
|
||||
|
||||
- name: Check outputs
|
||||
- name: Display information
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
66
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: Documentation
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.13"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
# for JamesIves/github-pages-deploy-action to push
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-"
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Build documentation
|
||||
run: make V=1 docs.clean docs.html
|
||||
|
||||
- if: github.ref_name == 'master'
|
||||
name: Release
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
folder: "dist/docs"
|
||||
branch: "gh-pages"
|
||||
commit-message: "[doc] build from commit ${{ github.sha }}"
|
||||
# Automatically remove deleted files from the deploy branch
|
||||
clean: "true"
|
||||
single-commit: "true"
|
||||
183
.github/workflows/integration.yml
vendored
@@ -1,143 +1,106 @@
|
||||
---
|
||||
name: Integration
|
||||
|
||||
on: # yamllint disable-line rule:truthy
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.13"
|
||||
|
||||
jobs:
|
||||
python:
|
||||
test:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Ubuntu packages
|
||||
run: |
|
||||
sudo ./utils/searxng.sh install packages
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: 'x64'
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ matrix.python-version }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ matrix.python-version }}-${{ runner.arch }}-"
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Run tests
|
||||
run: make V=1 ci.test
|
||||
|
||||
themes:
|
||||
name: Themes
|
||||
runs-on: ubuntu-24.04
|
||||
theme:
|
||||
name: Theme
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Ubuntu packages
|
||||
run: sudo ./utils/searxng.sh install buildhost
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
architecture: 'x64'
|
||||
- name: Build themes
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "./.nvmrc"
|
||||
|
||||
- name: Setup cache Node.js
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "nodejs-${{ runner.arch }}-${{ hashFiles('./.nvmrc', './package.json') }}"
|
||||
path: "./client/simple/node_modules/"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-"
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Build
|
||||
run: make themes.all
|
||||
|
||||
documentation:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write # for JamesIves/github-pages-deploy-action to push changes in repo
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
persist-credentials: false
|
||||
- name: Install Ubuntu packages
|
||||
run: sudo ./utils/searxng.sh install buildhost
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
architecture: 'x64'
|
||||
- name: Cache Python dependencies
|
||||
id: cache-python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
./local
|
||||
./.nvm
|
||||
./node_modules
|
||||
key: python-ubuntu-24.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
|
||||
- name: Build documentation
|
||||
run: |
|
||||
make V=1 docs.clean docs.html
|
||||
- name: Deploy
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: dist/docs
|
||||
CLEAN: true # Automatically remove deleted files from the deploy branch
|
||||
SINGLE_COMMIT: true
|
||||
COMMIT_MESSAGE: '[doc] build from commit ${{ github.sha }}'
|
||||
|
||||
babel:
|
||||
name: Update translations branch
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.repository_owner == 'searxng' && github.ref == 'refs/heads/master' }}
|
||||
needs:
|
||||
- python
|
||||
- themes
|
||||
- documentation
|
||||
permissions:
|
||||
contents: write # for make V=1 weblate.push.translations
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
token: ${{ secrets.WEBLATE_GITHUB_TOKEN }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
architecture: 'x64'
|
||||
- name: Cache Python dependencies
|
||||
id: cache-python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
./local
|
||||
./.nvm
|
||||
./node_modules
|
||||
key: python-ubuntu-20.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
|
||||
- name: weblate & git setup
|
||||
env:
|
||||
WEBLATE_CONFIG: ${{ secrets.WEBLATE_CONFIG }}
|
||||
run: |
|
||||
mkdir -p ~/.config
|
||||
echo "${WEBLATE_CONFIG}" > ~/.config/weblate
|
||||
git config --global user.email "searxng-bot@users.noreply.github.com"
|
||||
git config --global user.name "searxng-bot"
|
||||
- name: Update transations
|
||||
id: update
|
||||
run: |
|
||||
make V=1 weblate.push.translations
|
||||
|
||||
dockers:
|
||||
name: Docker
|
||||
if: github.ref == 'refs/heads/master'
|
||||
needs:
|
||||
- python
|
||||
- themes
|
||||
- documentation
|
||||
- test
|
||||
- theme
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
136
.github/workflows/l10n.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
name: Translation
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Integration
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: "05 07 * * 5"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.13"
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: github.repository_owner == 'searxng' && github.event.workflow_run.conclusion == 'success'
|
||||
name: Update
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
# For "make V=1 weblate.push.translations"
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-"
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Setup Weblate
|
||||
run: |
|
||||
mkdir -p ~/.config
|
||||
echo "${{ secrets.WEBLATE_CONFIG }}" > ~/.config/weblate
|
||||
|
||||
- name: Setup Git
|
||||
run: |
|
||||
git config --global user.email "searxng-bot@users.noreply.github.com"
|
||||
git config --global user.name "searxng-bot"
|
||||
|
||||
- name: Update translations
|
||||
run: make V=1 weblate.push.translations
|
||||
|
||||
pr:
|
||||
if: |
|
||||
github.repository_owner == 'searxng'
|
||||
&& (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')
|
||||
name: Pull Request
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
# For "make V=1 weblate.translations.commit"
|
||||
contents: write
|
||||
# For action "peter-evans/create-pull-request"
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-"
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
- name: Setup Weblate
|
||||
run: |
|
||||
mkdir -p ~/.config
|
||||
echo "${{ secrets.WEBLATE_CONFIG }}" > ~/.config/weblate
|
||||
|
||||
- name: Setup Git
|
||||
run: |
|
||||
git config --global user.email "searxng-bot@users.noreply.github.com"
|
||||
git config --global user.name "searxng-bot"
|
||||
|
||||
- name: Merge and push translation updates
|
||||
run: make V=1 weblate.translations.commit
|
||||
|
||||
- name: Create PR
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
author: "${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>"
|
||||
committer: "searxng-bot <searxng-bot@users.noreply.github.com>"
|
||||
title: "[l10n] update translations from Weblate"
|
||||
commit-message: "[l10n] update translations from Weblate"
|
||||
branch: "translations_update"
|
||||
delete-branch: "true"
|
||||
draft: "false"
|
||||
signoff: "false"
|
||||
labels: |
|
||||
translation
|
||||
|
||||
- name: Display information
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
51
.github/workflows/security.yml
vendored
@@ -1,28 +1,43 @@
|
||||
name: "Security checks"
|
||||
on: # yamllint disable-line rule:truthy
|
||||
---
|
||||
name: Security
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "42 05 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dockers:
|
||||
name: Trivy ${{ matrix.image }}
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
name: Container
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: 'searxng/searxng:latest'
|
||||
ignore-unfixed: false
|
||||
vuln-type: 'os,library'
|
||||
severity: 'UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL'
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
- name: Run Trivy scanner
|
||||
uses: aquasecurity/trivy-action@0.30.0
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
image-ref: "docker.io/searxng/searxng:latest"
|
||||
vuln-type: "os,library"
|
||||
severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
|
||||
ignore-unfixed: "false"
|
||||
format: "sarif"
|
||||
output: "./trivy-results.sarif"
|
||||
|
||||
- name: Upload SARIFs
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: "./trivy-results.sarif"
|
||||
|
||||
59
.github/workflows/translations-update.yml
vendored
@@ -1,59 +0,0 @@
|
||||
name: "Update translations"
|
||||
on: # yamllint disable-line rule:truthy
|
||||
schedule:
|
||||
- cron: "05 07 * * 5"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
babel:
|
||||
name: "create PR for additions from weblate"
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.repository_owner == 'searxng' && github.ref == 'refs/heads/master' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
token: ${{ secrets.WEBLATE_GITHUB_TOKEN }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
architecture: 'x64'
|
||||
- name: Cache Python dependencies
|
||||
id: cache-python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
./local
|
||||
./.nvm
|
||||
./node_modules
|
||||
key: python-ubuntu-24.04-3.12-${{ hashFiles('requirements*.txt', 'setup.py','.nvmrc', 'package.json') }}
|
||||
- name: weblate & git setup
|
||||
env:
|
||||
WEBLATE_CONFIG: ${{ secrets.WEBLATE_CONFIG }}
|
||||
run: |
|
||||
mkdir -p ~/.config
|
||||
echo "${WEBLATE_CONFIG}" > ~/.config/weblate
|
||||
git config --global user.email "searxng-bot@users.noreply.github.com"
|
||||
git config --global user.name "searxng-bot"
|
||||
- name: Merge and push transation updates
|
||||
run: |
|
||||
make V=1 weblate.translations.commit
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.WEBLATE_GITHUB_TOKEN }}
|
||||
commit-message: '[l10n] update translations from Weblate'
|
||||
committer: searxng-bot <searxng-bot@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: translations_update
|
||||
delete-branch: true
|
||||
draft: false
|
||||
title: '[l10n] update translations from Weblate'
|
||||
body: |
|
||||
update translations from Weblate
|
||||
labels: |
|
||||
translation
|
||||
179
Dockerfile
@@ -1,92 +1,107 @@
|
||||
FROM alpine:3.20
|
||||
ENTRYPOINT ["/sbin/tini","--","/usr/local/searxng/dockerfiles/docker-entrypoint.sh"]
|
||||
EXPOSE 8080
|
||||
VOLUME /etc/searxng
|
||||
FROM docker.io/library/python:3.13-slim AS builder
|
||||
|
||||
ARG SEARXNG_GID=977
|
||||
ARG SEARXNG_UID=977
|
||||
|
||||
RUN addgroup -g ${SEARXNG_GID} searxng && \
|
||||
adduser -u ${SEARXNG_UID} -D -h /usr/local/searxng -s /bin/sh -G searxng searxng
|
||||
|
||||
ENV INSTANCE_NAME=searxng \
|
||||
AUTOCOMPLETE= \
|
||||
BASE_URL= \
|
||||
BIND_ADDRESS=[::]:8080 \
|
||||
MORTY_KEY= \
|
||||
MORTY_URL= \
|
||||
SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml \
|
||||
UWSGI_SETTINGS_PATH=/etc/searxng/uwsgi.ini \
|
||||
UWSGI_WORKERS=%k \
|
||||
UWSGI_THREADS=4
|
||||
|
||||
WORKDIR /usr/local/searxng
|
||||
|
||||
COPY requirements.txt ./requirements.txt
|
||||
|
||||
RUN apk add --no-cache -t build-dependencies \
|
||||
build-base \
|
||||
py3-setuptools \
|
||||
python3-dev \
|
||||
libffi-dev \
|
||||
libxslt-dev \
|
||||
libxml2-dev \
|
||||
openssl-dev \
|
||||
tar \
|
||||
git \
|
||||
&& apk add --no-cache \
|
||||
ca-certificates \
|
||||
python3 \
|
||||
py3-pip \
|
||||
libxml2 \
|
||||
libxslt \
|
||||
openssl \
|
||||
tini \
|
||||
uwsgi \
|
||||
uwsgi-python3 \
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
brotli \
|
||||
&& pip3 install --break-system-packages --no-cache -r requirements.txt \
|
||||
&& apk del build-dependencies \
|
||||
&& rm -rf /root/.cache
|
||||
# lxml
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
zlib1g-dev \
|
||||
# uwsgi
|
||||
libpcre3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --chown=searxng:searxng dockerfiles ./dockerfiles
|
||||
COPY --chown=searxng:searxng searx ./searx
|
||||
WORKDIR /usr/local/searxng/
|
||||
|
||||
COPY ./requirements.txt ./requirements.txt
|
||||
|
||||
# Readd on #4707 "--mount=type=cache,id=pip,target=/root/.cache/pip"
|
||||
RUN python -m venv ./venv \
|
||||
&& . ./venv/bin/activate \
|
||||
&& pip install -r requirements.txt \
|
||||
&& pip install "uwsgi~=2.0"
|
||||
|
||||
COPY ./searx/ ./searx/
|
||||
|
||||
ARG TIMESTAMP_SETTINGS=0
|
||||
ARG TIMESTAMP_UWSGI=0
|
||||
ARG VERSION_GITCOMMIT=unknown
|
||||
|
||||
RUN su searxng -c "/usr/bin/python3 -m compileall -q searx" \
|
||||
&& touch -c --date=@${TIMESTAMP_SETTINGS} searx/settings.yml \
|
||||
&& touch -c --date=@${TIMESTAMP_UWSGI} dockerfiles/uwsgi.ini \
|
||||
&& find /usr/local/searxng/searx/static -a \( -name '*.html' -o -name '*.css' -o -name '*.js' \
|
||||
-o -name '*.svg' -o -name '*.ttf' -o -name '*.eot' \) \
|
||||
-type f -exec gzip -9 -k {} \+ -exec brotli --best {} \+
|
||||
RUN python -m compileall -q searx \
|
||||
&& touch -c --date=@$TIMESTAMP_SETTINGS ./searx/settings.yml \
|
||||
&& touch -c --date=@$TIMESTAMP_UWSGI ./dockerfiles/uwsgi.ini \
|
||||
&& find /usr/local/searxng/searx/static \
|
||||
\( -name '*.html' -o -name '*.css' -o -name '*.js' -o -name '*.svg' -o -name '*.ttf' -o -name '*.eot' \) \
|
||||
-type f -exec gzip -9 -k {} + -exec brotli --best {} +
|
||||
|
||||
ARG SEARXNG_UID=977
|
||||
ARG SEARXNG_GID=977
|
||||
|
||||
RUN grep -m1 root /etc/group > /tmp/.searxng.group \
|
||||
&& grep -m1 root /etc/passwd > /tmp/.searxng.passwd \
|
||||
&& echo "searxng:x:$SEARXNG_GID:" >> /tmp/.searxng.group \
|
||||
&& echo "searxng:x:$SEARXNG_UID:$SEARXNG_GID:searxng:/usr/local/searxng:/bin/bash" >> /tmp/.searxng.passwd
|
||||
|
||||
FROM docker.io/library/python:3.13-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
# healthcheck
|
||||
wget \
|
||||
# lxml (ARMv7)
|
||||
libxslt1.1 \
|
||||
# uwsgi
|
||||
libpcre3 \
|
||||
libxml2 \
|
||||
mailcap \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --chown=root:root --from=builder /tmp/.searxng.passwd /etc/passwd
|
||||
COPY --chown=root:root --from=builder /tmp/.searxng.group /etc/group
|
||||
|
||||
ARG LABEL_DATE="0001-01-01T00:00:00Z"
|
||||
ARG GIT_URL="unspecified"
|
||||
ARG SEARXNG_GIT_VERSION="unspecified"
|
||||
ARG LABEL_VCS_REF="unspecified"
|
||||
ARG LABEL_VCS_URL="unspecified"
|
||||
|
||||
WORKDIR /usr/local/searxng/
|
||||
|
||||
COPY --chown=searxng:searxng --from=builder /usr/local/searxng/venv/ ./venv/
|
||||
COPY --chown=searxng:searxng --from=builder /usr/local/searxng/searx/ ./searx/
|
||||
COPY --chown=searxng:searxng ./dockerfiles/ ./dockerfiles/
|
||||
|
||||
LABEL org.opencontainers.image.authors="searxng <$GIT_URL>" \
|
||||
org.opencontainers.image.created=$LABEL_DATE \
|
||||
org.opencontainers.image.description="A privacy-respecting, hackable metasearch engine" \
|
||||
org.opencontainers.image.documentation="https://github.com/searxng/searxng-docker" \
|
||||
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
|
||||
org.opencontainers.image.revision=$LABEL_VCS_REF \
|
||||
org.opencontainers.image.source=$LABEL_VCS_URL \
|
||||
org.opencontainers.image.title="searxng" \
|
||||
org.opencontainers.image.url=$LABEL_VCS_URL \
|
||||
org.opencontainers.image.version=$SEARXNG_GIT_VERSION
|
||||
|
||||
ENV CONFIG_PATH=/etc/searxng \
|
||||
DATA_PATH=/var/cache/searxng
|
||||
|
||||
ENV SEARXNG_VERSION=$SEARXNG_GIT_VERSION \
|
||||
INSTANCE_NAME=searxng \
|
||||
AUTOCOMPLETE="" \
|
||||
BASE_URL="" \
|
||||
BIND_ADDRESS=[::]:8080 \
|
||||
MORTY_KEY="" \
|
||||
MORTY_URL="" \
|
||||
SEARXNG_SETTINGS_PATH=$CONFIG_PATH/settings.yml \
|
||||
UWSGI_SETTINGS_PATH=$CONFIG_PATH/uwsgi.ini \
|
||||
UWSGI_WORKERS=%k \
|
||||
UWSGI_THREADS=4
|
||||
|
||||
VOLUME $CONFIG_PATH
|
||||
VOLUME $DATA_PATH
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK CMD wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
|
||||
|
||||
# Keep these arguments at the end to prevent redundant layer rebuilds
|
||||
ARG LABEL_DATE=
|
||||
ARG GIT_URL=unknown
|
||||
ARG SEARXNG_GIT_VERSION=unknown
|
||||
ARG SEARXNG_DOCKER_TAG=unknown
|
||||
ARG LABEL_VCS_REF=
|
||||
ARG LABEL_VCS_URL=
|
||||
LABEL maintainer="searxng <${GIT_URL}>" \
|
||||
description="A privacy-respecting, hackable metasearch engine." \
|
||||
version="${SEARXNG_GIT_VERSION}" \
|
||||
org.label-schema.schema-version="1.0" \
|
||||
org.label-schema.name="searxng" \
|
||||
org.label-schema.version="${SEARXNG_GIT_VERSION}" \
|
||||
org.label-schema.url="${LABEL_VCS_URL}" \
|
||||
org.label-schema.vcs-ref=${LABEL_VCS_REF} \
|
||||
org.label-schema.vcs-url=${LABEL_VCS_URL} \
|
||||
org.label-schema.build-date="${LABEL_DATE}" \
|
||||
org.label-schema.usage="https://github.com/searxng/searxng-docker" \
|
||||
org.opencontainers.image.title="searxng" \
|
||||
org.opencontainers.image.version="${SEARXNG_DOCKER_TAG}" \
|
||||
org.opencontainers.image.url="${LABEL_VCS_URL}" \
|
||||
org.opencontainers.image.revision=${LABEL_VCS_REF} \
|
||||
org.opencontainers.image.source=${LABEL_VCS_URL} \
|
||||
org.opencontainers.image.created="${LABEL_DATE}" \
|
||||
org.opencontainers.image.documentation="https://github.com/searxng/searxng-docker"
|
||||
ENTRYPOINT ["/usr/local/searxng/dockerfiles/docker-entrypoint.sh"]
|
||||
|
||||
507
client/simple/package-lock.json
generated
@@ -11,30 +11,30 @@
|
||||
"autocomplete-js": "^2.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@eslint/js": "^9.25.1",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"edge.js": "^6.2.1",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint": "^9.25.1",
|
||||
"filemanager-webpack-plugin": "^8.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"ionicons": "^7.4.0",
|
||||
"ionicons": "^8.0.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"less": "^4.3.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"less-loader": "^12.3.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"sharp": "^0.34.1",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^16.17.0",
|
||||
"stylelint": "^16.19.1",
|
||||
"stylelint-config-standard": "^38.0.0",
|
||||
"stylelint-config-standard-less": "^3.0.1",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"svgo": "^3.3.2",
|
||||
"swiped-events": "^1.2.0",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-static-copy": "^2.3.0",
|
||||
"vite": "^6.3.4",
|
||||
"vite-plugin-static-copy": "^2.3.1",
|
||||
"vite-plugin-stylelint": "^6.0.0",
|
||||
"webpack": "^5.99.5",
|
||||
"webpack": "^5.99.7",
|
||||
"webpack-cli": "^6.0.1"
|
||||
}
|
||||
},
|
||||
@@ -668,9 +668,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz",
|
||||
"integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==",
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
|
||||
"integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -678,9 +678,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
|
||||
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
|
||||
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -728,9 +728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz",
|
||||
"integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==",
|
||||
"version": "9.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz",
|
||||
"integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -748,13 +748,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
|
||||
"integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
|
||||
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.12.0",
|
||||
"@eslint/core": "^0.13.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1486,9 +1486,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz",
|
||||
"integrity": "sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
|
||||
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1500,9 +1500,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz",
|
||||
"integrity": "sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz",
|
||||
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1514,9 +1514,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz",
|
||||
"integrity": "sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz",
|
||||
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1528,9 +1528,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz",
|
||||
"integrity": "sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz",
|
||||
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1542,9 +1542,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz",
|
||||
"integrity": "sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz",
|
||||
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1556,9 +1556,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz",
|
||||
"integrity": "sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz",
|
||||
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1570,9 +1570,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz",
|
||||
"integrity": "sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz",
|
||||
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1584,9 +1584,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz",
|
||||
"integrity": "sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz",
|
||||
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1598,9 +1598,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1612,9 +1612,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz",
|
||||
"integrity": "sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz",
|
||||
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1626,9 +1626,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -1640,9 +1640,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -1654,9 +1654,23 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz",
|
||||
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -1668,9 +1682,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -1682,9 +1696,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz",
|
||||
"integrity": "sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1696,9 +1710,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz",
|
||||
"integrity": "sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz",
|
||||
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1710,9 +1724,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz",
|
||||
"integrity": "sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz",
|
||||
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1724,9 +1738,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz",
|
||||
"integrity": "sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz",
|
||||
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -1738,9 +1752,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz",
|
||||
"integrity": "sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz",
|
||||
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1752,9 +1766,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.26.0.tgz",
|
||||
"integrity": "sha512-+0Inu+dJ9/LgWSskcZwx7v17v4GILcwIYxNgD+OuK0U+D5z61WsxWw7yHkYG5OqGPBijsJMVssYRx/Tn+e7F9A==",
|
||||
"version": "4.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.31.0.tgz",
|
||||
"integrity": "sha512-Ei9MFJ6LPD9BMFs+klkHylbVOOYhG10Jv4bvoFf3GMH15kA41rSYkEdr4DiX84ZdErQE2qtFV/2SUyWoXh0AhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -1763,8 +1777,130 @@
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@stencil/core/node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -1815,9 +1951,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -2507,20 +2643,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cacheable": {
|
||||
"version": "1.8.9",
|
||||
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.9.tgz",
|
||||
"integrity": "sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ==",
|
||||
"version": "1.8.10",
|
||||
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.10.tgz",
|
||||
"integrity": "sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hookified": "^1.7.1",
|
||||
"keyv": "^5.3.1"
|
||||
"hookified": "^1.8.1",
|
||||
"keyv": "^5.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cacheable/node_modules/keyv": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz",
|
||||
"integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==",
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz",
|
||||
"integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3440,20 +3576,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.24.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
|
||||
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
|
||||
"version": "9.25.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz",
|
||||
"integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.20.0",
|
||||
"@eslint/config-helpers": "^0.2.0",
|
||||
"@eslint/core": "^0.12.0",
|
||||
"@eslint/config-helpers": "^0.2.1",
|
||||
"@eslint/core": "^0.13.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.24.0",
|
||||
"@eslint/plugin-kit": "^0.2.7",
|
||||
"@eslint/js": "9.25.1",
|
||||
"@eslint/plugin-kit": "^0.2.8",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
@@ -4018,9 +4154,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hookified": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.8.1.tgz",
|
||||
"integrity": "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==",
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.8.2.tgz",
|
||||
"integrity": "sha512-5nZbBNP44sFCDjSoB//0N7m508APCgbQ4mGGo1KJGBYyCKNHfry1Pvd0JVHZIxjdnqn8nFRBAN/eFB6Rk/4w5w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4203,13 +4339,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ionicons": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz",
|
||||
"integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==",
|
||||
"version": "8.0.8",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-8.0.8.tgz",
|
||||
"integrity": "sha512-CRHhDQA5vsNxC7raeEPqddgO90iR2F13VZS7hB0Vx7JrxJMM2060E3ddgYbpMQNJjzeBsgpkNwdBeK5qQ7RbLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.0.3"
|
||||
"@stencil/core": "^4.30.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
@@ -4472,9 +4608,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/known-css-properties": {
|
||||
"version": "0.35.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
|
||||
"integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
|
||||
"version": "0.36.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz",
|
||||
"integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4559,9 +4695,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/less-loader": {
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz",
|
||||
"integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==",
|
||||
"version": "12.3.0",
|
||||
"resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz",
|
||||
"integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -5623,13 +5759,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.34.7",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.7.tgz",
|
||||
"integrity": "sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==",
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
|
||||
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
"@types/estree": "1.0.7"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -5639,25 +5775,26 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.7",
|
||||
"@rollup/rollup-android-arm64": "4.34.7",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.7",
|
||||
"@rollup/rollup-darwin-x64": "4.34.7",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.7",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.7",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.7",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.7",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.7",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.7",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.7",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.7",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.7",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.7",
|
||||
"@rollup/rollup-android-arm-eabi": "4.40.0",
|
||||
"@rollup/rollup-android-arm64": "4.40.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.40.0",
|
||||
"@rollup/rollup-darwin-x64": "4.40.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.40.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.40.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.40.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.40.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.40.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.40.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.40.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.40.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.40.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.40.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -5733,9 +5870,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6083,9 +6220,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint": {
|
||||
"version": "16.18.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.18.0.tgz",
|
||||
"integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==",
|
||||
"version": "16.19.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.19.1.tgz",
|
||||
"integrity": "sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -6112,7 +6249,7 @@
|
||||
"debug": "^4.3.7",
|
||||
"fast-glob": "^3.3.3",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"file-entry-cache": "^10.0.7",
|
||||
"file-entry-cache": "^10.0.8",
|
||||
"global-modules": "^2.0.0",
|
||||
"globby": "^11.1.0",
|
||||
"globjoin": "^0.1.4",
|
||||
@@ -6120,7 +6257,7 @@
|
||||
"ignore": "^7.0.3",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"known-css-properties": "^0.35.0",
|
||||
"known-css-properties": "^0.36.0",
|
||||
"mathml-tag-names": "^2.1.3",
|
||||
"meow": "^13.2.0",
|
||||
"micromatch": "^4.0.8",
|
||||
@@ -6337,25 +6474,25 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stylelint/node_modules/file-entry-cache": {
|
||||
"version": "10.0.7",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.7.tgz",
|
||||
"integrity": "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==",
|
||||
"version": "10.0.8",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.8.tgz",
|
||||
"integrity": "sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"flat-cache": "^6.1.7"
|
||||
"flat-cache": "^6.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint/node_modules/flat-cache": {
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.7.tgz",
|
||||
"integrity": "sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==",
|
||||
"version": "6.1.8",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.8.tgz",
|
||||
"integrity": "sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cacheable": "^1.8.9",
|
||||
"flatted": "^3.3.3",
|
||||
"hookified": "^1.7.1"
|
||||
"hookified": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint/node_modules/globby": {
|
||||
@@ -6642,13 +6779,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
|
||||
"integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
|
||||
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.3",
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6659,9 +6796,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/fdir": {
|
||||
"version": "6.4.3",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
|
||||
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -6792,15 +6929,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
|
||||
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2",
|
||||
"postcss": "^8.5.3",
|
||||
"rollup": "^4.30.1"
|
||||
"rollup": "^4.34.9",
|
||||
"tinyglobby": "^0.2.13"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -6864,9 +7004,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-static-copy": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.3.0.tgz",
|
||||
"integrity": "sha512-LLKwhhHetGaCnWz4mas4qqjjguDka6/6b4+SeIohRroj8aCE7QTfiZECfPecslFQkWZ3HdQuq5kOPmWZjNYlKA==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.3.1.tgz",
|
||||
"integrity": "sha512-EfsPcBm3ewg3UMG8RJaC0ADq6/qnUZnokXx4By4+2cAcipjT9i0Y0owIJGqmZI7d6nxk4qB1q5aXOwNuSyPdyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6943,6 +7083,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
@@ -6958,14 +7126,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.99.5",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
|
||||
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
|
||||
"version": "5.99.7",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz",
|
||||
"integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
@@ -6982,7 +7151,7 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.0",
|
||||
"schema-utils": "^4.3.2",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"watchpack": "^2.4.1",
|
||||
|
||||
@@ -9,30 +9,30 @@
|
||||
"icons.html": "node theme_icons.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@eslint/js": "^9.25.1",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"edge.js": "^6.2.1",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint": "^9.25.1",
|
||||
"filemanager-webpack-plugin": "^8.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"ionicons": "^7.4.0",
|
||||
"ionicons": "^8.0.8",
|
||||
"leaflet": "^1.9.4",
|
||||
"less": "^4.3.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"less-loader": "^12.3.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"sharp": "^0.34.1",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^16.17.0",
|
||||
"stylelint": "^16.19.1",
|
||||
"stylelint-config-standard": "^38.0.0",
|
||||
"stylelint-config-standard-less": "^3.0.1",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"svgo": "^3.3.2",
|
||||
"swiped-events": "^1.2.0",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-static-copy": "^2.3.0",
|
||||
"vite": "^6.3.4",
|
||||
"vite-plugin-static-copy": "^2.3.1",
|
||||
"vite-plugin-stylelint": "^6.0.0",
|
||||
"webpack": "^5.99.5",
|
||||
"webpack": "^5.99.7",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,56 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
width="21.508125mm"
|
||||
height="21.508017mm"
|
||||
viewBox="0 0 21.508125 21.508015"
|
||||
version="1.1"
|
||||
viewBox="0 0 92 92"
|
||||
height="92mm"
|
||||
width="92mm">
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
id="defs1" />
|
||||
<g
|
||||
transform="translate(-40.921303,-17.416526)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="0"
|
||||
style="fill:none;stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
cy="92"
|
||||
cx="75"
|
||||
id="path3713" />
|
||||
<circle
|
||||
r="30"
|
||||
cy="53.902557"
|
||||
cx="75.921303"
|
||||
id="path834"
|
||||
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
id="layer1"
|
||||
transform="translate(-54.245938,-114.24604)">
|
||||
<path
|
||||
d="m 67.514849,37.91524 a 18,18 0 0 1 21.051475,3.312407 18,18 0 0 1 3.137312,21.078282"
|
||||
id="path852"
|
||||
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="rotate(-46.234709)"
|
||||
ry="1.8669105e-13"
|
||||
y="122.08995"
|
||||
x="3.7063529"
|
||||
height="39.963303"
|
||||
width="18.846331"
|
||||
id="rect912"
|
||||
style="opacity:1;fill:#3050ff;fill-opacity:1;stroke:none;stroke-width:8;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
style="fill:#222222;fill-opacity:1;stroke:#ffffff;stroke-width:1.50812;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 55,115 5.000001,4.99999 h 5 L 75,130 70.000002,135 H 60.000001 L 55,130 Z"
|
||||
id="path2" />
|
||||
<path
|
||||
style="fill:#222222;fill-opacity:1;stroke:#ffffff;stroke-width:1.50812;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 67,119.99999 h 3.000002 L 75,115 v 12.99999 z"
|
||||
id="path3" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 980 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 310 KiB |
@@ -51,11 +51,13 @@ function jinja_svg_catalog(dest, macros, items) {
|
||||
(item) => {
|
||||
|
||||
/** @type {import("svgo").Config} */
|
||||
// JSON.stringify & JSON.parse are used to create a deep copy of the
|
||||
// item.svgo_opts object
|
||||
const svgo_opts = JSON.parse(JSON.stringify(item.svgo_opts));
|
||||
svgo_opts.plugins.push({
|
||||
name: "addAttributesToSVGElement",
|
||||
name: "addClassesToSVGElement",
|
||||
params: {
|
||||
attributes: [{ "class": __jinja_class_placeholder__, }]
|
||||
classNames: [__jinja_class_placeholder__]
|
||||
}}
|
||||
);
|
||||
|
||||
|
||||
7
docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
nekosearch:
|
||||
build: .
|
||||
pull_policy: build
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${NEKOSEARCH_PORT}:8080"
|
||||
@@ -43,15 +43,7 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
get_searxng_version(){
|
||||
su searxng -c \
|
||||
'python3 -c "import six; import searx.version; six.print_(searx.version.VERSION_STRING)"' \
|
||||
2>/dev/null
|
||||
}
|
||||
|
||||
SEARXNG_VERSION="$(get_searxng_version)"
|
||||
export SEARXNG_VERSION
|
||||
echo "SearXNG version ${SEARXNG_VERSION}"
|
||||
echo "SearXNG version $SEARXNG_VERSION"
|
||||
|
||||
# helpers to update the configuration files
|
||||
patch_uwsgi_settings() {
|
||||
@@ -76,7 +68,7 @@ patch_searxng_settings() {
|
||||
-e "s|base_url: false|base_url: ${BASE_URL}|g" \
|
||||
-e "s/instance_name: \"SearXNG\"/instance_name: \"${INSTANCE_NAME}\"/g" \
|
||||
-e "s/autocomplete: \"\"/autocomplete: \"${AUTOCOMPLETE}\"/g" \
|
||||
-e "s/ultrasecretkey/$(openssl rand -hex 32)/g" \
|
||||
-e "s/ultrasecretkey/$(head -c 24 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9')/g" \
|
||||
"${CONF}"
|
||||
|
||||
# Morty configuration
|
||||
@@ -172,4 +164,4 @@ printf 'Listen on %s\n' "${BIND_ADDRESS}"
|
||||
|
||||
# Start uwsgi
|
||||
# TODO: "--http-socket" will be removed in the future (see uwsgi.ini.new config file): https://github.com/searxng/searxng/pull/4578
|
||||
exec uwsgi --http-socket "${BIND_ADDRESS}" "${UWSGI_SETTINGS_PATH}"
|
||||
exec /usr/local/searxng/venv/bin/uwsgi --http-socket "${BIND_ADDRESS}" "${UWSGI_SETTINGS_PATH}"
|
||||
|
||||
@@ -21,7 +21,6 @@ chmod-socket = 666
|
||||
# Plugin to use and interpreter config
|
||||
single-interpreter = true
|
||||
master = true
|
||||
plugin = python3
|
||||
lazy-apps = true
|
||||
enable-threads = true
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ Modify the ``/etc/searxng/settings.yml`` to your needs:
|
||||
|
||||
.. literalinclude:: ../../utils/templates/etc/searxng/settings.yml
|
||||
:language: yaml
|
||||
:end-before: # hostnames:
|
||||
:end-before: # preferences:
|
||||
|
||||
To see the entire file jump to :origin:`utils/templates/etc/searxng/settings.yml`
|
||||
|
||||
|
||||
@@ -148,6 +148,8 @@ engine is shown. Most of the options have a default value or even are optional.
|
||||
``display_error_messages`` : default ``true``
|
||||
When an engine returns an error, the message is displayed on the user interface.
|
||||
|
||||
.. _engine network:
|
||||
|
||||
``network`` : optional
|
||||
Use the network configuration from another engine.
|
||||
In addition, there are two default networks:
|
||||
@@ -257,4 +259,3 @@ Example configuration in settings.yml for a German and English speaker:
|
||||
|
||||
When searching, the default google engine will return German results and
|
||||
"google english" will return English results.
|
||||
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
open_metrics: ''
|
||||
|
||||
``debug`` : ``$SEARXNG_DEBUG``
|
||||
Allow a more detailed log if you run SearXNG directly. Display *detailed* error
|
||||
messages in the browser too, so this must be deactivated in production.
|
||||
In debug mode, the server provides an interactive debugger, will reload when
|
||||
code is changed and activates a verbose logging.
|
||||
|
||||
.. attention::
|
||||
|
||||
The debug setting is intended for local development server. Don't
|
||||
activate debug (don't use a development server) when deploying to
|
||||
production.
|
||||
|
||||
``donation_url`` :
|
||||
Set value to ``true`` to use your own donation page written in the
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
|
||||
``default_lang``:
|
||||
Default search language - leave blank to detect from browser information or
|
||||
use codes from :origin:`searx/languages.py`.
|
||||
use codes from :origin:`searx/sxng_locales.py`.
|
||||
|
||||
``languages``:
|
||||
List of available languages - leave unset to use all codes from
|
||||
:origin:`searx/languages.py`. Otherwise list codes of available languages.
|
||||
:origin:`searx/sxng_locales.py`. Otherwise list codes of available languages.
|
||||
The ``all`` value is shown as the ``Default language`` in the user interface
|
||||
(in most cases, it is meant to send the query without a language parameter ;
|
||||
in some cases, it means the English language) Example:
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
limiter: false
|
||||
public_instance: false
|
||||
image_proxy: false
|
||||
method: "POST"
|
||||
default_http_headers:
|
||||
X-Content-Type-Options : nosniff
|
||||
X-Download-Options : noopen
|
||||
X-Robots-Tag : noindex, nofollow
|
||||
Referrer-Policy : no-referrer
|
||||
|
||||
``base_url`` : ``$SEARXNG_URL``
|
||||
``base_url`` : ``$SEARXNG_BASE_URL``
|
||||
The base URL where SearXNG is deployed. Used to create correct inbound links.
|
||||
|
||||
``port`` & ``bind_address``: ``$SEARXNG_PORT`` & ``$SEARXNG_BIND_ADDRESS``
|
||||
@@ -28,6 +29,8 @@
|
||||
directly using ``python searx/webapp.py``. Doesn't apply to a SearXNG
|
||||
services running behind a proxy and using socket communications.
|
||||
|
||||
.. _server.secret_key:
|
||||
|
||||
``secret_key`` : ``$SEARXNG_SECRET``
|
||||
Used for cryptography purpose.
|
||||
|
||||
@@ -50,6 +53,11 @@
|
||||
``image_proxy`` : ``$SEARXNG_IMAGE_PROXY``
|
||||
Allow your instance of SearXNG of being able to proxy images. Uses memory space.
|
||||
|
||||
.. _method:
|
||||
|
||||
``method`` : ``$SEARXNG_METHOD``
|
||||
Whether to use ``GET`` or ``POST`` HTTP method when searching.
|
||||
|
||||
.. _HTTP headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
||||
|
||||
``default_http_headers`` :
|
||||
|
||||
@@ -27,23 +27,24 @@ Privacy-by-design
|
||||
-----------------
|
||||
|
||||
SearXNG was born out of the need for a **privacy-respecting** search tool which
|
||||
can be extended easily to maximize both, its search and its privacy protecting
|
||||
can be extended easily to maximize both its search and its privacy protecting
|
||||
capabilities.
|
||||
|
||||
A few widely used features work differently or turned off by default or not
|
||||
implemented at all **as a consequence of privacy-by-design**.
|
||||
Some widely used search engine features may work differently,
|
||||
may be turned off by default, or may not be implemented at all in SearXNG
|
||||
**as a consequence of a privacy-by-design approach**.
|
||||
|
||||
If a feature reduces the privacy preserving aspects of searx, it should be
|
||||
switched off by default or should not implemented at all. There are plenty of
|
||||
search engines already providing such features. If a feature reduces the
|
||||
protection of searx, users must be informed about the effect of choosing to
|
||||
enable it. Features that protect privacy but differ from the expectations of
|
||||
the user should also be explained.
|
||||
Following this approach, features reducing the privacy preserving aspects of SearXNG should be
|
||||
switched off by default or should not be implemented at all. There are plenty of
|
||||
search engines already providing such features. If a feature reduces
|
||||
SearXNG's efficacy in protecting a user's privacy, the user must be informed about
|
||||
the effect of choosing to enable it. Features that protect privacy but differ from the
|
||||
expectations of the user should also be carefully explained to them.
|
||||
|
||||
Also, if you think that something works weird with searx, it's might be because
|
||||
of the tool you use is designed in a way to interfere with the privacy respect.
|
||||
Also, if you think that something works weird with SearXNG, it might be because
|
||||
the tool you are using is designed in a way that interferes with SearXNG's privacy aspects.
|
||||
Submitting a bug report to the vendor of the tool that misbehaves might be a good
|
||||
feedback to reconsider the disrespect to its customers (e.g. ``GET`` vs ``POST``
|
||||
feedback for them to reconsider the disrespect to their customers (e.g., ``GET`` vs ``POST``
|
||||
requests in various browsers).
|
||||
|
||||
Remember the other prime directive of SearXNG is to be hackable, so if the above
|
||||
@@ -134,7 +135,7 @@ Here is an example which makes a complete rebuild:
|
||||
|
||||
.. _make docs.live:
|
||||
|
||||
live build
|
||||
Live build
|
||||
----------
|
||||
|
||||
.. _sphinx-autobuild:
|
||||
@@ -145,8 +146,8 @@ live build
|
||||
It is recommended to assert a complete rebuild before deploying (use
|
||||
``docs.clean``).
|
||||
|
||||
Live build is like WYSIWYG. If you want to edit the documentation, its
|
||||
recommended to use. The Makefile target ``docs.live`` builds the docs, opens
|
||||
Live build is like WYSIWYG. It's the recommended way to go if you want to edit the documentation.
|
||||
The Makefile target ``docs.live`` builds the docs, opens
|
||||
URL in your favorite browser and rebuilds every time a reST file has been
|
||||
changed (:ref:`make docs.clean`).
|
||||
|
||||
@@ -159,9 +160,9 @@ changed (:ref:`make docs.clean`).
|
||||
... Start watching changes
|
||||
|
||||
Live builds are implemented by sphinx-autobuild_. Use environment
|
||||
``$(SPHINXOPTS)`` to pass arguments to the sphinx-autobuild_ command. Except
|
||||
option ``--host`` (which is always set to ``0.0.0.0``) you can pass any
|
||||
argument. E.g to find and use a free port, use:
|
||||
``$(SPHINXOPTS)`` to pass arguments to the sphinx-autobuild_ command. You can
|
||||
pass any argument except for the ``--host`` option (which is always set to ``0.0.0.0``).
|
||||
E.g., to find and use a free port, use:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
|
||||
@@ -4,17 +4,11 @@
|
||||
Engine Library
|
||||
==============
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
:local:
|
||||
:backlinks: entry
|
||||
|
||||
.. automodule:: searx.enginelib
|
||||
:members:
|
||||
|
||||
.. _searx.enginelib.traits:
|
||||
|
||||
|
||||
Engine traits
|
||||
=============
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Programming Interface
|
||||
parameter. This function can be omitted, if there is no need to setup anything
|
||||
in advance.
|
||||
|
||||
:py:func:`search(query, params) <searx.engines.demo_offline.searc>`
|
||||
:py:func:`search(query, params) <searx.engines.demo_offline.search>`
|
||||
Each offline engine has a function named ``search``. This function is
|
||||
responsible to perform a search and return the results in a presentable
|
||||
format. (Where *presentable* means presentable by the selected result
|
||||
|
||||
8
docs/dev/engines/online/chinaso.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _chinaso engine:
|
||||
|
||||
=======
|
||||
ChinaSo
|
||||
=======
|
||||
|
||||
.. automodule:: searx.engines.chinaso
|
||||
:members:
|
||||
8
docs/dev/engines/online/huggingface.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _huggingface engine:
|
||||
|
||||
============
|
||||
Hugging Face
|
||||
============
|
||||
|
||||
.. automodule:: searx.engines.huggingface
|
||||
:members:
|
||||
@@ -4,10 +4,5 @@
|
||||
Mullvad-Leta
|
||||
============
|
||||
|
||||
.. contents:: Contents
|
||||
:depth: 2
|
||||
:local:
|
||||
:backlinks: entry
|
||||
|
||||
.. automodule:: searx.engines.mullvad_leta
|
||||
:members:
|
||||
|
||||
@@ -53,6 +53,9 @@ Probe HTTP headers
|
||||
.. automodule:: searx.botdetection.http_user_agent
|
||||
:members:
|
||||
|
||||
.. automodule:: searx.botdetection.http_sec_fetch
|
||||
:members:
|
||||
|
||||
.. _botdetection config:
|
||||
|
||||
Config
|
||||
|
||||
8
docs/src/searx.cache.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _searx.cache:
|
||||
|
||||
======
|
||||
Caches
|
||||
======
|
||||
|
||||
.. automodule:: searx.cache
|
||||
:members:
|
||||
@@ -2,7 +2,7 @@ mock==5.2.0
|
||||
nose2[coverage_plugin]==0.15.1
|
||||
cov-core==1.15.0
|
||||
black==24.3.0
|
||||
pylint==3.3.6
|
||||
pylint==3.3.7
|
||||
splinter==0.21.0
|
||||
selenium==4.31.0
|
||||
Pallets-Sphinx-Themes==2.3.0
|
||||
@@ -16,7 +16,7 @@ sphinx-notfound-page==1.1.0
|
||||
myst-parser==3.0.1
|
||||
linuxdoc==20240924
|
||||
aiounittest==1.5.0
|
||||
yamllint==1.37.0
|
||||
yamllint==1.37.1
|
||||
wlc==1.15
|
||||
coloredlogs==15.0.1
|
||||
docutils>=0.21.2
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
certifi==2025.1.31
|
||||
certifi==2025.4.26
|
||||
babel==2.17.0
|
||||
flask-babel==4.0.0
|
||||
flask==3.1.0
|
||||
jinja2==3.1.6
|
||||
lxml==5.3.2
|
||||
lxml==5.4.0
|
||||
pygments==2.19.1
|
||||
python-dateutil==2.9.0.post0
|
||||
pyyaml==6.0.2
|
||||
@@ -11,11 +11,11 @@ httpx[http2]==0.24.1
|
||||
Brotli==1.1.0
|
||||
uvloop==0.21.0
|
||||
httpx-socks[asyncio]==0.7.7
|
||||
setproctitle==1.3.5
|
||||
setproctitle==1.3.6
|
||||
redis==5.0.8
|
||||
markdown-it-py==3.0.0
|
||||
fasttext-predict==0.9.2.4
|
||||
tomli==2.2.1; python_version < '3.11'
|
||||
msgspec==0.19.0
|
||||
typer-slim==0.15.2
|
||||
typer-slim==0.15.3
|
||||
isodate==0.7.2
|
||||
|
||||
@@ -22,18 +22,18 @@ searx_dir = abspath(dirname(__file__))
|
||||
searx_parent_dir = abspath(dirname(dirname(__file__)))
|
||||
|
||||
settings = {}
|
||||
searx_debug = False
|
||||
sxng_debug = False
|
||||
logger = logging.getLogger('searx')
|
||||
|
||||
_unset = object()
|
||||
|
||||
|
||||
def init_settings():
|
||||
"""Initialize global ``settings`` and ``searx_debug`` variables and
|
||||
"""Initialize global ``settings`` and ``sxng_debug`` variables and
|
||||
``logger`` from ``SEARXNG_SETTINGS_PATH``.
|
||||
"""
|
||||
|
||||
global settings, searx_debug # pylint: disable=global-variable-not-assigned
|
||||
global settings, sxng_debug # pylint: disable=global-variable-not-assigned
|
||||
|
||||
cfg, msg = searx.settings_loader.load_settings(load_user_settings=True)
|
||||
cfg = cfg or {}
|
||||
@@ -42,8 +42,8 @@ def init_settings():
|
||||
settings.clear()
|
||||
settings.update(cfg)
|
||||
|
||||
searx_debug = settings['general']['debug']
|
||||
if searx_debug:
|
||||
sxng_debug = get_setting("general.debug")
|
||||
if sxng_debug:
|
||||
_logging_config_debug()
|
||||
else:
|
||||
logging.basicConfig(level=LOG_LEVEL_PROD, format=LOG_FORMAT_PROD)
|
||||
|
||||
@@ -34,6 +34,9 @@ def dump_request(request: SXNG_Request):
|
||||
+ " || Content-Length: %s" % request.headers.get('Content-Length')
|
||||
+ " || Connection: %s" % request.headers.get('Connection')
|
||||
+ " || User-Agent: %s" % request.headers.get('User-Agent')
|
||||
+ " || Sec-Fetch-Site: %s" % request.headers.get('Sec-Fetch-Site')
|
||||
+ " || Sec-Fetch-Mode: %s" % request.headers.get('Sec-Fetch-Mode')
|
||||
+ " || Sec-Fetch-Dest: %s" % request.headers.get('Sec-Fetch-Dest')
|
||||
)
|
||||
|
||||
|
||||
|
||||
103
searx/botdetection/http_sec_fetch.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Method ``http_sec_fetch``
|
||||
-------------------------
|
||||
|
||||
The ``http_sec_fetch`` method protect resources from web attacks with `Fetch
|
||||
Metadata`_. A request is filtered out in case of:
|
||||
|
||||
- http header Sec-Fetch-Mode_ is invalid
|
||||
- http header Sec-Fetch-Dest_ is invalid
|
||||
|
||||
.. _Fetch Metadata:
|
||||
https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header
|
||||
|
||||
.. _Sec-Fetch-Dest:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Request/destination
|
||||
|
||||
.. _Sec-Fetch-Mode:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
|
||||
|
||||
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from __future__ import annotations
|
||||
from ipaddress import (
|
||||
IPv4Network,
|
||||
IPv6Network,
|
||||
)
|
||||
|
||||
import re
|
||||
import flask
|
||||
import werkzeug
|
||||
|
||||
from searx.extended_types import SXNG_Request
|
||||
|
||||
from . import config
|
||||
from ._helpers import logger
|
||||
|
||||
|
||||
def is_browser_supported(user_agent: str) -> bool:
|
||||
"""Check if the browser supports Sec-Fetch headers.
|
||||
|
||||
https://caniuse.com/mdn-http_headers_sec-fetch-dest
|
||||
https://caniuse.com/mdn-http_headers_sec-fetch-mode
|
||||
https://caniuse.com/mdn-http_headers_sec-fetch-site
|
||||
|
||||
Supported browsers:
|
||||
- Chrome >= 80
|
||||
- Firefox >= 90
|
||||
- Safari >= 16.4
|
||||
- Edge (mirrors Chrome)
|
||||
- Opera (mirrors Chrome)
|
||||
"""
|
||||
user_agent = user_agent.lower()
|
||||
|
||||
# Chrome/Chromium/Edge/Opera
|
||||
chrome_match = re.search(r'chrome/(\d+)', user_agent)
|
||||
if chrome_match:
|
||||
version = int(chrome_match.group(1))
|
||||
return version >= 80
|
||||
|
||||
# Firefox
|
||||
firefox_match = re.search(r'firefox/(\d+)', user_agent)
|
||||
if firefox_match:
|
||||
version = int(firefox_match.group(1))
|
||||
return version >= 90
|
||||
|
||||
# Safari
|
||||
safari_match = re.search(r'version/(\d+)\.(\d+)', user_agent)
|
||||
if safari_match:
|
||||
major = int(safari_match.group(1))
|
||||
minor = int(safari_match.group(2))
|
||||
return major > 16 or (major == 16 and minor >= 4)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def filter_request(
|
||||
network: IPv4Network | IPv6Network,
|
||||
request: SXNG_Request,
|
||||
cfg: config.Config,
|
||||
) -> werkzeug.Response | None:
|
||||
|
||||
# Only check Sec-Fetch headers for supported browsers
|
||||
user_agent = request.headers.get('User-Agent', '')
|
||||
if is_browser_supported(user_agent):
|
||||
val = request.headers.get("Sec-Fetch-Mode", "")
|
||||
if val not in ('navigate', 'cors'):
|
||||
logger.debug("invalid Sec-Fetch-Mode '%s'", val)
|
||||
return flask.redirect(flask.url_for('index'), code=302)
|
||||
|
||||
val = request.headers.get("Sec-Fetch-Site", "")
|
||||
if val not in ('same-origin', 'same-site', 'none'):
|
||||
logger.debug("invalid Sec-Fetch-Site '%s'", val)
|
||||
flask.redirect(flask.url_for('index'), code=302)
|
||||
|
||||
val = request.headers.get("Sec-Fetch-Dest", "")
|
||||
if val not in ('document', 'empty'):
|
||||
logger.debug("invalid Sec-Fetch-Dest '%s'", val)
|
||||
flask.redirect(flask.url_for('index'), code=302)
|
||||
|
||||
return None
|
||||
405
searx/cache.py
Normal file
@@ -0,0 +1,405 @@
|
||||
"""Implementation of caching solutions.
|
||||
|
||||
- :py:obj:`searx.cache.ExpireCache` and its :py:obj:`searx.cache.ExpireCacheCfg`
|
||||
|
||||
----
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["ExpireCacheCfg", "ExpireCacheStats", "ExpireCache", "ExpireCacheSQLite"]
|
||||
|
||||
import abc
|
||||
import dataclasses
|
||||
import datetime
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import pickle
|
||||
import sqlite3
|
||||
import string
|
||||
import tempfile
|
||||
import time
|
||||
import typing
|
||||
|
||||
import msgspec
|
||||
|
||||
from searx import sqlitedb
|
||||
from searx import logger
|
||||
from searx import get_setting
|
||||
|
||||
log = logger.getChild("cache")
|
||||
|
||||
|
||||
class ExpireCacheCfg(msgspec.Struct): # pylint: disable=too-few-public-methods
|
||||
"""Configuration of a :py:obj:`ExpireCache` cache."""
|
||||
|
||||
name: str
|
||||
"""Name of the cache."""
|
||||
|
||||
db_url: str = ""
|
||||
"""URL of the SQLite DB, the path to the database file. If unset a default
|
||||
DB will be created in `/tmp/sxng_cache_{self.name}.db`"""
|
||||
|
||||
MAX_VALUE_LEN: int = 1024 * 10
|
||||
"""Max lenght of a *serialized* value."""
|
||||
|
||||
MAXHOLD_TIME: int = 60 * 60 * 24 * 7 # 7 days
|
||||
"""Hold time (default in sec.), after which a value is removed from the cache."""
|
||||
|
||||
MAINTENANCE_PERIOD: int = 60 * 60 # 2h
|
||||
"""Maintenance period in seconds / when :py:obj:`MAINTENANCE_MODE` is set to
|
||||
``auto``."""
|
||||
|
||||
MAINTENANCE_MODE: typing.Literal["auto", "off"] = "auto"
|
||||
"""Type of maintenance mode
|
||||
|
||||
``auto``:
|
||||
Maintenance is carried out automatically as part of the maintenance
|
||||
intervals (:py:obj:`MAINTENANCE_PERIOD`); no external process is required.
|
||||
|
||||
``off``:
|
||||
Maintenance is switched off and must be carried out by an external process
|
||||
if required.
|
||||
"""
|
||||
|
||||
password: bytes = get_setting("server.secret_key").encode() # type: ignore
|
||||
"""Password used by :py:obj:`ExpireCache.secret_hash`.
|
||||
|
||||
The default password is taken from :ref:`secret_key <server.secret_key>`.
|
||||
When the password is changed, the hashed keys in the cache can no longer be
|
||||
used, which is why all values in the cache are deleted when the password is
|
||||
changed.
|
||||
"""
|
||||
|
||||
def __post_init__(self):
|
||||
# if db_url is unset, use a default DB in /tmp/sxng_cache_{name}.db
|
||||
if not self.db_url:
|
||||
self.db_url = tempfile.gettempdir() + os.sep + f"sxng_cache_{ExpireCache.normalize_name(self.name)}.db"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExpireCacheStats:
|
||||
"""Dataclass wich provides information on the status of the cache."""
|
||||
|
||||
cached_items: dict[str, list[tuple[str, typing.Any, int]]]
|
||||
"""Values in the cache mapped by context name.
|
||||
|
||||
.. code: python
|
||||
|
||||
{
|
||||
"context name": [
|
||||
("foo key": "foo value", <expire>),
|
||||
("bar key": "bar value", <expire>),
|
||||
# ...
|
||||
],
|
||||
# ...
|
||||
}
|
||||
"""
|
||||
|
||||
def report(self):
|
||||
c_ctx = 0
|
||||
c_kv = 0
|
||||
lines = []
|
||||
|
||||
for ctx_name, kv_list in self.cached_items.items():
|
||||
c_ctx += 1
|
||||
if not kv_list:
|
||||
lines.append(f"[{ctx_name:20s}] empty")
|
||||
continue
|
||||
|
||||
for key, value, expire in kv_list:
|
||||
valid_until = datetime.datetime.fromtimestamp(expire).strftime("%Y-%m-%d %H:%M:%S")
|
||||
c_kv += 1
|
||||
lines.append(f"[{ctx_name:20s}] {valid_until} {key:12}" f" --> ({type(value).__name__}) {value} ")
|
||||
|
||||
lines.append(f"Number of contexts: {c_ctx}")
|
||||
lines.append(f"number of key/value pairs: {c_kv}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class ExpireCache(abc.ABC):
|
||||
"""Abstract base class for the implementation of a key/value cache
|
||||
with expire date."""
|
||||
|
||||
cfg: ExpireCacheCfg
|
||||
|
||||
hash_token = "hash_token"
|
||||
|
||||
@abc.abstractmethod
|
||||
def set(self, key: str, value: typing.Any, expire: int | None, ctx: str | None = None) -> bool:
|
||||
"""Set *key* to *value*. To set a timeout on key use argument
|
||||
``expire`` (in sec.). If expire is unset the default is taken from
|
||||
:py:obj:`ExpireCacheCfg.MAXHOLD_TIME`. After the timeout has expired,
|
||||
the key will automatically be deleted.
|
||||
|
||||
The ``ctx`` argument specifies the context of the ``key``. A key is
|
||||
only unique in its context.
|
||||
|
||||
The concrete implementations of this abstraction determine how the
|
||||
context is mapped in the connected database. In SQL databases, for
|
||||
example, the context is a DB table or in a Key/Value DB it could be
|
||||
a prefix for the key.
|
||||
|
||||
If the context is not specified (the default is ``None``) then a
|
||||
default context should be used, e.g. a default table for SQL databases
|
||||
or a default prefix in a Key/Value DB.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get(self, key: str, default=None, ctx: str | None = None) -> typing.Any:
|
||||
"""Return *value* of *key*. If key is unset, ``None`` is returned."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def maintenance(self, force: bool = False, truncate: bool = False) -> bool:
|
||||
"""Performs maintenance on the cache.
|
||||
|
||||
``force``:
|
||||
Maintenance should be carried out even if the maintenance interval has
|
||||
not yet been reached.
|
||||
|
||||
``truncate``:
|
||||
Truncate the entire cache, which is necessary, for example, if the
|
||||
password has changed.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def state(self) -> ExpireCacheStats:
|
||||
"""Returns a :py:obj:`ExpireCacheStats`, which provides information
|
||||
about the status of the cache."""
|
||||
|
||||
@staticmethod
|
||||
def build_cache(cfg: ExpireCacheCfg) -> ExpireCache:
|
||||
"""Factory to build a caching instance.
|
||||
|
||||
.. note::
|
||||
|
||||
Currently, only the SQLite adapter is available, but other database
|
||||
types could be implemented in the future, e.g. a Valkey (Redis)
|
||||
adapter.
|
||||
"""
|
||||
return ExpireCacheSQLite(cfg)
|
||||
|
||||
@staticmethod
|
||||
def normalize_name(name: str) -> str:
|
||||
"""Returns a normalized name that can be used as a file name or as a SQL
|
||||
table name (is used, for example, to normalize the context name)."""
|
||||
|
||||
_valid = "-_." + string.ascii_letters + string.digits
|
||||
return "".join([c for c in name if c in _valid])
|
||||
|
||||
def serialize(self, value: typing.Any) -> bytes:
|
||||
dump: bytes = pickle.dumps(value)
|
||||
return dump
|
||||
|
||||
def deserialize(self, value: bytes) -> typing.Any:
|
||||
obj = pickle.loads(value)
|
||||
return obj
|
||||
|
||||
def secret_hash(self, name: str | bytes) -> str:
|
||||
"""Creates a hash of the argument ``name``. The hash value is formed
|
||||
from the ``name`` combined with the :py:obj:`password
|
||||
<ExpireCacheCfg.password>`. Can be used, for example, to make the
|
||||
``key`` stored in the DB unreadable for third parties."""
|
||||
|
||||
if isinstance(name, str):
|
||||
name = bytes(name, encoding='utf-8')
|
||||
m = hmac.new(name + self.cfg.password, digestmod='sha256')
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache):
|
||||
"""Cache that manages key/value pairs in a SQLite DB. The DB model in the
|
||||
SQLite DB is implemented in abstract class :py:obj:`SQLiteAppl
|
||||
<searx.sqlitedb.SQLiteAppl>`.
|
||||
|
||||
The following configurations are required / supported:
|
||||
|
||||
- :py:obj:`ExpireCacheCfg.db_url`
|
||||
- :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`
|
||||
- :py:obj:`ExpireCacheCfg.MAINTENANCE_PERIOD`
|
||||
- :py:obj:`ExpireCacheCfg.MAINTENANCE_MODE`
|
||||
"""
|
||||
|
||||
DB_SCHEMA = 1
|
||||
|
||||
# The key/value tables will be created on demand by self.create_table
|
||||
DDL_CREATE_TABLES = {}
|
||||
|
||||
CACHE_TABLE_PREFIX = "CACHE-TABLE-"
|
||||
|
||||
def __init__(self, cfg: ExpireCacheCfg):
|
||||
"""An instance of the SQLite expire cache is build up from a
|
||||
:py:obj:`config <ExpireCacheCfg>`."""
|
||||
|
||||
self.cfg = cfg
|
||||
if cfg.db_url == ":memory:":
|
||||
log.critical("don't use SQLite DB in :memory: in production!!")
|
||||
super().__init__(cfg.db_url)
|
||||
|
||||
def init(self, conn: sqlite3.Connection) -> bool:
|
||||
ret_val = super().init(conn)
|
||||
if not ret_val:
|
||||
return False
|
||||
|
||||
new = hashlib.sha256(self.cfg.password).hexdigest()
|
||||
old = self.properties(self.hash_token)
|
||||
if old != new:
|
||||
if old is not None:
|
||||
log.warning("[%s] hash token changed: truncate all cache tables", self.cfg.name)
|
||||
self.maintenance(force=True, truncate=True)
|
||||
self.properties.set(self.hash_token, new)
|
||||
|
||||
return True
|
||||
|
||||
def maintenance(self, force: bool = False, truncate: bool = False) -> bool:
|
||||
|
||||
if not force and int(time.time()) < self.next_maintenance_time:
|
||||
# log.debug("no maintenance required yet, next maintenance interval is in the future")
|
||||
return False
|
||||
|
||||
# Prevent parallel DB maintenance cycles from other DB connections
|
||||
# (e.g. in multi thread or process environments).
|
||||
self.properties.set("LAST_MAINTENANCE", "") # hint: this (also) sets the m_time of the property!
|
||||
|
||||
if truncate:
|
||||
self.truncate_tables(self.table_names)
|
||||
return True
|
||||
|
||||
# drop items by expire time stamp ..
|
||||
expire = int(time.time())
|
||||
|
||||
with self.connect() as conn:
|
||||
for table in self.table_names:
|
||||
res = conn.execute(f"DELETE FROM {table} WHERE expire < ?", (expire,))
|
||||
log.debug("deleted %s keys from table %s (expire date reached)", res.rowcount, table)
|
||||
|
||||
# Vacuuming the WALs
|
||||
# https://www.theunterminatedstring.com/sqlite-vacuuming/
|
||||
|
||||
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
||||
conn.close()
|
||||
|
||||
return True
|
||||
|
||||
def create_table(self, table: str) -> bool:
|
||||
"""Create DB ``table`` if it has not yet been created, no recreates are
|
||||
initiated if the table already exists.
|
||||
"""
|
||||
if table in self.table_names:
|
||||
# log.debug("key/value table %s exists in DB (no need to recreate)", table)
|
||||
return False
|
||||
|
||||
log.info("key/value table '%s' NOT exists in DB -> create DB table ..", table)
|
||||
sql_table = "\n".join(
|
||||
[
|
||||
f"CREATE TABLE IF NOT EXISTS {table} (",
|
||||
" key TEXT,",
|
||||
" value BLOB,",
|
||||
f" expire INTEGER DEFAULT (strftime('%s', 'now') + {self.cfg.MAXHOLD_TIME}),",
|
||||
"PRIMARY KEY (key))",
|
||||
]
|
||||
)
|
||||
sql_index = f"CREATE INDEX IF NOT EXISTS index_expire_{table} ON {table}(expire);"
|
||||
with self.connect() as conn:
|
||||
conn.execute(sql_table)
|
||||
conn.execute(sql_index)
|
||||
conn.close()
|
||||
|
||||
self.properties.set(f"{self.CACHE_TABLE_PREFIX}-{table}", table)
|
||||
return True
|
||||
|
||||
@property
|
||||
def table_names(self) -> list[str]:
|
||||
"""List of key/value tables already created in the DB."""
|
||||
sql = f"SELECT value FROM properties WHERE name LIKE '{self.CACHE_TABLE_PREFIX}%%'"
|
||||
rows = self.DB.execute(sql).fetchall() or []
|
||||
return [r[0] for r in rows]
|
||||
|
||||
def truncate_tables(self, table_names: list[str]):
|
||||
log.debug("truncate table: %s", ",".join(table_names))
|
||||
with self.connect() as conn:
|
||||
for table in table_names:
|
||||
conn.execute(f"DELETE FROM {table}")
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
@property
|
||||
def next_maintenance_time(self) -> int:
|
||||
"""Returns (unix epoch) time of the next maintenance."""
|
||||
|
||||
return self.cfg.MAINTENANCE_PERIOD + self.properties.m_time("LAST_MAINTENANCE", int(time.time()))
|
||||
|
||||
# implement ABC methods of ExpireCache
|
||||
|
||||
def set(self, key: str, value: typing.Any, expire: int | None, ctx: str | None = None) -> bool:
|
||||
"""Set key/value in DB table given by argument ``ctx``. If expire is
|
||||
unset the default is taken from :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`.
|
||||
If ``ctx`` argument is ``None`` (the default), a table name is
|
||||
generated from the :py:obj:`ExpireCacheCfg.name`. If DB table does not
|
||||
exists, it will be created (on demand) by :py:obj:`self.create_table
|
||||
<ExpireCacheSQLite.create_table>`.
|
||||
"""
|
||||
table = ctx
|
||||
self.maintenance()
|
||||
|
||||
value = self.serialize(value=value)
|
||||
if len(value) > self.cfg.MAX_VALUE_LEN:
|
||||
log.warning("ExpireCache.set(): %s.key='%s' - value too big to cache (len: %s) ", table, value, len(value))
|
||||
return False
|
||||
|
||||
if not expire:
|
||||
expire = self.cfg.MAXHOLD_TIME
|
||||
expire = int(time.time()) + expire
|
||||
|
||||
table_name = table
|
||||
if not table_name:
|
||||
table_name = self.normalize_name(self.cfg.name)
|
||||
self.create_table(table_name)
|
||||
|
||||
sql = (
|
||||
f"INSERT INTO {table_name} (key, value, expire) VALUES (?, ?, ?)"
|
||||
f" ON CONFLICT DO "
|
||||
f"UPDATE SET value=?, expire=?"
|
||||
)
|
||||
|
||||
if table:
|
||||
with self.DB:
|
||||
self.DB.execute(sql, (key, value, expire, value, expire))
|
||||
else:
|
||||
with self.connect() as conn:
|
||||
conn.execute(sql, (key, value, expire, value, expire))
|
||||
conn.close()
|
||||
|
||||
return True
|
||||
|
||||
def get(self, key: str, default=None, ctx: str | None = None) -> typing.Any:
|
||||
"""Get value of ``key`` from table given by argument ``ctx``. If
|
||||
``ctx`` argument is ``None`` (the default), a table name is generated
|
||||
from the :py:obj:`ExpireCacheCfg.name`. If ``key`` not exists (in
|
||||
table), the ``default`` value is returned.
|
||||
|
||||
"""
|
||||
table = ctx
|
||||
self.maintenance()
|
||||
|
||||
if not table:
|
||||
table = self.normalize_name(self.cfg.name)
|
||||
|
||||
if table not in self.table_names:
|
||||
return default
|
||||
|
||||
sql = f"SELECT value FROM {table} WHERE key = ?"
|
||||
row = self.DB.execute(sql, (key,)).fetchone()
|
||||
if row is None:
|
||||
return default
|
||||
|
||||
return self.deserialize(row[0])
|
||||
|
||||
def state(self) -> ExpireCacheStats:
|
||||
cached_items = {}
|
||||
for table in self.table_names:
|
||||
cached_items[table] = []
|
||||
for row in self.DB.execute(f"SELECT key, value, expire FROM {table}"):
|
||||
cached_items[table].append((row[0], self.deserialize(row[1]), row[2]))
|
||||
return ExpireCacheStats(cached_items=cached_items)
|
||||
@@ -839,6 +839,7 @@
|
||||
"ja": "バハマ・ドル",
|
||||
"ko": "바하마 달러",
|
||||
"lt": "Bahamų doleris",
|
||||
"lv": "Bahamu dolārs",
|
||||
"nl": "Bahamaanse dollar",
|
||||
"oc": "Dolar de las Bahamas",
|
||||
"pa": "ਬਹਾਮਾਸੀ ਡਾਲਰ",
|
||||
@@ -2119,7 +2120,7 @@
|
||||
"de": "guatemaltekischer Quetzal",
|
||||
"en": "quetzal",
|
||||
"eo": "gvatemala kecalo",
|
||||
"es": "quetzal",
|
||||
"es": "Quetzal",
|
||||
"eu": "Quetzal",
|
||||
"fi": "Guatemalan quetzal",
|
||||
"fr": "Quetzal",
|
||||
@@ -2211,6 +2212,7 @@
|
||||
"ja": "香港ドル",
|
||||
"ko": "홍콩 달러",
|
||||
"lt": "Honkongo doleris",
|
||||
"lv": "Honkongas dolārs",
|
||||
"ml": "ഹോങ്കോങ്ങ് ഡോളർ",
|
||||
"ms": "dolar Hong Kong",
|
||||
"nl": "Hongkongse dollar",
|
||||
@@ -2424,7 +2426,7 @@
|
||||
"pa": "ਇਜ਼ਰਾਇਲੀ ਨਵਾਂ ਸ਼ੇਕਲ",
|
||||
"pl": "Nowy izraelski szekel",
|
||||
"pt": "novo shekel israelense",
|
||||
"ro": "Shekel",
|
||||
"ro": "shekel",
|
||||
"ru": "новый израильский шекель",
|
||||
"si": "සෙකල්",
|
||||
"sk": "Nový izraelský šekel",
|
||||
@@ -2591,6 +2593,7 @@
|
||||
"ja": "アイスランド・クローナ",
|
||||
"ko": "아이슬란드 크로나",
|
||||
"lt": "Islandijos krona",
|
||||
"lv": "Islandes krona",
|
||||
"nl": "IJslandse kroon",
|
||||
"pa": "ਆਈਸਲੈਂਡੀ ਕਰੋਨਾ",
|
||||
"pl": "Korona islandzka",
|
||||
@@ -2628,6 +2631,7 @@
|
||||
"ja": "ジャマイカ・ドル",
|
||||
"ko": "자메이카 달러",
|
||||
"lt": "Jamaikos doleris",
|
||||
"lv": "Jamaikas dolārs",
|
||||
"nl": "Jamaicaanse dollar",
|
||||
"pa": "ਜਮੈਕੀ ਡਾਲਰ",
|
||||
"pl": "Dolar jamajski",
|
||||
@@ -3104,6 +3108,7 @@
|
||||
"ja": "キープ",
|
||||
"ko": "라오스 킵",
|
||||
"lt": "Laoso kipas",
|
||||
"lv": "Laosas kips",
|
||||
"ms": "Kip",
|
||||
"nl": "Laotiaanse kip",
|
||||
"oc": "kip laossian",
|
||||
@@ -3221,6 +3226,7 @@
|
||||
"ja": "リベリア・ドル",
|
||||
"ko": "라이베리아 달러",
|
||||
"lt": "Liberijos doleris",
|
||||
"lv": "Libērijas dolārs",
|
||||
"ms": "Dolar Liberia",
|
||||
"nl": "Liberiaanse dollar",
|
||||
"oc": "Dolar liberian",
|
||||
@@ -3427,7 +3433,7 @@
|
||||
"cy": "denar (Macedonia)",
|
||||
"da": "Makedonske denarer",
|
||||
"de": "mazedonischer Denar",
|
||||
"en": "North Macedonian denar",
|
||||
"en": "Macedonian denar",
|
||||
"eo": "makedona denaro",
|
||||
"es": "denar macedonio",
|
||||
"et": "Makedoonia denaar",
|
||||
@@ -3691,7 +3697,7 @@
|
||||
"de": "Malawi-Kwacha",
|
||||
"en": "Malawian kwacha",
|
||||
"eo": "malavia kvaĉo",
|
||||
"es": "kwacha malauí",
|
||||
"es": "kuacha malauí",
|
||||
"et": "Malawi kvatša",
|
||||
"fi": "Malawin kwacha",
|
||||
"fr": "kwacha malawien",
|
||||
@@ -3867,6 +3873,7 @@
|
||||
"ja": "ナミビア・ドル",
|
||||
"ko": "나미비아 달러",
|
||||
"lt": "Namibijos doleris",
|
||||
"lv": "Namībijas dolārs",
|
||||
"ms": "Dolar Namibia",
|
||||
"nl": "Namibische dollar",
|
||||
"oc": "Dolar namibian",
|
||||
@@ -4874,6 +4881,7 @@
|
||||
"ja": "シンガポールドル",
|
||||
"ko": "싱가포르 달러",
|
||||
"lt": "Singapūro doleris",
|
||||
"lv": "Singapūras dolārs",
|
||||
"ml": "സിംഗപ്പൂർ ഡോളർ",
|
||||
"ms": "Dolar Singapura",
|
||||
"nl": "Singaporese dollar",
|
||||
@@ -5245,6 +5253,7 @@
|
||||
"ja": "ソモニ",
|
||||
"ko": "타지키스탄 소모니",
|
||||
"lt": "Somonis",
|
||||
"lv": "somoni",
|
||||
"ms": "Somoni",
|
||||
"nl": "Tadzjiekse somoni",
|
||||
"pa": "ਤਾਜਿਕਿਸਤਾਨੀ ਸੋਮੋਨੀ",
|
||||
@@ -5843,7 +5852,7 @@
|
||||
"ca": "tala",
|
||||
"cs": "Samojská tala",
|
||||
"de": "samoanischer Tala",
|
||||
"en": "Samoan Tālā",
|
||||
"en": "Samoan tālā",
|
||||
"eo": "samoa talao",
|
||||
"es": "tālā",
|
||||
"et": "Samoa tala",
|
||||
@@ -5998,6 +6007,7 @@
|
||||
"XCG": {
|
||||
"ar": "الجلدر الكاريبي",
|
||||
"ca": "florí caribeny",
|
||||
"cs": "Karibský gulden",
|
||||
"de": "Karibischer Gulden",
|
||||
"en": "Caribbean guilder",
|
||||
"eo": "Karibia guldeno",
|
||||
@@ -6006,6 +6016,7 @@
|
||||
"hr": "Karipski gulden",
|
||||
"hu": "karibi forint",
|
||||
"it": "fiorino caraibico",
|
||||
"ja": "カリブ・ギルダー",
|
||||
"nl": "Caribische gulden",
|
||||
"pap": "Florin karibense",
|
||||
"pt": "Florim do Caribe",
|
||||
@@ -6251,7 +6262,7 @@
|
||||
"de": "sambischer Kwacha",
|
||||
"en": "Zambian Kwacha",
|
||||
"eo": "zambia kvaĉo",
|
||||
"es": "kwacha zambiano",
|
||||
"es": "kuacha zambiano",
|
||||
"et": "Sambia kvatša",
|
||||
"fi": "Sambian kwacha",
|
||||
"fr": "kwacha zambien",
|
||||
@@ -6745,6 +6756,7 @@
|
||||
"bahamski dolar": "BSD",
|
||||
"bahamský dolar": "BSD",
|
||||
"bahamský dolár": "BSD",
|
||||
"bahamu dolārs": "BSD",
|
||||
"bahamų doleris": "BSD",
|
||||
"bahrain dinar": "BHD",
|
||||
"bahraini dinar": "BHD",
|
||||
@@ -8011,6 +8023,10 @@
|
||||
"dop": "DOP",
|
||||
"dopene": "DOP",
|
||||
"dòlar australià": "AUD",
|
||||
"dòlar bahamià": "BSD",
|
||||
"dòlar barbadià": "BBD",
|
||||
"dòlar belizià": "BZD",
|
||||
"dòlar bruneiès": "BND",
|
||||
"dòlar canadenc": "CAD",
|
||||
"dòlar de bahames": "BSD",
|
||||
"dòlar de barbados": "BBD",
|
||||
@@ -8042,14 +8058,18 @@
|
||||
"dòlar del canadà": "CAD",
|
||||
"dòlar del carib oriental": "XCD",
|
||||
"dòlar dels estats units": "USD",
|
||||
"dòlar estatunidenc": "USD",
|
||||
"dòlar etíop": "ETB",
|
||||
"dòlar fijià": "FJD",
|
||||
"dòlar guyanès": "GYD",
|
||||
"dòlar jamaicà": "JMD",
|
||||
"dòlar liberià": "LRD",
|
||||
"dòlar malai": "MYR",
|
||||
"dòlar namibi": "NAD",
|
||||
"dòlar namibià": "NAD",
|
||||
"dòlar neozelandès": "NZD",
|
||||
"dòlar salomonès": "SBD",
|
||||
"dòlar surinamès": "SRD",
|
||||
"dòlar taiwanès": "TWD",
|
||||
"dòlars canadencs": "CAD",
|
||||
"dòlars neozelandesos": "NZD",
|
||||
@@ -8252,6 +8272,7 @@
|
||||
"egyptská libra": "EGP",
|
||||
"eiro": "EUR",
|
||||
"ekialdeko karibeko dolar": "XCD",
|
||||
"el peso": "GTQ",
|
||||
"emalangeni": "SZL",
|
||||
"emas sebagai pelaburan": "XAU",
|
||||
"emirati dirham": "AED",
|
||||
@@ -8304,6 +8325,7 @@
|
||||
"escudo tanjung verde": "CVE",
|
||||
"escudo zielonego przylądka": "CVE",
|
||||
"escudos cabo verdianos": "CVE",
|
||||
"escut capverdià": "CVE",
|
||||
"escut de cap verd": "CVE",
|
||||
"esloti": [
|
||||
"PLZ",
|
||||
@@ -8490,6 +8512,7 @@
|
||||
"franc an chongó": "CDF",
|
||||
"franc burundais": "BIF",
|
||||
"franc burundez": "BIF",
|
||||
"franc burundès": "BIF",
|
||||
"franc burundi": "BIF",
|
||||
"franc centrafrican cfa": "XAF",
|
||||
"franc central african cfa": "XAF",
|
||||
@@ -8514,6 +8537,7 @@
|
||||
"franc cfa vest african": "XOF",
|
||||
"franc cfp": "XPF",
|
||||
"franc comorian": "KMF",
|
||||
"franc comorià": "KMF",
|
||||
"franc comorien": "KMF",
|
||||
"franc comoros": "KMF",
|
||||
"franc congo": "CDF",
|
||||
@@ -8530,6 +8554,7 @@
|
||||
"franc del congo belga": "CDF",
|
||||
"franc des collectivités françaises du pacifique": "XPF",
|
||||
"franc djibouti": "DJF",
|
||||
"franc djiboutià": "DJF",
|
||||
"franc djiboutien": "DJF",
|
||||
"franc elvețian": "CHF",
|
||||
"franc elveţian": "CHF",
|
||||
@@ -8741,6 +8766,7 @@
|
||||
"greenback": "USD",
|
||||
"grivina": "UAH",
|
||||
"grivna": "UAH",
|
||||
"grivna ucraina": "UAH",
|
||||
"grivna ucraniana": "UAH",
|
||||
"grivnas": "UAH",
|
||||
"grivnă": "UAH",
|
||||
@@ -8892,6 +8918,7 @@
|
||||
"hongkonský dolár": "HKD",
|
||||
"hongkonški dolar": "HKD",
|
||||
"honkonga dolaro": "HKD",
|
||||
"honkongas dolārs": "HKD",
|
||||
"honkongo doleris": "HKD",
|
||||
"honkonški dolar": "HKD",
|
||||
"hrivna": "UAH",
|
||||
@@ -9013,6 +9040,7 @@
|
||||
"irr": "IRR",
|
||||
"isk": "ISK",
|
||||
"islanda krono": "ISK",
|
||||
"islandes krona": "ISK",
|
||||
"islandi kroon": "ISK",
|
||||
"islandiar koroa": "ISK",
|
||||
"islandijos krona": "ISK",
|
||||
@@ -9077,6 +9105,7 @@
|
||||
"jamaika doları": "JMD",
|
||||
"jamaika dollar": "JMD",
|
||||
"jamaikan dollari": "JMD",
|
||||
"jamaikas dolārs": "JMD",
|
||||
"jamaikos doleris": "JMD",
|
||||
"jamajčanski dolar": "JMD",
|
||||
"jamajka dolaro": "JMD",
|
||||
@@ -9202,6 +9231,7 @@
|
||||
"karibia guldeno": "XCG",
|
||||
"karibischer gulden": "XCG",
|
||||
"karibski goldinar": "XCG",
|
||||
"karibský gulden": "XCG",
|
||||
"karipski gulden": "XCG",
|
||||
"karod": "NPR",
|
||||
"kartvela lario": "GEL",
|
||||
@@ -9395,6 +9425,8 @@
|
||||
"krw": "KRW",
|
||||
"ks": "MMK",
|
||||
"ksh": "KES",
|
||||
"kuacha malauí": "MWK",
|
||||
"kuacha zambiano": "ZMW",
|
||||
"kuanza angoleño": "AOA",
|
||||
"kuba peso": "CUP",
|
||||
"kubai peso": "CUP",
|
||||
@@ -9445,10 +9477,8 @@
|
||||
"kwacha do malawi": "MWK",
|
||||
"kwacha do maláui": "MWK",
|
||||
"kwacha do malávi": "MWK",
|
||||
"kwacha malaui": "MWK",
|
||||
"kwacha malauiana": "MWK",
|
||||
"kwacha malauiano": "MWK",
|
||||
"kwacha malauí": "MWK",
|
||||
"kwacha malaviana": "MWK",
|
||||
"kwacha malawi": "MWK",
|
||||
"kwacha malawiana": "MWK",
|
||||
@@ -9510,6 +9540,7 @@
|
||||
"lao kip": "LAK",
|
||||
"laos kipi": "LAK",
|
||||
"laosa kipo": "LAK",
|
||||
"laosas kips": "LAK",
|
||||
"laosin kip": "LAK",
|
||||
"laoski kip": "LAK",
|
||||
"laoský kip": "LAK",
|
||||
@@ -9671,6 +9702,7 @@
|
||||
"liberisk dollar": "LRD",
|
||||
"liberya doları": "LRD",
|
||||
"libériai dollár": "LRD",
|
||||
"libērijas dolārs": "LRD",
|
||||
"libia dinaro": "LYD",
|
||||
"libijos dinaras": "LYD",
|
||||
"libijski dinar": "LYD",
|
||||
@@ -10213,6 +10245,7 @@
|
||||
"namibya doları": "NAD",
|
||||
"namíbiai dollár": "NAD",
|
||||
"namíbijský dolár": "NAD",
|
||||
"namībijas dolārs": "NAD",
|
||||
"naujasis solis": "PEN",
|
||||
"naujasis taivano doleris": "TWD",
|
||||
"naujoji rumunijos lėja": "RON",
|
||||
@@ -11075,6 +11108,7 @@
|
||||
"riyal oman": "OMR",
|
||||
"riyal qatar": "QAR",
|
||||
"riyal qatari": "QAR",
|
||||
"riyal qatarià": "QAR",
|
||||
"riyal qatarien": "QAR",
|
||||
"riyal qatariota": "QAR",
|
||||
"riyal qatarita": "QAR",
|
||||
@@ -11256,6 +11290,7 @@
|
||||
"rupia índia": "INR",
|
||||
"rupia lankijska": "LKR",
|
||||
"rupia maldiva": "MVR",
|
||||
"rupia maldiviana": "MVR",
|
||||
"rupia maldívia": "MVR",
|
||||
"rupia malediwska": "MVR",
|
||||
"rupia mauricia": "MUR",
|
||||
@@ -11276,6 +11311,7 @@
|
||||
"rupia seszelska": "SCR",
|
||||
"rupia seychelense": "SCR",
|
||||
"rupia seychellense": "SCR",
|
||||
"rupia seychellesa": "SCR",
|
||||
"rupia singalesa": "LKR",
|
||||
"rupia singalese": "LKR",
|
||||
"rupia sri lanki": "LKR",
|
||||
@@ -11556,6 +11592,7 @@
|
||||
"singapurski dolar": "SGD",
|
||||
"singapurský dolar": "SGD",
|
||||
"singapurský dolár": "SGD",
|
||||
"singapūras dolārs": "SGD",
|
||||
"singapūro doleris": "SGD",
|
||||
"sint heleens pond": "SHP",
|
||||
"siria pundo": "SYP",
|
||||
@@ -11870,7 +11907,6 @@
|
||||
"švýcarský frank": "CHF",
|
||||
"șekel nou": "ILS",
|
||||
"şekel": "ILS",
|
||||
"şekel nou": "ILS",
|
||||
"şili pesosu": "CLP",
|
||||
"s₣": "CHF",
|
||||
"t": "TMT",
|
||||
@@ -11883,6 +11919,8 @@
|
||||
"tadžikistani somoni": "TJS",
|
||||
"tadžikistanin somoni": "TJS",
|
||||
"tadžikistanski somoni": "TJS",
|
||||
"tadžikistānas somoni": "TJS",
|
||||
"tadžiku somoni": "TJS",
|
||||
"taĝika somonio": "TJS",
|
||||
"tai baat": "THB",
|
||||
"tailando batas": "THB",
|
||||
@@ -11918,6 +11956,7 @@
|
||||
"tala samoana": "WST",
|
||||
"tala samoano": "WST",
|
||||
"tala samoà": "WST",
|
||||
"talao": "WST",
|
||||
"tambala": "MWK",
|
||||
"tamil rupee": "LKR",
|
||||
"tamilska rupija": "LKR",
|
||||
@@ -14413,6 +14452,9 @@
|
||||
"دوبرا": "STN",
|
||||
"دوبرا ساو تومي وبرينسيب": "STN",
|
||||
"دوبرا ساو تومية وبرينسيبية": "STN",
|
||||
"دولار الامريكي": "USD",
|
||||
"دولار الأمريكي": "USD",
|
||||
"دولار امريكي": "USD",
|
||||
"دولار أسترالي": "AUD",
|
||||
"دولار أمريكي": "USD",
|
||||
"دولار بربادوسي": "BBD",
|
||||
@@ -15109,6 +15151,7 @@
|
||||
"เยน": "JPY",
|
||||
"เรนมินบิ": "CNY",
|
||||
"เรอัลบราซิล": "BRL",
|
||||
"เรียล": "KHR",
|
||||
"เรียลกัมพูชา": "KHR",
|
||||
"เรียลบราซิล": "BRL",
|
||||
"เลวูโรมาเนีย": "RON",
|
||||
@@ -15479,7 +15522,6 @@
|
||||
"₽": "RUB",
|
||||
"₾": "GEL",
|
||||
"⃀": "KGS",
|
||||
"": "SAR",
|
||||
"〒": "KZT",
|
||||
"アイスランドクローナ": "ISK",
|
||||
"アイスランド・クローナ": "ISK",
|
||||
@@ -15535,6 +15577,7 @@
|
||||
"カタール・リヤル": "QAR",
|
||||
"カナダドル": "CAD",
|
||||
"カナダ・ドル": "CAD",
|
||||
"カリブ・ギルダー": "XCG",
|
||||
"カーボベルデ・エスクード": "CVE",
|
||||
"ギニア・フラン": "GNF",
|
||||
"ギニー": "EGP",
|
||||
@@ -15635,6 +15678,7 @@
|
||||
"チュニジア・ディナール": "TND",
|
||||
"チリの通貨": "CLP",
|
||||
"チリ・ペソ": "CLP",
|
||||
"デジタルルピー": "INR",
|
||||
"デンマーククローネ": "DKK",
|
||||
"デンマーク・クローネ": "DKK",
|
||||
"テンゲ": "KZT",
|
||||
|
||||
@@ -1684,8 +1684,10 @@
|
||||
"custom": {
|
||||
"ui_lang": {
|
||||
"bg": "bg",
|
||||
"br": "br",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"cy": "cy",
|
||||
"da": "da",
|
||||
"de-DE": "de-de",
|
||||
"el": "el",
|
||||
@@ -1695,9 +1697,11 @@
|
||||
"en-US": "en-us",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"eu": "eu",
|
||||
"fi-FI": "fi-fi",
|
||||
"fr-CA": "fr-ca",
|
||||
"fr-FR": "fr-fr",
|
||||
"gl": "gl",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"id": "id",
|
||||
@@ -1786,8 +1790,10 @@
|
||||
"custom": {
|
||||
"ui_lang": {
|
||||
"bg": "bg",
|
||||
"br": "br",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"cy": "cy",
|
||||
"da": "da",
|
||||
"de-DE": "de-de",
|
||||
"el": "el",
|
||||
@@ -1797,9 +1803,11 @@
|
||||
"en-US": "en-us",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"eu": "eu",
|
||||
"fi-FI": "fi-fi",
|
||||
"fr-CA": "fr-ca",
|
||||
"fr-FR": "fr-fr",
|
||||
"gl": "gl",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"id": "id",
|
||||
@@ -1888,8 +1896,10 @@
|
||||
"custom": {
|
||||
"ui_lang": {
|
||||
"bg": "bg",
|
||||
"br": "br",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"cy": "cy",
|
||||
"da": "da",
|
||||
"de-DE": "de-de",
|
||||
"el": "el",
|
||||
@@ -1899,9 +1909,11 @@
|
||||
"en-US": "en-us",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"eu": "eu",
|
||||
"fi-FI": "fi-fi",
|
||||
"fr-CA": "fr-ca",
|
||||
"fr-FR": "fr-fr",
|
||||
"gl": "gl",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"id": "id",
|
||||
@@ -1990,8 +2002,10 @@
|
||||
"custom": {
|
||||
"ui_lang": {
|
||||
"bg": "bg",
|
||||
"br": "br",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"cy": "cy",
|
||||
"da": "da",
|
||||
"de-DE": "de-de",
|
||||
"el": "el",
|
||||
@@ -2001,9 +2015,11 @@
|
||||
"en-US": "en-us",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"eu": "eu",
|
||||
"fi-FI": "fi-fi",
|
||||
"fr-CA": "fr-ca",
|
||||
"fr-FR": "fr-fr",
|
||||
"gl": "gl",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"id": "id",
|
||||
@@ -6628,6 +6644,172 @@
|
||||
"to-TO": "to"
|
||||
}
|
||||
},
|
||||
"mullvadleta": {
|
||||
"all_locale": null,
|
||||
"custom": {},
|
||||
"data_type": "traits_v1",
|
||||
"languages": {
|
||||
"ar": "ar",
|
||||
"bg": "bg",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"da": "da",
|
||||
"de": "de",
|
||||
"en": "en",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"fi": "fi",
|
||||
"fr": "fr",
|
||||
"he": "he",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"is": "is",
|
||||
"it": "it",
|
||||
"ja": "jp",
|
||||
"ko": "ko",
|
||||
"lt": "lt",
|
||||
"lv": "lv",
|
||||
"nb": "nb",
|
||||
"nl": "nl",
|
||||
"pl": "pl",
|
||||
"pt": "pt",
|
||||
"ro": "ro",
|
||||
"ru": "ru",
|
||||
"sk": "sk",
|
||||
"sl": "sl",
|
||||
"sr": "sr",
|
||||
"sv": "sv",
|
||||
"tr": "tr",
|
||||
"zh_Hans": "zh-hant",
|
||||
"zh_Hant": "zh-hans"
|
||||
},
|
||||
"regions": {
|
||||
"ar-SA": "sa",
|
||||
"da-DK": "dk",
|
||||
"de-AT": "at",
|
||||
"de-BE": "be",
|
||||
"de-CH": "ch",
|
||||
"de-DE": "de",
|
||||
"en-AU": "au",
|
||||
"en-CA": "ca",
|
||||
"en-GB": "uk",
|
||||
"en-IN": "in",
|
||||
"en-NZ": "nz",
|
||||
"en-PH": "ph",
|
||||
"en-US": "us",
|
||||
"en-ZA": "za",
|
||||
"es-AR": "ar",
|
||||
"es-CL": "cl",
|
||||
"es-ES": "es",
|
||||
"es-MX": "mx",
|
||||
"fi-FI": "fi",
|
||||
"fr-BE": "be",
|
||||
"fr-CA": "ca",
|
||||
"fr-CH": "ch",
|
||||
"fr-FR": "fr",
|
||||
"id-ID": "id",
|
||||
"it-CH": "ch",
|
||||
"it-IT": "it",
|
||||
"ja-JP": "jp",
|
||||
"ko-KR": "kr",
|
||||
"ms-MY": "my",
|
||||
"nb-NO": "no",
|
||||
"nl-BE": "be",
|
||||
"nl-NL": "nl",
|
||||
"pl-PL": "pl",
|
||||
"pt-BR": "br",
|
||||
"pt-PT": "pt",
|
||||
"ru-RU": "ru",
|
||||
"se-SE": "se",
|
||||
"tr-TR": "tr",
|
||||
"zh-CN": "cn",
|
||||
"zh-HK": "hk",
|
||||
"zh-TW": "tw"
|
||||
}
|
||||
},
|
||||
"mullvadleta brave": {
|
||||
"all_locale": null,
|
||||
"custom": {},
|
||||
"data_type": "traits_v1",
|
||||
"languages": {
|
||||
"ar": "ar",
|
||||
"bg": "bg",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"da": "da",
|
||||
"de": "de",
|
||||
"en": "en",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"fi": "fi",
|
||||
"fr": "fr",
|
||||
"he": "he",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"is": "is",
|
||||
"it": "it",
|
||||
"ja": "jp",
|
||||
"ko": "ko",
|
||||
"lt": "lt",
|
||||
"lv": "lv",
|
||||
"nb": "nb",
|
||||
"nl": "nl",
|
||||
"pl": "pl",
|
||||
"pt": "pt",
|
||||
"ro": "ro",
|
||||
"ru": "ru",
|
||||
"sk": "sk",
|
||||
"sl": "sl",
|
||||
"sr": "sr",
|
||||
"sv": "sv",
|
||||
"tr": "tr",
|
||||
"zh_Hans": "zh-hant",
|
||||
"zh_Hant": "zh-hans"
|
||||
},
|
||||
"regions": {
|
||||
"ar-SA": "sa",
|
||||
"da-DK": "dk",
|
||||
"de-AT": "at",
|
||||
"de-BE": "be",
|
||||
"de-CH": "ch",
|
||||
"de-DE": "de",
|
||||
"en-AU": "au",
|
||||
"en-CA": "ca",
|
||||
"en-GB": "uk",
|
||||
"en-IN": "in",
|
||||
"en-NZ": "nz",
|
||||
"en-PH": "ph",
|
||||
"en-US": "us",
|
||||
"en-ZA": "za",
|
||||
"es-AR": "ar",
|
||||
"es-CL": "cl",
|
||||
"es-ES": "es",
|
||||
"es-MX": "mx",
|
||||
"fi-FI": "fi",
|
||||
"fr-BE": "be",
|
||||
"fr-CA": "ca",
|
||||
"fr-CH": "ch",
|
||||
"fr-FR": "fr",
|
||||
"id-ID": "id",
|
||||
"it-CH": "ch",
|
||||
"it-IT": "it",
|
||||
"ja-JP": "jp",
|
||||
"ko-KR": "kr",
|
||||
"ms-MY": "my",
|
||||
"nb-NO": "no",
|
||||
"nl-BE": "be",
|
||||
"nl-NL": "nl",
|
||||
"pl-PL": "pl",
|
||||
"pt-BR": "br",
|
||||
"pt-PT": "pt",
|
||||
"ru-RU": "ru",
|
||||
"se-SE": "se",
|
||||
"tr-TR": "tr",
|
||||
"zh-CN": "cn",
|
||||
"zh-HK": "hk",
|
||||
"zh-TW": "tw"
|
||||
}
|
||||
},
|
||||
"odysee": {
|
||||
"all_locale": null,
|
||||
"custom": {},
|
||||
@@ -6934,6 +7116,7 @@
|
||||
"BY",
|
||||
"BZ",
|
||||
"CA",
|
||||
"CC",
|
||||
"CD",
|
||||
"CF",
|
||||
"CG",
|
||||
@@ -8105,6 +8288,7 @@
|
||||
"nr",
|
||||
"nrm",
|
||||
"nso",
|
||||
"nup",
|
||||
"nv",
|
||||
"ny",
|
||||
"oc",
|
||||
@@ -8121,7 +8305,6 @@
|
||||
"pdc",
|
||||
"pfl",
|
||||
"pi",
|
||||
"pih",
|
||||
"pl",
|
||||
"pms",
|
||||
"pnb",
|
||||
@@ -8410,46 +8593,6 @@
|
||||
"zh-classical": "zh-classical"
|
||||
}
|
||||
},
|
||||
"yahoo": {
|
||||
"all_locale": "any",
|
||||
"custom": {},
|
||||
"data_type": "traits_v1",
|
||||
"languages": {
|
||||
"ar": "ar",
|
||||
"bg": "bg",
|
||||
"cs": "cs",
|
||||
"da": "da",
|
||||
"de": "de",
|
||||
"el": "el",
|
||||
"en": "en",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"fi": "fi",
|
||||
"fr": "fr",
|
||||
"he": "he",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"it": "it",
|
||||
"ja": "ja",
|
||||
"ko": "ko",
|
||||
"lt": "lt",
|
||||
"lv": "lv",
|
||||
"nl": "nl",
|
||||
"no": "no",
|
||||
"pl": "pl",
|
||||
"pt": "pt",
|
||||
"ro": "ro",
|
||||
"ru": "ru",
|
||||
"sk": "sk",
|
||||
"sl": "sl",
|
||||
"sv": "sv",
|
||||
"th": "th",
|
||||
"tr": "tr",
|
||||
"zh_Hans": "zh_chs",
|
||||
"zh_Hant": "zh_cht"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
"z-library": {
|
||||
"all_locale": "",
|
||||
"custom": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
],
|
||||
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
|
||||
"versions": [
|
||||
"136.0",
|
||||
"135.0"
|
||||
"138.0",
|
||||
"137.0"
|
||||
]
|
||||
}
|
||||
@@ -4244,6 +4244,11 @@
|
||||
"symbol": "cm³/mol",
|
||||
"to_si_factor": 1e-06
|
||||
},
|
||||
"Q240468": {
|
||||
"si_name": null,
|
||||
"symbol": "syr£",
|
||||
"to_si_factor": null
|
||||
},
|
||||
"Q242988": {
|
||||
"si_name": null,
|
||||
"symbol": "Lib$",
|
||||
@@ -5279,6 +5284,11 @@
|
||||
"symbol": "bhp EDR",
|
||||
"to_si_factor": 12.958174
|
||||
},
|
||||
"Q3984193": {
|
||||
"si_name": "Q25269",
|
||||
"symbol": "TeV",
|
||||
"to_si_factor": 1.602176634e-07
|
||||
},
|
||||
"Q39978339": {
|
||||
"si_name": "Q25377184",
|
||||
"symbol": "kg/cm²",
|
||||
@@ -6595,14 +6605,14 @@
|
||||
"to_si_factor": null
|
||||
},
|
||||
"Q68725821": {
|
||||
"si_name": null,
|
||||
"si_name": "Q11579",
|
||||
"symbol": "°Rø",
|
||||
"to_si_factor": null
|
||||
"to_si_factor": 1.90476190476
|
||||
},
|
||||
"Q68726230": {
|
||||
"si_name": null,
|
||||
"si_name": "Q11579",
|
||||
"symbol": "°De",
|
||||
"to_si_factor": null
|
||||
"to_si_factor": 0.66667
|
||||
},
|
||||
"Q68726625": {
|
||||
"si_name": null,
|
||||
@@ -6917,7 +6927,7 @@
|
||||
"Q72081071": {
|
||||
"si_name": "Q25269",
|
||||
"symbol": "MeV",
|
||||
"to_si_factor": 1.602176634e-13
|
||||
"to_si_factor": 1.60217656535e-13
|
||||
},
|
||||
"Q7235648": {
|
||||
"si_name": null,
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Implementations of the framework for the SearXNG engines.
|
||||
|
||||
- :py:obj:`searx.enginelib.EngineCache`
|
||||
- :py:obj:`searx.enginelib.Engine`
|
||||
- :py:obj:`searx.enginelib.traits`
|
||||
|
||||
There is a command line for developer purposes and for deeper analysis. Here is
|
||||
an example in which the command line is called in the development environment::
|
||||
|
||||
$ ./manage pyenv.cmd bash --norc --noprofile
|
||||
(py3) python -m searx.enginelib --help
|
||||
|
||||
.. hint::
|
||||
|
||||
The long term goal is to modularize all implementations of the engine
|
||||
@@ -9,16 +19,158 @@
|
||||
- move implementations of the :ref:`searx.engines loader` to a new module in
|
||||
the :py:obj:`searx.enginelib` namespace.
|
||||
|
||||
-----
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import List, Callable, TYPE_CHECKING
|
||||
|
||||
__all__ = ["EngineCache", "Engine", "ENGINES_CACHE"]
|
||||
|
||||
from typing import List, Callable, TYPE_CHECKING, Any
|
||||
import string
|
||||
import typer
|
||||
|
||||
from ..cache import ExpireCache, ExpireCacheCfg
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from searx.enginelib import traits
|
||||
|
||||
|
||||
ENGINES_CACHE = ExpireCache.build_cache(
|
||||
ExpireCacheCfg(
|
||||
name="ENGINES_CACHE",
|
||||
MAXHOLD_TIME=60 * 60 * 24 * 7, # 7 days
|
||||
MAINTENANCE_PERIOD=60 * 60, # 2h
|
||||
)
|
||||
)
|
||||
"""Global :py:obj:`searx.cache.ExpireCacheSQLite` instance where the cached
|
||||
values from all engines are stored. The `MAXHOLD_TIME` is 7 days and the
|
||||
`MAINTENANCE_PERIOD` is set to two hours."""
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
|
||||
@app.command()
|
||||
def state():
|
||||
"""Show state for the caches of the engines."""
|
||||
|
||||
title = "cache tables and key/values"
|
||||
print(title)
|
||||
print("=" * len(title))
|
||||
print(ENGINES_CACHE.state().report())
|
||||
print()
|
||||
title = f"properties of {ENGINES_CACHE.cfg.name}"
|
||||
print(title)
|
||||
print("=" * len(title))
|
||||
print(str(ENGINES_CACHE.properties)) # type: ignore
|
||||
|
||||
|
||||
@app.command()
|
||||
def maintenance(force: bool = True):
|
||||
"""Carry out maintenance on cache of the engines."""
|
||||
ENGINES_CACHE.maintenance(force=force)
|
||||
|
||||
|
||||
class EngineCache:
|
||||
"""Persistent (SQLite) key/value cache that deletes its values again after
|
||||
``expire`` seconds (default/max: :py:obj:`MAXHOLD_TIME
|
||||
<searx.cache.ExpireCacheCfg.MAXHOLD_TIME>`). This class is a wrapper around
|
||||
:py:obj:`ENGINES_CACHE` (:py:obj:`ExpireCacheSQLite
|
||||
<searx.cache.ExpireCacheSQLite>`).
|
||||
|
||||
In the :origin:`searx/engines/demo_offline.py` engine you can find an
|
||||
exemplary implementation of such a cache other exaples are implemeted
|
||||
in:
|
||||
|
||||
- :origin:`searx/engines/radio_browser.py`
|
||||
- :origin:`searx/engines/soundcloud.py`
|
||||
- :origin:`searx/engines/startpage.py`
|
||||
|
||||
.. code: python
|
||||
|
||||
from searx.enginelib import EngineCache
|
||||
CACHE: EngineCache
|
||||
|
||||
def init(engine_settings):
|
||||
global CACHE
|
||||
CACHE = EngineCache(engine_settings["name"])
|
||||
|
||||
def request(query, params):
|
||||
token = CACHE.get(key="token")
|
||||
if token is None:
|
||||
token = get_token()
|
||||
# cache token of this engine for 1h
|
||||
CACHE.set(key="token", value=token, expire=3600)
|
||||
...
|
||||
|
||||
For introspection of the DB, jump into developer environment and run command to
|
||||
show cache state::
|
||||
|
||||
$ ./manage pyenv.cmd bash --norc --noprofile
|
||||
(py3) python -m searx.enginelib cache state
|
||||
|
||||
cache tables and key/values
|
||||
===========================
|
||||
[demo_offline ] 2025-04-22 11:32:50 count --> (int) 4
|
||||
[startpage ] 2025-04-22 12:32:30 SC_CODE --> (str) fSOBnhEMlDfE20
|
||||
[duckduckgo ] 2025-04-22 12:32:31 4dff493e.... --> (str) 4-128634958369380006627592672385352473325
|
||||
[duckduckgo ] 2025-04-22 12:40:06 3e2583e2.... --> (str) 4-263126175288871260472289814259666848451
|
||||
[radio_browser ] 2025-04-23 11:33:08 servers --> (list) ['https://de2.api.radio-browser.info', ...]
|
||||
[soundcloud ] 2025-04-29 11:40:06 guest_client_id --> (str) EjkRJG0BLNEZquRiPZYdNtJdyGtTuHdp
|
||||
[wolframalpha ] 2025-04-22 12:40:06 code --> (str) 5aa79f86205ad26188e0e26e28fb7ae7
|
||||
number of tables: 6
|
||||
number of key/value pairs: 7
|
||||
|
||||
In the "cache tables and key/values" section, the table name (engine name) is at
|
||||
first position on the second there is the calculated expire date and on the
|
||||
third and fourth position the key/value is shown.
|
||||
|
||||
About duckduckgo: The *vqd coode* of ddg depends on the query term and therefore
|
||||
the key is a hash value of the query term (to not to store the raw query term).
|
||||
|
||||
In the "properties of ENGINES_CACHE" section all properties of the SQLiteAppl /
|
||||
ExpireCache and their last modification date are shown::
|
||||
|
||||
properties of ENGINES_CACHE
|
||||
===========================
|
||||
[last modified: 2025-04-22 11:32:27] DB_SCHEMA : 1
|
||||
[last modified: 2025-04-22 11:32:27] LAST_MAINTENANCE :
|
||||
[last modified: 2025-04-22 11:32:27] crypt_hash : ca612e3566fdfd7cf7efe...
|
||||
[last modified: 2025-04-22 11:32:30] CACHE-TABLE--demo_offline: demo_offline
|
||||
[last modified: 2025-04-22 11:32:30] CACHE-TABLE--startpage: startpage
|
||||
[last modified: 2025-04-22 11:32:31] CACHE-TABLE--duckduckgo: duckduckgo
|
||||
[last modified: 2025-04-22 11:33:08] CACHE-TABLE--radio_browser: radio_browser
|
||||
[last modified: 2025-04-22 11:40:06] CACHE-TABLE--soundcloud: soundcloud
|
||||
[last modified: 2025-04-22 11:40:06] CACHE-TABLE--wolframalpha: wolframalpha
|
||||
|
||||
These properties provide information about the state of the ExpireCache and
|
||||
control the behavior. For example, the maintenance intervals are controlled by
|
||||
the last modification date of the LAST_MAINTENANCE property and the hash value
|
||||
of the password can be used to detect whether the password has been changed (in
|
||||
this case the DB entries can no longer be decrypted and the entire cache must be
|
||||
discarded).
|
||||
"""
|
||||
|
||||
def __init__(self, engine_name: str, expire: int | None = None):
|
||||
self.expire = expire or ENGINES_CACHE.cfg.MAXHOLD_TIME
|
||||
_valid = "-_." + string.ascii_letters + string.digits
|
||||
self.table_name = "".join([c if c in _valid else "_" for c in engine_name])
|
||||
|
||||
def set(self, key: str, value: Any, expire: int | None = None) -> bool:
|
||||
return ENGINES_CACHE.set(
|
||||
key=key,
|
||||
value=value,
|
||||
expire=expire or self.expire,
|
||||
ctx=self.table_name,
|
||||
)
|
||||
|
||||
def get(self, key: str, default=None) -> Any:
|
||||
return ENGINES_CACHE.get(key, default=default, ctx=self.table_name)
|
||||
|
||||
def secret_hash(self, name: str | bytes) -> str:
|
||||
return ENGINES_CACHE.secret_hash(name=name)
|
||||
|
||||
|
||||
class Engine: # pylint: disable=too-few-public-methods
|
||||
"""Class of engine instances build from YAML settings.
|
||||
|
||||
|
||||
21
searx/enginelib/__main__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Implementation of a command line for development purposes. To start a
|
||||
command, switch to the environment and run library module as a script::
|
||||
|
||||
$ ./manage pyenv.cmd bash --norc --noprofile
|
||||
(py3) python -m searx.enginelib --help
|
||||
|
||||
The following commands can be used for maintenance and introspection
|
||||
(development) of the engine cache::
|
||||
|
||||
(py3) python -m searx.enginelib cache state
|
||||
(py3) python -m searx.enginelib cache maintenance
|
||||
|
||||
"""
|
||||
|
||||
import typer
|
||||
|
||||
from .. import enginelib
|
||||
|
||||
app = typer.Typer()
|
||||
app.add_typer(enginelib.app, name="cache", help="Commands related to the cache of the engines.")
|
||||
app()
|
||||
81
searx/engines/ansa.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Engine for Ansa, Italy's oldest news agency.
|
||||
|
||||
To use this engine add the following entry to your engines
|
||||
list in ``settings.yml``:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
- name: ansa
|
||||
engine: ansa
|
||||
shortcut: ans
|
||||
disabled: false
|
||||
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
from lxml import html
|
||||
from searx.result_types import EngineResults, MainResult
|
||||
from searx.utils import eval_xpath, eval_xpath_list, extract_text
|
||||
|
||||
engine_type = 'online'
|
||||
language_support = False
|
||||
categories = ['news']
|
||||
paging = True
|
||||
page_size = 12
|
||||
base_url = 'https://www.ansa.it'
|
||||
|
||||
time_range_support = True
|
||||
time_range_args = {
|
||||
'day': 1,
|
||||
'week': 7,
|
||||
'month': 31,
|
||||
'year': 365,
|
||||
}
|
||||
# https://www.ansa.it/ricerca/ansait/search.shtml?start=0&any=houthi&periodo=&sort=data%3Adesc
|
||||
search_api = 'https://www.ansa.it/ricerca/ansait/search.shtml?'
|
||||
|
||||
about = {
|
||||
'website': 'https://www.ansa.it',
|
||||
'wikidata_id': 'Q392934',
|
||||
'official_api_documentation': None,
|
||||
'use_official_api': False,
|
||||
'require_api_key': False,
|
||||
'results': 'HTML',
|
||||
'language': 'it',
|
||||
}
|
||||
|
||||
|
||||
def request(query, params):
|
||||
query_params = {
|
||||
'any': query,
|
||||
'start': (params['pageno'] - 1) * page_size,
|
||||
'sort': "data:desc",
|
||||
}
|
||||
|
||||
if params['time_range']:
|
||||
query_params['periodo'] = time_range_args.get(params['time_range'])
|
||||
|
||||
params['url'] = search_api + urlencode(query_params)
|
||||
return params
|
||||
|
||||
|
||||
def response(resp) -> EngineResults:
|
||||
res = EngineResults()
|
||||
doc = html.fromstring(resp.text)
|
||||
|
||||
for result in eval_xpath_list(doc, "//div[@class='article']"):
|
||||
|
||||
res_obj = MainResult(
|
||||
title=extract_text(eval_xpath(result, "./div[@class='content']/h2[@class='title']/a")),
|
||||
content=extract_text(eval_xpath(result, "./div[@class='content']/div[@class='text']")),
|
||||
url=base_url + extract_text(eval_xpath(result, "./div[@class='content']/h2[@class='title']/a/@href")),
|
||||
)
|
||||
|
||||
thumbnail = extract_text(eval_xpath(result, "./div[@class='image']/a/img/@src"))
|
||||
if thumbnail:
|
||||
res_obj.thumbnail = base_url + thumbnail
|
||||
|
||||
res.append(res_obj)
|
||||
|
||||
return res
|
||||
@@ -129,6 +129,7 @@ from lxml import html
|
||||
|
||||
from searx import locales
|
||||
from searx.utils import (
|
||||
extr,
|
||||
extract_text,
|
||||
eval_xpath,
|
||||
eval_xpath_list,
|
||||
@@ -253,33 +254,6 @@ def _extract_published_date(published_date_raw):
|
||||
return None
|
||||
|
||||
|
||||
def parse_data_string(resp):
|
||||
# kit.start(app, element, {
|
||||
# node_ids: [0, 19],
|
||||
# data: [{"type":"data","data" .... ["q","goggles_id"],"route":1,"url":1}}]
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
kit_start = resp.text.index("kit.start(app,")
|
||||
start = resp.text[kit_start:].index('data: [{"type":"data"')
|
||||
start = kit_start + start + len('data: ')
|
||||
|
||||
lev = 0
|
||||
end = start
|
||||
inner = False
|
||||
for c in resp.text[start:]:
|
||||
if inner and lev == 0:
|
||||
break
|
||||
end += 1
|
||||
if c == "[":
|
||||
lev += 1
|
||||
inner = True
|
||||
continue
|
||||
if c == "]":
|
||||
lev -= 1
|
||||
|
||||
json_data = js_variable_to_python(resp.text[start:end])
|
||||
return json_data
|
||||
|
||||
|
||||
def response(resp) -> EngineResults:
|
||||
|
||||
if brave_category in ('search', 'goggles'):
|
||||
@@ -288,7 +262,15 @@ def response(resp) -> EngineResults:
|
||||
if brave_category in ('news'):
|
||||
return _parse_news(resp)
|
||||
|
||||
json_data = parse_data_string(resp)
|
||||
# Example script source containing the data:
|
||||
#
|
||||
# kit.start(app, element, {
|
||||
# node_ids: [0, 19],
|
||||
# data: [{type:"data",data: .... ["q","goggles_id"],route:1,url:1}}]
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
js_object = "[{" + extr(resp.text, "data: [{", "}}],") + "}}]"
|
||||
json_data = js_variable_to_python(js_object)
|
||||
|
||||
# json_data is a list and at the second position (0,1) in this list we find the "response" data we need ..
|
||||
json_resp = json_data[1]['data']['body']['response']
|
||||
|
||||
|
||||
@@ -1,5 +1,60 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""ChinaSo: A search engine from ChinaSo."""
|
||||
"""ChinaSo_, a search engine for the chinese language area.
|
||||
|
||||
.. attention::
|
||||
|
||||
ChinaSo engine does not return real URL, the links from these search
|
||||
engines violate the privacy of the users!!
|
||||
|
||||
We try to find a solution for this problem, please follow `issue #4694`_.
|
||||
|
||||
As long as the problem has not been resolved, these engines are
|
||||
not active in a standard setup (``inactive: true``).
|
||||
|
||||
.. _ChinaSo: https://www.chinaso.com/
|
||||
.. _issue #4694: https://github.com/searxng/searxng/issues/4694
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
The engine has the following additional settings:
|
||||
|
||||
- :py:obj:`chinaso_category` (:py:obj:`ChinasoCategoryType`)
|
||||
- :py:obj:`chinaso_news_source` (:py:obj:`ChinasoNewsSourceType`)
|
||||
|
||||
In the example below, all three ChinaSO engines are using the :ref:`network
|
||||
<engine network>` from the ``chinaso news`` engine.
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
- name: chinaso news
|
||||
engine: chinaso
|
||||
shortcut: chinaso
|
||||
categories: [news]
|
||||
chinaso_category: news
|
||||
chinaso_news_source: all
|
||||
|
||||
- name: chinaso images
|
||||
engine: chinaso
|
||||
network: chinaso news
|
||||
shortcut: chinasoi
|
||||
categories: [images]
|
||||
chinaso_category: images
|
||||
|
||||
- name: chinaso videos
|
||||
engine: chinaso
|
||||
network: chinaso news
|
||||
shortcut: chinasov
|
||||
categories: [videos]
|
||||
chinaso_category: videos
|
||||
|
||||
|
||||
Implementations
|
||||
===============
|
||||
|
||||
"""
|
||||
|
||||
import typing
|
||||
|
||||
from urllib.parse import urlencode
|
||||
from datetime import datetime
|
||||
@@ -20,13 +75,31 @@ paging = True
|
||||
time_range_support = True
|
||||
results_per_page = 10
|
||||
categories = []
|
||||
chinaso_category = 'news'
|
||||
|
||||
ChinasoCategoryType = typing.Literal['news', 'videos', 'images']
|
||||
"""ChinaSo supports news, videos, images search.
|
||||
|
||||
- ``news``: search for news
|
||||
- ``videos``: search for videos
|
||||
- ``images``: search for images
|
||||
|
||||
In the category ``news`` you can additionally filter by option
|
||||
:py:obj:`chinaso_news_source`.
|
||||
"""
|
||||
chinaso_category = 'news'
|
||||
"""Configure ChinaSo category (:py:obj:`ChinasoCategoryType`)."""
|
||||
|
||||
ChinasoNewsSourceType = typing.Literal['CENTRAL', 'LOCAL', 'BUSINESS', 'EPAPER', 'all']
|
||||
"""Filtering ChinaSo-News results by source:
|
||||
|
||||
- ``CENTRAL``: central publication
|
||||
- ``LOCAL``: local publication
|
||||
- ``BUSINESS``: business publication
|
||||
- ``EPAPER``: E-Paper
|
||||
- ``all``: all sources
|
||||
"""
|
||||
chinaso_news_source: ChinasoNewsSourceType = 'all'
|
||||
"""Configure ChinaSo-News type (:py:obj:`ChinasoNewsSourceType`)."""
|
||||
|
||||
time_range_dict = {'day': '24h', 'week': '1w', 'month': '1m', 'year': '1y'}
|
||||
|
||||
@@ -35,7 +108,9 @@ base_url = "https://www.chinaso.com"
|
||||
|
||||
def init(_):
|
||||
if chinaso_category not in ('news', 'videos', 'images'):
|
||||
raise SearxEngineAPIException(f"Unsupported category: {chinaso_category}")
|
||||
raise ValueError(f"Unsupported category: {chinaso_category}")
|
||||
if chinaso_category == 'news' and chinaso_news_source not in typing.get_args(ChinasoNewsSourceType):
|
||||
raise ValueError(f"Unsupported news source: {chinaso_news_source}")
|
||||
|
||||
|
||||
def request(query, params):
|
||||
@@ -56,6 +131,11 @@ def request(query, params):
|
||||
'params': {'start_index': (params["pageno"] - 1) * results_per_page, 'rn': results_per_page},
|
||||
},
|
||||
}
|
||||
if chinaso_news_source != 'all':
|
||||
if chinaso_news_source == 'EPAPER':
|
||||
category_config['news']['params']["type"] = 'EPAPER'
|
||||
else:
|
||||
category_config['news']['params']["cate"] = chinaso_news_source
|
||||
|
||||
query_params.update(category_config[chinaso_category]['params'])
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ close to the implementation, its just a simple example. To get in use of this
|
||||
import json
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
from searx.enginelib import EngineCache
|
||||
|
||||
engine_type = 'offline'
|
||||
categories = ['general']
|
||||
@@ -32,14 +33,18 @@ about = {
|
||||
# if there is a need for globals, use a leading underline
|
||||
_my_offline_engine: str = ""
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Persistent (SQLite) key/value cache that deletes its values after ``expire``
|
||||
seconds."""
|
||||
|
||||
def init(engine_settings=None):
|
||||
|
||||
def init(engine_settings):
|
||||
"""Initialization of the (offline) engine. The origin of this demo engine is a
|
||||
simple json string which is loaded in this example while the engine is
|
||||
initialized.
|
||||
initialized."""
|
||||
global _my_offline_engine, CACHE # pylint: disable=global-statement
|
||||
|
||||
"""
|
||||
global _my_offline_engine # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_settings["name"]) # type:ignore
|
||||
|
||||
_my_offline_engine = (
|
||||
'[ {"value": "%s"}'
|
||||
@@ -57,8 +62,8 @@ def search(query, request_params) -> EngineResults:
|
||||
results.
|
||||
"""
|
||||
res = EngineResults()
|
||||
count = CACHE.get("count", 0)
|
||||
|
||||
count = 0
|
||||
for row in json.loads(_my_offline_engine):
|
||||
count += 1
|
||||
kvmap = {
|
||||
@@ -75,4 +80,7 @@ def search(query, request_params) -> EngineResults:
|
||||
)
|
||||
)
|
||||
res.add(res.types.LegacyResult(number_of_results=count))
|
||||
|
||||
# cache counter value for 20sec
|
||||
CACHE.set("count", count, expire=20)
|
||||
return res
|
||||
|
||||
@@ -6,16 +6,17 @@ DuckDuckGo WEB
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import re
|
||||
from urllib.parse import quote_plus
|
||||
import json
|
||||
import re
|
||||
import typing
|
||||
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import babel
|
||||
import lxml.html
|
||||
|
||||
from searx import (
|
||||
locales,
|
||||
redislib,
|
||||
external_bang,
|
||||
)
|
||||
from searx.utils import (
|
||||
@@ -25,12 +26,12 @@ from searx.utils import (
|
||||
extract_text,
|
||||
)
|
||||
from searx.network import get # see https://github.com/searxng/searxng/issues/762
|
||||
from searx import redisdb
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
from searx.enginelib import EngineCache
|
||||
from searx.exceptions import SearxEngineCaptchaException
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
logger: logging.Logger
|
||||
@@ -61,28 +62,18 @@ url = "https://html.duckduckgo.com/html"
|
||||
|
||||
time_range_dict = {'day': 'd', 'week': 'w', 'month': 'm', 'year': 'y'}
|
||||
form_data = {'v': 'l', 'api': 'd.js', 'o': 'json'}
|
||||
__CACHE = []
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Persistent (SQLite) key/value cache that deletes its values after ``expire``
|
||||
seconds."""
|
||||
|
||||
|
||||
def _cache_key(query: str, region: str):
|
||||
return 'SearXNG_ddg_web_vqd' + redislib.secret_hash(f"{query}//{region}")
|
||||
def init(_): # pylint: disable=unused-argument
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache("duckduckgo") # type:ignore
|
||||
|
||||
|
||||
def cache_vqd(query: str, region: str, value: str):
|
||||
"""Caches a ``vqd`` value from a query."""
|
||||
c = redisdb.client()
|
||||
if c:
|
||||
logger.debug("VALKEY cache vqd value: %s (%s)", value, region)
|
||||
c.set(_cache_key(query, region), value, ex=600)
|
||||
|
||||
else:
|
||||
logger.debug("MEM cache vqd value: %s (%s)", value, region)
|
||||
if len(__CACHE) > 100: # cache vqd from last 100 queries
|
||||
__CACHE.pop(0)
|
||||
__CACHE.append((_cache_key(query, region), value))
|
||||
|
||||
|
||||
def get_vqd(query: str, region: str, force_request: bool = False):
|
||||
def get_vqd(query: str, region: str, force_request: bool = False) -> str:
|
||||
"""Returns the ``vqd`` that fits to the *query*.
|
||||
|
||||
:param query: The query term
|
||||
@@ -114,31 +105,34 @@ def get_vqd(query: str, region: str, force_request: bool = False):
|
||||
seems the block list is a sliding window: to get my IP rid from the bot list
|
||||
I had to cool down my IP for 1h (send no requests from that IP to DDG).
|
||||
"""
|
||||
key = _cache_key(query, region)
|
||||
|
||||
c = redisdb.client()
|
||||
if c:
|
||||
value = c.get(key)
|
||||
if value or value == b'':
|
||||
value = value.decode('utf-8') # type: ignore
|
||||
logger.debug("re-use CACHED vqd value: %s", value)
|
||||
key = CACHE.secret_hash(f"{query}//{region}")
|
||||
value = CACHE.get(key=key)
|
||||
if value is not None and not force_request:
|
||||
logger.debug("vqd: re-use cached value: %s", value)
|
||||
return value
|
||||
|
||||
for k, value in __CACHE:
|
||||
if k == key:
|
||||
logger.debug("MEM re-use CACHED vqd value: %s", value)
|
||||
return value
|
||||
|
||||
if force_request:
|
||||
logger.debug("vqd: request value from from duckduckgo.com")
|
||||
resp = get(f'https://duckduckgo.com/?q={quote_plus(query)}')
|
||||
if resp.status_code == 200: # type: ignore
|
||||
value = extr(resp.text, 'vqd="', '"') # type: ignore
|
||||
if value:
|
||||
logger.debug("vqd value from DDG request: %s", value)
|
||||
cache_vqd(query, region, value)
|
||||
logger.debug("vqd value from duckduckgo.com request: '%s'", value)
|
||||
else:
|
||||
logger.error("vqd: can't parse value from ddg response (return empty string)")
|
||||
return ""
|
||||
else:
|
||||
logger.error("vqd: got HTTP %s from duckduckgo.com", resp.status_code)
|
||||
|
||||
if value:
|
||||
CACHE.set(key=key, value=value)
|
||||
else:
|
||||
logger.error("vqd value from duckduckgo.com ", resp.status_code)
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
def set_vqd(query: str, region: str, value: str):
|
||||
key = CACHE.secret_hash(f"{query}//{region}")
|
||||
CACHE.set(key=key, value=value, expire=3600)
|
||||
|
||||
|
||||
def get_ddg_lang(eng_traits: EngineTraits, sxng_locale, default='en_US'):
|
||||
@@ -373,8 +367,11 @@ def response(resp) -> EngineResults:
|
||||
# some locales (at least China) does not have a "next page" button
|
||||
form = form[0]
|
||||
form_vqd = eval_xpath(form, '//input[@name="vqd"]/@value')[0]
|
||||
|
||||
cache_vqd(resp.search_params['data']['q'], resp.search_params['data']['kl'], form_vqd)
|
||||
set_vqd(
|
||||
query=resp.search_params['data']['q'],
|
||||
region=resp.search_params['data']['kl'],
|
||||
value=str(form_vqd),
|
||||
)
|
||||
|
||||
# just select "web-result" and ignore results of class "result--ad result--ad--small"
|
||||
for div_result in eval_xpath(doc, '//div[@id="links"]/div[contains(@class, "web-result")]'):
|
||||
@@ -401,7 +398,7 @@ def response(resp) -> EngineResults:
|
||||
results.add(
|
||||
results.types.Answer(
|
||||
answer=zero_click,
|
||||
url=eval_xpath_getindex(doc, '//div[@id="zero_click_abstract"]/a/@href', 0),
|
||||
url=eval_xpath_getindex(doc, '//div[@id="zero_click_abstract"]/a/@href', 0), # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
116
searx/engines/huggingface.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""`Hugging Face`_ search engine for SearXNG.
|
||||
|
||||
.. _Hugging Face: https://huggingface.co
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
The engine has the following additional settings:
|
||||
|
||||
- :py:obj:`huggingface_endpoint`
|
||||
|
||||
Configurations for endpoints:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
- name: huggingface
|
||||
engine: huggingface
|
||||
shortcut: hf
|
||||
|
||||
- name: huggingface datasets
|
||||
huggingface_endpoint: datasets
|
||||
engine: huggingface
|
||||
shortcut: hfd
|
||||
|
||||
- name: huggingface spaces
|
||||
huggingface_endpoint: spaces
|
||||
engine: huggingface
|
||||
shortcut: hfs
|
||||
|
||||
Implementations
|
||||
===============
|
||||
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
from datetime import datetime
|
||||
|
||||
from searx.exceptions import SearxEngineAPIException
|
||||
from searx.utils import html_to_text
|
||||
from searx.result_types import EngineResults, MainResult
|
||||
|
||||
about = {
|
||||
"website": "https://huggingface.co/",
|
||||
"wikidata_id": "Q108943604",
|
||||
"official_api_documentation": "https://huggingface.co/docs/hub/en/api",
|
||||
"use_official_api": True,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
categories = ['it', 'repos']
|
||||
|
||||
base_url = "https://huggingface.co"
|
||||
|
||||
huggingface_endpoint = 'models'
|
||||
"""Hugging Face supports datasets, models, spaces as search endpoint.
|
||||
|
||||
- ``datasets``: search for datasets
|
||||
- ``models``: search for models
|
||||
- ``spaces``: search for spaces
|
||||
"""
|
||||
|
||||
|
||||
def init(_):
|
||||
if huggingface_endpoint not in ('datasets', 'models', 'spaces'):
|
||||
raise SearxEngineAPIException(f"Unsupported Hugging Face endpoint: {huggingface_endpoint}")
|
||||
|
||||
|
||||
def request(query, params):
|
||||
query_params = {
|
||||
"direction": -1,
|
||||
"search": query,
|
||||
}
|
||||
|
||||
params["url"] = f"{base_url}/api/{huggingface_endpoint}?{urlencode(query_params)}"
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def response(resp) -> EngineResults:
|
||||
results = EngineResults()
|
||||
|
||||
data = resp.json()
|
||||
|
||||
for entry in data:
|
||||
if huggingface_endpoint != 'models':
|
||||
url = f"{base_url}/{huggingface_endpoint}/{entry['id']}"
|
||||
else:
|
||||
url = f"{base_url}/{entry['id']}"
|
||||
|
||||
published_date = None
|
||||
try:
|
||||
published_date = datetime.strptime(entry["createdAt"], "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
contents = []
|
||||
if entry.get("likes"):
|
||||
contents.append(f"Likes: {entry['likes']}")
|
||||
if entry.get("downloads"):
|
||||
contents.append(f"Downloads: {entry['downloads']:,}")
|
||||
if entry.get("tags"):
|
||||
contents.append(f"Tags: {', '.join(entry['tags'])}")
|
||||
if entry.get("description"):
|
||||
contents.append(f"Description: {entry['description']}")
|
||||
|
||||
item = MainResult(
|
||||
title=entry["id"],
|
||||
content=html_to_text(" | ".join(contents)),
|
||||
url=url,
|
||||
publishedDate=published_date,
|
||||
)
|
||||
results.add(item)
|
||||
|
||||
return results
|
||||
68
searx/engines/microsoft_learn.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Engine for Microsoft Learn, Microsoft's technical knowledge base.
|
||||
|
||||
To use this engine add the following entry to your engines list
|
||||
in ``settings.yml``:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
- name: microsoft learn
|
||||
engine: microsoft_learn
|
||||
shortcut: msl
|
||||
disabled: false
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
engine_type = "online"
|
||||
language_support = True
|
||||
categories = ["it"]
|
||||
paging = True
|
||||
page_size = 10
|
||||
time_range_support = False
|
||||
|
||||
search_api = "https://learn.microsoft.com/api/search?"
|
||||
|
||||
about = {
|
||||
"website": "https://learn.microsoft.com",
|
||||
"wikidata_id": "Q123663245",
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
|
||||
def request(query, params):
|
||||
|
||||
if params['language'] == 'all':
|
||||
params['language'] = 'en-us'
|
||||
|
||||
query_params = [
|
||||
("search", query),
|
||||
("locale", params["language"]),
|
||||
("scoringprofile", "semantic-answers"),
|
||||
("facet", "category"),
|
||||
("facet", "products"),
|
||||
("facet", "tags"),
|
||||
("$top", "10"),
|
||||
("$skip", (params["pageno"] - 1) * page_size),
|
||||
("expandScope", "true"),
|
||||
("includeQuestion", "false"),
|
||||
("applyOperator", "false"),
|
||||
("partnerId", "LearnSite"),
|
||||
]
|
||||
|
||||
params["url"] = search_api + urlencode(query_params)
|
||||
return params
|
||||
|
||||
|
||||
def response(resp) -> EngineResults:
|
||||
res = EngineResults()
|
||||
json_data = resp.json()
|
||||
|
||||
for result in json_data["results"]:
|
||||
res.add(res.types.MainResult(url=result["url"], title=result["title"], content=result.get("description", "")))
|
||||
|
||||
return res
|
||||
@@ -1,46 +1,61 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
"""This is the implementation of the Mullvad-Leta meta-search engine.
|
||||
|
||||
This engine **REQUIRES** that searxng operate within a Mullvad VPN
|
||||
|
||||
If using docker, consider using gluetun for easily connecting to the Mullvad
|
||||
|
||||
- https://github.com/qdm12/gluetun
|
||||
|
||||
Otherwise, follow instructions provided by Mullvad for enabling the VPN on Linux
|
||||
|
||||
- https://mullvad.net/en/help/install-mullvad-app-linux
|
||||
"""Mullvad Leta is a search engine proxy. Currently Leta only offers text
|
||||
search results not image, news or any other types of search result. Leta acts
|
||||
as a proxy to Google and Brave search results. You can select which backend
|
||||
search engine you wish to use, see (:py:obj:`leta_engine`).
|
||||
|
||||
.. hint::
|
||||
|
||||
The :py:obj:`EngineTraits` is empty by default. Maintainers have to run
|
||||
``make data.traits`` (in the Mullvad VPN / :py:obj:`fetch_traits`) and rebase
|
||||
the modified JSON file ``searx/data/engine_traits.json`` on every single
|
||||
update of SearXNG!
|
||||
Leta caches each search for up to 30 days. For example, if you use search
|
||||
terms like ``news``, contrary to your intention you'll get very old results!
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
The engine has the following additional settings:
|
||||
|
||||
- :py:obj:`leta_engine` (:py:obj:`LetaEnginesType`)
|
||||
|
||||
You can configure one Leta engine for Google and one for Brave:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
- name: mullvadleta
|
||||
engine: mullvad_leta
|
||||
leta_engine: google
|
||||
shortcut: ml
|
||||
|
||||
- name: mullvadleta brave
|
||||
engine: mullvad_leta
|
||||
network: mullvadleta # use network from engine "mullvadleta" configured above
|
||||
leta_engine: brave
|
||||
shortcut: mlb
|
||||
|
||||
Implementations
|
||||
===============
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import typing
|
||||
from urllib.parse import urlencode
|
||||
import babel
|
||||
from httpx import Response
|
||||
from lxml import html
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
from searx.locales import region_tag, get_official_locales
|
||||
from searx.utils import eval_xpath, extract_text, eval_xpath_list
|
||||
from searx.exceptions import SearxEngineResponseException
|
||||
from searx.locales import get_official_locales, language_tag, region_tag
|
||||
from searx.utils import eval_xpath_list
|
||||
from searx.result_types import EngineResults, MainResult
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
traits: EngineTraits
|
||||
|
||||
use_cache: bool = True # non-cache use only has 100 searches per day!
|
||||
|
||||
leta_engine: str = 'google'
|
||||
|
||||
search_url = "https://leta.mullvad.net"
|
||||
|
||||
# about
|
||||
@@ -54,154 +69,205 @@ about = {
|
||||
}
|
||||
|
||||
# engine dependent config
|
||||
categories = ['general', 'web']
|
||||
categories = ["general", "web"]
|
||||
paging = True
|
||||
max_page = 50
|
||||
max_page = 10
|
||||
time_range_support = True
|
||||
time_range_dict = {
|
||||
"day": "d1",
|
||||
"week": "w1",
|
||||
"month": "m1",
|
||||
"year": "y1",
|
||||
"day": "d",
|
||||
"week": "w",
|
||||
"month": "m",
|
||||
"year": "y",
|
||||
}
|
||||
|
||||
available_leta_engines = [
|
||||
'google', # first will be default if provided engine is invalid
|
||||
'brave',
|
||||
]
|
||||
LetaEnginesType = typing.Literal["google", "brave"]
|
||||
"""Engine types supported by mullvadleta."""
|
||||
|
||||
leta_engine: LetaEnginesType = "google"
|
||||
"""Select Leta's engine type from :py:obj:`LetaEnginesType`."""
|
||||
|
||||
|
||||
def is_vpn_connected(dom: html.HtmlElement) -> bool:
|
||||
"""Returns true if the VPN is connected, False otherwise"""
|
||||
connected_text = extract_text(eval_xpath(dom, '//main/div/p[1]'))
|
||||
return connected_text != 'You are not connected to Mullvad VPN.'
|
||||
def init(_):
|
||||
l = typing.get_args(LetaEnginesType)
|
||||
if leta_engine not in l:
|
||||
raise ValueError(f"leta_engine '{leta_engine}' is invalid, use one of {', '.join(l)}")
|
||||
|
||||
|
||||
def assign_headers(headers: dict) -> dict:
|
||||
"""Assigns the headers to make a request to Mullvad Leta"""
|
||||
headers['Accept'] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
|
||||
headers['Content-Type'] = "application/x-www-form-urlencoded"
|
||||
headers['Host'] = "leta.mullvad.net"
|
||||
headers['Origin'] = "https://leta.mullvad.net"
|
||||
return headers
|
||||
class DataNodeQueryMetaDataIndices(typing.TypedDict):
|
||||
"""Indices into query metadata."""
|
||||
|
||||
success: int
|
||||
q: int # pylint: disable=invalid-name
|
||||
country: int
|
||||
language: int
|
||||
lastUpdated: int
|
||||
engine: int
|
||||
items: int
|
||||
infobox: int
|
||||
news: int
|
||||
timestamp: int
|
||||
altered: int
|
||||
page: int
|
||||
next: int # if -1, there no more results are available
|
||||
previous: int
|
||||
|
||||
|
||||
class DataNodeResultIndices(typing.TypedDict):
|
||||
"""Indices into query resultsdata."""
|
||||
|
||||
link: int
|
||||
snippet: int
|
||||
title: int
|
||||
favicon: int
|
||||
|
||||
|
||||
def request(query: str, params: dict):
|
||||
country = traits.get_region(params.get('searxng_locale', 'all'), traits.all_locale) # type: ignore
|
||||
|
||||
result_engine = leta_engine
|
||||
if leta_engine not in available_leta_engines:
|
||||
result_engine = available_leta_engines[0]
|
||||
logger.warning(
|
||||
'Configured engine "%s" not one of the available engines %s, defaulting to "%s"',
|
||||
leta_engine,
|
||||
available_leta_engines,
|
||||
result_engine,
|
||||
)
|
||||
|
||||
params['url'] = search_url
|
||||
params['method'] = 'POST'
|
||||
params['data'] = {
|
||||
params["method"] = "GET"
|
||||
args = {
|
||||
"q": query,
|
||||
"gl": country if country is str else '',
|
||||
'engine': result_engine,
|
||||
"engine": leta_engine,
|
||||
"x-sveltekit-invalidated": "001", # hardcoded from all requests seen
|
||||
}
|
||||
# pylint: disable=undefined-variable
|
||||
if use_cache:
|
||||
params['data']['oc'] = "on"
|
||||
# pylint: enable=undefined-variable
|
||||
|
||||
if params['time_range'] in time_range_dict:
|
||||
params['dateRestrict'] = time_range_dict[params['time_range']]
|
||||
else:
|
||||
params['dateRestrict'] = ''
|
||||
country = traits.get_region(params.get("searxng_locale"), traits.all_locale) # type: ignore
|
||||
if country:
|
||||
args["country"] = country
|
||||
|
||||
if params['pageno'] > 1:
|
||||
# Page 1 is n/a, Page 2 is 11, page 3 is 21, ...
|
||||
params['data']['start'] = ''.join([str(params['pageno'] - 1), "1"])
|
||||
language = traits.get_language(params.get("searxng_locale"), traits.all_locale) # type: ignore
|
||||
if language:
|
||||
args["language"] = language
|
||||
|
||||
if params['headers'] is None:
|
||||
params['headers'] = {}
|
||||
if params["time_range"] in time_range_dict:
|
||||
args["lastUpdated"] = time_range_dict[params["time_range"]]
|
||||
|
||||
if params["pageno"] > 1:
|
||||
args["page"] = params["pageno"]
|
||||
|
||||
params["url"] = f"{search_url}/search/__data.json?{urlencode(args)}"
|
||||
|
||||
assign_headers(params['headers'])
|
||||
return params
|
||||
|
||||
|
||||
def extract_result(dom_result: list[html.HtmlElement]):
|
||||
# Infoboxes sometimes appear in the beginning and will have a length of 0
|
||||
if len(dom_result) == 3:
|
||||
[a_elem, h3_elem, p_elem] = dom_result
|
||||
elif len(dom_result) == 4:
|
||||
[_, a_elem, h3_elem, p_elem] = dom_result
|
||||
else:
|
||||
return None
|
||||
def response(resp: Response) -> EngineResults:
|
||||
json_response = resp.json()
|
||||
|
||||
return {
|
||||
'url': extract_text(a_elem.text),
|
||||
'title': extract_text(h3_elem),
|
||||
'content': extract_text(p_elem),
|
||||
}
|
||||
nodes = json_response["nodes"]
|
||||
# 0: is None
|
||||
# 1: has "connected=True", not useful
|
||||
# 2: query results within "data"
|
||||
|
||||
data_nodes = nodes[2]["data"]
|
||||
# Instead of nested object structure, all objects are flattened into a
|
||||
# list. Rather, the first object in data_node provides indices into the
|
||||
# "data_nodes" to access each searchresult (which is an object of more
|
||||
# indices)
|
||||
#
|
||||
# Read the relative TypedDict definitions for details
|
||||
|
||||
query_meta_data: DataNodeQueryMetaDataIndices = data_nodes[0]
|
||||
|
||||
query_items_indices = query_meta_data["items"]
|
||||
|
||||
results = EngineResults()
|
||||
for idx in data_nodes[query_items_indices]:
|
||||
query_item_indices: DataNodeResultIndices = data_nodes[idx]
|
||||
results.add(
|
||||
MainResult(
|
||||
url=data_nodes[query_item_indices["link"]],
|
||||
title=data_nodes[query_item_indices["title"]],
|
||||
content=data_nodes[query_item_indices["snippet"]],
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def extract_results(search_results: html.HtmlElement):
|
||||
for search_result in search_results:
|
||||
dom_result = eval_xpath_list(search_result, 'div/div/*')
|
||||
result = extract_result(dom_result)
|
||||
if result is not None:
|
||||
yield result
|
||||
def fetch_traits(engine_traits: EngineTraits) -> None:
|
||||
"""Fetch languages and regions from Mullvad-Leta"""
|
||||
|
||||
def extract_table_data(table):
|
||||
for row in table.xpath(".//tr")[2:]:
|
||||
cells = row.xpath(".//td | .//th") # includes headers and data
|
||||
if len(cells) > 1: # ensure the column exists
|
||||
cell0 = cells[0].text_content().strip()
|
||||
cell1 = cells[1].text_content().strip()
|
||||
yield [cell0, cell1]
|
||||
|
||||
def response(resp: Response):
|
||||
"""Checks if connected to Mullvad VPN, then extracts the search results from
|
||||
the DOM resp: requests response object"""
|
||||
|
||||
dom = html.fromstring(resp.text)
|
||||
if not is_vpn_connected(dom):
|
||||
raise SearxEngineResponseException('Not connected to Mullvad VPN')
|
||||
search_results = eval_xpath(dom.body, '//main/div[2]/div')
|
||||
return list(extract_results(search_results))
|
||||
|
||||
|
||||
def fetch_traits(engine_traits: EngineTraits):
|
||||
"""Fetch languages and regions from Mullvad-Leta
|
||||
|
||||
.. warning::
|
||||
|
||||
Fetching the engine traits also requires a Mullvad VPN connection. If
|
||||
not connected, then an error message will print and no traits will be
|
||||
updated.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
# see https://github.com/searxng/searxng/issues/762
|
||||
from searx.network import post as http_post
|
||||
from searx.network import get as http_get
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
resp = http_post(search_url, headers=assign_headers({}))
|
||||
|
||||
resp = http_get(f"{search_url}/documentation")
|
||||
if not isinstance(resp, Response):
|
||||
print("ERROR: failed to get response from mullvad-leta. Are you connected to the VPN?")
|
||||
return
|
||||
if not resp.ok:
|
||||
print("ERROR: response from mullvad-leta is not OK. Are you connected to the VPN?")
|
||||
return
|
||||
|
||||
dom = html.fromstring(resp.text)
|
||||
if not is_vpn_connected(dom):
|
||||
print('ERROR: Not connected to Mullvad VPN')
|
||||
return
|
||||
# supported region codes
|
||||
options = eval_xpath_list(dom.body, '//main/div/form/div[2]/div/select[1]/option')
|
||||
if options is None or len(options) <= 0:
|
||||
print('ERROR: could not find any results. Are you connected to the VPN?')
|
||||
for x in options:
|
||||
eng_country = x.get("value")
|
||||
|
||||
sxng_locales = get_official_locales(eng_country, engine_traits.languages.keys(), regional=True)
|
||||
# There are 4 HTML tables on the documentation page for extracting information:
|
||||
# 0. Keyboard Shortcuts
|
||||
# 1. Query Parameters (shoutout to Mullvad for accessible docs for integration)
|
||||
# 2. Country Codes [Country, Code]
|
||||
# 3. Language Codes [Language, Code]
|
||||
tables = eval_xpath_list(dom.body, "//table")
|
||||
if tables is None or len(tables) <= 0:
|
||||
print("ERROR: could not find any tables. Was the page updated?")
|
||||
|
||||
if not sxng_locales:
|
||||
print(
|
||||
"ERROR: can't map from Mullvad-Leta country %s (%s) to a babel region."
|
||||
% (x.get('data-name'), eng_country)
|
||||
)
|
||||
language_table = tables[3]
|
||||
lang_map = {
|
||||
"zh-hant": "zh_Hans",
|
||||
"zh-hans": "zh_Hant",
|
||||
"jp": "ja",
|
||||
}
|
||||
|
||||
for language, code in extract_table_data(language_table):
|
||||
|
||||
locale_tag = lang_map.get(code, code).replace("-", "_") # type: ignore
|
||||
try:
|
||||
locale = babel.Locale.parse(locale_tag)
|
||||
except babel.UnknownLocaleError:
|
||||
print(f"ERROR: Mullvad-Leta language {language} ({code}) is unknown by babel")
|
||||
continue
|
||||
|
||||
for sxng_locale in sxng_locales:
|
||||
engine_traits.regions[region_tag(sxng_locale)] = eng_country
|
||||
sxng_tag = language_tag(locale)
|
||||
engine_traits.languages[sxng_tag] = code
|
||||
|
||||
country_table = tables[2]
|
||||
country_map = {
|
||||
"cn": "zh-CN",
|
||||
"hk": "zh-HK",
|
||||
"jp": "ja-JP",
|
||||
"my": "ms-MY",
|
||||
"tw": "zh-TW",
|
||||
"uk": "en-GB",
|
||||
"us": "en-US",
|
||||
}
|
||||
|
||||
for country, code in extract_table_data(country_table):
|
||||
|
||||
sxng_tag = country_map.get(code)
|
||||
if sxng_tag:
|
||||
engine_traits.regions[sxng_tag] = code
|
||||
continue
|
||||
|
||||
try:
|
||||
locale = babel.Locale.parse(f"{code.lower()}_{code.upper()}")
|
||||
except babel.UnknownLocaleError:
|
||||
locale = None
|
||||
|
||||
if locale:
|
||||
engine_traits.regions[region_tag(locale)] = code
|
||||
continue
|
||||
|
||||
official_locales = get_official_locales(code, engine_traits.languages.keys(), regional=True)
|
||||
if not official_locales:
|
||||
print(f"ERROR: Mullvad-Leta country '{code}' ({country}) could not be mapped as expected.")
|
||||
continue
|
||||
|
||||
for locale in official_locales:
|
||||
engine_traits.regions[region_tag(locale)] = code
|
||||
|
||||
@@ -40,7 +40,10 @@ about = {
|
||||
}
|
||||
|
||||
base_url = 'https://oqi2j6v4iz-dsn.algolia.net'
|
||||
pdia_config_url = 'https://pdimagearchive.org/_astro/config.BiNvrvzG.js'
|
||||
pdia_base_url = 'https://pdimagearchive.org'
|
||||
pdia_search_url = pdia_base_url + '/search/?q='
|
||||
pdia_config_start = "/_astro/InfiniteSearch."
|
||||
pdia_config_end = ".js"
|
||||
categories = ['images']
|
||||
page_size = 20
|
||||
paging = True
|
||||
@@ -62,11 +65,17 @@ def _get_algolia_api_key():
|
||||
if __CACHED_API_KEY:
|
||||
return __CACHED_API_KEY
|
||||
|
||||
resp = get(pdia_search_url)
|
||||
if resp.status_code != 200:
|
||||
raise LookupError("Failed to fetch config location (and as such the API key) for PDImageArchive")
|
||||
pdia_config_filepart = extr(resp.text, pdia_config_start, pdia_config_end)
|
||||
pdia_config_url = pdia_base_url + pdia_config_start + pdia_config_filepart + pdia_config_end
|
||||
|
||||
resp = get(pdia_config_url)
|
||||
if resp.status_code != 200:
|
||||
raise LookupError("Failed to obtain Algolia API key for PDImageArchive")
|
||||
|
||||
api_key = extr(resp.text, 'r="', '"', default=None)
|
||||
api_key = extr(resp.text, 'const r="', '"', default=None)
|
||||
|
||||
if api_key is None:
|
||||
raise LookupError("Couldn't obtain Algolia API key for PDImageArchive")
|
||||
|
||||
@@ -11,7 +11,7 @@ from searx.exceptions import SearxEngineAPIException, SearxEngineCaptchaExceptio
|
||||
|
||||
# Metadata
|
||||
about = {
|
||||
"website": "https://m.quark.cn/",
|
||||
"website": "https://quark.sm.cn/",
|
||||
"wikidata_id": "Q48816502",
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
@@ -53,7 +53,7 @@ def request(query, params):
|
||||
|
||||
category_config = {
|
||||
'general': {
|
||||
'endpoint': 'https://m.quark.cn/s',
|
||||
'endpoint': 'https://quark.sm.cn/s',
|
||||
'params': {
|
||||
"q": query,
|
||||
"layout": "html",
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
https://de1.api.radio-browser.info/#Advanced_station_search
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
import random
|
||||
import socket
|
||||
from urllib.parse import urlencode
|
||||
@@ -13,9 +15,15 @@ import babel
|
||||
from flask_babel import gettext
|
||||
|
||||
from searx.network import get
|
||||
from searx.enginelib import EngineCache
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
from searx.locales import language_tag
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
traits: EngineTraits
|
||||
|
||||
about = {
|
||||
@@ -52,11 +60,24 @@ none filters are applied. Valid filters are:
|
||||
|
||||
"""
|
||||
|
||||
servers = []
|
||||
CACHE: EngineCache
|
||||
"""Persistent (SQLite) key/value cache that deletes its values after ``expire``
|
||||
seconds."""
|
||||
|
||||
|
||||
def init(_):
|
||||
# see https://api.radio-browser.info
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache("radio_browser")
|
||||
server_list()
|
||||
|
||||
|
||||
def server_list() -> list[str]:
|
||||
|
||||
servers = CACHE.get("servers", [])
|
||||
if servers:
|
||||
return servers
|
||||
|
||||
# hint: can take up to 40sec!
|
||||
ips = socket.getaddrinfo("all.api.radio-browser.info", 80, 0, 0, socket.IPPROTO_TCP)
|
||||
for ip_tuple in ips:
|
||||
_ip: str = ip_tuple[4][0] # type: ignore
|
||||
@@ -65,8 +86,22 @@ def init(_):
|
||||
if srv not in servers:
|
||||
servers.append(srv)
|
||||
|
||||
# update server list once in 24h
|
||||
CACHE.set(key="servers", value=servers, expire=60 * 60 * 24)
|
||||
|
||||
return servers
|
||||
|
||||
|
||||
def request(query, params):
|
||||
|
||||
servers = server_list()
|
||||
if not servers:
|
||||
logger.error("Fetched server list is empty!")
|
||||
params["url"] = None
|
||||
return
|
||||
|
||||
server = random.choice(servers)
|
||||
|
||||
args = {
|
||||
'name': query,
|
||||
'order': 'votes',
|
||||
@@ -87,8 +122,7 @@ def request(query, params):
|
||||
if countrycode in traits.custom['countrycodes']: # type: ignore
|
||||
args['countrycode'] = countrycode
|
||||
|
||||
params['url'] = f"{random.choice(servers)}/json/stations/search?{urlencode(args)}"
|
||||
return params
|
||||
params['url'] = f"{server}/json/stations/search?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp):
|
||||
@@ -154,8 +188,9 @@ def fetch_traits(engine_traits: EngineTraits):
|
||||
|
||||
babel_reg_list = get_global("territory_languages").keys()
|
||||
|
||||
language_list = get(f'{servers[0]}/json/languages').json() # type: ignore
|
||||
country_list = get(f'{servers[0]}/json/countries').json() # type: ignore
|
||||
server = server_list()[0]
|
||||
language_list = get(f'{server}/json/languages').json() # type: ignore
|
||||
country_list = get(f'{server}/json/countries').json() # type: ignore
|
||||
|
||||
for lang in language_list:
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Semantic Scholar (Science)
|
||||
"""
|
||||
"""Semantic Scholar (Science)"""
|
||||
|
||||
from json import dumps, loads
|
||||
from json import dumps
|
||||
from datetime import datetime
|
||||
from lxml import html
|
||||
|
||||
from flask_babel import gettext
|
||||
from searx.network import get
|
||||
from searx.utils import eval_xpath_getindex, gen_useragent, html_to_text
|
||||
|
||||
|
||||
about = {
|
||||
"website": 'https://www.semanticscholar.org/',
|
||||
@@ -19,13 +22,31 @@ about = {
|
||||
categories = ['science', 'scientific publications']
|
||||
paging = True
|
||||
search_url = 'https://www.semanticscholar.org/api/1/search'
|
||||
paper_url = 'https://www.semanticscholar.org/paper'
|
||||
base_url = 'https://www.semanticscholar.org'
|
||||
|
||||
|
||||
def _get_ui_version():
|
||||
resp = get(base_url)
|
||||
if not resp.ok:
|
||||
raise RuntimeError("Can't determine Semantic Scholar UI version")
|
||||
|
||||
doc = html.fromstring(resp.text)
|
||||
ui_version = eval_xpath_getindex(doc, "//meta[@name='s2-ui-version']/@content", 0)
|
||||
if not ui_version:
|
||||
raise RuntimeError("Can't determine Semantic Scholar UI version")
|
||||
|
||||
return ui_version
|
||||
|
||||
|
||||
def request(query, params):
|
||||
params['url'] = search_url
|
||||
params['method'] = 'POST'
|
||||
params['headers']['content-type'] = 'application/json'
|
||||
params['headers'] = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-S2-UI-Version': _get_ui_version(),
|
||||
'X-S2-Client': "webapp-browser",
|
||||
'User-Agent': gen_useragent(),
|
||||
}
|
||||
params['data'] = dumps(
|
||||
{
|
||||
"queryString": query,
|
||||
@@ -43,7 +64,8 @@ def request(query, params):
|
||||
|
||||
|
||||
def response(resp):
|
||||
res = loads(resp.text)
|
||||
res = resp.json()
|
||||
|
||||
results = []
|
||||
for result in res['results']:
|
||||
url = result.get('primaryPaperLink', {}).get('url')
|
||||
@@ -54,7 +76,7 @@ def response(resp):
|
||||
if alternatePaperLinks:
|
||||
url = alternatePaperLinks[0].get('url')
|
||||
if not url:
|
||||
url = paper_url + '/%s' % result['id']
|
||||
url = base_url + '/paper/%s' % result['id']
|
||||
|
||||
# publishedDate
|
||||
if 'pubDate' in result:
|
||||
@@ -88,7 +110,7 @@ def response(resp):
|
||||
'template': 'paper.html',
|
||||
'url': url,
|
||||
'title': result['title']['text'],
|
||||
'content': result['paperAbstract']['text'],
|
||||
'content': html_to_text(result['paperAbstract']['text']),
|
||||
'journal': result.get('venue', {}).get('text') or result.get('journal', {}).get('name'),
|
||||
'doi': result.get('doiInfo', {}).get('doi'),
|
||||
'tags': result.get('fieldsOfStudy'),
|
||||
|
||||
151
searx/engines/senscritique.py
Normal file
@@ -0,0 +1,151 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""SensCritique (movies)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from json import dumps, loads
|
||||
from typing import Any, Optional
|
||||
from searx.result_types import EngineResults, MainResult
|
||||
|
||||
about = {
|
||||
"website": 'https://www.senscritique.com/',
|
||||
"wikidata_id": 'Q16676060',
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": 'JSON',
|
||||
'language': 'fr',
|
||||
}
|
||||
|
||||
categories = ['movies']
|
||||
paging = True
|
||||
page_size = 16
|
||||
graphql_url = 'https://apollo.senscritique.com/'
|
||||
|
||||
graphql_query = """query SearchProductExplorer($query: String, $offset: Int, $limit: Int,
|
||||
$sortBy: SearchProductExplorerSort) {
|
||||
searchProductExplorer(
|
||||
query: $query
|
||||
filters: []
|
||||
sortBy: $sortBy
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
) {
|
||||
items {
|
||||
category
|
||||
dateRelease
|
||||
duration
|
||||
id
|
||||
originalTitle
|
||||
rating
|
||||
title
|
||||
url
|
||||
yearOfProduction
|
||||
medias {
|
||||
picture
|
||||
}
|
||||
countries {
|
||||
name
|
||||
}
|
||||
genresInfos {
|
||||
label
|
||||
}
|
||||
directors {
|
||||
name
|
||||
}
|
||||
stats {
|
||||
ratingCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
|
||||
def request(query: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
offset = (params['pageno'] - 1) * page_size
|
||||
|
||||
data = {
|
||||
"operationName": "SearchProductExplorer",
|
||||
"variables": {"offset": offset, "limit": page_size, "query": query, "sortBy": "RELEVANCE"},
|
||||
"query": graphql_query,
|
||||
}
|
||||
|
||||
params['url'] = graphql_url
|
||||
params['method'] = 'POST'
|
||||
params['headers']['Content-Type'] = 'application/json'
|
||||
params['data'] = dumps(data)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def response(resp) -> EngineResults:
|
||||
res = EngineResults()
|
||||
response_data = loads(resp.text)
|
||||
|
||||
items = response_data.get('data', {}).get('searchProductExplorer', {}).get('items', [])
|
||||
if not items:
|
||||
return res
|
||||
|
||||
for item in items:
|
||||
result = parse_item(item)
|
||||
if not result:
|
||||
continue
|
||||
res.add(result=result)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def parse_item(item: dict[str, Any]) -> MainResult | None:
|
||||
"""Parse a single item from the SensCritique API response"""
|
||||
title = item.get('title', '')
|
||||
if not title:
|
||||
return None
|
||||
year = item.get('yearOfProduction')
|
||||
original_title = item.get('originalTitle')
|
||||
|
||||
thumbnail: str = ""
|
||||
if item.get('medias', {}) and item['medias'].get('picture'):
|
||||
thumbnail = item['medias']['picture']
|
||||
|
||||
content_parts = build_content_parts(item, title, original_title)
|
||||
url = f"https://www.senscritique.com{item['url']}"
|
||||
|
||||
return MainResult(
|
||||
url=url,
|
||||
title=title + (f' ({year})' if year else ''),
|
||||
content=' | '.join(content_parts),
|
||||
thumbnail=thumbnail,
|
||||
)
|
||||
|
||||
|
||||
def build_content_parts(item: dict[str, Any], title: str, original_title: Optional[str]) -> list[str]:
|
||||
"""Build the content parts for an item"""
|
||||
content_parts = []
|
||||
|
||||
if item.get('category'):
|
||||
content_parts.append(item['category'])
|
||||
|
||||
if original_title and original_title != title:
|
||||
content_parts.append(f"Original title: {original_title}")
|
||||
|
||||
if item.get('directors'):
|
||||
directors = [director['name'] for director in item['directors']]
|
||||
content_parts.append(f"Director(s): {', '.join(directors)}")
|
||||
|
||||
if item.get('countries'):
|
||||
countries = [country['name'] for country in item['countries']]
|
||||
content_parts.append(f"Country: {', '.join(countries)}")
|
||||
|
||||
if item.get('genresInfos'):
|
||||
genres = [genre['label'] for genre in item['genresInfos']]
|
||||
content_parts.append(f"Genre(s): {', '.join(genres)}")
|
||||
|
||||
if item.get('duration'):
|
||||
minutes = item['duration'] // 60
|
||||
if minutes > 0:
|
||||
content_parts.append(f"Duration: {minutes} min")
|
||||
|
||||
if item.get('rating') and item.get('stats', {}).get('ratingCount'):
|
||||
content_parts.append(f"Rating: {item['rating']}/10 ({item['stats']['ratingCount']} votes)")
|
||||
|
||||
return content_parts
|
||||
@@ -1,17 +1,26 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""SoundCloud is a German audio streaming service."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
import typing
|
||||
import datetime
|
||||
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
|
||||
from dateutil import parser
|
||||
from lxml import html
|
||||
|
||||
from searx.network import get as http_get
|
||||
from searx.enginelib import EngineCache
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
logger: logging.Logger
|
||||
|
||||
about = {
|
||||
"website": "ttps://soundcloud.com",
|
||||
"website": "https://soundcloud.com",
|
||||
"wikidata_id": "Q568769",
|
||||
"official_api_documentation": "https://developers.soundcloud.com/docs/api/guide",
|
||||
"use_official_api": False,
|
||||
@@ -28,7 +37,6 @@ HTML frontend of the common WEB site.
|
||||
"""
|
||||
|
||||
cid_re = re.compile(r'client_id:"([^"]*)"', re.I | re.U)
|
||||
guest_client_id = ""
|
||||
results_per_page = 10
|
||||
|
||||
soundcloud_facet = "model"
|
||||
@@ -48,6 +56,10 @@ app_locale_map = {
|
||||
"sv": "sv",
|
||||
}
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Persistent (SQLite) key/value cache that deletes its values after ``expire``
|
||||
seconds."""
|
||||
|
||||
|
||||
def request(query, params):
|
||||
|
||||
@@ -55,6 +67,12 @@ def request(query, params):
|
||||
# - user_id=451561-497874-703312-310156
|
||||
# - app_version=1740727428
|
||||
|
||||
guest_client_id = CACHE.get("guest_client_id")
|
||||
if guest_client_id is None:
|
||||
guest_client_id = get_client_id()
|
||||
if guest_client_id:
|
||||
CACHE.set(key="guest_client_id", value=guest_client_id)
|
||||
|
||||
args = {
|
||||
"q": query,
|
||||
"offset": (params['pageno'] - 1) * results_per_page,
|
||||
@@ -104,12 +122,12 @@ def response(resp):
|
||||
return results
|
||||
|
||||
|
||||
def init(engine_settings=None): # pylint: disable=unused-argument
|
||||
global guest_client_id # pylint: disable=global-statement
|
||||
guest_client_id = get_client_id()
|
||||
def init(engine_settings): # pylint: disable=unused-argument
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_settings["name"]) # type:ignore
|
||||
|
||||
|
||||
def get_client_id() -> str:
|
||||
def get_client_id() -> str | None:
|
||||
|
||||
client_id = ""
|
||||
url = "https://soundcloud.com"
|
||||
@@ -143,4 +161,4 @@ def get_client_id() -> str:
|
||||
logger.info("using client_id '%s' for soundclud queries", client_id)
|
||||
else:
|
||||
logger.warning("missing valid client_id for soundclud queries")
|
||||
return client_id
|
||||
return client_id or None
|
||||
|
||||
@@ -84,7 +84,6 @@ from typing import TYPE_CHECKING, Any
|
||||
from collections import OrderedDict
|
||||
import re
|
||||
from unicodedata import normalize, combining
|
||||
from time import time
|
||||
from datetime import datetime, timedelta
|
||||
from json import loads
|
||||
|
||||
@@ -97,6 +96,7 @@ from searx.network import get # see https://github.com/searxng/searxng/issues/7
|
||||
from searx.exceptions import SearxEngineCaptchaException
|
||||
from searx.locales import region_tag
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
from searx.enginelib import EngineCache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import logging
|
||||
@@ -159,10 +159,21 @@ search_form_xpath = '//form[@id="search"]'
|
||||
</form>
|
||||
"""
|
||||
|
||||
# timestamp of the last fetch of 'sc' code
|
||||
sc_code_ts = 0
|
||||
sc_code = ''
|
||||
sc_code_cache_sec = 30
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Persistent (SQLite) key/value cache that deletes its values after ``expire``
|
||||
seconds."""
|
||||
|
||||
|
||||
def init(_):
|
||||
global CACHE # pylint: disable=global-statement
|
||||
|
||||
# hint: all three startpage engines (WEB, Images & News) can/should use the
|
||||
# same sc_code ..
|
||||
CACHE = EngineCache("startpage") # type:ignore
|
||||
|
||||
|
||||
sc_code_cache_sec = 3600
|
||||
"""Time in seconds the sc-code is cached in memory :py:obj:`get_sc_code`."""
|
||||
|
||||
|
||||
@@ -176,14 +187,10 @@ def get_sc_code(searxng_locale, params):
|
||||
|
||||
Startpage's search form generates a new sc-code on each request. This
|
||||
function scrap a new sc-code from Startpage's home page every
|
||||
:py:obj:`sc_code_cache_sec` seconds.
|
||||
:py:obj:`sc_code_cache_sec` seconds."""
|
||||
|
||||
"""
|
||||
|
||||
global sc_code_ts, sc_code # pylint: disable=global-statement
|
||||
|
||||
if sc_code and (time() < (sc_code_ts + sc_code_cache_sec)):
|
||||
logger.debug("get_sc_code: reuse '%s'", sc_code)
|
||||
sc_code = CACHE.get("SC_CODE", "")
|
||||
if sc_code:
|
||||
return sc_code
|
||||
|
||||
headers = {**params['headers']}
|
||||
@@ -233,8 +240,9 @@ def get_sc_code(searxng_locale, params):
|
||||
message="get_sc_code: [PR-695] query new sc time-stamp failed! (%s)" % resp.url, # type: ignore
|
||||
) from exc
|
||||
|
||||
sc_code_ts = time()
|
||||
sc_code = str(sc_code)
|
||||
logger.debug("get_sc_code: new value is: %s", sc_code)
|
||||
CACHE.set(key="SC_CODE", value=sc_code, expire=sc_code_cache_sec)
|
||||
return sc_code
|
||||
|
||||
|
||||
|
||||
51
searx/engines/steam.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Steam (store) for SearXNG."""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from searx.utils import html_to_text
|
||||
from searx.result_types import EngineResults, MainResult
|
||||
|
||||
about = {
|
||||
"website": 'https://store.steampowered.com/',
|
||||
"wikidata_id": 'Q337535',
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": 'JSON',
|
||||
}
|
||||
|
||||
categories = []
|
||||
|
||||
base_url = "https://store.steampowered.com"
|
||||
|
||||
|
||||
def request(query, params):
|
||||
query_params = {"term": query, "cc": "us", "l": "en"}
|
||||
params['url'] = f'{base_url}/api/storesearch/?{urlencode(query_params)}'
|
||||
return params
|
||||
|
||||
|
||||
def response(resp) -> EngineResults:
|
||||
results = EngineResults()
|
||||
search_results = resp.json()
|
||||
|
||||
for item in search_results.get('items', []):
|
||||
app_id = item.get('id')
|
||||
|
||||
currency = item.get('price', {}).get('currency', 'USD')
|
||||
price = item.get('price', {}).get('final', 0) / 100
|
||||
|
||||
platforms = ', '.join([platform for platform, supported in item.get('platforms', {}).items() if supported])
|
||||
|
||||
content = [f'Price: {price:.2f} {currency}', f'Platforms: {platforms}']
|
||||
|
||||
results.add(
|
||||
MainResult(
|
||||
title=item.get('name'),
|
||||
content=html_to_text(' | '.join(content)),
|
||||
url=f'{base_url}/app/{app_id}',
|
||||
thumbnail=item.get('tiny_image', ''),
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from lxml import etree
|
||||
import lxml.etree
|
||||
|
||||
# about
|
||||
about = {
|
||||
@@ -72,7 +72,7 @@ def replace_pua_chars(text):
|
||||
def response(resp):
|
||||
results = []
|
||||
|
||||
search_results = etree.XML(resp.content)
|
||||
search_results = lxml.etree.XML(resp.content)
|
||||
|
||||
# return empty array if there are no results
|
||||
if search_results.xpath(failure_xpath):
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
Wolfram|Alpha (Science)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from json import loads
|
||||
from time import time
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from searx.network import get as http_get
|
||||
from searx.enginelib import EngineCache
|
||||
|
||||
# about
|
||||
about = {
|
||||
@@ -40,41 +42,39 @@ search_url = (
|
||||
|
||||
referer_url = url + 'input/?{query}'
|
||||
|
||||
token = {'value': '', 'last_updated': None}
|
||||
|
||||
# pods to display as image in infobox
|
||||
# this pods do return a plaintext, but they look better and are more useful as images
|
||||
image_pods = {'VisualRepresentation', 'Illustration', 'Symbol'}
|
||||
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Persistent (SQLite) key/value cache that deletes its values after ``expire``
|
||||
seconds."""
|
||||
|
||||
|
||||
def init(engine_settings):
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_settings["name"]) # type:ignore
|
||||
|
||||
|
||||
def obtain_token() -> str:
|
||||
token = CACHE.get(key="token")
|
||||
if token is None:
|
||||
resp = http_get('https://www.wolframalpha.com/input/api/v1/code?ts=9999999999999999999', timeout=2.0)
|
||||
token = resp.json()["code"]
|
||||
# seems, wolframalpha resets its token in every hour
|
||||
def obtain_token():
|
||||
update_time = time() - (time() % 3600)
|
||||
try:
|
||||
token_response = http_get('https://www.wolframalpha.com/input/api/v1/code?ts=9999999999999999999', timeout=2.0)
|
||||
token['value'] = loads(token_response.text)['code']
|
||||
token['last_updated'] = update_time
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
CACHE.set(key="code", value=token, expire=3600)
|
||||
return token
|
||||
|
||||
|
||||
def init(engine_settings=None): # pylint: disable=unused-argument
|
||||
obtain_token()
|
||||
|
||||
|
||||
# do search-request
|
||||
def request(query, params):
|
||||
# obtain token if last update was more than an hour
|
||||
if time() - (token['last_updated'] or 0) > 3600:
|
||||
obtain_token()
|
||||
params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value'])
|
||||
token = obtain_token()
|
||||
params['url'] = search_url.format(query=urlencode({'input': query}), token=token)
|
||||
params['headers']['Referer'] = referer_url.format(query=urlencode({'i': query}))
|
||||
|
||||
return params
|
||||
|
||||
|
||||
# get response from search-request
|
||||
def response(resp):
|
||||
results = []
|
||||
|
||||
|
||||
@@ -63,21 +63,52 @@ lang2domain = {
|
||||
}
|
||||
"""Map language to domain"""
|
||||
|
||||
locale_aliases = {
|
||||
'zh': 'zh_Hans',
|
||||
'zh-HK': 'zh_Hans',
|
||||
'zh-CN': 'zh_Hans', # dead since 2015 / routed to hk.search.yahoo.com
|
||||
'zh-TW': 'zh_Hant',
|
||||
yahoo_languages = {
|
||||
"all": "any",
|
||||
"ar": "ar",
|
||||
"bg": "bg",
|
||||
"cs": "cs",
|
||||
"da": "da",
|
||||
"de": "de",
|
||||
"el": "el",
|
||||
"en": "en",
|
||||
"es": "es",
|
||||
"et": "et",
|
||||
"fi": "fi",
|
||||
"fr": "fr",
|
||||
"he": "he",
|
||||
"hr": "hr",
|
||||
"hu": "hu",
|
||||
"it": "it",
|
||||
"ja": "ja",
|
||||
"ko": "ko",
|
||||
"lt": "lt",
|
||||
"lv": "lv",
|
||||
"nl": "nl",
|
||||
"no": "no",
|
||||
"pl": "pl",
|
||||
"pt": "pt",
|
||||
"ro": "ro",
|
||||
"ru": "ru",
|
||||
"sk": "sk",
|
||||
"sl": "sl",
|
||||
"sv": "sv",
|
||||
"th": "th",
|
||||
"tr": "tr",
|
||||
"zh": "zh_chs",
|
||||
"zh_Hans": "zh_chs",
|
||||
'zh-CN': "zh_chs",
|
||||
"zh_Hant": "zh_cht",
|
||||
"zh-HK": "zh_cht",
|
||||
'zh-TW': "zh_cht",
|
||||
}
|
||||
|
||||
|
||||
def request(query, params):
|
||||
"""build request"""
|
||||
|
||||
lang = locale_aliases.get(params['language'], None)
|
||||
if not lang:
|
||||
lang = params['language'].split('-')[0]
|
||||
lang = traits.get_language(lang, traits.all_locale)
|
||||
lang = params["language"].split("-")[0]
|
||||
lang = yahoo_languages.get(lang, "any")
|
||||
|
||||
offset = (params['pageno'] - 1) * 7 + 1
|
||||
age, btf = time_range_dict.get(params['time_range'], ('', ''))
|
||||
@@ -154,39 +185,3 @@ def response(resp):
|
||||
results.append({'suggestion': extract_text(suggestion)})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def fetch_traits(engine_traits: EngineTraits):
|
||||
"""Fetch languages from yahoo"""
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import babel
|
||||
from searx import network
|
||||
from searx.locales import language_tag
|
||||
|
||||
engine_traits.all_locale = 'any'
|
||||
|
||||
resp = network.get('https://search.yahoo.com/preferences/languages')
|
||||
if not resp.ok:
|
||||
print("ERROR: response from yahoo is not OK.")
|
||||
|
||||
dom = html.fromstring(resp.text)
|
||||
offset = len('lang_')
|
||||
|
||||
eng2sxng = {'zh_chs': 'zh_Hans', 'zh_cht': 'zh_Hant'}
|
||||
|
||||
for val in eval_xpath_list(dom, '//div[contains(@class, "lang-item")]/input/@value'):
|
||||
eng_tag = val[offset:]
|
||||
|
||||
try:
|
||||
sxng_tag = language_tag(babel.Locale.parse(eng2sxng.get(eng_tag, eng_tag)))
|
||||
except babel.UnknownLocaleError:
|
||||
print('ERROR: unknown language --> %s' % eng_tag)
|
||||
continue
|
||||
|
||||
conflict = engine_traits.languages.get(sxng_tag)
|
||||
if conflict:
|
||||
if conflict != eng_tag:
|
||||
print("CONFLICT: babel %s --> %s, %s" % (sxng_tag, conflict, eng_tag))
|
||||
continue
|
||||
engine_traits.languages[sxng_tag] = eng_tag
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Implementations for providing the favicons in SearXNG"""
|
||||
"""Implementations for providing the favicons in SearXNG.
|
||||
|
||||
There is a command line for developer purposes and for deeper analysis. Here is
|
||||
an example in which the command line is called in the development environment::
|
||||
|
||||
$ ./manage pyenv.cmd bash --norc --noprofile
|
||||
(py3) python -m searx.favicons --help
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -236,6 +236,12 @@ class FaviconCacheSQLite(sqlitedb.SQLiteAppl, FaviconCache):
|
||||
model in the SQLite DB is implemented using the abstract class
|
||||
:py:obj:`sqlitedb.SQLiteAppl`.
|
||||
|
||||
For introspection of the DB, jump into developer environment and run command
|
||||
to show cache state::
|
||||
|
||||
$ ./manage pyenv.cmd bash --norc --noprofile
|
||||
(py3) python -m searx.favicons cache state
|
||||
|
||||
The following configurations are required / supported:
|
||||
|
||||
- :py:obj:`FaviconCacheConfig.db_url`
|
||||
@@ -357,6 +363,10 @@ CREATE TABLE IF NOT EXISTS blob_map (
|
||||
if sha256 != FALLBACK_ICON:
|
||||
conn.execute(self.SQL_INSERT_BLOBS, (sha256, bytes_c, mime, data))
|
||||
conn.execute(self.SQL_INSERT_BLOB_MAP, (sha256, resolver, authority))
|
||||
# hint: the with context of the connection object closes the transaction
|
||||
# but not the DB connection. The connection has to be closed by the
|
||||
# caller of self.connect()!
|
||||
conn.close()
|
||||
|
||||
return True
|
||||
|
||||
@@ -376,7 +386,8 @@ CREATE TABLE IF NOT EXISTS blob_map (
|
||||
return
|
||||
self.properties.set("LAST_MAINTENANCE", "") # hint: this (also) sets the m_time of the property!
|
||||
|
||||
# do maintenance tasks
|
||||
# Do maintenance tasks. This can be take a little more time, to avoid
|
||||
# DB locks, etablish a new DB connecton.
|
||||
|
||||
with self.connect() as conn:
|
||||
|
||||
@@ -407,6 +418,12 @@ CREATE TABLE IF NOT EXISTS blob_map (
|
||||
conn.execute("DELETE FROM blob_map WHERE sha256 IN ('%s')" % "','".join(sha_list))
|
||||
logger.debug("dropped %s blobs with total size of %s bytes", len(sha_list), c)
|
||||
|
||||
# Vacuuming the WALs
|
||||
# https://www.theunterminatedstring.com/sqlite-vacuuming/
|
||||
|
||||
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
||||
conn.close()
|
||||
|
||||
def _query_val(self, sql, default=None):
|
||||
val = self.DB.execute(sql).fetchone()
|
||||
if val is not None:
|
||||
|
||||
@@ -112,6 +112,7 @@ from searx.botdetection import (
|
||||
http_accept_encoding,
|
||||
http_accept_language,
|
||||
http_user_agent,
|
||||
http_sec_fetch,
|
||||
ip_limit,
|
||||
ip_lists,
|
||||
get_network,
|
||||
@@ -179,16 +180,17 @@ def filter_request(request: SXNG_Request) -> werkzeug.Response | None:
|
||||
logger.error("BLOCK %s: matched BLOCKLIST - %s", network.compressed, msg)
|
||||
return flask.make_response(('IP is on BLOCKLIST - %s' % msg, 429))
|
||||
|
||||
# methods applied on /
|
||||
# methods applied on all requests
|
||||
|
||||
for func in [
|
||||
http_user_agent,
|
||||
]:
|
||||
val = func.filter_request(network, request, cfg)
|
||||
if val is not None:
|
||||
logger.debug(f"NOT OK ({func.__name__}): {network}: %s", dump_request(sxng_request))
|
||||
return val
|
||||
|
||||
# methods applied on /search
|
||||
# methods applied on /search requests
|
||||
|
||||
if request.path == '/search':
|
||||
|
||||
@@ -197,11 +199,14 @@ def filter_request(request: SXNG_Request) -> werkzeug.Response | None:
|
||||
http_accept_encoding,
|
||||
http_accept_language,
|
||||
http_user_agent,
|
||||
http_sec_fetch,
|
||||
ip_limit,
|
||||
]:
|
||||
val = func.filter_request(network, request, cfg)
|
||||
if val is not None:
|
||||
logger.debug(f"NOT OK ({func.__name__}): {network}: %s", dump_request(sxng_request))
|
||||
return val
|
||||
|
||||
logger.debug(f"OK {network}: %s", dump_request(sxng_request))
|
||||
return None
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import Dict
|
||||
|
||||
import httpx
|
||||
|
||||
from searx import logger, searx_debug
|
||||
from searx import logger, sxng_debug
|
||||
from searx.extended_types import SXNG_Response
|
||||
from .client import new_client, get_loop, AsyncHTTPTransportNoHttp
|
||||
from .raise_for_httperror import raise_for_httperror
|
||||
@@ -186,7 +186,7 @@ class Network:
|
||||
local_address = next(self._local_addresses_cycle)
|
||||
proxies = next(self._proxies_cycle) # is a tuple so it can be part of the key
|
||||
key = (verify, max_redirects, local_address, proxies)
|
||||
hook_log_response = self.log_response if searx_debug else None
|
||||
hook_log_response = self.log_response if sxng_debug else None
|
||||
if key not in self._clients or self._clients[key].is_closed:
|
||||
client = new_client(
|
||||
self.enable_http,
|
||||
|
||||
@@ -33,7 +33,7 @@ class SXNGPlugin(Plugin):
|
||||
"""Rewrite hostnames, remove results or prioritize them."""
|
||||
|
||||
id = "tor_check"
|
||||
keywords = ["tor-check"]
|
||||
keywords = ["tor-check", "tor_check", "torcheck", "tor", "tor check"]
|
||||
|
||||
def __init__(self, plg_cfg: "PluginCfg") -> None:
|
||||
super().__init__(plg_cfg)
|
||||
@@ -53,7 +53,7 @@ class SXNGPlugin(Plugin):
|
||||
if search.search_query.pageno > 1:
|
||||
return results
|
||||
|
||||
if search.search_query.query.lower() == "tor-check":
|
||||
if search.search_query.query.lower() in self.keywords:
|
||||
|
||||
# Request the list of tor exit nodes.
|
||||
try:
|
||||
|
||||
@@ -155,7 +155,7 @@ class ExternalBangParser(QueryPartParser):
|
||||
return raw_value.startswith('!!') and len(raw_value) > 2
|
||||
|
||||
def __call__(self, raw_value):
|
||||
value = raw_value[2:]
|
||||
value = raw_value[2:].lower()
|
||||
found, bang_ac_list = self._parse(value) if len(value) > 0 else (False, [])
|
||||
if self.enable_autocomplete:
|
||||
self._autocomplete(bang_ac_list)
|
||||
@@ -183,7 +183,7 @@ class BangParser(QueryPartParser):
|
||||
return raw_value[0] == '!' and (len(raw_value) < 2 or raw_value[1] != '!')
|
||||
|
||||
def __call__(self, raw_value):
|
||||
value = raw_value[1:].replace('-', ' ').replace('_', ' ')
|
||||
value = raw_value[1:].replace('-', ' ').replace('_', ' ').lower()
|
||||
found = self._parse(value) if len(value) > 0 else False
|
||||
if found and raw_value[0] == '!':
|
||||
self.raw_text_query.specific = True
|
||||
|
||||
@@ -55,8 +55,7 @@ def _normalize_url_fields(result: Result | LegacyResult):
|
||||
result.parsed_url = result.parsed_url._replace(
|
||||
# if the result has no scheme, use http as default
|
||||
scheme=result.parsed_url.scheme or "http",
|
||||
# normalize ``example.com/path/`` to ``example.com/path``
|
||||
path=result.parsed_url.path.rstrip("/"),
|
||||
path=result.parsed_url.path,
|
||||
)
|
||||
result.url = result.parsed_url.geturl()
|
||||
|
||||
@@ -73,7 +72,7 @@ def _normalize_url_fields(result: Result | LegacyResult):
|
||||
item["url"] = _url._replace(
|
||||
scheme=_url.scheme or "http",
|
||||
# netloc=_url.netloc.replace("www.", ""),
|
||||
path=_url.path.rstrip("/"),
|
||||
path=_url.path,
|
||||
).geturl()
|
||||
|
||||
infobox_id = getattr(result, "id", None)
|
||||
@@ -82,7 +81,7 @@ def _normalize_url_fields(result: Result | LegacyResult):
|
||||
result.id = _url._replace(
|
||||
scheme=_url.scheme or "http",
|
||||
# netloc=_url.netloc.replace("www.", ""),
|
||||
path=_url.path.rstrip("/"),
|
||||
path=_url.path,
|
||||
).geturl()
|
||||
|
||||
|
||||
@@ -259,9 +258,6 @@ class Result(msgspec.Struct, kw_only=True):
|
||||
``parse_url`` from field ``url``. The ``url`` field is initialized
|
||||
with the resulting value in ``parse_url``, if ``url`` and
|
||||
``parse_url`` are not equal.
|
||||
|
||||
- ``example.com/path/`` and ``example.com/path`` are equivalent and are
|
||||
normalized to ``example.com/path``.
|
||||
"""
|
||||
_normalize_url_fields(self)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union
|
||||
|
||||
import redis.exceptions
|
||||
|
||||
from searx import logger, settings, searx_debug
|
||||
from searx import logger, settings, sxng_debug
|
||||
from searx.redisdb import client as get_redis_client
|
||||
from searx.exceptions import SearxSettingsException
|
||||
from searx.search.processors import PROCESSORS
|
||||
@@ -139,7 +139,7 @@ def initialize():
|
||||
signal.signal(signal.SIGUSR1, _signal_handler)
|
||||
|
||||
# special case when debug is activate
|
||||
if searx_debug and settings['checker']['off_when_debug']:
|
||||
if sxng_debug and settings['checker']['off_when_debug']:
|
||||
logger.info('debug mode: checker is disabled')
|
||||
return
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ general:
|
||||
# Debug mode, only for development. Is overwritten by ${SEARXNG_DEBUG}
|
||||
debug: false
|
||||
# displayed name
|
||||
instance_name: "SearXNG"
|
||||
instance_name: "nekosearch"
|
||||
# For example: https://example.com/privacy
|
||||
privacypolicy_url: false
|
||||
# use true to use your own donation page written in searx/info/en/donate.md
|
||||
@@ -32,16 +32,16 @@ brand:
|
||||
|
||||
search:
|
||||
# Filter results. 0: None, 1: Moderate, 2: Strict
|
||||
safe_search: 0
|
||||
safe_search: 2
|
||||
# Existing autocomplete backends: "360search", "baidu", "brave", "dbpedia", "duckduckgo", "google", "yandex",
|
||||
# "mwmbl", "seznam", "sogou", "stract", "swisscows", "quark", "qwant", "wikipedia" -
|
||||
# "mwmbl", "seznam", "sogou", "stract", "swisscows", "qwant", "wikipedia" -
|
||||
# leave blank to turn it off by default.
|
||||
autocomplete: ""
|
||||
autocomplete: "duckduckgo"
|
||||
# minimun characters to type before autocompleter starts
|
||||
autocomplete_min: 4
|
||||
# backend for the favicon near URL in search results.
|
||||
# Available resolvers: "allesedv", "duckduckgo", "google", "yandex" - leave blank to turn it off by default.
|
||||
favicon_resolver: ""
|
||||
favicon_resolver: "duckduckgo"
|
||||
# Default search language - leave blank to detect from browser information or
|
||||
# use codes from 'languages.py'
|
||||
default_lang: "auto"
|
||||
@@ -84,7 +84,7 @@ server:
|
||||
bind_address: "127.0.0.1"
|
||||
# public URL of the instance, to ensure correct inbound links. Is overwritten
|
||||
# by ${SEARXNG_URL}.
|
||||
base_url: false # "http://example.com/location"
|
||||
base_url: https://nekosear.ch # "http://example.com/location"
|
||||
# rate limit the number of request on the instance, block some bots.
|
||||
# Is overwritten by ${SEARXNG_LIMITER}
|
||||
limiter: false
|
||||
@@ -95,7 +95,7 @@ server:
|
||||
# If your instance owns a /etc/searxng/settings.yml file, then set the following
|
||||
# values there.
|
||||
|
||||
secret_key: "ultrasecretkey" # Is overwritten by ${SEARXNG_SECRET}
|
||||
secret_key: "a9b0aafe71f3dbe131d66762f03e27ab02dfa409541184f3d34036926d7dd79e" # Is overwritten by ${SEARXNG_SECRET}
|
||||
# Proxy image results through SearXNG. Is overwritten by ${SEARXNG_IMAGE_PROXY}
|
||||
image_proxy: false
|
||||
# 1.0 and 1.1 are supported
|
||||
@@ -123,13 +123,13 @@ ui:
|
||||
templates_path: ""
|
||||
# query_in_title: When true, the result page's titles contains the query
|
||||
# it decreases the privacy, since the browser can records the page titles.
|
||||
query_in_title: false
|
||||
query_in_title: true
|
||||
# infinite_scroll: When true, automatically loads the next page when scrolling to bottom of the current page.
|
||||
infinite_scroll: false
|
||||
infinite_scroll: true
|
||||
# ui theme
|
||||
default_theme: simple
|
||||
# center the results ?
|
||||
center_alignment: false
|
||||
center_alignment: true
|
||||
# URL prefix of the internet archive, don't forget trailing slash (if needed).
|
||||
# cache_url: "https://webcache.googleusercontent.com/search?q=cache:"
|
||||
# Default interface locale - leave blank to detect from browser information or
|
||||
@@ -139,7 +139,7 @@ ui:
|
||||
# results_on_new_tab: false
|
||||
theme_args:
|
||||
# style of simple theme: auto, light, dark
|
||||
simple_style: auto
|
||||
simple_style: dark
|
||||
# Perform search immediately if a category selected.
|
||||
# Disable to select multiple categories at once and start the search manually.
|
||||
search_on_category_select: true
|
||||
@@ -207,8 +207,16 @@ outgoing:
|
||||
# are also supported: see
|
||||
# https://2.python-requests.org/en/latest/user/advanced/#socks
|
||||
#
|
||||
proxies:
|
||||
all://:
|
||||
- http://38.33.146.13:2000
|
||||
- http://38.33.148.3:2000
|
||||
- http://38.38.254.150:2000
|
||||
- http://38.33.146.96:2000
|
||||
#https: http://192.168.1.78:8888
|
||||
#proxies:
|
||||
#all://:
|
||||
#- http://192.168.1.78:8888
|
||||
# - http://proxy1:8080
|
||||
# - http://proxy2:8080
|
||||
#
|
||||
@@ -216,7 +224,7 @@ outgoing:
|
||||
#
|
||||
# Extra seconds to add in order to account for the time taken by the proxy
|
||||
#
|
||||
# extra_proxy_timeout: 10
|
||||
# extra_proxy_timeout: 20
|
||||
#
|
||||
# uncomment below section only if you have more than one network interface
|
||||
# which can be the source of outgoing search requests
|
||||
@@ -226,38 +234,29 @@ outgoing:
|
||||
# - 1.1.1.2
|
||||
# - fe80::/126
|
||||
|
||||
# Plugin configuration, for more details see
|
||||
# External plugin configuration, for more details see
|
||||
# https://docs.searxng.org/admin/settings/settings_plugins.html
|
||||
#
|
||||
plugins:
|
||||
|
||||
searx.plugins.calculator.SXNGPlugin:
|
||||
active: true
|
||||
|
||||
searx.plugins.hash_plugin.SXNGPlugin:
|
||||
active: true
|
||||
|
||||
searx.plugins.self_info.SXNGPlugin:
|
||||
active: true
|
||||
|
||||
searx.plugins.unit_converter.SXNGPlugin:
|
||||
active: true
|
||||
|
||||
searx.plugins.ahmia_filter.SXNGPlugin:
|
||||
active: true
|
||||
|
||||
searx.plugins.hostnames.SXNGPlugin:
|
||||
active: true
|
||||
|
||||
searx.plugins.oa_doi_rewrite.SXNGPlugin:
|
||||
active: false
|
||||
|
||||
searx.plugins.tor_check.SXNGPlugin:
|
||||
active: false
|
||||
|
||||
searx.plugins.tracker_url_remover.SXNGPlugin:
|
||||
active: false
|
||||
# plugins:
|
||||
# - mypackage.mymodule.MyPlugin
|
||||
# - mypackage.mymodule.MyOtherPlugin
|
||||
# - ...
|
||||
|
||||
# Comment or un-comment plugin to activate / deactivate by default.
|
||||
# https://docs.searxng.org/admin/settings/settings_plugins.html
|
||||
#
|
||||
# enabled_plugins:
|
||||
# # these plugins are enabled if nothing is configured ..
|
||||
# - 'Basic Calculator'
|
||||
# - 'Hash plugin'
|
||||
# - 'Self Information'
|
||||
# - 'Tracker URL remover'
|
||||
# - 'Unit converter plugin'
|
||||
# - 'Ahmia blacklist' # activation depends on outgoing.using_tor_proxy
|
||||
# # these plugins are disabled if nothing is configured ..
|
||||
# - 'Hostnames plugin' # see 'hostnames' configuration below
|
||||
# - 'Open Access DOI rewrite'
|
||||
# - 'Tor check plugin'
|
||||
|
||||
# Configuration of the "Hostnames plugin":
|
||||
#
|
||||
@@ -361,11 +360,6 @@ engines:
|
||||
shortcut: 9g
|
||||
disabled: true
|
||||
|
||||
- name: acfun
|
||||
engine: acfun
|
||||
shortcut: acf
|
||||
disabled: true
|
||||
|
||||
- name: adobe stock
|
||||
engine: adobe_stock
|
||||
shortcut: asi
|
||||
@@ -489,6 +483,7 @@ engines:
|
||||
engine: artic
|
||||
shortcut: arc
|
||||
timeout: 4.0
|
||||
disabled: true
|
||||
|
||||
- name: arxiv
|
||||
engine: arxiv
|
||||
@@ -510,27 +505,6 @@ engines:
|
||||
shortcut: bc
|
||||
categories: music
|
||||
|
||||
- name: baidu
|
||||
baidu_category: general
|
||||
categories: [general]
|
||||
engine: baidu
|
||||
shortcut: bd
|
||||
disabled: true
|
||||
|
||||
- name: baidu images
|
||||
baidu_category: images
|
||||
categories: [images]
|
||||
engine: baidu
|
||||
shortcut: bdi
|
||||
disabled: true
|
||||
|
||||
- name: baidu kaifa
|
||||
baidu_category: it
|
||||
categories: [it]
|
||||
engine: baidu
|
||||
shortcut: bdk
|
||||
disabled: true
|
||||
|
||||
- name: wikipedia
|
||||
engine: wikipedia
|
||||
shortcut: wp
|
||||
@@ -546,7 +520,6 @@ engines:
|
||||
- name: bing
|
||||
engine: bing
|
||||
shortcut: bi
|
||||
disabled: true
|
||||
|
||||
- name: bing images
|
||||
engine: bing_images
|
||||
@@ -560,11 +533,6 @@ engines:
|
||||
engine: bing_videos
|
||||
shortcut: biv
|
||||
|
||||
- name: bitchute
|
||||
engine: bitchute
|
||||
shortcut: bit
|
||||
disabled: true
|
||||
|
||||
- name: bitbucket
|
||||
engine: xpath
|
||||
paging: true
|
||||
@@ -613,24 +581,6 @@ engines:
|
||||
# to show premium or plus results too:
|
||||
# skip_premium: false
|
||||
|
||||
- name: chinaso news
|
||||
chinaso_category: news
|
||||
engine: chinaso
|
||||
shortcut: chinaso
|
||||
disabled: true
|
||||
|
||||
- name: chinaso images
|
||||
chinaso_category: images
|
||||
engine: chinaso
|
||||
shortcut: chinasoi
|
||||
disabled: true
|
||||
|
||||
- name: chinaso videos
|
||||
chinaso_category: videos
|
||||
engine: chinaso
|
||||
shortcut: chinasov
|
||||
disabled: true
|
||||
|
||||
- name: cloudflareai
|
||||
engine: cloudflareai
|
||||
shortcut: cfai
|
||||
@@ -744,6 +694,7 @@ engines:
|
||||
engine: deviantart
|
||||
shortcut: da
|
||||
timeout: 3.0
|
||||
disabled: true
|
||||
|
||||
- name: ddg definitions
|
||||
engine: duckduckgo_definitions
|
||||
@@ -803,13 +754,12 @@ engines:
|
||||
results: HTML
|
||||
|
||||
# - name: elasticsearch
|
||||
# shortcut: els
|
||||
# shortcut: es
|
||||
# engine: elasticsearch
|
||||
# base_url: http://localhost:9200
|
||||
# username: elastic
|
||||
# password: changeme
|
||||
# index: my-index
|
||||
# enable_http: true
|
||||
# # available options: match, simple_query_string, term, terms, custom
|
||||
# query_type: match
|
||||
# # if query_type is set to custom, provide your query here
|
||||
@@ -925,6 +875,7 @@ engines:
|
||||
# api_key: 'apikey' # required!
|
||||
# Or you can use the html non-stable engine, activated by default
|
||||
engine: flickr_noapi
|
||||
disabled: true
|
||||
|
||||
- name: free software directory
|
||||
engine: mediawiki
|
||||
@@ -1013,11 +964,11 @@ engines:
|
||||
engine: goodreads
|
||||
shortcut: good
|
||||
timeout: 4.0
|
||||
disabled: true
|
||||
|
||||
- name: google
|
||||
engine: google
|
||||
shortcut: go
|
||||
disabled: true
|
||||
# additional_tests:
|
||||
# android: *test_android
|
||||
|
||||
@@ -1126,11 +1077,6 @@ engines:
|
||||
require_api_key: false
|
||||
results: JSON
|
||||
|
||||
- name: il post
|
||||
engine: il_post
|
||||
shortcut: pst
|
||||
disabled: true
|
||||
|
||||
- name: imdb
|
||||
engine: imdb
|
||||
shortcut: imdb
|
||||
@@ -1164,11 +1110,6 @@ engines:
|
||||
shortcut: ip
|
||||
disabled: true
|
||||
|
||||
- name: iqiyi
|
||||
engine: iqiyi
|
||||
shortcut: iq
|
||||
disabled: true
|
||||
|
||||
- name: jisho
|
||||
engine: jisho
|
||||
shortcut: js
|
||||
@@ -1317,14 +1258,12 @@ engines:
|
||||
disabled: true
|
||||
number_of_results: 20
|
||||
|
||||
# https://docs.searxng.org/dev/engines/offline/search-indexer-engines.html#module-searx.engines.meilisearch
|
||||
# - name: meilisearch
|
||||
# engine: meilisearch
|
||||
# shortcut: mes
|
||||
# enable_http: true
|
||||
# base_url: http://localhost:7700
|
||||
# index: my-index
|
||||
# auth_key: Bearer XXXX
|
||||
|
||||
- name: mixcloud
|
||||
engine: mixcloud
|
||||
@@ -1361,11 +1300,6 @@ engines:
|
||||
shortcut: mwm
|
||||
disabled: true
|
||||
|
||||
- name: niconico
|
||||
engine: niconico
|
||||
shortcut: nico
|
||||
disabled: true
|
||||
|
||||
- name: npm
|
||||
engine: npm
|
||||
shortcut: npm
|
||||
@@ -1407,11 +1341,6 @@ engines:
|
||||
shortcut: od
|
||||
disabled: true
|
||||
|
||||
- name: ollama
|
||||
engine: ollama
|
||||
shortcut: ollama
|
||||
disabled: true
|
||||
|
||||
- name: openairedatasets
|
||||
engine: json_engine
|
||||
paging: true
|
||||
@@ -1673,24 +1602,11 @@ engines:
|
||||
shortcut: pypi
|
||||
engine: pypi
|
||||
|
||||
- name: quark
|
||||
quark_category: general
|
||||
categories: [general]
|
||||
engine: quark
|
||||
shortcut: qk
|
||||
disabled: true
|
||||
|
||||
- name: quark images
|
||||
quark_category: images
|
||||
categories: [images]
|
||||
engine: quark
|
||||
shortcut: qki
|
||||
disabled: true
|
||||
|
||||
- name: qwant
|
||||
qwant_categ: web
|
||||
engine: qwant
|
||||
shortcut: qw
|
||||
disabled: true
|
||||
categories: [general, web]
|
||||
additional_tests:
|
||||
rosebud: *test_rosebud
|
||||
@@ -1748,12 +1664,6 @@ engines:
|
||||
page_size: 25
|
||||
disabled: true
|
||||
|
||||
- name: reuters
|
||||
engine: reuters
|
||||
shortcut: reu
|
||||
# https://docs.searxng.org/dev/engines/online/reuters.html
|
||||
# sort_order = "relevance"
|
||||
|
||||
- name: right dao
|
||||
engine: xpath
|
||||
paging: true
|
||||
@@ -1808,12 +1718,6 @@ engines:
|
||||
about:
|
||||
website: https://searchmysite.net
|
||||
|
||||
- name: selfhst icons
|
||||
engine: selfhst
|
||||
shortcut: si
|
||||
inactive: true
|
||||
disabled: true
|
||||
|
||||
- name: sepiasearch
|
||||
engine: sepiasearch
|
||||
shortcut: sep
|
||||
@@ -1944,6 +1848,7 @@ engines:
|
||||
startpage_categ: images
|
||||
categories: [images, web]
|
||||
shortcut: spi
|
||||
disabled: true
|
||||
|
||||
- name: tokyotoshokan
|
||||
engine: tokyotoshokan
|
||||
@@ -1962,13 +1867,13 @@ engines:
|
||||
# For this demo of the sqlite engine download:
|
||||
# https://liste.mediathekview.de/filmliste-v2.db.bz2
|
||||
# and unpack into searx/data/filmliste-v2.db
|
||||
# Query to test: "!mediathekview concert"
|
||||
# Query to test: "!demo concert"
|
||||
#
|
||||
# - name: mediathekview
|
||||
# - name: demo
|
||||
# engine: sqlite
|
||||
# shortcut: mediathekview
|
||||
# categories: [general, videos]
|
||||
# result_type: MainResult
|
||||
# shortcut: demo
|
||||
# categories: general
|
||||
# result_template: default.html
|
||||
# database: searx/data/filmliste-v2.db
|
||||
# query_str: >-
|
||||
# SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title,
|
||||
@@ -2104,7 +2009,6 @@ engines:
|
||||
content_query: Snippet
|
||||
categories: [general, web]
|
||||
shortcut: wib
|
||||
disabled: true
|
||||
about:
|
||||
website: https://wiby.me/
|
||||
|
||||
@@ -2313,8 +2217,8 @@ engines:
|
||||
- name: mojeek
|
||||
shortcut: mjk
|
||||
engine: mojeek
|
||||
categories: [general, web]
|
||||
disabled: true
|
||||
categories: [general, web]
|
||||
|
||||
- name: mojeek images
|
||||
shortcut: mjkimg
|
||||
@@ -2348,7 +2252,6 @@ engines:
|
||||
content_xpath: //div[@class="total_dsc_wrap"]/a
|
||||
first_page_num: 1
|
||||
page_size: 10
|
||||
disabled: true
|
||||
about:
|
||||
website: https://www.naver.com/
|
||||
wikidata_id: Q485639
|
||||
|
||||
@@ -182,7 +182,7 @@ SCHEMA = {
|
||||
'base_url': SettingsValue((False, str), False, 'SEARXNG_BASE_URL'),
|
||||
'image_proxy': SettingsValue(bool, False, 'SEARXNG_IMAGE_PROXY'),
|
||||
'http_protocol_version': SettingsValue(('1.0', '1.1'), '1.0'),
|
||||
'method': SettingsValue(('POST', 'GET'), 'POST'),
|
||||
'method': SettingsValue(('POST', 'GET'), 'POST', 'SEARXNG_METHOD'),
|
||||
'default_http_headers': SettingsValue(dict, {}),
|
||||
},
|
||||
'redis': {
|
||||
|
||||
@@ -7,20 +7,83 @@
|
||||
:py:obj:`SQLiteProperties`:
|
||||
Class to manage properties stored in a database.
|
||||
|
||||
----
|
||||
Examplarical implementations based on :py:obj:`SQLiteAppl`:
|
||||
|
||||
:py:obj:`searx.cache.ExpireCacheSQLite` :
|
||||
Cache that manages key/value pairs in a SQLite DB, in which the key/value
|
||||
pairs are deleted after an "expire" time. This type of cache is used, for
|
||||
example, for the engines, see :py:obj:`searx.enginelib.EngineCache`.
|
||||
|
||||
:py:obj:`searx.favicons.cache.FaviconCacheSQLite` :
|
||||
Favicon cache that manages the favicon BLOBs in a SQLite DB.
|
||||
|
||||
----
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import abc
|
||||
import datetime
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
import threading
|
||||
import abc
|
||||
import uuid
|
||||
|
||||
from searx import logger
|
||||
|
||||
logger = logger.getChild('sqlitedb')
|
||||
logger = logger.getChild("sqlitedb")
|
||||
|
||||
THREAD_LOCAL = threading.local()
|
||||
|
||||
|
||||
class DBSession:
|
||||
"""A *thead-local* DB session"""
|
||||
|
||||
@classmethod
|
||||
def get_connect(cls, app: SQLiteAppl) -> sqlite3.Connection:
|
||||
"""Returns a thread local DB connection. The connection is only
|
||||
established once per thread.
|
||||
"""
|
||||
if getattr(THREAD_LOCAL, "DBSession_map", None) is None:
|
||||
THREAD_LOCAL.DBSession_map = {}
|
||||
|
||||
session = THREAD_LOCAL.DBSession_map.get(app.db_url)
|
||||
if session is None:
|
||||
session = cls(app)
|
||||
return session.conn
|
||||
|
||||
def __init__(self, app: SQLiteAppl):
|
||||
self.uuid = uuid.uuid4()
|
||||
self.app = app
|
||||
self._conn = None
|
||||
# self.__del__ will be called, when thread ends
|
||||
if getattr(THREAD_LOCAL, "DBSession_map", None) is None:
|
||||
THREAD_LOCAL.DBSession_map = {}
|
||||
THREAD_LOCAL.DBSession_map[self.app.db_url] = self
|
||||
|
||||
@property
|
||||
def conn(self) -> sqlite3.Connection:
|
||||
msg = f"[{threading.current_thread().ident}] DBSession: " f"{self.app.__class__.__name__}({self.app.db_url})"
|
||||
if self._conn is None:
|
||||
self._conn = self.app.connect()
|
||||
logger.debug("%s --> created new connection", msg)
|
||||
# else:
|
||||
# logger.debug("%s --> already connected", msg)
|
||||
|
||||
return self._conn
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
if self._conn is not None:
|
||||
# HINT: Don't use Python's logging facility in a destructor, it
|
||||
# will produce error reports when python aborts the process or
|
||||
# thread, because at this point objects that the logging module
|
||||
# needs, do not exist anymore.
|
||||
# msg = f"DBSession: close [{self.uuid}] {self.app.__class__.__name__}({self.app.db_url})"
|
||||
# logger.debug(msg)
|
||||
self._conn.close()
|
||||
except Exception: # pylint: disable=broad-exception-caught
|
||||
pass
|
||||
|
||||
|
||||
class SQLiteAppl(abc.ABC):
|
||||
@@ -51,13 +114,18 @@ class SQLiteAppl(abc.ABC):
|
||||
"""
|
||||
|
||||
SQLITE_JOURNAL_MODE = "WAL"
|
||||
"""``SQLiteAppl`` applications are optimzed for WAL_ mode, its not recommend
|
||||
to change the journal mode (see :py:obj:`SQLiteAppl.tear_down`).
|
||||
|
||||
.. _WAL: https://sqlite.org/wal.html
|
||||
"""
|
||||
SQLITE_CONNECT_ARGS = {
|
||||
# "timeout": 5.0,
|
||||
# "detect_types": 0,
|
||||
"check_same_thread": bool(SQLITE_THREADING_MODE != "serialized"),
|
||||
"cached_statements": 0, # https://github.com/python/cpython/issues/118172
|
||||
# "uri": False,
|
||||
"autocommit": False,
|
||||
"isolation_level": None,
|
||||
} # fmt:skip
|
||||
"""Connection arguments (:py:obj:`sqlite3.connect`)
|
||||
|
||||
@@ -66,10 +134,6 @@ class SQLiteAppl(abc.ABC):
|
||||
``serialized``. The check is more of a hindrance in this case because it
|
||||
would prevent a DB connector from being used in multiple threads.
|
||||
|
||||
``autocommit``:
|
||||
Is disabled by default. Note: autocommit option has been added in Python
|
||||
3.12.
|
||||
|
||||
``cached_statements``:
|
||||
Is set to ``0`` by default. Note: Python 3.12+ fetch result are not
|
||||
consistent in multi-threading application and causing an API misuse error.
|
||||
@@ -89,9 +153,17 @@ class SQLiteAppl(abc.ABC):
|
||||
|
||||
self.db_url = db_url
|
||||
self.properties = SQLiteProperties(db_url)
|
||||
self.thread_local = threading.local()
|
||||
self._init_done = False
|
||||
self._compatibility()
|
||||
# atexit.register(self.tear_down)
|
||||
|
||||
# def tear_down(self):
|
||||
# """:ref:`Vacuuming the WALs` upon normal interpreter termination
|
||||
# (:py:obj:`atexit.register`).
|
||||
|
||||
# .. _SQLite: Vacuuming the WALs: https://www.theunterminatedstring.com/sqlite-vacuuming/
|
||||
# """
|
||||
# self.DB.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
||||
|
||||
def _compatibility(self):
|
||||
|
||||
@@ -113,19 +185,31 @@ class SQLiteAppl(abc.ABC):
|
||||
"SQLite runtime library version %s is not supported (require >= 3.35)", sqlite3.sqlite_version
|
||||
)
|
||||
|
||||
def _connect(self) -> sqlite3.Connection:
|
||||
conn = sqlite3.Connection(self.db_url, **self.SQLITE_CONNECT_ARGS) # type: ignore
|
||||
conn.execute(f"PRAGMA journal_mode={self.SQLITE_JOURNAL_MODE}")
|
||||
self.register_functions(conn)
|
||||
return conn
|
||||
|
||||
def connect(self) -> sqlite3.Connection:
|
||||
"""Creates a new DB connection (:py:obj:`SQLITE_CONNECT_ARGS`). If not
|
||||
already done, the DB schema is set up
|
||||
already done, the DB schema is set up. The caller must take care of
|
||||
closing the resource. Alternatively, :py:obj:`SQLiteAppl.DB` can also
|
||||
be used (the resource behind `self.DB` is automatically closed when the
|
||||
process or thread is terminated).
|
||||
"""
|
||||
if sys.version_info < (3, 12):
|
||||
# Prior Python 3.12 there is no "autocommit" option
|
||||
self.SQLITE_CONNECT_ARGS.pop("autocommit", None)
|
||||
|
||||
self.init()
|
||||
logger.debug("%s: connect to DB: %s // %s", self.__class__.__name__, self.db_url, self.SQLITE_CONNECT_ARGS)
|
||||
conn = sqlite3.Connection(self.db_url, **self.SQLITE_CONNECT_ARGS) # type: ignore
|
||||
conn.execute(f"PRAGMA journal_mode={self.SQLITE_JOURNAL_MODE}")
|
||||
self.register_functions(conn)
|
||||
msg = (
|
||||
f"[{threading.current_thread().ident}] {self.__class__.__name__}({self.db_url})"
|
||||
f" {self.SQLITE_CONNECT_ARGS} // {self.SQLITE_JOURNAL_MODE}"
|
||||
)
|
||||
logger.debug(msg)
|
||||
|
||||
with self._connect() as conn:
|
||||
self.init(conn)
|
||||
return conn
|
||||
|
||||
def register_functions(self, conn):
|
||||
@@ -150,7 +234,7 @@ class SQLiteAppl(abc.ABC):
|
||||
.. _re.search: https://docs.python.org/3/library/re.html#re.search
|
||||
"""
|
||||
|
||||
conn.create_function('regexp', 2, lambda x, y: 1 if re.search(x, y) else 0, deterministic=True)
|
||||
conn.create_function("regexp", 2, lambda x, y: 1 if re.search(x, y) else 0, deterministic=True)
|
||||
|
||||
@property
|
||||
def DB(self) -> sqlite3.Connection:
|
||||
@@ -168,57 +252,66 @@ class SQLiteAppl(abc.ABC):
|
||||
https://docs.python.org/3/library/sqlite3.html#sqlite3-controlling-transactions
|
||||
"""
|
||||
|
||||
if getattr(self.thread_local, 'DB', None) is None:
|
||||
self.thread_local.DB = self.connect()
|
||||
conn = None
|
||||
|
||||
# Theoretically it is possible to reuse the DB cursor across threads as
|
||||
# of Python 3.12, in practice the threading of the cursor seems to me to
|
||||
# be so faulty that I prefer to establish one connection per thread
|
||||
|
||||
self.thread_local.DB.commit()
|
||||
return self.thread_local.DB
|
||||
|
||||
# In "serialized" mode, SQLite can be safely used by multiple threads
|
||||
# with no restriction.
|
||||
#
|
||||
# if self.SQLITE_THREADING_MODE != "serialized":
|
||||
# if getattr(self.thread_local, 'DB', None) is None:
|
||||
# self.thread_local.DB = self.connect()
|
||||
# return self.thread_local.DB
|
||||
if self.SQLITE_THREADING_MODE == "serialized":
|
||||
# Theoretically it is possible to reuse the DB cursor across threads
|
||||
# as of Python 3.12, in practice the threading of the cursor seems
|
||||
# to me a little faulty that I prefer to establish one connection
|
||||
# per thread.
|
||||
#
|
||||
# may we can activate this code one day ..
|
||||
# if self._DB is None:
|
||||
# self._DB = self.connect() # pylint: disable=attribute-defined-outside-init
|
||||
# return self._DB
|
||||
# self._DB = self.connect()
|
||||
# conn = self._DB
|
||||
conn = DBSession.get_connect(self)
|
||||
else:
|
||||
conn = DBSession.get_connect(self)
|
||||
|
||||
def init(self):
|
||||
# Since more than one instance of SQLiteAppl share the same DB
|
||||
# connection, we need to make sure that each SQLiteAppl instance has run
|
||||
# its init method at least once.
|
||||
self.init(conn)
|
||||
|
||||
return conn
|
||||
|
||||
def init(self, conn: sqlite3.Connection) -> bool:
|
||||
"""Initializes the DB schema and properties, is only executed once even
|
||||
if called several times."""
|
||||
if called several times.
|
||||
|
||||
If the initialization has not yet taken place, it is carried out and a
|
||||
`True` is returned to the caller at the end. If the initialization has
|
||||
already been carried out in the past, `False` is returned.
|
||||
"""
|
||||
|
||||
if self._init_done:
|
||||
return
|
||||
return False
|
||||
self._init_done = True
|
||||
|
||||
logger.debug("init DB: %s", self.db_url)
|
||||
self.properties.init()
|
||||
self.properties.init(conn)
|
||||
|
||||
ver = self.properties("DB_SCHEMA")
|
||||
if ver is None:
|
||||
with self.properties.DB:
|
||||
self.create_schema(self.properties.DB)
|
||||
with conn:
|
||||
self.create_schema(conn)
|
||||
else:
|
||||
ver = int(ver)
|
||||
if ver != self.DB_SCHEMA:
|
||||
raise sqlite3.DatabaseError("Expected DB schema v%s, DB schema is v%s" % (self.DB_SCHEMA, ver))
|
||||
logger.debug("DB_SCHEMA = %s", ver)
|
||||
|
||||
def create_schema(self, conn):
|
||||
return True
|
||||
|
||||
def create_schema(self, conn: sqlite3.Connection):
|
||||
|
||||
logger.debug("create schema ..")
|
||||
self.properties.set("DB_SCHEMA", self.DB_SCHEMA)
|
||||
self.properties.set("LAST_MAINTENANCE", "")
|
||||
with conn:
|
||||
for table_name, sql in self.DDL_CREATE_TABLES.items():
|
||||
conn.execute(sql)
|
||||
self.properties.set(f"Table {table_name} created", table_name)
|
||||
self.properties.set("DB_SCHEMA", self.DB_SCHEMA)
|
||||
self.properties.set("LAST_MAINTENANCE", "")
|
||||
|
||||
|
||||
class SQLiteProperties(SQLiteAppl):
|
||||
@@ -253,33 +346,32 @@ CREATE TABLE IF NOT EXISTS properties (
|
||||
" ON CONFLICT(name) DO UPDATE"
|
||||
" SET value=excluded.value, m_time=strftime('%s', 'now')"
|
||||
)
|
||||
SQL_DELETE = "DELETE FROM properties WHERE name = ?"
|
||||
SQL_TABLE_EXISTS = (
|
||||
"SELECT name FROM sqlite_master"
|
||||
" WHERE type='table' AND name='properties'"
|
||||
) # fmt:skip
|
||||
SQLITE_CONNECT_ARGS = dict(SQLiteAppl.SQLITE_CONNECT_ARGS)
|
||||
SQLITE_CONNECT_ARGS["autocommit"] = True # This option has no effect before Python 3.12
|
||||
|
||||
def __init__(self, db_url: str): # pylint: disable=super-init-not-called
|
||||
|
||||
self.db_url = db_url
|
||||
self.thread_local = threading.local()
|
||||
self._init_done = False
|
||||
self._compatibility()
|
||||
|
||||
def init(self):
|
||||
def init(self, conn: sqlite3.Connection) -> bool:
|
||||
"""Initializes DB schema of the properties in the DB."""
|
||||
|
||||
if self._init_done:
|
||||
return
|
||||
return False
|
||||
self._init_done = True
|
||||
logger.debug("init properties of DB: %s", self.db_url)
|
||||
with self.DB as conn:
|
||||
res = conn.execute(self.SQL_TABLE_EXISTS)
|
||||
if res.fetchone() is None: # DB schema needs to be be created
|
||||
self.create_schema(conn)
|
||||
return True
|
||||
|
||||
def __call__(self, name, default=None):
|
||||
def __call__(self, name: str, default=None):
|
||||
"""Returns the value of the property ``name`` or ``default`` if property
|
||||
not exists in DB."""
|
||||
|
||||
@@ -288,36 +380,47 @@ CREATE TABLE IF NOT EXISTS properties (
|
||||
return default
|
||||
return res[0]
|
||||
|
||||
def set(self, name, value):
|
||||
def set(self, name: str, value: str | int):
|
||||
"""Set ``value`` of property ``name`` in DB. If property already
|
||||
exists, update the ``m_time`` (and the value)."""
|
||||
|
||||
with self.DB:
|
||||
self.DB.execute(self.SQL_SET, (name, value))
|
||||
|
||||
if sys.version_info <= (3, 12):
|
||||
# Prior Python 3.12 there is no "autocommit" option / lets commit
|
||||
# explicitely.
|
||||
self.DB.commit()
|
||||
def delete(self, name: str) -> int:
|
||||
"""Delete of property ``name`` from DB."""
|
||||
with self.DB:
|
||||
cur = self.DB.execute(self.SQL_DELETE, (name,))
|
||||
return cur.rowcount
|
||||
|
||||
def row(self, name, default=None):
|
||||
def row(self, name: str, default=None):
|
||||
"""Returns the DB row of property ``name`` or ``default`` if property
|
||||
not exists in DB."""
|
||||
|
||||
cur = self.DB.cursor()
|
||||
cur.execute("SELECT * FROM properties WHERE name = ?", (name,))
|
||||
res = cur.fetchone()
|
||||
if res is None:
|
||||
res = self.DB.execute("SELECT * FROM properties WHERE name = ?", (name,))
|
||||
row = res.fetchone()
|
||||
if row is None:
|
||||
return default
|
||||
col_names = [column[0] for column in cur.description]
|
||||
return dict(zip(col_names, res))
|
||||
|
||||
def m_time(self, name, default: int = 0) -> int:
|
||||
col_names = [column[0] for column in row.description]
|
||||
return dict(zip(col_names, row))
|
||||
|
||||
def m_time(self, name: str, default: int = 0) -> int:
|
||||
"""Last modification time of this property."""
|
||||
res = self.DB.execute(self.SQL_M_TIME, (name,)).fetchone()
|
||||
if res is None:
|
||||
res = self.DB.execute(self.SQL_M_TIME, (name,))
|
||||
row = res.fetchone()
|
||||
if row is None:
|
||||
return default
|
||||
return int(res[0])
|
||||
return int(row[0])
|
||||
|
||||
def create_schema(self, conn):
|
||||
with conn:
|
||||
conn.execute(self.DDL_PROPERTIES)
|
||||
|
||||
def __str__(self) -> str:
|
||||
lines = []
|
||||
for row in self.DB.execute("SELECT name, value, m_time FROM properties"):
|
||||
name, value, m_time = row
|
||||
m_time = datetime.datetime.fromtimestamp(m_time).strftime("%Y-%m-%d %H:%M:%S")
|
||||
lines.append(f"[last modified: {m_time}] {name:20s}: {value}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 21 KiB |
@@ -1 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92mm" height="92mm" viewBox="0 0 92 92"><g transform="translate(-40.921 -17.417)"><circle cx="75.921" cy="53.903" r="30" style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><path d="M67.515 37.915a18 18 0 0 1 21.051 3.313 18 18 0 0 1 3.138 21.078" style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><rect width="18.846" height="39.963" x="3.706" y="122.09" ry="0" style="opacity:1;fill:#3050ff;fill-opacity:1;stroke:none;stroke-width:8;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" transform="rotate(-46.235)"/></g></svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="21.508125mm"
|
||||
height="21.508017mm"
|
||||
viewBox="0 0 21.508125 21.508015"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-54.245938,-114.24604)">
|
||||
<path
|
||||
style="fill:#222222;fill-opacity:1;stroke:#ffffff;stroke-width:1.50812;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 55,115 5.000001,4.99999 h 5 L 75,130 70.000002,135 H 60.000001 L 55,130 Z"
|
||||
id="path2" />
|
||||
<path
|
||||
style="fill:#222222;fill-opacity:1;stroke:#ffffff;stroke-width:1.50812;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 67,119.99999 h 3.000002 L 75,115 v 12.99999 z"
|
||||
id="path3" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 726 B After Width: | Height: | Size: 980 B |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 310 KiB |
242
searx/static/themes/simple/js/oneko-ie6.js
Normal file
@@ -0,0 +1,242 @@
|
||||
function neko() {
|
||||
var nekoEl = document.createElement("div");
|
||||
|
||||
var nekoPosX = 32;
|
||||
var nekoPosY = 32;
|
||||
|
||||
var mousePosX = 0;
|
||||
var mousePosY = 0;
|
||||
|
||||
var frameCount = 0;
|
||||
var idleTime = 0;
|
||||
var idleAnimation = null;
|
||||
var idleAnimationFrame = 0;
|
||||
var direction;
|
||||
|
||||
var IE = document.all ? true : false;
|
||||
|
||||
var nekoSpeed = 10;
|
||||
var spriteSets = {
|
||||
idle: [[-3, -3]],
|
||||
alert: [[-7, -3]],
|
||||
scratchSelf: [
|
||||
[-5, 0],
|
||||
[-6, 0],
|
||||
[-7, 0],
|
||||
],
|
||||
scratchWallN: [
|
||||
[0, 0],
|
||||
[0, -1],
|
||||
],
|
||||
scratchWallS: [
|
||||
[-7, -1],
|
||||
[-6, -2],
|
||||
],
|
||||
scratchWallE: [
|
||||
[-2, -2],
|
||||
[-2, -3],
|
||||
],
|
||||
scratchWallW: [
|
||||
[-4, 0],
|
||||
[-4, -1],
|
||||
],
|
||||
tired: [[-3, -2]],
|
||||
sleeping: [
|
||||
[-2, 0],
|
||||
[-2, -1],
|
||||
],
|
||||
N: [
|
||||
[-1, -2],
|
||||
[-1, -3],
|
||||
],
|
||||
NE: [
|
||||
[0, -2],
|
||||
[0, -3],
|
||||
],
|
||||
E: [
|
||||
[-3, 0],
|
||||
[-3, -1],
|
||||
],
|
||||
SE: [
|
||||
[-5, -1],
|
||||
[-5, -2],
|
||||
],
|
||||
S: [
|
||||
[-6, -3],
|
||||
[-7, -2],
|
||||
],
|
||||
SW: [
|
||||
[-5, -3],
|
||||
[-6, -1],
|
||||
],
|
||||
W: [
|
||||
[-4, -2],
|
||||
[-4, -3],
|
||||
],
|
||||
NW: [
|
||||
[-1, 0],
|
||||
[-1, -1],
|
||||
],
|
||||
};
|
||||
function init() {
|
||||
nekoEl.id = "oneko";
|
||||
nekoEl.ariaHidden = true;
|
||||
nekoEl.style.width = "32px";
|
||||
nekoEl.style.height = "32px";
|
||||
nekoEl.style.position = "absolute";
|
||||
nekoEl.style.pointerEvents = "none";
|
||||
nekoEl.style.backgroundImage = "url('oneko.gif')";
|
||||
nekoEl.style.imageRendering = "pixelated";
|
||||
nekoEl.style.left = nekoPosX - 16 + "px";
|
||||
nekoEl.style.top = nekoPosY - 16 + "px";
|
||||
nekoEl.style.zIndex = Number.MAX_VALUE;
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
function mousePos(event) {
|
||||
if (IE) {
|
||||
event = window.event;
|
||||
}
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY - 16;
|
||||
}
|
||||
document.onmousemove = mousePos;
|
||||
window.onekoInterval = setInterval(frame, 100);
|
||||
}
|
||||
|
||||
function setSprite(name, frame) {
|
||||
var length = spriteSets[name].length;
|
||||
if (IE) {
|
||||
length = 0;
|
||||
// Internet explorer is really fucking dumb
|
||||
while (length < spriteSets[name].length) {
|
||||
if (spriteSets[name][length] != null) {
|
||||
length = length + 1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
var sprite = spriteSets[name][frame % length];
|
||||
nekoEl.style.backgroundPosition =
|
||||
sprite["0"] * 32 + "px " + sprite["1"] * 32 + "px";
|
||||
}
|
||||
|
||||
function resetIdleAnimation() {
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
}
|
||||
|
||||
function idle() {
|
||||
idleTime = idleTime + 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) == 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
var avalibleIdleAnimations = ["sleeping", "scratchSelf"];
|
||||
if (nekoPosX < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallW");
|
||||
}
|
||||
if (nekoPosY < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallN");
|
||||
}
|
||||
if (nekoPosX > window.innerWidth - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallE");
|
||||
}
|
||||
if (nekoPosY > window.innerHeight - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallS");
|
||||
}
|
||||
idleAnimation =
|
||||
avalibleIdleAnimations[
|
||||
Math.floor(Math.random() * avalibleIdleAnimations.length)
|
||||
];
|
||||
}
|
||||
|
||||
switch (idleAnimation) {
|
||||
case "sleeping":
|
||||
if (idleAnimationFrame < 8) {
|
||||
setSprite("tired", 0);
|
||||
break;
|
||||
}
|
||||
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
|
||||
if (idleAnimationFrame > 192) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
case "scratchWallN":
|
||||
case "scratchWallS":
|
||||
case "scratchWallE":
|
||||
case "scratchWallW":
|
||||
case "scratchSelf":
|
||||
setSprite(idleAnimation, idleAnimationFrame);
|
||||
if (idleAnimationFrame > 9) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setSprite("idle", 0);
|
||||
return;
|
||||
}
|
||||
idleAnimationFrame = idleAnimationFrame + 1;
|
||||
}
|
||||
|
||||
function frame() {
|
||||
frameCount = frameCount + 1;
|
||||
var diffX = nekoPosX - mousePosX;
|
||||
var diffY = nekoPosY - mousePosY;
|
||||
var distance = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2));
|
||||
|
||||
if (distance < nekoSpeed || distance < 48) {
|
||||
idle();
|
||||
return;
|
||||
}
|
||||
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
|
||||
if (idleTime > 1) {
|
||||
setSprite("alert", 0);
|
||||
// count down after being alerted before moving
|
||||
idleTime = Math.min(idleTime, 7);
|
||||
idleTime = idleTime - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
direction = "";
|
||||
if (diffY / distance > 0.5) {
|
||||
direction = "N";
|
||||
} else if (diffY / distance < -0.5) {
|
||||
direction = "S";
|
||||
}
|
||||
if (diffX / distance > 0.5) {
|
||||
direction = direction + "W";
|
||||
} else if (diffX / distance < -0.5) {
|
||||
direction = direction + "E";
|
||||
}
|
||||
setSprite(direction, frameCount);
|
||||
|
||||
if (distance > nekoSpeed) {
|
||||
nekoPosX = nekoPosX - (diffX / distance) * nekoSpeed;
|
||||
nekoPosY = nekoPosY - (diffY / distance) * nekoSpeed;
|
||||
} else {
|
||||
nekoPosX = mousePosX;
|
||||
nekoPosY = mousePosY;
|
||||
}
|
||||
|
||||
nekoPosX = Math.min(
|
||||
Math.max(16, nekoPosX),
|
||||
document.getElementsByTagName("body")[0].clientWidth - 16
|
||||
);
|
||||
nekoPosY = Math.min(
|
||||
Math.max(16, nekoPosY),
|
||||
document.getElementsByTagName("body")[0].clientHeight - 16
|
||||
);
|
||||
|
||||
nekoEl.style.left = nekoPosX - 16 + "px";
|
||||
nekoEl.style.top = nekoPosY - 16 + "px";
|
||||
}
|
||||
init();
|
||||
}
|
||||
neko();
|
||||
291
searx/static/themes/simple/js/oneko-webring.js
Normal file
@@ -0,0 +1,291 @@
|
||||
// oneko.js: https://github.com/adryd325/oneko.js (webring variant)
|
||||
|
||||
(function oneko() {
|
||||
const isReducedMotion =
|
||||
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
|
||||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
|
||||
|
||||
if (isReducedMotion) return;
|
||||
|
||||
const nekoEl = document.createElement("div");
|
||||
|
||||
let nekoPosX = 32;
|
||||
let nekoPosY = 32;
|
||||
|
||||
let mousePosX = 0;
|
||||
let mousePosY = 0;
|
||||
|
||||
// please use data-neko="true" on your A elements that link to another site with oneko-webring.js instead of this
|
||||
// this is deprecated and will eventually be removed
|
||||
const nekoSites = [
|
||||
"localhost",
|
||||
];
|
||||
|
||||
try {
|
||||
const searchParams = location.search
|
||||
.replace("?", "")
|
||||
.split("&")
|
||||
.map((keyvaluepair) => keyvaluepair.split("="));
|
||||
// This is so much repeated code, I don't like it
|
||||
tmp = searchParams.find((a) => a[0] == "catx");
|
||||
if (tmp && tmp[1]) nekoPosX = parseInt(tmp[1]);
|
||||
tmp = searchParams.find((a) => a[0] == "caty");
|
||||
if (tmp && tmp[1]) nekoPosY = parseInt(tmp[1]);
|
||||
tmp = searchParams.find((a) => a[0] == "catdx");
|
||||
if (tmp && tmp[1]) mousePosX = parseInt(tmp[1]);
|
||||
tmp = searchParams.find((a) => a[0] == "catdy");
|
||||
if (tmp && tmp[1]) mousePosY = parseInt(tmp[1]);
|
||||
} catch (e) {
|
||||
console.error("oneko.js: failed to parse query params.");
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
function onClick(event) {
|
||||
const target = event.target.closest("A");
|
||||
if (target === null || !target.getAttribute("href")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newLocation;
|
||||
try {
|
||||
newLocation = new URL(target.href);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(nekoSites.includes(newLocation.host) && newLocation.pathname == "/") ||
|
||||
target.dataset.neko
|
||||
) {
|
||||
newLocation.searchParams.append("catx", Math.floor(nekoPosX));
|
||||
newLocation.searchParams.append("caty", Math.floor(nekoPosY));
|
||||
newLocation.searchParams.append("catdx", Math.floor(mousePosX));
|
||||
newLocation.searchParams.append("catdy", Math.floor(mousePosY));
|
||||
event.preventDefault();
|
||||
window.location.href = newLocation.toString();
|
||||
}
|
||||
}
|
||||
document.addEventListener("click", onClick);
|
||||
|
||||
let frameCount = 0;
|
||||
let idleTime = 0;
|
||||
let idleAnimation = null;
|
||||
let idleAnimationFrame = 0;
|
||||
|
||||
const nekoSpeed = 10;
|
||||
const spriteSets = {
|
||||
idle: [[-3, -3]],
|
||||
alert: [[-7, -3]],
|
||||
scratchSelf: [
|
||||
[-5, 0],
|
||||
[-6, 0],
|
||||
[-7, 0],
|
||||
],
|
||||
scratchWallN: [
|
||||
[0, 0],
|
||||
[0, -1],
|
||||
],
|
||||
scratchWallS: [
|
||||
[-7, -1],
|
||||
[-6, -2],
|
||||
],
|
||||
scratchWallE: [
|
||||
[-2, -2],
|
||||
[-2, -3],
|
||||
],
|
||||
scratchWallW: [
|
||||
[-4, 0],
|
||||
[-4, -1],
|
||||
],
|
||||
tired: [[-3, -2]],
|
||||
sleeping: [
|
||||
[-2, 0],
|
||||
[-2, -1],
|
||||
],
|
||||
N: [
|
||||
[-1, -2],
|
||||
[-1, -3],
|
||||
],
|
||||
NE: [
|
||||
[0, -2],
|
||||
[0, -3],
|
||||
],
|
||||
E: [
|
||||
[-3, 0],
|
||||
[-3, -1],
|
||||
],
|
||||
SE: [
|
||||
[-5, -1],
|
||||
[-5, -2],
|
||||
],
|
||||
S: [
|
||||
[-6, -3],
|
||||
[-7, -2],
|
||||
],
|
||||
SW: [
|
||||
[-5, -3],
|
||||
[-6, -1],
|
||||
],
|
||||
W: [
|
||||
[-4, -2],
|
||||
[-4, -3],
|
||||
],
|
||||
NW: [
|
||||
[-1, 0],
|
||||
[-1, -1],
|
||||
],
|
||||
};
|
||||
|
||||
function init() {
|
||||
nekoEl.id = "oneko";
|
||||
nekoEl.ariaHidden = true;
|
||||
nekoEl.style.width = "32px";
|
||||
nekoEl.style.height = "32px";
|
||||
nekoEl.style.position = "fixed";
|
||||
nekoEl.style.pointerEvents = "none";
|
||||
nekoEl.style.imageRendering = "pixelated";
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = Number.MAX_VALUE;
|
||||
|
||||
let nekoFile = "./oneko.gif"
|
||||
const curScript = document.currentScript
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
let lastFrameTimestamp;
|
||||
|
||||
function onAnimationFrame(timestamp) {
|
||||
// Stops execution if the neko element is removed from DOM
|
||||
if (!nekoEl.isConnected) {
|
||||
return;
|
||||
}
|
||||
if (!lastFrameTimestamp) {
|
||||
lastFrameTimestamp = timestamp;
|
||||
}
|
||||
if (timestamp - lastFrameTimestamp > 100) {
|
||||
lastFrameTimestamp = timestamp
|
||||
frame()
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
function setSprite(name, frame) {
|
||||
const sprite = spriteSets[name][frame % spriteSets[name].length];
|
||||
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
|
||||
}
|
||||
|
||||
function resetIdleAnimation() {
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
}
|
||||
|
||||
function idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) == 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
|
||||
if (nekoPosX < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallW");
|
||||
}
|
||||
if (nekoPosY < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallN");
|
||||
}
|
||||
if (nekoPosX > window.innerWidth - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallE");
|
||||
}
|
||||
if (nekoPosY > window.innerHeight - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallS");
|
||||
}
|
||||
idleAnimation =
|
||||
avalibleIdleAnimations[
|
||||
Math.floor(Math.random() * avalibleIdleAnimations.length)
|
||||
];
|
||||
}
|
||||
|
||||
switch (idleAnimation) {
|
||||
case "sleeping":
|
||||
if (idleAnimationFrame < 8) {
|
||||
setSprite("tired", 0);
|
||||
break;
|
||||
}
|
||||
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
|
||||
if (idleAnimationFrame > 192) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
case "scratchWallN":
|
||||
case "scratchWallS":
|
||||
case "scratchWallE":
|
||||
case "scratchWallW":
|
||||
case "scratchSelf":
|
||||
setSprite(idleAnimation, idleAnimationFrame);
|
||||
if (idleAnimationFrame > 9) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setSprite("idle", 0);
|
||||
return;
|
||||
}
|
||||
idleAnimationFrame += 1;
|
||||
}
|
||||
|
||||
function frame() {
|
||||
frameCount += 1;
|
||||
const diffX = nekoPosX - mousePosX;
|
||||
const diffY = nekoPosY - mousePosY;
|
||||
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
|
||||
|
||||
if (distance < nekoSpeed || distance < 48) {
|
||||
idle();
|
||||
return;
|
||||
}
|
||||
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
|
||||
if (idleTime > 1) {
|
||||
setSprite("alert", 0);
|
||||
// count down after being alerted before moving
|
||||
idleTime = Math.min(idleTime, 7);
|
||||
idleTime -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let direction;
|
||||
direction = diffY / distance > 0.5 ? "N" : "";
|
||||
direction += diffY / distance < -0.5 ? "S" : "";
|
||||
direction += diffX / distance > 0.5 ? "W" : "";
|
||||
direction += diffX / distance < -0.5 ? "E" : "";
|
||||
setSprite(direction, frameCount);
|
||||
|
||||
nekoPosX -= (diffX / distance) * nekoSpeed;
|
||||
nekoPosY -= (diffY / distance) * nekoSpeed;
|
||||
|
||||
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
|
||||
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
|
||||
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
BIN
searx/static/themes/simple/js/oneko.gif
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
240
searx/static/themes/simple/js/oneko.js
Normal file
@@ -0,0 +1,240 @@
|
||||
// oneko.js: https://github.com/adryd325/oneko.js
|
||||
|
||||
(function oneko() {
|
||||
const isReducedMotion =
|
||||
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
|
||||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
|
||||
|
||||
if (isReducedMotion) return;
|
||||
|
||||
const nekoEl = document.createElement("div");
|
||||
|
||||
let nekoPosX = 32;
|
||||
let nekoPosY = 32;
|
||||
|
||||
let mousePosX = 0;
|
||||
let mousePosY = 0;
|
||||
|
||||
let frameCount = 0;
|
||||
let idleTime = 0;
|
||||
let idleAnimation = null;
|
||||
let idleAnimationFrame = 0;
|
||||
|
||||
const nekoSpeed = 10;
|
||||
const spriteSets = {
|
||||
idle: [[-3, -3]],
|
||||
alert: [[-7, -3]],
|
||||
scratchSelf: [
|
||||
[-5, 0],
|
||||
[-6, 0],
|
||||
[-7, 0],
|
||||
],
|
||||
scratchWallN: [
|
||||
[0, 0],
|
||||
[0, -1],
|
||||
],
|
||||
scratchWallS: [
|
||||
[-7, -1],
|
||||
[-6, -2],
|
||||
],
|
||||
scratchWallE: [
|
||||
[-2, -2],
|
||||
[-2, -3],
|
||||
],
|
||||
scratchWallW: [
|
||||
[-4, 0],
|
||||
[-4, -1],
|
||||
],
|
||||
tired: [[-3, -2]],
|
||||
sleeping: [
|
||||
[-2, 0],
|
||||
[-2, -1],
|
||||
],
|
||||
N: [
|
||||
[-1, -2],
|
||||
[-1, -3],
|
||||
],
|
||||
NE: [
|
||||
[0, -2],
|
||||
[0, -3],
|
||||
],
|
||||
E: [
|
||||
[-3, 0],
|
||||
[-3, -1],
|
||||
],
|
||||
SE: [
|
||||
[-5, -1],
|
||||
[-5, -2],
|
||||
],
|
||||
S: [
|
||||
[-6, -3],
|
||||
[-7, -2],
|
||||
],
|
||||
SW: [
|
||||
[-5, -3],
|
||||
[-6, -1],
|
||||
],
|
||||
W: [
|
||||
[-4, -2],
|
||||
[-4, -3],
|
||||
],
|
||||
NW: [
|
||||
[-1, 0],
|
||||
[-1, -1],
|
||||
],
|
||||
};
|
||||
|
||||
function init() {
|
||||
nekoEl.id = "oneko";
|
||||
nekoEl.ariaHidden = true;
|
||||
nekoEl.style.width = "32px";
|
||||
nekoEl.style.height = "32px";
|
||||
nekoEl.style.position = "fixed";
|
||||
nekoEl.style.pointerEvents = "none";
|
||||
nekoEl.style.imageRendering = "pixelated";
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = 2147483647;
|
||||
|
||||
let nekoFile = "/static/themes/simple/js/oneko.gif"
|
||||
const curScript = document.currentScript
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
let lastFrameTimestamp;
|
||||
|
||||
function onAnimationFrame(timestamp) {
|
||||
// Stops execution if the neko element is removed from DOM
|
||||
if (!nekoEl.isConnected) {
|
||||
return;
|
||||
}
|
||||
if (!lastFrameTimestamp) {
|
||||
lastFrameTimestamp = timestamp;
|
||||
}
|
||||
if (timestamp - lastFrameTimestamp > 100) {
|
||||
lastFrameTimestamp = timestamp
|
||||
frame()
|
||||
}
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
function setSprite(name, frame) {
|
||||
const sprite = spriteSets[name][frame % spriteSets[name].length];
|
||||
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
|
||||
}
|
||||
|
||||
function resetIdleAnimation() {
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
}
|
||||
|
||||
function idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) == 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
|
||||
if (nekoPosX < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallW");
|
||||
}
|
||||
if (nekoPosY < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallN");
|
||||
}
|
||||
if (nekoPosX > window.innerWidth - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallE");
|
||||
}
|
||||
if (nekoPosY > window.innerHeight - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallS");
|
||||
}
|
||||
idleAnimation =
|
||||
avalibleIdleAnimations[
|
||||
Math.floor(Math.random() * avalibleIdleAnimations.length)
|
||||
];
|
||||
}
|
||||
|
||||
switch (idleAnimation) {
|
||||
case "sleeping":
|
||||
if (idleAnimationFrame < 8) {
|
||||
setSprite("tired", 0);
|
||||
break;
|
||||
}
|
||||
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
|
||||
if (idleAnimationFrame > 192) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
case "scratchWallN":
|
||||
case "scratchWallS":
|
||||
case "scratchWallE":
|
||||
case "scratchWallW":
|
||||
case "scratchSelf":
|
||||
setSprite(idleAnimation, idleAnimationFrame);
|
||||
if (idleAnimationFrame > 9) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setSprite("idle", 0);
|
||||
return;
|
||||
}
|
||||
idleAnimationFrame += 1;
|
||||
}
|
||||
|
||||
function frame() {
|
||||
frameCount += 1;
|
||||
const diffX = nekoPosX - mousePosX;
|
||||
const diffY = nekoPosY - mousePosY;
|
||||
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
|
||||
|
||||
if (distance < nekoSpeed || distance < 48) {
|
||||
idle();
|
||||
return;
|
||||
}
|
||||
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
|
||||
if (idleTime > 1) {
|
||||
setSprite("alert", 0);
|
||||
// count down after being alerted before moving
|
||||
idleTime = Math.min(idleTime, 7);
|
||||
idleTime -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let direction;
|
||||
direction = diffY / distance > 0.5 ? "N" : "";
|
||||
direction += diffY / distance < -0.5 ? "S" : "";
|
||||
direction += diffX / distance > 0.5 ? "W" : "";
|
||||
direction += diffX / distance < -0.5 ? "E" : "";
|
||||
setSprite(direction, frameCount);
|
||||
|
||||
nekoPosX -= (diffX / distance) * nekoSpeed;
|
||||
nekoPosY -= (diffY / distance) * nekoSpeed;
|
||||
|
||||
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
|
||||
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
|
||||
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
7
searx/static/themes/simple/js/oneko.js-main/LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright © 2022 adryd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
19
searx/static/themes/simple/js/oneko.js-main/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# oneko.js
|
||||
|
||||
A hacky script I wrote to put a cat on my site.
|
||||
|
||||
The default image is `oneko.gif` in the same directory as the script. This can be changed by adding `data-cat="yourimage.png"` to your `<script>` tag.
|
||||
|
||||
demo: https://adryd.com
|
||||
|
||||
This script is meant to be simple so that it can easily be extended upon. Pull requests adding features not seen in the [original neko program
|
||||
](https://en.wikipedia.org/wiki/Neko_(software)) will probably not be merged.
|
||||
|
||||
implemented in a few different places
|
||||
- Userscript: https://openuserjs.org/scripts/sjehuda/Oneko_WebMate
|
||||
- Vencord: https://vencord.dev/plugins/oneko
|
||||
- Spicetify: https://github.com/kyrie25/spicetify-oneko
|
||||
|
||||
feature forks
|
||||
- Pet the cat: https://github.com/tylxr59/oneko.js/tree/main
|
||||
- Move the cat using scroll wheel: https://github.com/rozbrajaczpoziomow/fork-oneko.js/tree/main
|
||||
17
searx/static/themes/simple/js/oneko.js-main/demo.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>oneko.js</title>
|
||||
</head>
|
||||
<body>
|
||||
<!--[if IE]>
|
||||
<script src="./oneko-ie6.js"></script>
|
||||
<![endif]-->
|
||||
<!--[if !IE]><!-->
|
||||
<script src="./oneko.js"></script>
|
||||
<!--<![endif]-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,6 +5,12 @@
|
||||
"src": "js/searxng.head.js",
|
||||
"isEntry": true
|
||||
},
|
||||
"js/oneko.js": {
|
||||
"file": "js/oneko.js",
|
||||
"name": "js/oneko.js",
|
||||
"src": "js/oneko.js",
|
||||
"isEntry": true
|
||||
},
|
||||
"js/searxng.js": {
|
||||
"file": "js/searxng.min.js",
|
||||
"name": "js/searxng.min",
|
||||
|
||||
@@ -23,6 +23,7 @@ sxng_locales = (
|
||||
('da-DK', 'Dansk', 'Danmark', 'Danish', '\U0001f1e9\U0001f1f0'),
|
||||
('de', 'Deutsch', '', 'German', '\U0001f310'),
|
||||
('de-AT', 'Deutsch', 'Österreich', 'German', '\U0001f1e6\U0001f1f9'),
|
||||
('de-BE', 'Deutsch', 'Belgien', 'German', '\U0001f1e7\U0001f1ea'),
|
||||
('de-CH', 'Deutsch', 'Schweiz', 'German', '\U0001f1e8\U0001f1ed'),
|
||||
('de-DE', 'Deutsch', 'Deutschland', 'German', '\U0001f1e9\U0001f1ea'),
|
||||
('el', 'Ελληνικά', '', 'Greek', '\U0001f310'),
|
||||
|
||||
BIN
searx/templates/simple/.base.html.kate-swp
Normal file
@@ -2,8 +2,8 @@
|
||||
<html class="no-js theme-{{ preferences.get_value('simple_style') or 'auto' }} center-alignment-{{ preferences.get_value('center_alignment') and 'yes' or 'no' }}" lang="{{ locale_rfc5646 }}" {% if rtl %} dir="rtl"{% endif %}>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" content="SearXNG — a privacy-respecting, open metasearch engine">
|
||||
<meta name="keywords" content="SearXNG, search, search engine, metasearch, meta search">
|
||||
<meta name="description" content="nekosearch — a privacy-respecting, open metasearch engine for nekos">
|
||||
<meta name="keywords" content="nekosearch, SearXNG, search, search engine, metasearch, meta search">
|
||||
<meta name="generator" content="searxng/{{ searx_version }}">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="robots" content="noarchive">
|
||||
@@ -84,6 +84,7 @@
|
||||
</footer>
|
||||
<!--[if gte IE 9]>-->
|
||||
<script src="{{ url_for('static', filename='js/searxng.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/oneko.js') }}"></script>
|
||||
<!--<![endif]-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,35 +7,35 @@ template. This file from:
|
||||
|
||||
{%-
|
||||
set catalog = {
|
||||
'alert' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M256 80c-8.66 0-16.58 7.36-16 16l8 216a8 8 0 008 8h0a8 8 0 008-8l8-216c.58-8.64-7.34-16-16-16z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><circle cx="256" cy="416" r="16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'appstore' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><rect x="64" y="64" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="216" y="64" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="368" y="64" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="64" y="216" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="216" y="216" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="368" y="216" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="64" y="368" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="216" y="368" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><rect x="368" y="368" width="80" height="80" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>',
|
||||
'book' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M256 160c16-63.16 76.43-95.41 208-96a15.94 15.94 0 0116 16v288a16 16 0 01-16 16c-128 0-177.45 25.81-208 64-30.37-38-80-64-208-64-9.88 0-16-8.05-16-17.93V80a15.94 15.94 0 0116-16c131.57.59 192 32.84 208 96zM256 160v288" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'close' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/></svg>',
|
||||
'download' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M336 176h40a40 40 0 0140 40v208a40 40 0 01-40 40H136a40 40 0 01-40-40V216a40 40 0 0140-40h40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 272l80 80 80-80M256 48v288"/></svg>',
|
||||
'ellipsis-vertical' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><circle cx="256" cy="256" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><circle cx="256" cy="416" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><circle cx="256" cy="96" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>',
|
||||
'file-tray-full' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M384 80H128c-26 0-43 14-48 40L48 272v112a48.14 48.14 0 0048 48h320a48.14 48.14 0 0048-48V272l-32-152c-5-27-23-40-48-40z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M48 272h144M320 272h144M192 272a64 64 0 00128 0M144 144h224M128 208h256"/></svg>',
|
||||
'film' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><rect x="48" y="96" width="416" height="320" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="384" y="336" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="384" y="256" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="384" y="176" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="384" y="96" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="48" y="336" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="48" y="256" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="48" y="176" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="48" y="96" width="80" height="80" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="128" y="96" width="256" height="160" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><rect x="128" y="256" width="256" height="160" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'globe' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208 208-93.13 208-208S370.87 48 256 48z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M256 48c-58.07 0-112.67 93.13-112.67 208S197.93 464 256 464s112.67-93.13 112.67-208S314.07 48 256 48z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M117.33 117.33c38.24 27.15 86.38 43.34 138.67 43.34s100.43-16.19 138.67-43.34M394.67 394.67c-38.24-27.15-86.38-43.34-138.67-43.34s-100.43 16.19-138.67 43.34" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M256 48v416M464 256H48"/></svg>',
|
||||
'heart' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M352.92 80C288 80 256 144 256 144s-32-64-96.92-64c-52.76 0-94.54 44.14-95.08 96.81-1.1 109.33 86.73 187.08 183 252.42a16 16 0 0018 0c96.26-65.34 184.09-143.09 183-252.42-.54-52.67-42.32-96.81-95.08-96.81z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'image' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><rect x="48" y="80" width="416" height="352" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><circle cx="336" cy="176" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M304 335.79l-90.66-90.49a32 32 0 00-43.87-1.3L48 352M224 432l123.34-123.34a32 32 0 0143.11-2L464 368" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'layers' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M434.8 137.65l-149.36-68.1c-16.19-7.4-42.69-7.4-58.88 0L77.3 137.65c-17.6 8-17.6 21.09 0 29.09l148 67.5c16.89 7.7 44.69 7.7 61.58 0l148-67.5c17.52-8 17.52-21.1-.08-29.09zM160 308.52l-82.7 37.11c-17.6 8-17.6 21.1 0 29.1l148 67.5c16.89 7.69 44.69 7.69 61.58 0l148-67.5c17.6-8 17.6-21.1 0-29.1l-79.94-38.47" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M160 204.48l-82.8 37.16c-17.6 8-17.6 21.1 0 29.1l148 67.49c16.89 7.7 44.69 7.7 61.58 0l148-67.49c17.7-8 17.7-21.1.1-29.1L352 204.48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'leecher' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M112 268l144 144 144-144M256 392V100"/></svg>',
|
||||
'location' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M256 48c-79.5 0-144 61.39-144 137 0 87 96 224.87 131.25 272.49a15.77 15.77 0 0025.5 0C304 409.89 400 272.07 400 185c0-75.61-64.5-137-144-137z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><circle cx="256" cy="192" r="48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'magnet' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M421.83 293.82A144 144 0 00218.18 90.17M353.94 225.94a48 48 0 00-67.88-67.88" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M192 464v-48M90.18 421.82l33.94-33.94M48 320h48"/><path d="M286.06 158.06L172.92 271.19a32 32 0 01-45.25 0L105 248.57a32 32 0 010-45.26L218.18 90.17M421.83 293.82L308.69 407a32 32 0 01-45.26 0l-22.62-22.63a32 32 0 010-45.26l113.13-113.17M139.6 169.98l67.88 67.89M275.36 305.75l67.89 67.88" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'musical-notes' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M192 218v-6c0-14.84 10-27 24.24-30.59l174.59-46.68A20 20 0 01416 154v22" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M416 295.94v80c0 13.91-8.93 25.59-22 30l-22 8c-25.9 8.72-52-10.42-52-38h0a33.37 33.37 0 0123-32l51-18.15c13.07-4.4 22-15.94 22-29.85V58a10 10 0 00-12.6-9.61L204 102a16.48 16.48 0 00-12 16v226c0 13.91-8.93 25.6-22 30l-52 18c-13.88 4.68-22 17.22-22 32h0c0 27.58 26.52 46.55 52 38l22-8c13.07-4.4 22-16.08 22-30v-80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'navigate-down' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M112 184l144 144 144-144"/></svg>',
|
||||
'navigate-left' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M328 112L184 256l144 144"/></svg>',
|
||||
'navigate-right' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M184 112l144 144-144 144"/></svg>',
|
||||
'navigate-up' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M112 328l144-144 144 144"/></svg>',
|
||||
'people' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M402 168c-2.93 40.67-33.1 72-66 72s-63.12-31.32-66-72c-3-42.31 26.37-72 66-72s69 30.46 66 72z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M336 304c-65.17 0-127.84 32.37-143.54 95.41-2.08 8.34 3.15 16.59 11.72 16.59h263.65c8.57 0 13.77-8.25 11.72-16.59C463.85 335.36 401.18 304 336 304z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M200 185.94c-2.34 32.48-26.72 58.06-53 58.06s-50.7-25.57-53-58.06C91.61 152.15 115.34 128 147 128s55.39 24.77 53 57.94z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M206 306c-18.05-8.27-37.93-11.45-59-11.45-52 0-102.1 25.85-114.65 76.2-1.65 6.66 2.53 13.25 9.37 13.25H154" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/></svg>',
|
||||
'play' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M112 111v290c0 17.44 17 28.52 31 20.16l247.9-148.37c12.12-7.25 12.12-26.33 0-33.58L143 90.84c-14-8.36-31 2.72-31 20.16z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>',
|
||||
'radio' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><circle cx="256" cy="256.02" r="32"/><path d="M184.25 192.25a96 96 0 000 127.52M327.77 319.77a96 96 0 000-127.52M133.28 141.28a168 168 0 000 229.44M378.72 370.72a168 168 0 000-229.44M435 416a240.34 240.34 0 000-320M77 96a240.34 240.34 0 000 320" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'save' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M380.93 57.37A32 32 0 00358.3 48H94.22A46.21 46.21 0 0048 94.22v323.56A46.21 46.21 0 0094.22 464h323.56A46.36 46.36 0 00464 417.78V153.7a32 32 0 00-9.37-22.63zM256 416a64 64 0 1164-64 63.92 63.92 0 01-64 64zm48-224H112a16 16 0 01-16-16v-64a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-16 16z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'school' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M32 192L256 64l224 128-224 128L32 192z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M112 240v128l144 80 144-80V240M480 368V192M256 320v128"/></svg>',
|
||||
'search' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M338.29 338.29L448 448"/></svg>',
|
||||
'seeder' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M464 208L352 96 240 208M352 113.13V416M48 304l112 112 112-112M160 398V96"/></svg>',
|
||||
'settings' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M262.29 192.31a64 64 0 1057.4 57.4 64.13 64.13 0 00-57.4-57.4zM416.39 256a154.34 154.34 0 01-1.53 20.79l45.21 35.46a10.81 10.81 0 012.45 13.75l-42.77 74a10.81 10.81 0 01-13.14 4.59l-44.9-18.08a16.11 16.11 0 00-15.17 1.75A164.48 164.48 0 01325 400.8a15.94 15.94 0 00-8.82 12.14l-6.73 47.89a11.08 11.08 0 01-10.68 9.17h-85.54a11.11 11.11 0 01-10.69-8.87l-6.72-47.82a16.07 16.07 0 00-9-12.22 155.3 155.3 0 01-21.46-12.57 16 16 0 00-15.11-1.71l-44.89 18.07a10.81 10.81 0 01-13.14-4.58l-42.77-74a10.8 10.8 0 012.45-13.75l38.21-30a16.05 16.05 0 006-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 00-6.07-13.94l-38.19-30A10.81 10.81 0 0149.48 186l42.77-74a10.81 10.81 0 0113.14-4.59l44.9 18.08a16.11 16.11 0 0015.17-1.75A164.48 164.48 0 01187 111.2a15.94 15.94 0 008.82-12.14l6.73-47.89A11.08 11.08 0 01213.23 42h85.54a11.11 11.11 0 0110.69 8.87l6.72 47.82a16.07 16.07 0 009 12.22 155.3 155.3 0 0121.46 12.57 16 16 0 0015.11 1.71l44.89-18.07a10.81 10.81 0 0113.14 4.58l42.77 74a10.8 10.8 0 01-2.45 13.75l-38.21 30a16.05 16.05 0 00-6.05 14.08c.33 4.14.55 8.3.55 12.47z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>',
|
||||
'tv' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><rect x="32" y="96" width="448" height="272" rx="32.14" ry="32.14" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M128 416h256"/></svg>',
|
||||
'alert' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M256 80c-8.66 0-16.58 7.36-16 16l8 216a8 8 0 0 0 8 8h0a8 8 0 0 0 8-8l8-216c.58-8.64-7.34-16-16-16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><circle cx="256" cy="416" r="16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'appstore' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><rect width="80" height="80" x="64" y="64" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="216" y="64" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="368" y="64" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="64" y="216" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="216" y="216" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="368" y="216" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="64" y="368" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="216" y="368" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><rect width="80" height="80" x="368" y="368" rx="40" ry="40" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'book' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M256 160c16-63.16 76.43-95.41 208-96a15.94 15.94 0 0 1 16 16v288a16 16 0 0 1-16 16c-128 0-177.45 25.81-208 64-30.37-38-80-64-208-64-9.88 0-16-8.05-16-17.93V80a15.94 15.94 0 0 1 16-16c131.57.59 192 32.84 208 96M256 160v288" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'close' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M368 368 144 144M368 144 144 368" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'download' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M336 176h40a40 40 0 0 1 40 40v208a40 40 0 0 1-40 40H136a40 40 0 0 1-40-40V216a40 40 0 0 1 40-40h40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="m176 272 80 80 80-80M256 48v288" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'ellipsis-vertical' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><circle cx="256" cy="256" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><circle cx="256" cy="416" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><circle cx="256" cy="96" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'file-tray-full' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M384 80H128c-26 0-43 14-48 40L48 272v112a48.14 48.14 0 0 0 48 48h320a48.14 48.14 0 0 0 48-48V272l-32-152c-5-27-23-40-48-40Z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><path d="M48 272h144M320 272h144M192 272a64 64 0 0 0 128 0M144 144h224M128 208h256" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'film' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><rect width="416" height="320" x="48" y="96" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="384" y="336" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="384" y="256" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="384" y="176" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="384" y="96" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="48" y="336" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="48" y="256" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="48" y="176" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="80" height="80" x="48" y="96" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="256" height="160" x="128" y="96" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><rect width="256" height="160" x="128" y="256" rx="28" ry="28" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'globe' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208 208-93.13 208-208S370.87 48 256 48Z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="M256 48c-58.07 0-112.67 93.13-112.67 208S197.93 464 256 464s112.67-93.13 112.67-208S314.07 48 256 48Z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="M117.33 117.33c38.24 27.15 86.38 43.34 138.67 43.34s100.43-16.19 138.67-43.34M394.67 394.67c-38.24-27.15-86.38-43.34-138.67-43.34s-100.43 16.19-138.67 43.34" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="M256 48v416M464 256H48" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'heart' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M352.92 80C288 80 256 144 256 144s-32-64-96.92-64c-52.76 0-94.54 44.14-95.08 96.81-1.1 109.33 86.73 187.08 183 252.42a16 16 0 0 0 18 0c96.26-65.34 184.09-143.09 183-252.42-.54-52.67-42.32-96.81-95.08-96.81" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'image' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><rect width="416" height="352" x="48" y="80" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><circle cx="336" cy="176" r="32" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="m304 335.79-90.66-90.49a32 32 0 0 0-43.87-1.3L48 352M224 432l123.34-123.34a32 32 0 0 1 43.11-2L464 368" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'layers' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="m434.8 137.65-149.36-68.1c-16.19-7.4-42.69-7.4-58.88 0L77.3 137.65c-17.6 8-17.6 21.09 0 29.09l148 67.5c16.89 7.7 44.69 7.7 61.58 0l148-67.5c17.52-8 17.52-21.1-.08-29.09M160 308.52l-82.7 37.11c-17.6 8-17.6 21.1 0 29.1l148 67.5c16.89 7.69 44.69 7.69 61.58 0l148-67.5c17.6-8 17.6-21.1 0-29.1l-79.94-38.47" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="m160 204.48-82.8 37.16c-17.6 8-17.6 21.1 0 29.1l148 67.49c16.89 7.7 44.69 7.7 61.58 0l148-67.49c17.7-8 17.7-21.1.1-29.1L352 204.48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'leecher' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="m112 268 144 144 144-144M256 392V100" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48px"/></svg>',
|
||||
'location' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M256 48c-79.5 0-144 61.39-144 137 0 87 96 224.87 131.25 272.49a15.77 15.77 0 0 0 25.5 0C304 409.89 400 272.07 400 185c0-75.61-64.5-137-144-137" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><circle cx="256" cy="192" r="48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'magnet' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M421.83 293.82A144 144 0 0 0 218.18 90.17" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="M353.94 225.94a48 48 0 0 0-67.88-67.88" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="M192 464v-48M90.18 421.82l33.94-33.94M48 320h48" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32px"/><path d="M286.06 158.06 172.92 271.19a32 32 0 0 1-45.25 0L105 248.57a32 32 0 0 1 0-45.26L218.18 90.17M421.83 293.82 308.69 407a32 32 0 0 1-45.26 0l-22.62-22.63a32 32 0 0 1 0-45.26l113.13-113.17M139.6 169.98l67.88 67.89M275.36 305.75l67.89 67.88" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'musical-notes' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M192 218v-6c0-14.84 10-27 24.24-30.59l174.59-46.68A20 20 0 0 1 416 154v22" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="M416 295.94v80c0 13.91-8.93 25.59-22 30l-22 8c-25.9 8.72-52-10.42-52-38h0a33.37 33.37 0 0 1 23-32l51-18.15c13.07-4.4 22-15.94 22-29.85V58a10 10 0 0 0-12.6-9.61L204 102a16.48 16.48 0 0 0-12 16v226c0 13.91-8.93 25.6-22 30l-52 18c-13.88 4.68-22 17.22-22 32h0c0 27.58 26.52 46.55 52 38l22-8c13.07-4.4 22-16.08 22-30v-80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'navigate-down' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="m112 184 144 144 144-144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48px"/></svg>',
|
||||
'navigate-left' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M328 112 184 256l144 144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48px"/></svg>',
|
||||
'navigate-right' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="m184 112 144 144-144 144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48px"/></svg>',
|
||||
'navigate-up' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="m112 328 144-144 144 144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48px"/></svg>',
|
||||
'people' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M402 168c-2.93 40.67-33.1 72-66 72s-63.12-31.32-66-72c-3-42.31 26.37-72 66-72s69 30.46 66 72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="M336 304c-65.17 0-127.84 32.37-143.54 95.41-2.08 8.34 3.15 16.59 11.72 16.59h263.65c8.57 0 13.77-8.25 11.72-16.59C463.85 335.36 401.18 304 336 304Z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="M200 185.94c-2.34 32.48-26.72 58.06-53 58.06s-50.7-25.57-53-58.06C91.61 152.15 115.34 128 147 128s55.39 24.77 53 57.94" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="M206 306c-18.05-8.27-37.93-11.45-59-11.45-52 0-102.1 25.85-114.65 76.2-1.65 6.66 2.53 13.25 9.37 13.25H154" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'play' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M112 111v290c0 17.44 17 28.52 31 20.16l247.9-148.37c12.12-7.25 12.12-26.33 0-33.58L143 90.84c-14-8.36-31 2.72-31 20.16Z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'radio' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><circle cx="256" cy="256.02" r="32"/><path d="M184.25 192.25a96 96 0 0 0 0 127.52M327.77 319.77a96 96 0 0 0 0-127.52M133.28 141.28a168 168 0 0 0 0 229.44M378.72 370.72a168 168 0 0 0 0-229.44" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="M435 416a240.34 240.34 0 0 0 0-320M77 96a240.34 240.34 0 0 0 0 320" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'save' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M380.93 57.37A32 32 0 0 0 358.3 48H94.22A46.21 46.21 0 0 0 48 94.22v323.56A46.21 46.21 0 0 0 94.22 464h323.56A46.36 46.36 0 0 0 464 417.78V153.7a32 32 0 0 0-9.37-22.63ZM256 416a64 64 0 1 1 64-64 63.92 63.92 0 0 1-64 64m48-224H112a16 16 0 0 1-16-16v-64a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16v64a16 16 0 0 1-16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'school' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M32 192 256 64l224 128-224 128z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/><path d="M112 240v128l144 80 144-80V240M480 368V192M256 320v128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'search' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M221.09 64a157.09 157.09 0 1 0 157.09 157.09A157.1 157.1 0 0 0 221.09 64Z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32px"/><path d="M338.29 338.29 448 448" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'seeder' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M464 208 352 96 240 208M352 113.13V416M48 304l112 112 112-112M160 398V96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'settings' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><path d="M262.29 192.31a64 64 0 1 0 57.4 57.4 64.13 64.13 0 0 0-57.4-57.4M416.39 256a154 154 0 0 1-1.53 20.79l45.21 35.46a10.81 10.81 0 0 1 2.45 13.75l-42.77 74a10.81 10.81 0 0 1-13.14 4.59l-44.9-18.08a16.11 16.11 0 0 0-15.17 1.75A164.5 164.5 0 0 1 325 400.8a15.94 15.94 0 0 0-8.82 12.14l-6.73 47.89a11.08 11.08 0 0 1-10.68 9.17h-85.54a11.11 11.11 0 0 1-10.69-8.87l-6.72-47.82a16.07 16.07 0 0 0-9-12.22 155 155 0 0 1-21.46-12.57 16 16 0 0 0-15.11-1.71l-44.89 18.07a10.81 10.81 0 0 1-13.14-4.58l-42.77-74a10.8 10.8 0 0 1 2.45-13.75l38.21-30a16.05 16.05 0 0 0 6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 0 0-6.07-13.94l-38.19-30A10.81 10.81 0 0 1 49.48 186l42.77-74a10.81 10.81 0 0 1 13.14-4.59l44.9 18.08a16.11 16.11 0 0 0 15.17-1.75A164.5 164.5 0 0 1 187 111.2a15.94 15.94 0 0 0 8.82-12.14l6.73-47.89A11.08 11.08 0 0 1 213.23 42h85.54a11.11 11.11 0 0 1 10.69 8.87l6.72 47.82a16.07 16.07 0 0 0 9 12.22 155 155 0 0 1 21.46 12.57 16 16 0 0 0 15.11 1.71l44.89-18.07a10.81 10.81 0 0 1 13.14 4.58l42.77 74a10.8 10.8 0 0 1-2.45 13.75l-38.21 30a16.05 16.05 0 0 0-6.05 14.08c.33 4.14.55 8.3.55 12.47" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>',
|
||||
'tv' : '<svg viewBox="0 0 512 512" class="ionicon __jinja_class_placeholder__" aria-hidden="true"><rect width="448" height="272" x="32" y="96" rx="32.14" ry="32.14" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32px"/><path d="M128 416h256" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32px"/></svg>',
|
||||
'information-circle' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M248 64C146.39 64 64 146.39 64 248s82.39 184 184 184 184-82.39 184-184S349.61 64 248 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M220 220h32v116"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M208 340h88"/><path d="M248 130a26 26 0 1026 26 26 26 0 00-26-26z" fill="currentColor" stroke="currentColor" stroke-miterlimit="10" stroke-width="1"/></svg>',
|
||||
'newspaper' : '<svg viewBox="0 0 512 512" aria-hidden="true" class="__jinja_class_placeholder__"><path d="M368 415.86V72a24.07 24.07 0 00-24-24H72a24.07 24.07 0 00-24 24v352a40.12 40.12 0 0040 40h328" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M416 464h0a48 48 0 01-48-48V128h72a24 24 0 0124 24v264a48 48 0 01-48 48z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M240 128h64M240 192h64M112 256h192M112 320h192M112 384h192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M176 208h-64a16 16 0 01-16-16v-64a16 16 0 0116-16h64a16 16 0 0116 16v64a16 16 0 01-16 16z" fill="currentColor" stroke="currentColor" stroke-linejoin="round" stroke-width="1"/></svg>',
|
||||
}
|
||||
|
||||
@@ -25,13 +25,14 @@
|
||||
# Yahya-Lando <yahya-lando@users.noreply.translate.codeberg.org>, 2025.
|
||||
# curtwheeler <curtwheeler@users.noreply.translate.codeberg.org>, 2025.
|
||||
# return42 <return42@noreply.codeberg.org>, 2025.
|
||||
# DZDevelopers <dzdevelopers@noreply.codeberg.org>, 2025.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: searx\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-03-29 09:21+0000\n"
|
||||
"PO-Revision-Date: 2025-03-31 18:08+0000\n"
|
||||
"Last-Translator: return42 <return42@noreply.codeberg.org>\n"
|
||||
"PO-Revision-Date: 2025-04-15 10:37+0000\n"
|
||||
"Last-Translator: DZDevelopers <dzdevelopers@noreply.codeberg.org>\n"
|
||||
"Language-Team: Arabic <https://translate.codeberg.org/projects/searxng/"
|
||||
"searxng/ar/>\n"
|
||||
"Language: ar\n"
|
||||
@@ -535,15 +536,15 @@ msgstr "جودة الملف"
|
||||
|
||||
#: searx/plugins/ahmia_filter.py:32
|
||||
msgid "Ahmia blacklist"
|
||||
msgstr ""
|
||||
msgstr "{دالة}"
|
||||
|
||||
#: searx/plugins/ahmia_filter.py:33
|
||||
msgid "Filter out onion results that appear in Ahmia's blacklist."
|
||||
msgstr ""
|
||||
msgstr "قم بتصفية نتائج .onion التي تظهر في القائمة السوداء الخاصة بـ Ahmia."
|
||||
|
||||
#: searx/plugins/calculator.py:38
|
||||
msgid "Basic Calculator"
|
||||
msgstr ""
|
||||
msgstr "آلة حاسبة بسيطة"
|
||||
|
||||
#: searx/plugins/calculator.py:39
|
||||
msgid "Calculate mathematical expressions via the search bar"
|
||||
@@ -639,7 +640,7 @@ msgstr ""
|
||||
|
||||
#: searx/plugins/unit_converter.py:49
|
||||
msgid "Unit converter plugin"
|
||||
msgstr ""
|
||||
msgstr "إضافة محول الوحدات"
|
||||
|
||||
#: searx/plugins/unit_converter.py:50
|
||||
msgid "Convert between units"
|
||||
@@ -1190,6 +1191,8 @@ msgid ""
|
||||
"A URL containing your preferences. This URL can be used to restore your "
|
||||
"settings on a different device."
|
||||
msgstr ""
|
||||
"رابط يحتوي على تفضيلاتك. يمكن استخدام هذا الرابط لاستعادة إعداداتك على جهاز "
|
||||
"مختلف."
|
||||
|
||||
#: searx/templates/simple/preferences/cookies.html:46
|
||||
msgid "Copy preferences hash"
|
||||
|
||||
@@ -18,21 +18,22 @@
|
||||
# Kjev <Kjev@users.noreply.translate.codeberg.org>, 2025.
|
||||
# KinoCineaste <kinocineaste@users.noreply.translate.codeberg.org>, 2025.
|
||||
# AlanBacker <alanbacker@users.noreply.translate.codeberg.org>, 2025.
|
||||
# return42 <return42@noreply.codeberg.org>, 2025.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: searx\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-03-29 09:21+0000\n"
|
||||
"PO-Revision-Date: 2025-02-17 13:01+0000\n"
|
||||
"Last-Translator: AlanBacker "
|
||||
"<alanbacker@users.noreply.translate.codeberg.org>\n"
|
||||
"PO-Revision-Date: 2025-05-09 07:09+0000\n"
|
||||
"Last-Translator: return42 <return42@noreply.codeberg.org>\n"
|
||||
"Language-Team: Esperanto <https://translate.codeberg.org/projects/searxng/"
|
||||
"searxng/eo/>\n"
|
||||
"Language: eo\n"
|
||||
"Language-Team: Esperanto "
|
||||
"<https://translate.codeberg.org/projects/searxng/searxng/eo/>\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.10.2\n"
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||
@@ -260,7 +261,7 @@ msgstr ""
|
||||
#: searx/engines/duckduckgo_weather.py:81 searx/engines/wttr.py:36
|
||||
#: searx/searxng.msg
|
||||
msgid "Sunrise"
|
||||
msgstr ""
|
||||
msgstr "Sunleviĝo"
|
||||
|
||||
#. WEATHER_TERMS['SUNSET']
|
||||
#: searx/engines/duckduckgo_weather.py:82 searx/engines/wttr.py:37
|
||||
@@ -2067,4 +2068,3 @@ msgstr "kaŝi videojn"
|
||||
#~ "Specifante kutimajn agordojn en la URL"
|
||||
#~ " de preferoj povas esti uzata por "
|
||||
#~ "sinkronigi preferojn tra aparatoj."
|
||||
|
||||
|
||||
@@ -43,13 +43,14 @@
|
||||
# pxrb <pxrb@users.noreply.translate.codeberg.org>, 2025.
|
||||
# curtwheeler <curtwheeler@users.noreply.translate.codeberg.org>, 2025.
|
||||
# return42 <return42@noreply.codeberg.org>, 2025.
|
||||
# Atul_Eterno <atul_eterno@noreply.codeberg.org>, 2025.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: searx\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-03-29 09:21+0000\n"
|
||||
"PO-Revision-Date: 2025-04-08 11:34+0000\n"
|
||||
"Last-Translator: return42 <return42@noreply.codeberg.org>\n"
|
||||
"PO-Revision-Date: 2025-04-24 14:06+0000\n"
|
||||
"Last-Translator: Atul_Eterno <atul_eterno@noreply.codeberg.org>\n"
|
||||
"Language-Team: Spanish <https://translate.codeberg.org/projects/searxng/"
|
||||
"searxng/es/>\n"
|
||||
"Language: es\n"
|
||||
@@ -553,7 +554,7 @@ msgstr "Calidad del archivo"
|
||||
|
||||
#: searx/plugins/ahmia_filter.py:32
|
||||
msgid "Ahmia blacklist"
|
||||
msgstr ""
|
||||
msgstr "Lista negra de Ahmia"
|
||||
|
||||
#: searx/plugins/ahmia_filter.py:33
|
||||
msgid "Filter out onion results that appear in Ahmia's blacklist."
|
||||
@@ -1210,6 +1211,8 @@ msgid ""
|
||||
"A URL containing your preferences. This URL can be used to restore your "
|
||||
"settings on a different device."
|
||||
msgstr ""
|
||||
"Una URL conteniendo sus preferencias. Esta URL puede ser usada para "
|
||||
"restaurar sus ajustes en un dispositivo diferente."
|
||||
|
||||
#: searx/templates/simple/preferences/cookies.html:46
|
||||
msgid "Copy preferences hash"
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
# Parsa Ranjbar <parsa@users.noreply.translate.codeberg.org>, 2025.
|
||||
# arashe22 <arashe22@users.noreply.translate.codeberg.org>, 2025.
|
||||
# return42 <return42@noreply.codeberg.org>, 2025.
|
||||
# ehsanrs2 <ehsanrs2@noreply.codeberg.org>, 2025.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: searx\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-03-29 09:21+0000\n"
|
||||
"PO-Revision-Date: 2025-03-31 18:08+0000\n"
|
||||
"Last-Translator: return42 <return42@noreply.codeberg.org>\n"
|
||||
"PO-Revision-Date: 2025-05-06 12:53+0000\n"
|
||||
"Last-Translator: ehsanrs2 <ehsanrs2@noreply.codeberg.org>\n"
|
||||
"Language-Team: Persian <https://translate.codeberg.org/projects/searxng/"
|
||||
"searxng/fa/>\n"
|
||||
"Language: fa_IR\n"
|
||||
@@ -533,15 +534,15 @@ msgstr "کیفیت فایل"
|
||||
|
||||
#: searx/plugins/ahmia_filter.py:32
|
||||
msgid "Ahmia blacklist"
|
||||
msgstr ""
|
||||
msgstr "لیست سیاه Ahmia"
|
||||
|
||||
#: searx/plugins/ahmia_filter.py:33
|
||||
msgid "Filter out onion results that appear in Ahmia's blacklist."
|
||||
msgstr ""
|
||||
msgstr "نتایج onion که در لیست سیاه Ahmia ظاهر میشوند را فیلتر کنید."
|
||||
|
||||
#: searx/plugins/calculator.py:38
|
||||
msgid "Basic Calculator"
|
||||
msgstr ""
|
||||
msgstr "ماشین حساب اولیه"
|
||||
|
||||
#: searx/plugins/calculator.py:39
|
||||
msgid "Calculate mathematical expressions via the search bar"
|
||||
@@ -614,7 +615,7 @@ msgstr ""
|
||||
|
||||
#: searx/plugins/tor_check.py:65
|
||||
msgid "Could not download the list of Tor exit-nodes from"
|
||||
msgstr "دانلود لیست گرههای خروجی تور از این مسیر ممکن نیست:"
|
||||
msgstr "نتوانستم لیست گرههای خروجی Tor را از اینجا دانلود کنم"
|
||||
|
||||
#: searx/plugins/tor_check.py:72
|
||||
msgid "You are using Tor and it looks like you have the external IP address"
|
||||
@@ -634,7 +635,7 @@ msgstr "آرگومان های ردیاب ها را از URL برگشتی حذف
|
||||
|
||||
#: searx/plugins/unit_converter.py:49
|
||||
msgid "Unit converter plugin"
|
||||
msgstr ""
|
||||
msgstr "افزونه تبدیل واحد"
|
||||
|
||||
#: searx/plugins/unit_converter.py:50
|
||||
msgid "Convert between units"
|
||||
@@ -681,7 +682,7 @@ msgstr "ردیاب مشکل"
|
||||
|
||||
#: searx/templates/simple/base.html:70 searx/templates/simple/stats.html:18
|
||||
msgid "Engine stats"
|
||||
msgstr "آمار موتور"
|
||||
msgstr "وضعیت موتور"
|
||||
|
||||
#: searx/templates/simple/base.html:72
|
||||
msgid "Public instances"
|
||||
@@ -693,7 +694,7 @@ msgstr "سیاست حفظ حریم خصوصی"
|
||||
|
||||
#: searx/templates/simple/base.html:78
|
||||
msgid "Contact instance maintainer"
|
||||
msgstr "تماس با مسئولنگهداری نمونه"
|
||||
msgstr "تماس با نگهدارنده نمونه"
|
||||
|
||||
#: searx/templates/simple/categories.html:30
|
||||
msgid "Click on the magnifier to perform search"
|
||||
@@ -1185,6 +1186,8 @@ msgid ""
|
||||
"A URL containing your preferences. This URL can be used to restore your "
|
||||
"settings on a different device."
|
||||
msgstr ""
|
||||
"یک URL حاوی تنظیمات برگزیده شما. از این URL میتوان برای بازیابی تنظیمات شما "
|
||||
"در دستگاه دیگری استفاده کرد."
|
||||
|
||||
#: searx/templates/simple/preferences/cookies.html:46
|
||||
msgid "Copy preferences hash"
|
||||
@@ -1244,7 +1247,7 @@ msgstr "زمان بیشینه"
|
||||
|
||||
#: searx/templates/simple/preferences/favicon.html:2
|
||||
msgid "Favicon Resolver"
|
||||
msgstr ""
|
||||
msgstr "حل کننده فاویکون"
|
||||
|
||||
#: searx/templates/simple/preferences/favicon.html:15
|
||||
msgid "Display favicons near search results"
|
||||
|
||||