mirror of
https://github.com/searxng/searxng.git
synced 2026-09-13 01:36:04 +00:00
Compare commits
164 Commits
bfeaad6c37
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1d4e26880 | ||
|
|
a0ab3b4f11 | ||
|
|
56b1f64541 | ||
|
|
923a307454 | ||
|
|
87bf8c86ed | ||
|
|
61d660276f | ||
|
|
6a27c21008 | ||
|
|
ffe96f8a6f | ||
|
|
931fd9787b | ||
|
|
42e1d61296 | ||
|
|
765a9999df | ||
|
|
ba055b3e09 | ||
|
|
3fdc6d753a | ||
|
|
3e454637fb | ||
|
|
c7f3080aac | ||
|
|
072311b5e0 | ||
|
|
c06e9f0889 | ||
|
|
4781754dc4 | ||
|
|
28b61729c7 | ||
|
|
14a9f84c6c | ||
|
|
8b01679e8f | ||
|
|
eaf1fcb349 | ||
|
|
e20e370353 | ||
|
|
3605a2d58b | ||
|
|
aef258321c | ||
|
|
a303e9c0ca | ||
|
|
ccffbfc164 | ||
|
|
242dc6e398 | ||
|
|
22056605a6 | ||
|
|
23e7e4da00 | ||
|
|
15a91992e4 | ||
|
|
03c439a5b9 | ||
|
|
be836e614a | ||
|
|
15b0c8ef3a | ||
|
|
a1144dda3e | ||
|
|
86008c9dd6 | ||
|
|
8f452ee892 | ||
|
|
745d5b6fc5 | ||
|
|
05cd77f71b | ||
|
|
b4f616fed7 | ||
|
|
fd29de6c55 | ||
|
|
eca1af4be1 | ||
|
|
7a01f5830c | ||
|
|
18af21159b | ||
|
|
248e37991c | ||
|
|
ff424745fd | ||
|
|
79c8ffe0da | ||
|
|
2b1c88c54c | ||
|
|
d226b78bc4 | ||
|
|
bdbf9774f5 | ||
|
|
451c46aa32 | ||
|
|
a30b2d4749 | ||
|
|
9fea41204f | ||
|
|
777ba8fa48 | ||
|
|
a4cb7df053 | ||
|
|
bbb3c7d829 | ||
|
|
f31ff05db3 | ||
|
|
5f4005996e | ||
|
|
487d7a96e0 | ||
|
|
8d3dd0cd45 | ||
|
|
5ffd32ca2f | ||
|
|
374939b888 | ||
|
|
b2da6b90f2 | ||
|
|
36698aff6b | ||
|
|
094c33d406 | ||
|
|
ef9a188cc8 | ||
|
|
cdfdaa5a88 | ||
|
|
5638231358 | ||
|
|
1c3bb1e88f | ||
|
|
54613defc7 | ||
|
|
e8e710e42a | ||
|
|
7b0c7f0bf8 | ||
|
|
e033be7c7c | ||
|
|
0a118066d8 | ||
|
|
b023a28bab | ||
|
|
c63835bd2a | ||
|
|
1689cb1b53 | ||
|
|
aa059419ff | ||
|
|
0734ee6c71 | ||
|
|
d81810d2a7 | ||
|
|
8892414dc3 | ||
|
|
6bfd82705a | ||
|
|
057a77168d | ||
|
|
0be6f87801 | ||
|
|
ef3a6ea9fd | ||
|
|
f25b75e613 | ||
|
|
98e10f9ab4 | ||
|
|
a449518ed4 | ||
|
|
702f702f9b | ||
|
|
afdfd81613 | ||
|
|
c81ed99c69 | ||
|
|
ecf8497b65 | ||
|
|
e28131f8d4 | ||
|
|
81b0ed7b38 | ||
|
|
c01178d031 | ||
|
|
c73861ab46 | ||
|
|
891bc69550 | ||
|
|
d661b6114d | ||
|
|
8372f5d855 | ||
|
|
8f8b5d2b8d | ||
|
|
b060c780d0 | ||
|
|
6d8b550280 | ||
|
|
0909dbc9ef | ||
|
|
b4e94417b7 | ||
|
|
4f64d95013 | ||
|
|
ef8f6470e0 | ||
|
|
6da6eee265 | ||
|
|
277d8469cd | ||
|
|
81c9c23862 | ||
|
|
6913fba208 | ||
|
|
2daa4d4815 | ||
|
|
de8f73f434 | ||
|
|
9f9c00819e | ||
|
|
b72a87676f | ||
|
|
f2432e33d6 | ||
|
|
7b2199ecdf | ||
|
|
4a9c19d7bf | ||
|
|
5cb4cb2bc5 | ||
|
|
5a448596ab | ||
|
|
9c49b7e0d7 | ||
|
|
58e02a01ae | ||
|
|
7fa9f16225 | ||
|
|
9e25585aec | ||
|
|
c19d86faa3 | ||
|
|
21fa7b0be1 | ||
|
|
74b4e7c8d1 | ||
|
|
39f4dd24a5 | ||
|
|
62a1ab7edd | ||
|
|
4abac08de5 | ||
|
|
6a4d5148d6 | ||
|
|
799086874d | ||
|
|
83139c26b3 | ||
|
|
8456831a04 | ||
|
|
b512eaa272 | ||
|
|
1412926f5c | ||
|
|
3b573e0f89 | ||
|
|
da6a230413 | ||
|
|
f69b22c45c | ||
|
|
f930443726 | ||
|
|
d58ced8f71 | ||
|
|
556d08c395 | ||
|
|
1017631800 | ||
|
|
b64e6ee44a | ||
|
|
a6438586a5 | ||
|
|
fd5eb84a37 | ||
|
|
888364c1ce | ||
|
|
1cdf01a719 | ||
|
|
747cec4c23 | ||
|
|
4ef70e9451 | ||
|
|
80c9806de1 | ||
|
|
9d7ca4febc | ||
|
|
c5cd510d82 | ||
|
|
73a0219ab8 | ||
|
|
7ed7adfb05 | ||
|
|
d7367e0897 | ||
|
|
21773bbb2d | ||
|
|
67973783de | ||
|
|
0f9e30e3f9 | ||
|
|
640f88c9bd | ||
|
|
c5d8d05f03 | ||
|
|
d115c61a70 | ||
|
|
c5b1d066e5 | ||
|
|
774616ada6 | ||
|
|
6b2ec018e2 |
39
.github/scripts/ai_policy.cjs
vendored
Normal file
39
.github/scripts/ai_policy.cjs
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Closes issues and prs whose authors/agents don't accept the ai policy
|
||||
// https://github.com/searxng/searxng/blob/master/AI_POLICY.rst
|
||||
|
||||
module.exports = async ({ github, context }) => {
|
||||
const item = context.payload.pull_request || context.payload.issue;
|
||||
const body = item.body || '';
|
||||
const kind = context.payload.pull_request ? 'pull request' : 'issue';
|
||||
|
||||
// https://github.com/searxng/searxng/pull/6476#discussion_r3683782481
|
||||
const hasBox = /\[[Xx]\].*AI Policy/.test(body);
|
||||
const hasRef = /\[AI Policy\]:\s*https:\/\/github\.com\/searxng\/searxng\/.*AI_POLICY/.test(body);
|
||||
if (hasBox && hasRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { owner, repo } = context.repo;
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: item.number,
|
||||
body:
|
||||
'Hello! Thank you for your contribution.\n\n' +
|
||||
`Unfortunately your ${kind} was closed as the AI Policy has not been accepted.\n\n` +
|
||||
`Please open a new ${kind} after confirming your contribution aligns with our AI Policy.`,
|
||||
});
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: item.number,
|
||||
labels: ['invalid:slop'],
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: item.number,
|
||||
state: 'closed',
|
||||
state_reason: 'not_planned',
|
||||
});
|
||||
};
|
||||
38
.github/workflows/ai-policy.yml
vendored
Normal file
38
.github/workflows/ai-policy.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
# yamllint disable rule:line-length
|
||||
name: AI Policy
|
||||
|
||||
# Closes any new issues and PRs from people (or agents) who don't accept the AI Policy
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check AI Policy
|
||||
# for issues with an author who has not contributed before
|
||||
if: >-
|
||||
github.event.sender.type != 'Bot' &&
|
||||
contains(fromJSON('["NONE","FIRST_TIMER","FIRST_TIME_CONTRIBUTOR"]'),
|
||||
github.event.issue.author_association ||
|
||||
github.event.pull_request.author_association)
|
||||
runs-on: ubuntu-26.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/scripts/ai_policy.cjs');
|
||||
await script({ github, context });
|
||||
139
.github/workflows/container.yml
vendored
139
.github/workflows/container.yml
vendored
@@ -25,25 +25,21 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'searxng' || github.event_name == 'workflow_dispatch'
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|| (github.repository_owner == 'searxng' && github.event.workflow_run.conclusion == 'success')
|
||||
name: Build (${{ matrix.arch }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
march: amd64
|
||||
os: ubuntu-24.04
|
||||
emulation: false
|
||||
- arch: arm64
|
||||
march: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
emulation: false
|
||||
- arch: armv7
|
||||
march: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
emulation: true
|
||||
- runner: ubuntu-26.04
|
||||
arch: amd64
|
||||
- runner: ubuntu-26.04-arm
|
||||
arch: arm64
|
||||
- runner: ubuntu-26.04-arm
|
||||
arch: armv7
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
@@ -53,109 +49,83 @@ jobs:
|
||||
git_url: ${{ steps.build.outputs.git_url }}
|
||||
|
||||
steps:
|
||||
# yamllint disable rule:line-length
|
||||
- name: Setup podman
|
||||
env:
|
||||
PODMAN_VERSION: "v5.7.1"
|
||||
run: |
|
||||
sudo apt-get purge -y podman runc crun conmon
|
||||
|
||||
curl -fsSLO "https://github.com/mgoltzsche/podman-static/releases/download/${{ env.PODMAN_VERSION }}/podman-linux-${{ matrix.march }}.tar.gz"
|
||||
curl -fsSLO "https://github.com/mgoltzsche/podman-static/releases/download/${{ env.PODMAN_VERSION }}/podman-linux-${{ matrix.march }}.tar.gz.asc"
|
||||
gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 0CCF102C4F95D89E583FF1D4F8B5AF50344BB503
|
||||
gpg --batch --verify "podman-linux-${{ matrix.march }}.tar.gz.asc" "podman-linux-${{ matrix.march }}.tar.gz"
|
||||
|
||||
tar -xzf "podman-linux-${{ matrix.march }}.tar.gz"
|
||||
sudo cp -rfv ./podman-linux-${{ matrix.march }}/etc/. /etc/
|
||||
sudo cp -rfv ./podman-linux-${{ matrix.march }}/usr/. /usr/
|
||||
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
# yamllint enable rule:line-length
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: "${{ github.repository_owner }}"
|
||||
password: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: "${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
persist-credentials: "false"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-
|
||||
path: "./local/"
|
||||
|
||||
- name: Get date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y%m%d')" >>$GITHUB_OUTPUT
|
||||
|
||||
- name: Setup cache container
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "container-${{ matrix.arch }}-${{ steps.date.outputs.date }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
key: "container-${{ matrix.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
container-${{ matrix.arch }}-${{ steps.date.outputs.date }}-
|
||||
container-${{ matrix.arch }}-
|
||||
path: "/var/tmp/buildah-cache-*/*"
|
||||
|
||||
- if: ${{ matrix.emulation }}
|
||||
name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: "${{ github.repository_owner }}"
|
||||
password: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
env:
|
||||
OVERRIDE_ARCH: "${{ matrix.arch }}"
|
||||
run: make podman.build
|
||||
run: make container.build
|
||||
|
||||
test:
|
||||
name: Test (${{ matrix.arch }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
os: ubuntu-24.04
|
||||
emulation: false
|
||||
- arch: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
emulation: false
|
||||
- arch: armv7
|
||||
os: ubuntu-24.04-arm
|
||||
emulation: true
|
||||
- runner: ubuntu-26.04
|
||||
arch: amd64
|
||||
- runner: ubuntu-26.04-arm
|
||||
arch: arm64
|
||||
# FIXME: https://github.com/searxng/searxng/pull/6655#issuecomment-5550293085
|
||||
# - runner: ubuntu-26.04-arm
|
||||
# arch: armv7
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- if: ${{ matrix.emulation }}
|
||||
name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: "${{ github.repository_owner }}"
|
||||
password: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: "${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
OVERRIDE_ARCH: "${{ matrix.arch }}"
|
||||
@@ -165,7 +135,7 @@ jobs:
|
||||
release:
|
||||
if: github.repository_owner == 'searxng' && github.ref_name == 'master'
|
||||
name: Release
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
needs:
|
||||
- build
|
||||
- test
|
||||
@@ -174,24 +144,25 @@ jobs:
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
registry: "docker.io"
|
||||
username: "${{ secrets.DOCKER_USER }}"
|
||||
password: "${{ secrets.DOCKER_TOKEN }}"
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: "${{ github.repository_owner }}"
|
||||
password: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
registry: "docker.io"
|
||||
username: "${{ secrets.DOCKER_USER }}"
|
||||
password: "${{ secrets.DOCKER_TOKEN }}"
|
||||
ref: "${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
|
||||
25
.github/workflows/data-update.yml
vendored
25
.github/workflows/data-update.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
data:
|
||||
if: github.repository_owner == 'searxng'
|
||||
name: ${{ matrix.fetch }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -31,9 +31,8 @@ jobs:
|
||||
- update_external_bangs.py
|
||||
- update_firefox_version.py
|
||||
- update_engine_traits.py
|
||||
- update_wikidata_units.py
|
||||
- update_wikidata.py
|
||||
- update_engine_descriptions.py
|
||||
- update_gsa_useragents.py
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -41,17 +40,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
@@ -65,23 +64,17 @@ jobs:
|
||||
run: V=1 ./manage pyenv.cmd python "./searxng_extra/update/${{ matrix.fetch }}"
|
||||
|
||||
- name: Create PR
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
author: "searxng-bot <searxng-bot@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 }}"
|
||||
title: "[mod] data: update searx.data - ${{ matrix.fetch }}"
|
||||
commit-message: "[mod] data: update searx.data - ${{ matrix.fetch }}"
|
||||
branch: "ci-data-${{ matrix.fetch }}"
|
||||
delete-branch: "true"
|
||||
draft: "false"
|
||||
signoff: "false"
|
||||
body: |
|
||||
[data] update searx.data - ${{ matrix.fetch }}
|
||||
Update searx.data - ${{ matrix.fetch }}
|
||||
labels: |
|
||||
data
|
||||
|
||||
- name: Display information
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
12
.github/workflows/documentation.yml
vendored
12
.github/workflows/documentation.yml
vendored
@@ -25,25 +25,25 @@ jobs:
|
||||
release:
|
||||
if: github.repository_owner == 'searxng' || github.event_name == 'workflow_dispatch'
|
||||
name: Release
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
permissions:
|
||||
# for JamesIves/github-pages-deploy-action to push
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
@@ -61,11 +61,11 @@ jobs:
|
||||
|
||||
- if: github.ref_name == 'master'
|
||||
name: Release
|
||||
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
|
||||
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
|
||||
with:
|
||||
folder: "dist/docs"
|
||||
branch: "gh-pages"
|
||||
commit-message: "[doc] build from commit ${{ github.sha }}"
|
||||
commit-message: "[mod] docs: build from commit ${{ github.sha }}"
|
||||
# Automatically remove deleted files from the deploy branch
|
||||
clean: "true"
|
||||
single-commit: "true"
|
||||
|
||||
41
.github/workflows/integration.yml
vendored
41
.github/workflows/integration.yml
vendored
@@ -23,7 +23,7 @@ env:
|
||||
jobs:
|
||||
test:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
@@ -34,17 +34,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ matrix.python-version }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
@@ -59,37 +59,40 @@ jobs:
|
||||
|
||||
theme:
|
||||
name: Theme
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: "26"
|
||||
check-latest: "true"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: "./.nvmrc"
|
||||
|
||||
- name: Setup cache Node.js
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
with:
|
||||
key: "nodejs-${{ runner.arch }}-${{ hashFiles('./.nvmrc', './package.json') }}"
|
||||
path: "./client/simple/node_modules/"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-
|
||||
path: "./local/"
|
||||
|
||||
- name: Setup cache Node.js
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "nodejs-${{ runner.arch }}-${{ hashFiles('**/package-lock.json') }}"
|
||||
restore-keys: |
|
||||
nodejs-${{ runner.arch }}-
|
||||
path: "./client/simple/node_modules/"
|
||||
|
||||
- name: Setup venv
|
||||
run: make V=1 install
|
||||
|
||||
|
||||
30
.github/workflows/l10n.yml
vendored
30
.github/workflows/l10n.yml
vendored
@@ -26,27 +26,27 @@ env:
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: github.repository_owner == 'searxng' && github.event.workflow_run.conclusion == 'success'
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.repository_owner == 'searxng'
|
||||
name: Update
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
permissions:
|
||||
# For "make V=1 weblate.push.translations"
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
github.repository_owner == 'searxng'
|
||||
&& (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')
|
||||
name: Pull Request
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
permissions:
|
||||
# For "make V=1 weblate.translations.commit"
|
||||
contents: write
|
||||
@@ -83,18 +83,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
token: "${{ secrets.WEBLATE_GITHUB_TOKEN }}"
|
||||
fetch-depth: "0"
|
||||
|
||||
- name: Setup cache Python
|
||||
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
key: "python-${{ env.PYTHON_VERSION }}-${{ runner.arch }}-${{ hashFiles('./requirements*.txt') }}"
|
||||
restore-keys: |
|
||||
@@ -118,23 +118,17 @@ jobs:
|
||||
run: make V=1 weblate.translations.commit
|
||||
|
||||
- name: Create PR
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
author: "searxng-bot <searxng-bot@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"
|
||||
title: "[mod] i18n: update translations from Weblate"
|
||||
commit-message: "[mod] i18n: update translations from Weblate"
|
||||
branch: "translations_update"
|
||||
delete-branch: "true"
|
||||
draft: "false"
|
||||
signoff: "false"
|
||||
body: |
|
||||
[l10n] update translations from Weblate
|
||||
Update translations from Weblate
|
||||
labels: |
|
||||
area:i18n
|
||||
|
||||
- name: Display information
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
46
.github/workflows/security.yml
vendored
46
.github/workflows/security.yml
vendored
@@ -1,46 +0,0 @@
|
||||
---
|
||||
name: Security
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "42 05 * * *"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
container:
|
||||
if: github.repository_owner == 'searxng'
|
||||
name: Container
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: "false"
|
||||
|
||||
- name: Sync GHCS from Docker Scout
|
||||
uses: docker/scout-action@7520205ff60037fdc436b40b6a1d1e55a839ec2d # v1.22.0
|
||||
with:
|
||||
organization: "searxng"
|
||||
dockerhub-user: "${{ secrets.DOCKER_USER }}"
|
||||
dockerhub-password: "${{ secrets.DOCKER_TOKEN }}"
|
||||
image: "registry://ghcr.io/searxng/searxng:latest"
|
||||
command: "cves"
|
||||
sarif-file: "./scout.sarif"
|
||||
exit-code: "false"
|
||||
write-comment: "false"
|
||||
|
||||
- name: Upload SARIFs
|
||||
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
sarif_file: "./scout.sarif"
|
||||
2
Makefile
2
Makefile
@@ -63,7 +63,7 @@ format: format.python format.shell
|
||||
# wrap ./manage script
|
||||
|
||||
MANAGE += weblate.translations.commit weblate.push.translations
|
||||
MANAGE += data.all data.traits data.useragents data.gsa_useragents data.locales data.currencies
|
||||
MANAGE += data.all data.traits data.useragents data.locales data.currencies
|
||||
MANAGE += docs.html docs.live docs.gh-pages docs.prebuild docs.clean
|
||||
MANAGE += podman.build
|
||||
MANAGE += docker.build docker.buildx
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
this file is generated automatically by searxng_extra/update/update_pygments.py
|
||||
using pygments version 2.20.0:
|
||||
using pygments version 2.21.0:
|
||||
|
||||
./manage templates.simple.pygments
|
||||
*/
|
||||
@@ -114,14 +114,14 @@
|
||||
.gd { color: #FF4689 } /* Generic.Deleted */
|
||||
.ge { color: #F8F8F2; font-style: italic } /* Generic.Emph */
|
||||
.ges { color: #F8F8F2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
||||
.gr { color: #F8F8F2 } /* Generic.Error */
|
||||
.gr { color: #FF4689 } /* Generic.Error */
|
||||
.gh { color: #F8F8F2 } /* Generic.Heading */
|
||||
.gi { color: #A6E22E } /* Generic.Inserted */
|
||||
.go { color: #66D9EF } /* Generic.Output */
|
||||
.gp { color: #FF4689; font-weight: bold } /* Generic.Prompt */
|
||||
.gs { color: #F8F8F2; font-weight: bold } /* Generic.Strong */
|
||||
.gu { color: #959077 } /* Generic.Subheading */
|
||||
.gt { color: #F8F8F2 } /* Generic.Traceback */
|
||||
.gt { color: #66D9EF } /* Generic.Traceback */
|
||||
.kc { color: #66D9EF } /* Keyword.Constant */
|
||||
.kd { color: #66D9EF } /* Keyword.Declaration */
|
||||
.kn { color: #FF4689 } /* Keyword.Namespace */
|
||||
@@ -132,7 +132,7 @@
|
||||
.m { color: #AE81FF } /* Literal.Number */
|
||||
.s { color: #E6DB74 } /* Literal.String */
|
||||
.na { color: #A6E22E } /* Name.Attribute */
|
||||
.nb { color: #F8F8F2 } /* Name.Builtin */
|
||||
.nb { color: #A6E22E } /* Name.Builtin */
|
||||
.nc { color: #A6E22E } /* Name.Class */
|
||||
.no { color: #66D9EF } /* Name.Constant */
|
||||
.nd { color: #A6E22E } /* Name.Decorator */
|
||||
@@ -166,7 +166,7 @@
|
||||
.sr { color: #E6DB74 } /* Literal.String.Regex */
|
||||
.s1 { color: #E6DB74 } /* Literal.String.Single */
|
||||
.ss { color: #E6DB74 } /* Literal.String.Symbol */
|
||||
.bp { color: #F8F8F2 } /* Name.Builtin.Pseudo */
|
||||
.bp { color: #A6E22E } /* Name.Builtin.Pseudo */
|
||||
.fm { color: #A6E22E } /* Name.Function.Magic */
|
||||
.vc { color: #F8F8F2 } /* Name.Variable.Class */
|
||||
.vg { color: #F8F8F2 } /* Name.Variable.Global */
|
||||
|
||||
1826
client/simple/package-lock.json
generated
1826
client/simple/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,27 +23,27 @@
|
||||
"not dead"
|
||||
],
|
||||
"dependencies": {
|
||||
"ionicons": "^8.0.13",
|
||||
"ionicons": "^8.1.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"ol": "^10.9.0",
|
||||
"ol": "^10.10.0",
|
||||
"swiped-events": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.5.0",
|
||||
"@types/node": "^26.0.0",
|
||||
"browserslist": "^4.28.2",
|
||||
"@biomejs/biome": "2.5.12",
|
||||
"@types/node": "^26.5.0",
|
||||
"browserslist": "^4.28.8",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"edge.js": "^6.5.1",
|
||||
"less": "^4.6.6",
|
||||
"less": "^4.9.0",
|
||||
"mathjs": "^15.2.0",
|
||||
"sharp": "~0.35.1",
|
||||
"sharp": "~0.35.4",
|
||||
"sort-package-json": "^4.0.0",
|
||||
"stylelint": "^17.13.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"stylelint-config-standard-less": "^4.1.0",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"svgo": "^4.0.1",
|
||||
"typescript": "~6.0.3",
|
||||
"vite": "^8.0.16",
|
||||
"vite-bundle-analyzer": "^1.3.8"
|
||||
"svgo": "^4.1.0",
|
||||
"typescript": "~7.0.2",
|
||||
"vite": "^8.2.2",
|
||||
"vite-bundle-analyzer": "^1.3.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,7 @@ import { assertElement } from "../util/assertElement.ts";
|
||||
|
||||
const fetchResults = async (qInput: HTMLInputElement, query: string): Promise<void> => {
|
||||
try {
|
||||
let res: Response;
|
||||
|
||||
if (settings.method === "GET") {
|
||||
res = await http("GET", `./autocompleter?q=${query}`);
|
||||
} else {
|
||||
res = await http("POST", "./autocompleter", { body: new URLSearchParams({ q: query }) });
|
||||
}
|
||||
const res = await http("GET", `./autocompleter?q=${query}`);
|
||||
|
||||
const results = await res.json();
|
||||
|
||||
|
||||
@@ -35,8 +35,9 @@ const imageLoader = (resultElement: HTMLElement): void => {
|
||||
}, 1000) as unknown as number;
|
||||
};
|
||||
|
||||
const imageThumbnails: NodeListOf<HTMLImageElement> =
|
||||
document.querySelectorAll<HTMLImageElement>("#urls img.image_thumbnail");
|
||||
const imageThumbnails: NodeListOf<HTMLImageElement> = document.querySelectorAll<HTMLImageElement>(
|
||||
"#urls img.image_thumbnail, img.thumbnail"
|
||||
);
|
||||
for (const thumbnail of imageThumbnails) {
|
||||
if (thumbnail.complete && thumbnail.naturalWidth === 0) {
|
||||
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
|
||||
|
||||
@@ -80,7 +80,12 @@ export default class Calculator extends Plugin {
|
||||
|
||||
try {
|
||||
const node = Calculator.math.parse(searchInput.value);
|
||||
return `${node.toString()} = ${node.evaluate()}`;
|
||||
const value = node.evaluate();
|
||||
if (typeof value !== "number") {
|
||||
return;
|
||||
}
|
||||
|
||||
return `${node.toString()} = ${value}`;
|
||||
} catch {
|
||||
// not a compatible math expression
|
||||
return;
|
||||
|
||||
@@ -23,7 +23,7 @@ export const appendAnswerElement = (element: HTMLElement | string | number): voi
|
||||
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
const span = document.createElement("span");
|
||||
span.innerHTML = element.toString();
|
||||
span.textContent = element.toString();
|
||||
// biome-ignore lint/style/noParameterAssign: TODO
|
||||
element = span;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ghcr.io/searxng/base:searxng-builder AS builder
|
||||
FROM docker.io/searxng/base:searxng-builder AS builder
|
||||
|
||||
COPY ./requirements.txt ./requirements-server.txt ./
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ ARG CONTAINER_IMAGE_ORGANIZATION="searxng"
|
||||
ARG CONTAINER_IMAGE_NAME="searxng"
|
||||
|
||||
FROM localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:builder AS builder
|
||||
FROM ghcr.io/searxng/base:searxng AS dist
|
||||
FROM docker.io/searxng/base:searxng AS dist
|
||||
|
||||
COPY --chown=977:977 --from=builder /usr/local/searxng/.venv/ ./.venv/
|
||||
COPY --chown=977:977 --from=builder /usr/local/searxng/searx/ ./searx/
|
||||
|
||||
@@ -112,6 +112,15 @@ if [ "$(id -u)" -eq 0 ]; then
|
||||
fi
|
||||
|
||||
# ENVs aliases
|
||||
export GRANIAN_PORT="${SEARXNG_PORT:-$GRANIAN_PORT}"
|
||||
# https://github.com/searxng/searxng/issues/5934
|
||||
case "${SEARXNG_PORT:-}" in
|
||||
'') ;;
|
||||
*[!0-9]*)
|
||||
unset SEARXNG_PORT
|
||||
;;
|
||||
*)
|
||||
export GRANIAN_PORT="$SEARXNG_PORT"
|
||||
;;
|
||||
esac
|
||||
|
||||
exec /usr/local/searxng/.venv/bin/granian searx.webapp:app
|
||||
|
||||
@@ -283,12 +283,12 @@ container images are not officially supported):
|
||||
$ make container
|
||||
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
localhost/searxng/searxng 2025.8.1-3d96414 ... About a minute ago 183 MB
|
||||
localhost/searxng/searxng latest ... About a minute ago 183 MB
|
||||
localhost/searxng/searxng builder ... About a minute ago 524 MB
|
||||
ghcr.io/searxng/base searxng-builder ... 2 days ago 378 MB
|
||||
ghcr.io/searxng/base searxng ... 2 days ago 42.2 MB
|
||||
REPOSITORY TAG IMAGE ID SIZE
|
||||
localhost/searxng/searxng 2026.6.19-93f66bfb4 ... 265 MB
|
||||
localhost/searxng/searxng latest ... 265 MB
|
||||
localhost/searxng/searxng builder ... 687 MB
|
||||
docker.io/searxng/base searxng-builder ... 565 MB
|
||||
docker.io/searxng/base searxng ... 143 MB
|
||||
|
||||
Migrate from ``searxng-docker``
|
||||
===============================
|
||||
|
||||
@@ -29,10 +29,11 @@ By default and without any extensions, SearXNG serves these resolvers:
|
||||
- ``duckduckgo``
|
||||
- ``allesedv``
|
||||
- ``google``
|
||||
- ``kagi``
|
||||
- ``yandex``
|
||||
|
||||
With the above setting favicons are displayed, the user has the option to
|
||||
deactivate this feature in his settings. If the user is to have the option of
|
||||
deactivate this feature in their settings. If the user is to have the option of
|
||||
selecting from several *resolvers*, a further setting is required / but this
|
||||
setting will be discussed :ref:`later <register resolvers>` in this article,
|
||||
first we have to setup the favicons cache.
|
||||
@@ -208,6 +209,7 @@ choose from, the following configuration could be used:
|
||||
"duckduckgo" = "searx.favicons.resolvers.duckduckgo"
|
||||
"allesedv" = "searx.favicons.resolvers.allesedv"
|
||||
# "google" = "searx.favicons.resolvers.google"
|
||||
# "kagi" = "searx.favicons.resolvers.kagi"
|
||||
# "yandex" = "searx.favicons.resolvers.yandex"
|
||||
|
||||
.. note::
|
||||
@@ -226,6 +228,7 @@ into the *proxy*:
|
||||
- :py:obj:`searx.favicons.resolvers.duckduckgo`
|
||||
- :py:obj:`searx.favicons.resolvers.allesedv`
|
||||
- :py:obj:`searx.favicons.resolvers.google`
|
||||
- :py:obj:`searx.favicons.resolvers.kagi`
|
||||
- :py:obj:`searx.favicons.resolvers.yandex`
|
||||
|
||||
|
||||
|
||||
@@ -58,10 +58,9 @@ engine is shown. Most of the options have a default value or even are optional.
|
||||
|
||||
# overwrite values from section 'outgoing:'
|
||||
enable_http2: false
|
||||
enable_http3: false
|
||||
retries: 1
|
||||
max_connections: 100
|
||||
max_keepalive_connections: 10
|
||||
keepalive_expiry: 5.0
|
||||
using_tor_proxy: false
|
||||
proxies:
|
||||
http:
|
||||
@@ -163,6 +162,16 @@ engine is shown. Most of the options have a default value or even are optional.
|
||||
``enable_http`` : optional
|
||||
Enable HTTP for this engine (by default only HTTPS is enabled).
|
||||
|
||||
``enable_http3`` : optional
|
||||
Use HTTP/3 (falls back to HTTP/2). Default ``false``.
|
||||
Ignored when a proxy is set.
|
||||
|
||||
.. hint::
|
||||
|
||||
HTTP/3 places demands on the IP infrastructure that are not met in every
|
||||
environment. Enable this option only if you are aware of these requirements
|
||||
and the extent to which they are met.
|
||||
|
||||
``retry_on_http_error`` : optional
|
||||
Retry request on some HTTP status code.
|
||||
|
||||
@@ -179,20 +188,12 @@ engine is shown. Most of the options have a default value or even are optional.
|
||||
Using tor proxy (``true``) or not (``false``) for this engine. The default is
|
||||
taken from ``using_tor_proxy`` of the :ref:`settings outgoing`.
|
||||
|
||||
.. _Pool limit configuration: https://www.python-httpx.org/advanced/#pool-limit-configuration
|
||||
|
||||
``max_keepalive_connection#s`` :
|
||||
`Pool limit configuration`_, overwrites value ``pool_maxsize`` from
|
||||
:ref:`settings outgoing` for this engine.
|
||||
.. _Pool limit configuration: https://curl-cffi.readthedocs.io/en/latest/api.html#sessions
|
||||
|
||||
``max_connections`` :
|
||||
`Pool limit configuration`_, overwrites value ``pool_connections`` from
|
||||
:ref:`settings outgoing` for this engine.
|
||||
|
||||
``keepalive_expiry`` :
|
||||
`Pool limit configuration`_, overwrites value ``keepalive_expiry`` from
|
||||
:ref:`settings outgoing` for this engine.
|
||||
|
||||
|
||||
.. _private engines:
|
||||
|
||||
|
||||
@@ -12,20 +12,12 @@ Communication with search engines.
|
||||
request_timeout: 2.0 # default timeout in seconds, can be override by engine
|
||||
max_request_timeout: 10.0 # the maximum timeout in seconds
|
||||
useragent_suffix: "" # information like an email address to the administrator
|
||||
pool_connections: 100 # Maximum number of allowable connections, or null
|
||||
# for no limits. The default is 100.
|
||||
pool_maxsize: 10 # Number of allowable keep-alive connections, or null
|
||||
# to always allow. The default is 10.
|
||||
enable_http2: true # See https://www.python-httpx.org/http2/
|
||||
pool_connections: 100 # Maximum number of concurrent connections (default: 100)
|
||||
enable_http2: true # Enables the use of HTTP2
|
||||
# uncomment below section if you want to use a custom server certificate
|
||||
# see https://www.python-httpx.org/advanced/#changing-the-verification-defaults
|
||||
# and https://www.python-httpx.org/compatibility/#ssl-configuration
|
||||
# verify: ~/.mitmproxy/mitmproxy-ca-cert.cer
|
||||
#
|
||||
# uncomment below section if you want to use a proxyq see: SOCKS proxies
|
||||
# https://2.python-requests.org/en/latest/user/advanced/#proxies
|
||||
# are also supported: see
|
||||
# https://2.python-requests.org/en/latest/user/advanced/#socks
|
||||
# uncomment below section if you want to use a proxy
|
||||
#
|
||||
# proxies:
|
||||
# all://:
|
||||
@@ -46,30 +38,26 @@ Communication with search engines.
|
||||
timeout to load). Can be override by ``timeout`` in the :ref:`settings engines`.
|
||||
|
||||
``useragent_suffix`` :
|
||||
Suffix to the user-agent SearXNG uses to send requests to others engines. If an
|
||||
engine wish to block you, a contact info here may be useful to avoid that.
|
||||
Suffix to add when an engine's User-Agent is set via searxng_useragent().
|
||||
Contact info here may be useful to avoid an engine blocking you.
|
||||
|
||||
.. _Pool limit configuration: https://www.python-httpx.org/advanced/#pool-limit-configuration
|
||||
|
||||
``pool_maxsize``:
|
||||
Number of allowable keep-alive connections, or ``null`` to always allow. The
|
||||
default is 10. See ``max_keepalive_connections`` `Pool limit configuration`_.
|
||||
.. _Pool limit configuration: https://curl-cffi.readthedocs.io/en/latest/api.html#sessions
|
||||
|
||||
``pool_connections`` :
|
||||
Maximum number of allowable connections, or ``null`` # for no limits. The
|
||||
default is 100. See ``max_connections`` `Pool limit configuration`_.
|
||||
Maximum number of concurrent connections. The default is 100.
|
||||
See ``max_clients`` `Pool limit configuration`_.
|
||||
|
||||
``keepalive_expiry`` :
|
||||
Number of seconds to keep a connection in the pool. By default 5.0 seconds.
|
||||
See ``keepalive_expiry`` `Pool limit configuration`_.
|
||||
|
||||
.. _httpx proxies: https://www.python-httpx.org/advanced/#http-proxying
|
||||
.. _curl_cffi proxies: https://curl-cffi.readthedocs.io/en/latest/quick_start.html
|
||||
|
||||
``proxies`` :
|
||||
Define one or more proxies you wish to use, see `httpx proxies`_.
|
||||
Define one or more proxies you wish to use, see `curl_cffi proxies`_.
|
||||
If there are more than one proxy for one protocol (http, https),
|
||||
requests to the engines are distributed in a round-robin fashion.
|
||||
|
||||
HTTP, HTTPS, SOCKS4, SOCKS5 and SOCKS5h proxies are supported
|
||||
(``http://``, ``https://``, ``socks4://``, ``socks5://``, ``socks5h://``). You should
|
||||
use ``socks5h://`` when using Tor so hostnames are resolved by the proxy.
|
||||
|
||||
``source_ips`` :
|
||||
If you use multiple network interfaces, define from which IP the requests must
|
||||
be made. Example:
|
||||
@@ -87,18 +75,15 @@ Communication with search engines.
|
||||
different proxy and source ip.
|
||||
|
||||
``enable_http2`` :
|
||||
Enable by default. Set to ``false`` to disable HTTP/2.
|
||||
|
||||
.. _httpx verification defaults: https://www.python-httpx.org/advanced/#changing-the-verification-defaults
|
||||
.. _httpx ssl configuration: https://www.python-httpx.org/compatibility/#ssl-configuration
|
||||
Enable by default (HTTP/2). Set to ``false`` to force HTTP/1.1.
|
||||
HTTP/3 is opt-in per engine (``enable_http3``).
|
||||
|
||||
``verify``: : ``$SSL_CERT_FILE``, ``$SSL_CERT_DIR``
|
||||
Allow to specify a path to certificate.
|
||||
see `httpx verification defaults`_.
|
||||
HTTPS verification uses the OS's trust store by default.
|
||||
Set a path to use a custom CA file.
|
||||
|
||||
In addition to ``verify``, SearXNG supports the ``$SSL_CERT_FILE`` (for a file) and
|
||||
``$SSL_CERT_DIR`` (for a directory) OpenSSL variables.
|
||||
see `httpx ssl configuration`_.
|
||||
|
||||
``max_redirects`` :
|
||||
30 by default. Maximum redirect before it is an error.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
search:
|
||||
safe_search: 0
|
||||
autocomplete: ""
|
||||
autocomplete: "duckduckgo"
|
||||
favicon_resolver: ""
|
||||
default_lang: ""
|
||||
ban_time_on_fail: 5
|
||||
@@ -32,7 +32,7 @@
|
||||
- ``2``: Strict
|
||||
|
||||
``autocomplete``:
|
||||
Existing autocomplete backends, leave blank to turn it off.
|
||||
Existing autocomplete backends, set blank to turn it off.
|
||||
|
||||
- ``360search``
|
||||
- ``baidu``
|
||||
@@ -41,6 +41,7 @@
|
||||
- ``dbpedia``
|
||||
- ``duckduckgo``
|
||||
- ``google``
|
||||
- ``kagi``
|
||||
- ``mwmbl``
|
||||
- ``naver``
|
||||
- ``privacywall``
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
limiter: false
|
||||
public_instance: false
|
||||
image_proxy: false
|
||||
method: "POST"
|
||||
method: "GET"
|
||||
default_http_headers:
|
||||
X-Content-Type-Options : nosniff
|
||||
X-Download-Options : noopen
|
||||
@@ -58,8 +58,8 @@
|
||||
|
||||
``method`` : ``GET`` | ``POST``
|
||||
|
||||
HTTP method. By defaults ``POST`` is used / The ``POST`` method has the
|
||||
advantage with some WEB browsers that the history is not easy to read, but
|
||||
HTTP method. By default, ``GET`` is used / The ``POST`` method has the
|
||||
advantage with some browsers that the history is not saved, but
|
||||
there are also various disadvantages that sometimes **severely restrict the
|
||||
ease of use for the end user** (e.g. back button to jump back to the previous
|
||||
search page and drag & drop of search term to new tabs do not work as
|
||||
|
||||
@@ -143,7 +143,7 @@ parameters with default value can be redefined for special purposes.
|
||||
data dict ``{}``
|
||||
cookies dict ``{}``
|
||||
verify bool ``True``
|
||||
headers.User-Agent str a random User-Agent
|
||||
headers.User-Agent str ``''``
|
||||
category str current category, like ``'general'``
|
||||
safesearch int ``0``, between ``0`` and ``2`` (normal, moderate, strict)
|
||||
time_range Optional[str] ``None``, can be ``day``, ``week``, ``month``, ``year``
|
||||
@@ -229,6 +229,8 @@ following parameters can be used to specify a search request:
|
||||
max_redirects int maximum redirects, hard limit
|
||||
soft_max_redirects int maximum redirects, soft limit. Record an error but don't stop the engine
|
||||
raise_for_httperror bool True by default: raise an exception if the HTTP code of response is >= 300
|
||||
impersonate str curl_cffi impersonate target (default: chrome, none to disable)
|
||||
curl_options dict Any extra libcurl options for the request
|
||||
=================== =========== ==========================================================================
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
.. _cara engine:
|
||||
|
||||
===========
|
||||
Cara Images
|
||||
===========
|
||||
|
||||
.. automodule:: searx.engines.cara
|
||||
:members:
|
||||
8
docs/dev/engines/online/europepmc.rst
Normal file
8
docs/dev/engines/online/europepmc.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _europepmc engine:
|
||||
|
||||
==========
|
||||
Europe PMC
|
||||
==========
|
||||
|
||||
.. automodule:: searx.engines.europepmc
|
||||
:members:
|
||||
8
docs/dev/engines/online/exaapi.rst
Normal file
8
docs/dev/engines/online/exaapi.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _exaapi engine:
|
||||
|
||||
==============
|
||||
Exa API Engine
|
||||
==============
|
||||
|
||||
.. automodule:: searx.engines.exaapi
|
||||
:members:
|
||||
8
docs/dev/engines/online/jina.rst
Normal file
8
docs/dev/engines/online/jina.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _jina engine:
|
||||
|
||||
===========
|
||||
Jina Engine
|
||||
===========
|
||||
|
||||
.. automodule:: searx.engines.jina
|
||||
:members:
|
||||
@@ -1,8 +0,0 @@
|
||||
.. _engine presearch:
|
||||
|
||||
================
|
||||
Presearch Engine
|
||||
================
|
||||
|
||||
.. automodule:: searx.engines.presearch
|
||||
:members:
|
||||
8
docs/dev/engines/online/yandex_api.rst
Normal file
8
docs/dev/engines/online/yandex_api.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
.. _yandex api engine:
|
||||
|
||||
=================
|
||||
Yandex Search API
|
||||
=================
|
||||
|
||||
.. automodule:: searx.engines.yandex_api
|
||||
:members:
|
||||
@@ -80,8 +80,8 @@ same environment, here are a few examples::
|
||||
# to test one of the update scripts
|
||||
(dev.env)$ searxng_extra/update/update_engine_traits.py --help
|
||||
|
||||
# to test the update of the wikidata units
|
||||
(dev.env)$ searxng_extra/update/update_wikidata_units.py
|
||||
# to test the update of the wikidata units and property names
|
||||
(dev.env)$ searxng_extra/update/update_wikidata.py
|
||||
|
||||
|
||||
.. sidebar:: further read
|
||||
|
||||
@@ -286,7 +286,7 @@ content becomes smart.
|
||||
files & folders origin :origin:`docs/dev/reST.rst` ``:origin:`docs/dev/reST.rst```
|
||||
pull request :pull:`4` ``:pull:`4```
|
||||
patch :patch:`af2cae6` ``:patch:`af2cae6```
|
||||
PyPi package :pypi:`httpx` ``:pypi:`httpx```
|
||||
PyPi package :pypi:`curl_cffi` ``:pypi:`curl_cffi```
|
||||
manual page man :man:`bash` ``:man:`bash```
|
||||
intersphinx_
|
||||
--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -4,31 +4,32 @@
|
||||
Search API
|
||||
==========
|
||||
|
||||
SearXNG supports querying via a simple HTTP API.
|
||||
Two endpoints, ``/`` and ``/search``, are supported for both GET and POST methods.
|
||||
The GET method expects parameters as URL query parameters, while the POST method expects parameters as form data.
|
||||
SearXNG supports querying via a simple HTTP API. Two endpoints, ``/`` and
|
||||
``/search``, are supported for both GET and POST methods. The ``GET`` method
|
||||
expects parameters as URL query parameters, while the POST method expects
|
||||
parameters as form data (``application/x-www-form-urlencoded``).
|
||||
|
||||
If you want to consume the results as JSON, CSV, or RSS, you need to set the
|
||||
``format`` parameter accordingly. Supported formats are defined in ``settings.yml``, under the ``search`` section.
|
||||
Requesting an unset format will return a 403 Forbidden error. Be aware that many public instances have these formats disabled.
|
||||
|
||||
``format`` parameter accordingly. Supported formats are defined in
|
||||
``settings.yml``, under the :ref:`settings search` section. Requesting an
|
||||
unset format will return a 403 Forbidden error. Be aware that many public
|
||||
instances have these formats disabled.
|
||||
|
||||
Endpoints:
|
||||
|
||||
``GET /``
|
||||
``GET /search``
|
||||
.. code::
|
||||
|
||||
``POST /``
|
||||
``POST /search``
|
||||
GET /
|
||||
GET /search
|
||||
POST /
|
||||
POST /search
|
||||
|
||||
example cURL calls:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code:: bash
|
||||
|
||||
curl 'https://searx.example.org/search?q=searxng&format=json'
|
||||
|
||||
curl -X POST 'https://searx.example.org/search' -d 'q=searxng&format=csv'
|
||||
|
||||
curl -L -X POST -d 'q=searxng&format=json' 'https://searx.example.org/'
|
||||
|
||||
Parameters
|
||||
@@ -53,90 +54,27 @@ Parameters
|
||||
Comma separated list, specifies the active search categories (see
|
||||
:ref:`configured engines`)
|
||||
|
||||
``engines`` : optional
|
||||
Comma separated list, specifies the active search engines (see
|
||||
:ref:`configured engines`).
|
||||
|
||||
``language`` : default from :ref:`settings search`
|
||||
Code of the language.
|
||||
|
||||
``pageno`` : default ``1``
|
||||
Search page number.
|
||||
|
||||
``time_range`` : optional
|
||||
[ ``day``, ``month``, ``year`` ]
|
||||
|
||||
``time_range`` : optional : [ ``day``, ``month``, ``year`` ]
|
||||
Time range of search for engines which support it. See if an engine supports
|
||||
time range search in the preferences page of an instance.
|
||||
|
||||
``format`` : optional
|
||||
[ ``json``, ``csv``, ``rss`` ]
|
||||
|
||||
``format`` : optional : [ ``json``, ``csv``, ``rss`` ]
|
||||
Output format of results. Format needs to be activated in :ref:`settings
|
||||
search`.
|
||||
|
||||
``results_on_new_tab`` : default ``0``
|
||||
[ ``0``, ``1`` ]
|
||||
|
||||
Open search results on new tab.
|
||||
|
||||
``image_proxy`` : default from :ref:`settings server`
|
||||
[ ``True``, ``False`` ]
|
||||
|
||||
Proxy image results through SearXNG.
|
||||
|
||||
``autocomplete`` : default from :ref:`settings search`
|
||||
[ ``google``, ``dbpedia``, ``duckduckgo``, ``mwmbl``, ``startpage``,
|
||||
``privacywall``, ``wikipedia``, ``swisscows``, ``qwant`` ]
|
||||
|
||||
Service which completes words as you type.
|
||||
|
||||
``safesearch`` : default from :ref:`settings search`
|
||||
[ ``0``, ``1``, ``2`` ]
|
||||
|
||||
``safesearch`` : default from :ref:`settings search` : [ ``0``, ``1``, ``2`` ]
|
||||
Filter search results of engines which support safe search. See if an engine
|
||||
supports safe search in the preferences page of an instance.
|
||||
|
||||
``theme`` : default ``simple``
|
||||
[ ``simple`` ]
|
||||
|
||||
``theme`` : default ``simple`` : [ ``simple`` ]
|
||||
Theme of instance.
|
||||
|
||||
Please note, available themes depend on an instance. It is possible that an
|
||||
instance administrator deleted, created or renamed themes on their instance.
|
||||
See the available options in the preferences page of the instance.
|
||||
|
||||
``enabled_plugins`` : optional
|
||||
List of enabled plugins.
|
||||
|
||||
:default:
|
||||
``Hash_plugin``, ``Self_Information``,
|
||||
``Tracker_URL_remover``, ``Ahmia_blacklist``
|
||||
|
||||
:values:
|
||||
.. enabled by default
|
||||
|
||||
``Hash_plugin``, ``Self_Information``,
|
||||
``Tracker_URL_remover``, ``Ahmia_blacklist``,
|
||||
|
||||
.. disabled by default
|
||||
|
||||
``Hostnames_plugin``, ``Open_Access_DOI_rewrite``,
|
||||
``Vim-like_hotkeys``, ``Tor_check_plugin``
|
||||
|
||||
``disabled_plugins``: optional
|
||||
List of disabled plugins.
|
||||
|
||||
:default:
|
||||
``Hostnames_plugin``, ``Open_Access_DOI_rewrite``,
|
||||
``Vim-like_hotkeys``, ``Tor_check_plugin``
|
||||
|
||||
:values:
|
||||
see values from ``enabled_plugins``
|
||||
|
||||
``enabled_engines`` : optional : *all* :origin:`engines <searx/engines>`
|
||||
List of enabled engines.
|
||||
|
||||
``disabled_engines`` : optional : *all* :origin:`engines <searx/engines>`
|
||||
List of disabled engines.
|
||||
|
||||
|
||||
@@ -90,10 +90,10 @@ Scripts to update static data in :origin:`searx/data/`
|
||||
:members:
|
||||
|
||||
|
||||
``update_wikidata_units.py``
|
||||
``update_wikidata.py``
|
||||
============================
|
||||
|
||||
:origin:`[source] <searxng_extra/update/update_wikidata_units.py>`
|
||||
:origin:`[source] <searxng_extra/update/update_wikidata.py>`
|
||||
|
||||
.. automodule:: searxng_extra.update.update_wikidata_units
|
||||
.. automodule:: searxng_extra.update.update_wikidata
|
||||
:members:
|
||||
|
||||
@@ -20,15 +20,11 @@ If you don't trust anyone, you can set up your own, see :ref:`installation`.
|
||||
|
||||
- :ref:`self hosted <installation>`
|
||||
- :ref:`no user tracking / no profiling <SearXNG protect privacy>`
|
||||
- script & cookies are optional
|
||||
- secure, encrypted connections
|
||||
- javascript & cookies are optional
|
||||
- :ref:`{{engines | length}} search engines <configured engines>`
|
||||
- `58 translations <https://translate.codeberg.org/projects/searxng/searxng/>`_
|
||||
- about 70 `well maintained <https://uptime.searxng.org/>`__ instances on searx.space_
|
||||
- :ref:`easy integration of search engines <demo online engine>`
|
||||
- professional development: `CI <https://github.com/searxng/searxng/actions>`_,
|
||||
`quality assurance <https://dev.searxng.org/>`_ &
|
||||
`automated tested UI <https://dev.searxng.org/screenshots.html>`_
|
||||
|
||||
.. sidebar:: be a part
|
||||
|
||||
|
||||
2
manage
2
manage
@@ -48,7 +48,7 @@ PATH="${PY_ENV}/bin:${REPO_ROOT}/node_modules/.bin:${GOROOT}/bin:${GOPATH}/bin:$
|
||||
|
||||
PYOBJECTS="searx"
|
||||
PY_SETUP_EXTRAS='[test]'
|
||||
GECKODRIVER_VERSION="v0.36.0"
|
||||
GECKODRIVER_VERSION="v0.37.0"
|
||||
# SPHINXOPTS=
|
||||
BLACK_OPTIONS=("--target-version" "py311" "--line-length" "120" "--skip-string-normalization")
|
||||
BLACK_TARGETS=("--exclude" "(searx/static|searx/languages.py)" "--include" 'searxng.msg|\.pyi?$' "searx" "searxng_extra" "tests")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
mock==5.2.0
|
||||
nose2[coverage_plugin]==0.16.0
|
||||
cov-core==1.15.0
|
||||
black==25.9.0
|
||||
pylint==4.0.6
|
||||
black==26.5.1
|
||||
pylint==4.0.8
|
||||
splinter==0.21.0
|
||||
selenium==4.45.0
|
||||
selenium==4.48.0
|
||||
Sphinx==8.2.3;python_version <= "3.11"
|
||||
Sphinx==9.1.0; python_version > "3.11"
|
||||
sphinx-issues==6.0.0
|
||||
@@ -18,11 +18,11 @@ myst-parser==5.0.0
|
||||
linuxdoc==20260504
|
||||
aiounittest==1.5.0
|
||||
yamllint==1.38.0
|
||||
wlc==2.0.0
|
||||
wlc==2.1.1
|
||||
coloredlogs==15.0.1
|
||||
docutils>=0.21.2;python_version <= "3.11"
|
||||
docutils>=0.22.4; python_version > "3.11"
|
||||
parameterized==0.9.0
|
||||
granian[reload]==2.7.7
|
||||
basedpyright==1.39.8
|
||||
granian[reload]==2.8.2
|
||||
basedpyright==1.39.10
|
||||
types-lxml==2026.2.16
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
granian==2.7.7
|
||||
granian[pname]==2.7.7
|
||||
granian==2.8.2
|
||||
granian[pname]==2.8.2
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
certifi==2026.6.17
|
||||
certifi==2026.7.22
|
||||
babel==2.18.0
|
||||
flask-babel==4.0.0
|
||||
flask==3.1.3
|
||||
jinja2==3.1.6
|
||||
lxml==6.1.1
|
||||
pygments==2.20.0
|
||||
lxml==6.1.2
|
||||
pygments==2.21.0
|
||||
python-dateutil==2.9.0.post0
|
||||
pyyaml==6.0.3
|
||||
httpx[http2]==0.28.1
|
||||
httpx-socks[asyncio]==0.10.0
|
||||
sniffio==1.3.1
|
||||
curl_cffi==0.16.1
|
||||
valkey==6.1.1
|
||||
markdown-it-py==4.2.0
|
||||
msgspec==0.21.1
|
||||
typer==0.26.7
|
||||
typer==0.27.2
|
||||
isodate==0.7.2
|
||||
whitenoise==6.12.0
|
||||
typing-extensions==4.15.0
|
||||
typing-extensions==4.16.0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Implementation of the :py:obj:`preference <searx.preference>` settings."""
|
||||
|
||||
# pylint: disable = too-few-public-methods
|
||||
|
||||
import typing as t
|
||||
|
||||
@@ -38,7 +38,6 @@ area:
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__all__ = ["AnswererInfo", "Answerer", "AnswerStorage"]
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ from dataclasses import dataclass
|
||||
from searx.utils import load_module
|
||||
from searx.result_types.answer import BaseAnswer
|
||||
|
||||
|
||||
_default = pathlib.Path(__file__).parent
|
||||
log: logging.Logger = logging.getLogger("searx.answerers")
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from . import Answerer, AnswererInfo
|
||||
|
||||
def random_characters():
|
||||
random_string_letters = string.ascii_lowercase + string.digits + string.ascii_uppercase
|
||||
return [random.choice(random_string_letters) for _ in range(random.randint(8, 32))]
|
||||
return random.choices(random_string_letters, k=random.randint(8, 32))
|
||||
|
||||
|
||||
def random_string():
|
||||
|
||||
@@ -11,7 +11,7 @@ from urllib.parse import urlencode
|
||||
|
||||
import lxml.etree
|
||||
import lxml.html
|
||||
from httpx import HTTPError
|
||||
from curl_cffi.requests.exceptions import RequestException
|
||||
|
||||
from searx import settings
|
||||
from searx.engines import (
|
||||
@@ -21,6 +21,8 @@ from searx.engines import (
|
||||
from searx.network import get as http_get, post as http_post
|
||||
from searx.exceptions import SearxEngineResponseException
|
||||
from searx.utils import extr, gen_useragent
|
||||
from searx.data import ENGINE_TRAITS
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
@@ -60,8 +62,8 @@ def bing(query: str, _sxng_locale: str) -> list[str]:
|
||||
# bing search autocompleter
|
||||
base_url = "https://www.bing.com/AS/Suggestions?"
|
||||
# cvid has to be a 32 character long string consisting of numbers and uppsercase characters
|
||||
cvid = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
|
||||
response = get(base_url + urlencode({'qry': query, 'csr': 1, 'cvid': cvid}))
|
||||
cvid = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
|
||||
response = get(base_url + urlencode({'qry': query, 'csr': 1, 'cvid': cvid}), enable_http3=True)
|
||||
results: list[str] = []
|
||||
|
||||
if response.ok:
|
||||
@@ -81,7 +83,7 @@ def brave(query: str, _sxng_locale: str) -> list[str]:
|
||||
url = 'https://search.brave.com/api/suggest?'
|
||||
url += urlencode({'q': query})
|
||||
country = 'all'
|
||||
kwargs = {'cookies': {'country': country}}
|
||||
kwargs = {'cookies': {'country': country}, 'enable_http3': True}
|
||||
resp = get(url, **kwargs)
|
||||
results: list[str] = []
|
||||
|
||||
@@ -125,16 +127,17 @@ def duckduckgo(query: str, sxng_locale: str) -> list[str]:
|
||||
|
||||
|
||||
def google_complete(query: str, sxng_locale: str) -> list[str]:
|
||||
"""Autocomplete from Google. Supports Google's languages and subdomains
|
||||
"""Autocomplete from Google. Supports Google's languages
|
||||
(:py:obj:`searx.engines.google.get_google_info`) by using the async REST
|
||||
API::
|
||||
|
||||
https://{subdomain}/complete/search?{args}
|
||||
https://www.google.com/complete/search?{args}
|
||||
|
||||
"""
|
||||
|
||||
google_info: dict[str, t.Any] = google.get_google_info({'searxng_locale': sxng_locale}, engines['google'].traits)
|
||||
url = 'https://{subdomain}/complete/search?{args}'
|
||||
data = ENGINE_TRAITS.get("google") or {}
|
||||
traits = EngineTraits(**data)
|
||||
google_info: dict[str, t.Any] = google.get_google_info({'searxng_locale': sxng_locale}, traits)
|
||||
args = urlencode(
|
||||
{
|
||||
'q': query,
|
||||
@@ -144,7 +147,7 @@ def google_complete(query: str, sxng_locale: str) -> list[str]:
|
||||
)
|
||||
results: list[str] = []
|
||||
|
||||
resp = get(url.format(subdomain=google_info['subdomain'], args=args))
|
||||
resp = get('https://www.google.com/complete/search?' + args, enable_http3=True)
|
||||
if resp and resp.ok:
|
||||
json_txt = resp.text[resp.text.find('[') : resp.text.find(']', -3) + 1]
|
||||
data = json.loads(json_txt)
|
||||
@@ -153,6 +156,24 @@ def google_complete(query: str, sxng_locale: str) -> list[str]:
|
||||
return results
|
||||
|
||||
|
||||
def kagi(query: str, sxng_locale: str) -> list[str]:
|
||||
"""Autocomplete from Kagi."""
|
||||
|
||||
args: dict[str, str] = {'q': query}
|
||||
|
||||
if '-' in sxng_locale:
|
||||
args['r'] = sxng_locale.split('-')[1].lower()
|
||||
|
||||
resp = get("https://kagisuggest.com/api/autosuggest?" + urlencode(args))
|
||||
results: list[str] = []
|
||||
|
||||
if resp.ok:
|
||||
data = resp.json()
|
||||
if len(data) > 1:
|
||||
results = data[1]
|
||||
return results
|
||||
|
||||
|
||||
def mwmbl(query: str, _sxng_locale: str) -> list[str]:
|
||||
"""Autocomplete from Mwmbl_."""
|
||||
|
||||
@@ -376,6 +397,7 @@ backends: dict[str, t.Callable[[str, str], list[str]]] = {
|
||||
'dbpedia': dbpedia,
|
||||
'duckduckgo': duckduckgo,
|
||||
'google': google_complete,
|
||||
'kagi': kagi,
|
||||
'mwmbl': mwmbl,
|
||||
'naver': naver,
|
||||
'privacywall': privacywall,
|
||||
@@ -396,5 +418,5 @@ def search_autocomplete(backend_name: str, query: str, sxng_locale: str) -> list
|
||||
return []
|
||||
try:
|
||||
return backend(query, sxng_locale)
|
||||
except (HTTPError, SearxEngineResponseException):
|
||||
except (RequestException, SearxEngineResponseException):
|
||||
return []
|
||||
|
||||
@@ -5,7 +5,6 @@ Implementations used for bot detection.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__all__ = ["init", "dump_request", "get_network", "too_many_requests", "ProxyFix"]
|
||||
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ class Config:
|
||||
if default is UNSET:
|
||||
raise KeyError(name)
|
||||
return default
|
||||
(modulename, name) = str(fqn).rsplit('.', 1)
|
||||
modulename, name = str(fqn).rsplit('.', 1)
|
||||
m = __import__(modulename, {}, {}, [name], 0)
|
||||
return getattr(m, name)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ Accept_ header ..
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from ipaddress import (
|
||||
IPv4Network,
|
||||
IPv6Network,
|
||||
|
||||
@@ -14,7 +14,6 @@ bot if the Accept-Encoding_ header ..
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from ipaddress import (
|
||||
IPv4Network,
|
||||
IPv6Network,
|
||||
|
||||
@@ -11,7 +11,6 @@ if the Accept-Language_ header is unset.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from ipaddress import (
|
||||
IPv4Network,
|
||||
IPv6Network,
|
||||
|
||||
@@ -11,7 +11,6 @@ the Connection_ header is set to ``close``.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from ipaddress import (
|
||||
IPv4Network,
|
||||
IPv6Network,
|
||||
|
||||
@@ -20,6 +20,7 @@ Metadata`_. A request is filtered out in case of:
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ the User-Agent_ header is unset or matches the regular expression
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import re
|
||||
from ipaddress import (
|
||||
IPv4Network,
|
||||
@@ -25,7 +24,6 @@ import flask
|
||||
from . import config
|
||||
from ._helpers import too_many_requests
|
||||
|
||||
|
||||
USER_AGENT = (
|
||||
r'('
|
||||
+ r'unknown'
|
||||
|
||||
@@ -55,7 +55,6 @@ from ._helpers import (
|
||||
logger,
|
||||
)
|
||||
|
||||
|
||||
logger = logger.getChild('ip_limit')
|
||||
|
||||
BURST_WINDOW = 20
|
||||
|
||||
@@ -23,6 +23,7 @@ The ``ip_lists`` method implements :py:obj:`block-list <block_ip>` and
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
|
||||
|
||||
@@ -151,6 +151,6 @@ def get_token() -> str:
|
||||
if token:
|
||||
token = token.decode('UTF-8') # type: ignore
|
||||
else:
|
||||
token = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||
token = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
|
||||
valkey_client.set(TOKEN_KEY, token, ex=TOKEN_LIVE_TIME)
|
||||
return token
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Implementation of a middleware to determine the real IP of an HTTP request
|
||||
(:py:obj:`flask.request.remote_addr`) behind a proxy chain."""
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
|
||||
@@ -63,6 +64,20 @@ class ProxyFix:
|
||||
proxy_list: list[str] = cfg.get("botdetection.trusted_proxies", default=[])
|
||||
return [ip_network(net, strict=False) for net in proxy_list]
|
||||
|
||||
def is_trusted_proxy(
|
||||
self,
|
||||
addr: IPv4Address | IPv6Address | None,
|
||||
trusted_proxies: list[IPv4Network | IPv6Network],
|
||||
) -> bool:
|
||||
if addr is None:
|
||||
return False
|
||||
|
||||
for net in trusted_proxies:
|
||||
if addr.version == net.version and addr in net:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def trusted_remote_addr(
|
||||
self,
|
||||
x_forwarded_for: list[IPv4Address | IPv6Address],
|
||||
@@ -70,16 +85,8 @@ class ProxyFix:
|
||||
) -> str:
|
||||
# always rtl
|
||||
for addr in reversed(x_forwarded_for):
|
||||
trust: bool = False
|
||||
|
||||
for net in trusted_proxies:
|
||||
if addr.version == net.version and addr in net:
|
||||
logger.debug("trust proxy %s (member of %s)", addr, net)
|
||||
trust = True
|
||||
break
|
||||
|
||||
# client address
|
||||
if not trust:
|
||||
if not self.is_trusted_proxy(addr, trusted_proxies):
|
||||
logger.debug("client address from X-Forwarded-For: %s", addr)
|
||||
return addr.compressed
|
||||
|
||||
# fallback to first address
|
||||
@@ -95,19 +102,21 @@ class ProxyFix:
|
||||
# in this function!
|
||||
|
||||
orig_remote_addr: str | None = environ.pop("REMOTE_ADDR")
|
||||
orig_remote_ip: IPv4Address | IPv6Address | None = None
|
||||
|
||||
# Validate the IPs involved in this game and delete all invalid ones
|
||||
# from the WSGI environment.
|
||||
|
||||
if orig_remote_addr:
|
||||
try:
|
||||
addr = ip_address(orig_remote_addr)
|
||||
if addr.version == 6 and addr.ipv4_mapped:
|
||||
addr = addr.ipv4_mapped
|
||||
orig_remote_addr = addr.compressed
|
||||
orig_remote_ip = ip_address(orig_remote_addr)
|
||||
if orig_remote_ip.version == 6 and orig_remote_ip.ipv4_mapped:
|
||||
orig_remote_ip = orig_remote_ip.ipv4_mapped
|
||||
orig_remote_addr = orig_remote_ip.compressed
|
||||
except ValueError as exc:
|
||||
logger.error("REMOTE_ADDR: %s / discard REMOTE_ADDR from WSGI environment", exc)
|
||||
orig_remote_addr = None
|
||||
orig_remote_ip = None
|
||||
|
||||
x_real_ip: str | None = environ.get("HTTP_X_REAL_IP")
|
||||
if x_real_ip:
|
||||
@@ -141,11 +150,13 @@ class ProxyFix:
|
||||
if not x_forwarded_for and not x_real_ip:
|
||||
log_error_only_once("X-Forwarded-For nor X-Real-IP header is set!")
|
||||
|
||||
if x_forwarded_for and not trusted_proxies:
|
||||
if x_forwarded_for or x_real_ip:
|
||||
if not trusted_proxies:
|
||||
log_error_only_once("missing botdetection.trusted_proxies config")
|
||||
# without trusted_proxies, this variable is useless for determining
|
||||
# the real IP
|
||||
|
||||
if not self.is_trusted_proxy(orig_remote_ip, trusted_proxies):
|
||||
x_forwarded_for = []
|
||||
x_real_ip = None
|
||||
|
||||
# securing the WSGI environment variables that are adjusted
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Providing a Valkey database for the botdetection methods."""
|
||||
|
||||
|
||||
import valkey
|
||||
|
||||
__all__ = ["set_valkey_client", "get_valkey_client"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Implementations needed for a branding of SearXNG."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
# Struct fields aren't discovered in Python 3.14
|
||||
|
||||
@@ -48,7 +48,7 @@ class ExpireCacheCfg(msgspec.Struct): # pylint: disable=too-few-public-methods
|
||||
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: int = 60 * 60 # 1h
|
||||
"""Maintenance period in seconds / when :py:obj:`MAINTENANCE_MODE` is set to
|
||||
``auto``."""
|
||||
|
||||
@@ -458,12 +458,22 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache):
|
||||
# Before values are taken from the table, a maintenance interval may
|
||||
# need to be carried out.
|
||||
self.maintenance()
|
||||
sql = f"SELECT value FROM {table} WHERE key = ?"
|
||||
sql = f"SELECT value, expire FROM {table} WHERE key = ?"
|
||||
row = self.DB.execute(sql, (key,)).fetchone()
|
||||
if row is None:
|
||||
return default
|
||||
|
||||
return self.deserialize(row[0])
|
||||
# Check if value is expired. It's possible that it's expired but has not
|
||||
# yet been automatically deleted by the periodic maintenance
|
||||
value, expire = row
|
||||
now = time.time()
|
||||
if expire < now:
|
||||
# The record is deleted during the maintenance interval. Deleting
|
||||
# the record at this point offers no advantage, as a SELECT
|
||||
# statement must be executed for every cache.get request anyways.
|
||||
return default
|
||||
|
||||
return self.deserialize(value)
|
||||
|
||||
def pairs(self, ctx: str) -> Iterator[tuple[str, typing.Any]]:
|
||||
"""Iterate over key/value pairs from table given by argument ``ctx``.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
# limiter backward compatibility
|
||||
# ------------------------------
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
make data.all
|
||||
|
||||
"""
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
__all__ = ["ahmia_blacklist_loader", "gsa_useragents_loader", "data_dir", "get_cache"]
|
||||
__all__ = ["ahmia_blacklist_loader", "data_dir", "get_cache"]
|
||||
|
||||
import json
|
||||
import typing as t
|
||||
@@ -32,6 +33,13 @@ class WikiDataUnitType(t.TypedDict):
|
||||
to_si_factor: float
|
||||
|
||||
|
||||
WikiDataPropertyNameType = str | dict[str, str]
|
||||
"""Name of a Wikidata property. Can be either the plain name or a dictionary of
|
||||
language code to property name, e.g. ``{"en": "Date of birth"}``."""
|
||||
WikiDataPropertiesType = dict[str, WikiDataPropertyNameType]
|
||||
"""Dictionary from wikidata property ID to property name."""
|
||||
|
||||
|
||||
class LocalesType(t.TypedDict):
|
||||
"""Data structure of an item in ``locales.json``"""
|
||||
|
||||
@@ -41,6 +49,7 @@ class LocalesType(t.TypedDict):
|
||||
|
||||
USER_AGENTS: UserAgentType
|
||||
WIKIDATA_UNITS: dict[str, WikiDataUnitType]
|
||||
WIKIDATA_PROPERTIES: WikiDataPropertiesType
|
||||
TRACKER_PATTERNS: TrackerPatternsDB
|
||||
LOCALES: LocalesType
|
||||
CURRENCIES: CurrenciesDB
|
||||
@@ -52,24 +61,25 @@ ENGINE_DESCRIPTIONS: dict[str, dict[str, t.Any]]
|
||||
ENGINE_TRAITS: dict[str, dict[str, t.Any]]
|
||||
|
||||
|
||||
lazy_globals = {
|
||||
lazy_globals: dict[str, t.Any] = {
|
||||
"CURRENCIES": CurrenciesDB(),
|
||||
"USER_AGENTS": None,
|
||||
"EXTERNAL_URLS": None,
|
||||
"WIKIDATA_UNITS": None,
|
||||
"WIKIDATA_PROPERTIES": None,
|
||||
"EXTERNAL_BANGS": None,
|
||||
"OSM_KEYS_TAGS": None,
|
||||
"ENGINE_DESCRIPTIONS": None,
|
||||
"ENGINE_TRAITS": None,
|
||||
"LOCALES": None,
|
||||
"TRACKER_PATTERNS": TrackerPatternsDB(),
|
||||
"GSA_USER_AGENTS": None,
|
||||
}
|
||||
|
||||
data_json_files = {
|
||||
"USER_AGENTS": "useragents.json",
|
||||
"EXTERNAL_URLS": "external_urls.json",
|
||||
"WIKIDATA_UNITS": "wikidata_units.json",
|
||||
"WIKIDATA_PROPERTIES": "wikidata_properties.json",
|
||||
"EXTERNAL_BANGS": "external_bangs.json",
|
||||
"OSM_KEYS_TAGS": "osm_keys_tags.json",
|
||||
"ENGINE_DESCRIPTIONS": "engine_descriptions.json",
|
||||
@@ -106,24 +116,3 @@ def ahmia_blacklist_loader() -> list[str]:
|
||||
"""
|
||||
with open(data_dir / 'ahmia_blacklist.txt', encoding='utf-8') as f:
|
||||
return f.read().split()
|
||||
|
||||
|
||||
def gsa_useragents_loader() -> list[str]:
|
||||
"""Load data from `gsa_useragents.txt` and return a list of user agents
|
||||
suitable for Google. The user agents are fetched by::
|
||||
|
||||
searxng_extra/update/update_gsa_useragents.py
|
||||
|
||||
This function is used by :py:mod:`searx.engines.google`.
|
||||
|
||||
"""
|
||||
data = lazy_globals["GSA_USER_AGENTS"]
|
||||
if data is not None:
|
||||
return data
|
||||
|
||||
log.debug("init searx.data.%s", "GSA_USER_AGENTS")
|
||||
|
||||
with open(data_dir / 'gsa_useragents.txt', encoding='utf-8') as f:
|
||||
lazy_globals["GSA_USER_AGENTS"] = f.read().splitlines()
|
||||
|
||||
return lazy_globals["GSA_USER_AGENTS"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -288,7 +288,7 @@
|
||||
"oc": "Kwanza",
|
||||
"pa": "ਅੰਗੋਲਨ ਕਵਾਂਜ਼ਾ",
|
||||
"pl": "Kwanza",
|
||||
"pt": "Kwanza",
|
||||
"pt": "kwanza",
|
||||
"ru": "ангольская кванза",
|
||||
"si": "ක්වන්සා",
|
||||
"sr": "анголска кванза",
|
||||
@@ -334,6 +334,7 @@
|
||||
"ro": "Peso argentinian",
|
||||
"ru": "аргентинское песо",
|
||||
"sk": "Argentinské peso",
|
||||
"sl": "argentinski peso",
|
||||
"sr": "аргентински пезос",
|
||||
"sv": "Argentinsk peso",
|
||||
"ta": "ஆர்ஜென்டின பீசோ",
|
||||
@@ -2002,7 +2003,7 @@
|
||||
"eo": "ganaa cedio",
|
||||
"es": "cedi",
|
||||
"fi": "Cedi",
|
||||
"fr": "Cedi",
|
||||
"fr": "cedi",
|
||||
"ga": "cedi",
|
||||
"gl": "Cedi",
|
||||
"he": "סדי גאני",
|
||||
@@ -2952,7 +2953,7 @@
|
||||
"pap": "won nortkoreano",
|
||||
"pl": "won północnokoreański",
|
||||
"pt": "won norte-coreano",
|
||||
"ro": "Won nord-coreean",
|
||||
"ro": "won nord-coreean",
|
||||
"ru": "вона КНДР",
|
||||
"sk": "severokorejsky won",
|
||||
"sl": "severnokorejski von",
|
||||
@@ -3094,6 +3095,7 @@
|
||||
"ca": "tenge",
|
||||
"cs": "Tenge",
|
||||
"cy": "tenge Casachstan",
|
||||
"da": "Tenge",
|
||||
"de": "Tenge",
|
||||
"en": "Kazakhstani tenge",
|
||||
"eo": "kazaĥa tengo",
|
||||
@@ -4834,6 +4836,7 @@
|
||||
"nl": "Seychelse roepie",
|
||||
"pl": "Rupia seszelska",
|
||||
"pt": "rupia das Seicheles",
|
||||
"ro": "rupie seychelloză",
|
||||
"ru": "сейшельская рупия",
|
||||
"sk": "Seychelská rupia",
|
||||
"sl": "sejšelska rupija",
|
||||
@@ -5064,6 +5067,7 @@
|
||||
"nl": "Somalische shilling",
|
||||
"pl": "Szyling somalijski",
|
||||
"pt": "xelim somaliano",
|
||||
"ro": "șiling somalez",
|
||||
"ru": "сомалийский шиллинг",
|
||||
"sk": "Somálsky šiling",
|
||||
"sl": "somalski šiling",
|
||||
@@ -5883,6 +5887,7 @@
|
||||
"ja": "ドン",
|
||||
"ko": "베트남 동",
|
||||
"lt": "Vietnamo dongas",
|
||||
"ms": "Dồng Vietnam",
|
||||
"nl": "Vietnamese dong",
|
||||
"oc": "Dong",
|
||||
"pa": "ਵੀਅਤਨਾਮੀ ਦੋਙ",
|
||||
@@ -6122,7 +6127,8 @@
|
||||
"ro": "Gulden caraibian",
|
||||
"ru": "Карибский гульден",
|
||||
"sk": "Karibský gulden",
|
||||
"sl": "karibski goldinar"
|
||||
"sl": "karibski goldinar",
|
||||
"sv": "Karibisk gulden"
|
||||
},
|
||||
"XDR": {
|
||||
"ar": "حقوق السحب الخاصة",
|
||||
@@ -6724,6 +6730,8 @@
|
||||
"antilliaanse gulden": "ANG",
|
||||
"antilski gulden": "ANG",
|
||||
"aoa": "AOA",
|
||||
"apvienotās karalistes ekonomika": "GBP",
|
||||
"apvienotās karalistes saimniecība": "GBP",
|
||||
"apvienotās karalistes sterliņu mārciņa": "GBP",
|
||||
"ar": "MGA",
|
||||
"arab accounting dinar": "XAD",
|
||||
@@ -6836,6 +6844,7 @@
|
||||
"avustralya doları": "AUD",
|
||||
"awg": "AWG",
|
||||
"az arany mint befektetés": "XAU",
|
||||
"az egyesült királyság gazdasága": "GBP",
|
||||
"azerbaidžanin manat": "AZN",
|
||||
"azerbaidžano manatas": "AZN",
|
||||
"azerbaidžānas manats": "AZN",
|
||||
@@ -7019,6 +7028,7 @@
|
||||
"bir etíope": "ETB",
|
||||
"biras": "ETB",
|
||||
"birleşik arap emirlikleri dirhemi": "AED",
|
||||
"birleşik krallık ekonomisi": "GBP",
|
||||
"birma kjato": "MMK",
|
||||
"birr": "ETB",
|
||||
"birr da etiópia": "ETB",
|
||||
@@ -7106,15 +7116,19 @@
|
||||
"brit font": "GBP",
|
||||
"brita pundo": "GBP",
|
||||
"britaj pundoj": "GBP",
|
||||
"britannian talous": "GBP",
|
||||
"britanska funta": "GBP",
|
||||
"britanski funt": "GBP",
|
||||
"britische wirtschaft": "GBP",
|
||||
"britisches pfund": "GBP",
|
||||
"british economy": "GBP",
|
||||
"british pound": "GBP",
|
||||
"britisk pund": "GBP",
|
||||
"britiske pund": "GBP",
|
||||
"brits pond": "GBP",
|
||||
"britse pond": "GBP",
|
||||
"britská libra": "GBP",
|
||||
"brittisk ekonomi": "GBP",
|
||||
"brittiska pund": "GBP",
|
||||
"brittiskt pund": "GBP",
|
||||
"brunei doları": "BND",
|
||||
@@ -7198,6 +7212,7 @@
|
||||
"cedi du ghana": "GHS",
|
||||
"cedi ghana": "GHS",
|
||||
"cedi ghanese": "GHS",
|
||||
"cedi ghanéen": "GHS",
|
||||
"centr afrika franko": "XAF",
|
||||
"central african cfa franc": "XAF",
|
||||
"centralafrikansk cfa franc": "XAF",
|
||||
@@ -7300,7 +7315,6 @@
|
||||
"colón costa ricense": "CRC",
|
||||
"colón costa riquenho": "CRC",
|
||||
"colón costa riquense": "CRC",
|
||||
"colón costa riqueny": "CRC",
|
||||
"colón costaricain": "CRC",
|
||||
"colón costaricano": "CRC",
|
||||
"colón costaricien": "CRC",
|
||||
@@ -8413,6 +8427,7 @@
|
||||
"dólares canadenses": "CAD",
|
||||
"dólares estadounidenses": "USD",
|
||||
"dólares neozelandeses": "NZD",
|
||||
"dồng vietnam": "VND",
|
||||
"dram": "AMD",
|
||||
"dram armean": "AMD",
|
||||
"dram armenia": "AMD",
|
||||
@@ -8436,6 +8451,7 @@
|
||||
"droits de tirage speciaux": "XDR",
|
||||
"droits de tirage spéciaux": "XDR",
|
||||
"dschibuti franc": "DJF",
|
||||
"dvn": "VND",
|
||||
"dzd": "DZD",
|
||||
"dzsibuti frank": "DJF",
|
||||
"džibučio frankas": "DJF",
|
||||
@@ -8449,6 +8465,21 @@
|
||||
"eastern caribbean currency union": "XCD",
|
||||
"eastern caribbean dollar": "XCD",
|
||||
"ec$": "XCD",
|
||||
"economi'r deyrnas unedig": "GBP",
|
||||
"economia": "GBP",
|
||||
"economia del regne unit": "GBP",
|
||||
"economia del regno unito": "GBP",
|
||||
"economia del reialme unit": "GBP",
|
||||
"economia del reino unido": "GBP",
|
||||
"economia do reino unido": "GBP",
|
||||
"economia regatului unit": "GBP",
|
||||
"economie du royaume uni": "GBP",
|
||||
"economie van het verenigd koninkrijk": "GBP",
|
||||
"economía del reino unido": "GBP",
|
||||
"economía do reino unido": "GBP",
|
||||
"economy": "GBP",
|
||||
"economy of the uk": "GBP",
|
||||
"economy of the united kingdom": "GBP",
|
||||
"egipatska funta": "EGP",
|
||||
"egipta pundo": "EGP",
|
||||
"egipto svaras": "EGP",
|
||||
@@ -8468,6 +8499,12 @@
|
||||
"einr": "INR",
|
||||
"eiro": "EUR",
|
||||
"ekialdeko karibeko dolar": "XCD",
|
||||
"ekonomi britania raya": "GBP",
|
||||
"ekonomi united kingdom": "GBP",
|
||||
"ekonomie van die verenigde koninkryk": "GBP",
|
||||
"ekonomika spojeného království": "GBP",
|
||||
"ekonomika v spojenom kráľovstve": "GBP",
|
||||
"ekonomio de britujo": "GBP",
|
||||
"el peso": "GTQ",
|
||||
"emalangeni": "SZL",
|
||||
"emas sebagai pelaburan": "XAU",
|
||||
@@ -8499,6 +8536,7 @@
|
||||
"ermenistan dramı": "AMD",
|
||||
"ern": "ERN",
|
||||
"erreal brasildar": "BRL",
|
||||
"erresuma batuko ekonomia": "GBP",
|
||||
"errublo": "RUB",
|
||||
"errublo errusiar": "RUB",
|
||||
"errupia indiar": "INR",
|
||||
@@ -8569,6 +8607,8 @@
|
||||
"eyrir": "ISK",
|
||||
"e£": "EGP",
|
||||
"èuro": "EUR",
|
||||
"économie britannique": "GBP",
|
||||
"économie du royaume uni": "GBP",
|
||||
"észak ír font": "GBP",
|
||||
"észak koreai von": "KPW",
|
||||
"e₹": "INR",
|
||||
@@ -8702,6 +8742,9 @@
|
||||
"forintti": "HUF",
|
||||
"forinți": "HUF",
|
||||
"fòrint": "HUF",
|
||||
"förenade konungariket storbritannien och irlands ekonomi": "GBP",
|
||||
"förenade konungariket storbritannien och nordirlands ekonomi": "GBP",
|
||||
"förenade kungarikets ekonomi": "GBP",
|
||||
"franak cfp": "XPF",
|
||||
"franc": [
|
||||
"XPF",
|
||||
@@ -8954,6 +8997,9 @@
|
||||
"gold als kapitalanlage": "XAU",
|
||||
"gold as an investment": "XAU",
|
||||
"gold as currency": "XAU",
|
||||
"gospodarka wielkiej brytanii": "GBP",
|
||||
"gospodarstvo ujedinjenog kraljevstva": "GBP",
|
||||
"gospodarstvo združenega kraljestva": "GBP",
|
||||
"gourde": "HTG",
|
||||
"gourde haiti": "HTG",
|
||||
"gourde haitiano": "HTG",
|
||||
@@ -9373,6 +9419,7 @@
|
||||
"juaņs": "CNY",
|
||||
"juhokoréjsky won": "KRW",
|
||||
"juhosudánska libra": "SSP",
|
||||
"jungtinės karalystės ekonomika": "GBP",
|
||||
"jungtinių arabų emyratų dirhamas": "AED",
|
||||
"jungtinių valstijų doleris": "USD",
|
||||
"južnoafrički rand": "ZAR",
|
||||
@@ -9437,6 +9484,7 @@
|
||||
"karibi forint": "XCG",
|
||||
"karibia guldeno": "XCG",
|
||||
"karibischer gulden": "XCG",
|
||||
"karibisk gulden": "XCG",
|
||||
"karibski goldinar": "XCG",
|
||||
"karibský gulden": "XCG",
|
||||
"karipski gulden": "XCG",
|
||||
@@ -9497,6 +9545,9 @@
|
||||
"kina papua nugini": "PGK",
|
||||
"kina papuana": "PGK",
|
||||
"kina papuásia": "PGK",
|
||||
"kinh tế anh": "GBP",
|
||||
"kinh tế vương quốc anh": "GBP",
|
||||
"kinh tế vương quốc liên hiệp anh và bắc ireland": "GBP",
|
||||
"kip": "LAK",
|
||||
"kip laos": "LAK",
|
||||
"kip laosiano": "LAK",
|
||||
@@ -11141,6 +11192,7 @@
|
||||
"põhja korea won": "KPW",
|
||||
"põhja makedoonia denaar": "MKD",
|
||||
"prata como investimento": "XAG",
|
||||
"produits agricole de l'angleterre": "GBP",
|
||||
"pula": "BWP",
|
||||
"pula botswana": "BWP",
|
||||
"pula botswanais": "BWP",
|
||||
@@ -11191,6 +11243,7 @@
|
||||
"qatarisk rial": "QAR",
|
||||
"qäpik": "AZN",
|
||||
"qindarka": "ALL",
|
||||
"quanza": "AOA",
|
||||
"quetzal": "GTQ",
|
||||
"quetzal guatemala": "GTQ",
|
||||
"quetzal guatemalteco": "GTQ",
|
||||
@@ -11516,6 +11569,7 @@
|
||||
"rupia del pakistan": "PKR",
|
||||
"rupia dell'india": "INR",
|
||||
"rupia delle seychelles": "SCR",
|
||||
"rupia din seychelles": "SCR",
|
||||
"rupia do nepal": "NPR",
|
||||
"rupia do paquistão": "PKR",
|
||||
"rupia do seri lanca": "LKR",
|
||||
@@ -11571,6 +11625,7 @@
|
||||
],
|
||||
"rupie indiană": "INR",
|
||||
"rupie indiane": "INR",
|
||||
"rupie seychelloză": "SCR",
|
||||
"rupies índies": "INR",
|
||||
"rupija": [
|
||||
"NPR",
|
||||
@@ -12000,6 +12055,10 @@
|
||||
"sterliņu mārciņa": "GBP",
|
||||
"stērliņu mārciņa": "GBP",
|
||||
"stn": "STN",
|
||||
"storbritannien och irlands ekonomi": "GBP",
|
||||
"storbritannien och nordirlands ekonomi": "GBP",
|
||||
"storbritanniens ekonomi": "GBP",
|
||||
"storbritanniens økonomi": "GBP",
|
||||
"stredoafrický frank": "XAF",
|
||||
"středoafrický frank": "XAF",
|
||||
"sucre": "XSU",
|
||||
@@ -12049,6 +12108,7 @@
|
||||
"suriye lirası": "SYP",
|
||||
"suudi arabistan riyali": "SAR",
|
||||
"suudi riyali": "SAR",
|
||||
"suurbritannia majandus": "GBP",
|
||||
"suurbritannia nael": "GBP",
|
||||
"suurbritannia naelsterling": "GBP",
|
||||
"suvereni bolivar": "VES",
|
||||
@@ -12155,6 +12215,7 @@
|
||||
"švicarski frank": "CHF",
|
||||
"švýcarský frank": "CHF",
|
||||
"șekel nou": "ILS",
|
||||
"șiling somalez": "SOS",
|
||||
"şekel": "ILS",
|
||||
"şili pesosu": "CLP",
|
||||
"s₣": "CHF",
|
||||
@@ -12497,6 +12558,8 @@
|
||||
"uguiya": "MRU",
|
||||
"ugx": "UGX",
|
||||
"ui": "UYI",
|
||||
"uk economy": "GBP",
|
||||
"uk's economy": "GBP",
|
||||
"ukl": "GBP",
|
||||
"ukraina grivna": "UAH",
|
||||
"ukraina hrivno": "UAH",
|
||||
@@ -12537,6 +12600,8 @@
|
||||
"unidades de inversion": "MXV",
|
||||
"unidades de inversión": "MXV",
|
||||
"united arab emirates dirham": "AED",
|
||||
"united kingdom economy": "GBP",
|
||||
"united kingdom's economy": "GBP",
|
||||
"united states dollar": [
|
||||
"USN",
|
||||
"USD"
|
||||
@@ -12638,6 +12703,7 @@
|
||||
"venemaa rubla": "RUB",
|
||||
"venezuelai bolívar": "VES",
|
||||
"venezuelan digital bolívar": "VED",
|
||||
"verenigd koninkrijk economie": "GBP",
|
||||
"verenigde arabiese emirate dirham": "AED",
|
||||
"verenigde arabische emiraten dirham": "AED",
|
||||
"ves": "VES",
|
||||
@@ -12669,6 +12735,12 @@
|
||||
"wir euro": "CHE",
|
||||
"wir franc": "CHW",
|
||||
"wir franken": "CHW",
|
||||
"wirtschaft": "GBP",
|
||||
"wirtschaft des vereinigten königreichs": "GBP",
|
||||
"wirtschaft im vereinigten königreich": "GBP",
|
||||
"wirtschaft in dem vereinigten königreich": "GBP",
|
||||
"wirtschaft vom vereinigten königreich": "GBP",
|
||||
"wirtschaft von dem vereinigten königreich": "GBP",
|
||||
"wit russische roebel": "BYN",
|
||||
"won": "KRW",
|
||||
"won bắc triều tiên": "KPW",
|
||||
@@ -12762,6 +12834,7 @@
|
||||
"yeşil burun adaları eskudosu": "CVE",
|
||||
"yên nhật": "JPY",
|
||||
"yhdistyneen kuningaskunnan punta": "GBP",
|
||||
"yhdistyneen kuningaskunnan talous": "GBP",
|
||||
"yhdistyneiden arabiemiraattien dirhami": "AED",
|
||||
"yhdysvaltain dollari": "USD",
|
||||
"ytl": "TRY",
|
||||
@@ -13511,6 +13584,8 @@
|
||||
"египетский фунт": "EGP",
|
||||
"единая система региональных взаиморасчётов": "XSU",
|
||||
"единая система региональных взаиморасчетов": "XSU",
|
||||
"економіка великобританії": "GBP",
|
||||
"економіка великої британії": "GBP",
|
||||
"енглеска фунта": "GBP",
|
||||
"еритрейська накфа": "ERN",
|
||||
"еритрејска накфа": "ERN",
|
||||
@@ -13567,6 +13642,8 @@
|
||||
"израелски шекел": "ILS",
|
||||
"израильский новый шекель": "ILS",
|
||||
"източнокарибски долар": "XCD",
|
||||
"икономика на великобритания": "GBP",
|
||||
"икономика на обединеното кралство": "GBP",
|
||||
"индийска рупия": "INR",
|
||||
"индийская рупия": "INR",
|
||||
"индијска рупија": "INR",
|
||||
@@ -13980,6 +14057,7 @@
|
||||
"PLZ",
|
||||
"PLN"
|
||||
],
|
||||
"привреда уједињеног краљевства": "GBP",
|
||||
"пула": "BWP",
|
||||
"південно африканський ранд": "ZAR",
|
||||
"південнокорейська вона": "KRW",
|
||||
@@ -14067,6 +14145,7 @@
|
||||
"севернокорејски вон": "KPW",
|
||||
"северо корейская вона": "KPW",
|
||||
"северокорейская вона": "KPW",
|
||||
"седі": "GHS",
|
||||
"сейшел рупиясе": "SCR",
|
||||
"сейшелска рупия": "SCR",
|
||||
"сейшельская рупия": "SCR",
|
||||
@@ -14119,6 +14198,8 @@
|
||||
"старый румынский лей": "RON",
|
||||
"стерлинг фунты": "GBP",
|
||||
"стерлиң фунты": "GBP",
|
||||
"стопанство на великобритания": "GBP",
|
||||
"стопанство на обединеното кралство": "GBP",
|
||||
"суверен боливар": "VES",
|
||||
"суверенний болівар": "VES",
|
||||
"суверенный боливар": "VES",
|
||||
@@ -14369,6 +14450,7 @@
|
||||
"шриланкийска рупия": "LKR",
|
||||
"шриланчанска рупија": "LKR",
|
||||
"щатски долар": "USD",
|
||||
"экономика великобритании": "GBP",
|
||||
"эритрейская накфа": "ERN",
|
||||
"эритрея накфасы": "ERN",
|
||||
"эсватини лилангение": "SZL",
|
||||
@@ -14518,6 +14600,8 @@
|
||||
"יואן סיני": "CNY",
|
||||
"ין יפני": "JPY",
|
||||
"כארתולי לארי": "GEL",
|
||||
"כלכלת בריטניה": "GBP",
|
||||
"כלכלת הממלכה המאוחדת": "GBP",
|
||||
"כתר דני": "DKK",
|
||||
"כתר נורבגי": "NOK",
|
||||
"כתר נורווגי": "NOK",
|
||||
@@ -14665,6 +14749,7 @@
|
||||
"استثمار البلاتين": "XPT",
|
||||
"استثمار الذهب": "XAU",
|
||||
"استثمار الفضة": "XAG",
|
||||
"اقتصاد المملكة المتحدة": "GBP",
|
||||
"الاستثمار في الذهب": "XAU",
|
||||
"الأوقية الموريتانية": "MRU",
|
||||
"البات": "THB",
|
||||
@@ -14718,6 +14803,7 @@
|
||||
"أوقية": "MRU",
|
||||
"أوقية موريتانية": "MRU",
|
||||
"أوقيه موريتانيه": "MRU",
|
||||
"إقتصاد بريطانى": "GBP",
|
||||
"إيسكودو جزر الرأس الأخضر": "CVE",
|
||||
"بات": "THB",
|
||||
"بات تايلاندي": "THB",
|
||||
@@ -15100,6 +15186,7 @@
|
||||
"মালদ্বীপীয় রুফিয়াহ": "MVR",
|
||||
"মিয়ানমার ক্যত": "MMK",
|
||||
"মিশরীয় পাউন্ড": "EGP",
|
||||
"যুক্তরাজ্যের অর্থনীতি": "GBP",
|
||||
"রুশ রুবল": "RUB",
|
||||
"রেনমিনবি": "CNY",
|
||||
"রেন্মিন্বি": "CNY",
|
||||
@@ -15731,6 +15818,7 @@
|
||||
"엔": "JPY",
|
||||
"엔화": "JPY",
|
||||
"영국 파운드": "GBP",
|
||||
"영국의 경제": "GBP",
|
||||
"예멘 리알": "YER",
|
||||
"예멘 리얄": "YER",
|
||||
"예멘리얄": "YER",
|
||||
@@ -15938,9 +16026,11 @@
|
||||
"イエメン・リアル": "YER",
|
||||
"イエメン・リヤル": "YER",
|
||||
"イエメン・リヤール": "YER",
|
||||
"イギリスの経済": "GBP",
|
||||
"イギリスの通貨": "GBP",
|
||||
"イギリスポンド": "GBP",
|
||||
"イギリス・ポンド": "GBP",
|
||||
"イギリス経済": "GBP",
|
||||
"イラクの通貨": "IQD",
|
||||
"イラク・ディナール": "IQD",
|
||||
"イランの通貨": "IRR",
|
||||
@@ -16242,6 +16332,7 @@
|
||||
"英ポンド": "GBP",
|
||||
"西アフリカcfaフラン": "XOF",
|
||||
"豪ドル": "AUD",
|
||||
"財政・経済政策": "GBP",
|
||||
"越南銅": "VND",
|
||||
"金投資": "XAU",
|
||||
"韓国ウォン": "KRW",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Simple implementation to store TrackerPatterns data in a SQL database."""
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
import typing as t
|
||||
@@ -10,7 +11,7 @@ import re
|
||||
from collections.abc import Iterator
|
||||
from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
|
||||
|
||||
from httpx import HTTPError
|
||||
from curl_cffi.requests.exceptions import RequestException
|
||||
|
||||
from searx.data.core import get_cache, log
|
||||
from searx.network import get as http_get
|
||||
@@ -28,11 +29,11 @@ class TrackerPatternsDB:
|
||||
|
||||
ctx_name = "data_tracker_patterns"
|
||||
|
||||
CLEAR_LIST_URL = [
|
||||
# ClearURL rule lists, the first one that responds HTTP 200 is used
|
||||
"https://rules1.clearurls.xyz/data.minify.json",
|
||||
CLEAR_LIST_URL = [
|
||||
"https://cdn.jsdelivr.net/gh/clearurls/rules@refs/heads/gh-pages/data.minify.json",
|
||||
"https://rules2.clearurls.xyz/data.minify.json",
|
||||
"https://raw.githubusercontent.com/ClearURLs/Rules/refs/heads/master/data.min.json",
|
||||
"https://rules1.clearurls.xyz/data.minify.json",
|
||||
]
|
||||
|
||||
class Fields:
|
||||
@@ -87,8 +88,8 @@ class TrackerPatternsDB:
|
||||
try:
|
||||
resp = http_get(url, timeout=3)
|
||||
|
||||
except HTTPError as exc:
|
||||
log.warning("TRACKER_PATTERNS: HTTPError (%s) occured while fetching %s", url, exc)
|
||||
except RequestException as exc:
|
||||
log.warning("TRACKER_PATTERNS: RequestException while fetching %s: %s", url, exc)
|
||||
continue
|
||||
|
||||
if resp.status_code != 200:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
],
|
||||
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
|
||||
"versions": [
|
||||
"152.0",
|
||||
"151.0"
|
||||
"154.0",
|
||||
"153.0"
|
||||
]
|
||||
}
|
||||
23618
searx/data/wikidata_properties.json
Normal file
23618
searx/data/wikidata_properties.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3474,11 +3474,6 @@
|
||||
"symbol": "mm⁻²",
|
||||
"to_si_factor": 1e-06
|
||||
},
|
||||
"Q136039973": {
|
||||
"si_name": "Q6137407",
|
||||
"symbol": "FPS",
|
||||
"to_si_factor": 1.0
|
||||
},
|
||||
"Q1361854": {
|
||||
"si_name": "Q11570",
|
||||
"symbol": "dwt",
|
||||
@@ -3521,7 +3516,7 @@
|
||||
},
|
||||
"Q1377741": {
|
||||
"si_name": "Q25250",
|
||||
"symbol": "V_P",
|
||||
"symbol": "V<sub>P</sub>",
|
||||
"to_si_factor": 1.0429e+27
|
||||
},
|
||||
"Q1386162": {
|
||||
@@ -3694,6 +3689,11 @@
|
||||
"symbol": "apc",
|
||||
"to_si_factor": 0.0308568
|
||||
},
|
||||
"Q16068": {
|
||||
"si_name": null,
|
||||
"symbol": "DM",
|
||||
"to_si_factor": null
|
||||
},
|
||||
"Q160857": {
|
||||
"si_name": "Q25236",
|
||||
"symbol": "hp",
|
||||
@@ -3872,11 +3872,11 @@
|
||||
"Q180892": {
|
||||
"si_name": "Q11570",
|
||||
"symbol": "M☉",
|
||||
"to_si_factor": 1.9884e+30
|
||||
"to_si_factor": 1.988416e+30
|
||||
},
|
||||
"Q1811": {
|
||||
"si_name": "Q11573",
|
||||
"symbol": "AU",
|
||||
"symbol": "au",
|
||||
"to_si_factor": 149597870700.0
|
||||
},
|
||||
"Q1815100": {
|
||||
@@ -4454,6 +4454,11 @@
|
||||
"symbol": "ng",
|
||||
"to_si_factor": 1e-12
|
||||
},
|
||||
"Q2285395": {
|
||||
"si_name": null,
|
||||
"symbol": "dBW",
|
||||
"to_si_factor": null
|
||||
},
|
||||
"Q22934083": {
|
||||
"si_name": "Q25406",
|
||||
"symbol": "nC",
|
||||
@@ -5244,6 +5249,11 @@
|
||||
"symbol": "μA",
|
||||
"to_si_factor": 1e-06
|
||||
},
|
||||
"Q31274648": {
|
||||
"si_name": "Q6137407",
|
||||
"symbol": "FPS",
|
||||
"to_si_factor": 1.0
|
||||
},
|
||||
"Q3186734": {
|
||||
"si_name": "Q3186734",
|
||||
"symbol": "J/(m³ K)",
|
||||
@@ -6316,7 +6326,7 @@
|
||||
},
|
||||
"Q536785": {
|
||||
"si_name": "Q844211",
|
||||
"symbol": "ρ_P",
|
||||
"symbol": "ρ<sub>P</sub>",
|
||||
"to_si_factor": 5.155e+96
|
||||
},
|
||||
"Q53679433": {
|
||||
@@ -6971,7 +6981,7 @@
|
||||
},
|
||||
"Q685662": {
|
||||
"si_name": "Q44395",
|
||||
"symbol": "p_P",
|
||||
"symbol": "p<sub>P</sub>",
|
||||
"to_si_factor": 4.633e+113
|
||||
},
|
||||
"Q686163": {
|
||||
|
||||
@@ -47,7 +47,7 @@ ENGINES_CACHE: ExpireCacheSQLite = ExpireCacheSQLite.build_cache(
|
||||
ExpireCacheCfg(
|
||||
name="ENGINES_CACHE",
|
||||
MAXHOLD_TIME=60 * 60 * 24 * 7, # 7 days
|
||||
MAINTENANCE_PERIOD=60 * 60, # 2h
|
||||
MAINTENANCE_PERIOD=60 * 60, # 1h
|
||||
MAX_VALUE_LEN=1024 * 1024 * 1024, # 1MB
|
||||
)
|
||||
)
|
||||
@@ -305,7 +305,7 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
|
||||
|
||||
region: str = ""
|
||||
"""For an engine, when there is ``region: ...`` in the YAML settings the engine
|
||||
does support only this one region::
|
||||
does support only this one region:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
@@ -317,6 +317,9 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
|
||||
enable_http: bool
|
||||
"""Enable HTTP (by default only HTTPS is enabled)."""
|
||||
|
||||
enable_http3: bool = False
|
||||
"""Enables the use of HTTP/3 if available"""
|
||||
|
||||
shortcut: str
|
||||
"""Code used to execute bang requests (``!foo``)"""
|
||||
|
||||
@@ -361,37 +364,43 @@ class Engine(abc.ABC): # pylint: disable=too-few-public-methods
|
||||
https: socks5://proxy:port
|
||||
"""
|
||||
|
||||
def setup(self, engine_settings: dict[str, t.Any]) -> bool: # pylint: disable=unused-argument
|
||||
def setup(self, engine_settings: dict[str, t.Any]) -> bool | None: # pylint: disable=unused-argument
|
||||
"""Dynamic setup of the engine settings.
|
||||
|
||||
With this method, the engine's setup is carried out. For example, to
|
||||
check or dynamically adapt the values handed over in the parameter
|
||||
``engine_settings``. The return value (True/False) indicates whether
|
||||
the setup was successful and the engine can be built or rejected.
|
||||
``engine_settings``.
|
||||
|
||||
The method is optional and is called synchronously as part of the
|
||||
Whether the initialization was successful can be indicated by the return
|
||||
value ``True`` or even ``False``.
|
||||
|
||||
- If no return value (``None`` ) is given from this method , this is
|
||||
equivalent to ``True``.
|
||||
|
||||
- If an exception is thrown as part of the initialization, this is
|
||||
equivalent to ``False``.
|
||||
|
||||
The method is optional and is called **synchronously** as part of the
|
||||
initialization of the service and is therefore only suitable for simple
|
||||
(local) exams/changes at the engine setting. The :py:obj:`Engine.init`
|
||||
method must be used for longer tasks in which values of a remote must be
|
||||
determined, for example.
|
||||
(local) exams/changes at the engine setting.
|
||||
|
||||
The :py:obj:`Engine.init` method must be used for longer tasks in which
|
||||
values of a remote must be determined, for example.
|
||||
"""
|
||||
return True
|
||||
|
||||
def init(self, engine_settings: dict[str, t.Any]) -> bool | None: # pylint: disable=unused-argument
|
||||
"""Initialization of the engine.
|
||||
|
||||
The method is optional and asynchronous (in a thread). It is suitable,
|
||||
for example, for setting up a cache (for the engine) or for querying
|
||||
values (required by the engine) from a remote.
|
||||
The method is optional and called **asynchronous** (in a thread). The
|
||||
method is comparable to :py:obj:`Engine.setup`, it is suitable, for
|
||||
caching data that first needs to be requested from a remote.
|
||||
|
||||
Whether the initialization was successful can be indicated by the return
|
||||
value ``True`` or even ``False``.
|
||||
The method is optional and runs **asynchronously** (in a thread), it is
|
||||
comparable to :py:obj:`Engine.setup`. For instance, it is suitable for
|
||||
caching data that first needs to be requested from a remote source.
|
||||
|
||||
- If no return value is given from this init method (``None``), this is
|
||||
equivalent to ``True``.
|
||||
|
||||
- If an exception is thrown as part of the initialization, this is
|
||||
equivalent to ``False``.
|
||||
The evaluation of the return value is analogous to :py:obj:`Engine.setup`.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ from urllib.parse import urlencode
|
||||
from datetime import datetime
|
||||
|
||||
from searx.exceptions import SearxEngineAPIException
|
||||
from searx.utils import html_to_text, get_embeded_stream_url
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import html_to_text
|
||||
|
||||
about = {
|
||||
"website": "https://tv.360kan.com/",
|
||||
@@ -29,12 +30,12 @@ def request(query, params):
|
||||
return params
|
||||
|
||||
|
||||
def response(resp):
|
||||
def response(resp) -> EngineResults:
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception as e:
|
||||
raise SearxEngineAPIException(f"Invalid response: {e}") from e
|
||||
results = []
|
||||
res = EngineResults()
|
||||
|
||||
if "data" not in data or "result" not in data["data"]:
|
||||
raise SearxEngineAPIException("Invalid response")
|
||||
@@ -50,16 +51,15 @@ def response(resp):
|
||||
except (ValueError, TypeError):
|
||||
published_date = None
|
||||
|
||||
results.append(
|
||||
{
|
||||
'url': entry["play_url"],
|
||||
'title': html_to_text(entry["title"]),
|
||||
'content': html_to_text(entry["description"]),
|
||||
'template': 'videos.html',
|
||||
'publishedDate': published_date,
|
||||
'thumbnail': entry["cover_img"],
|
||||
"iframe_src": get_embeded_stream_url(entry["play_url"]),
|
||||
}
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
url=entry["play_url"],
|
||||
title=html_to_text(entry["title"]),
|
||||
content=html_to_text(entry["description"]),
|
||||
template='videos.html',
|
||||
publishedDate=published_date,
|
||||
thumbnail=entry["cover_img"],
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
return res
|
||||
|
||||
@@ -82,7 +82,7 @@ fragment SXNG_query on Query {
|
||||
|
||||
def setup(_) -> bool:
|
||||
global SXNG_query # pylint: disable=global-statement
|
||||
rand_str: str = "".join(random.choice(string.ascii_letters) for _ in range(5))
|
||||
rand_str: str = "".join(random.choices(string.ascii_letters, k=5))
|
||||
SXNG_query = SXNG_query.replace("SXNG_query", "PhotoSearchPaginationContainer_query_1" + rand_str)
|
||||
return True
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ categories: list[str]
|
||||
disabled: bool
|
||||
display_error_messages: bool
|
||||
enable_http: bool
|
||||
enable_http3: bool
|
||||
engine_type: str
|
||||
inactive: bool
|
||||
max_page: int
|
||||
|
||||
@@ -187,8 +187,9 @@ def set_loggers(engine: "Engine|types.ModuleType", engine_name: str):
|
||||
def update_engine_attributes(engine: "Engine | types.ModuleType", engine_data: dict[str, t.Any]):
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
# set engine attributes from engine_data
|
||||
# set / update engine attributes from engine_data
|
||||
kvargs: dict[str, t.Any]
|
||||
engine.about = getattr(engine, "about", EngineAbout())
|
||||
if isinstance(engine.about, EngineAbout):
|
||||
kvargs = {**msgspec.to_builtins(engine.about), **engine_data.get("about", {})}
|
||||
else:
|
||||
@@ -269,21 +270,27 @@ def is_engine_active(engine: "Engine | types.ModuleType"):
|
||||
|
||||
|
||||
def call_engine_setup(engine: "Engine | types.ModuleType", engine_data: dict[str, t.Any]) -> bool:
|
||||
setup_ok = False
|
||||
|
||||
setup_ok: bool | None = False
|
||||
setup_func = getattr(engine, "setup", None)
|
||||
|
||||
if setup_func is None:
|
||||
setup_ok = True
|
||||
elif not callable(setup_func):
|
||||
logger.error("engine's setup method isn't a callable (is of type: %s)", type(setup_func))
|
||||
logger.error(f"engine's setup method isn't a callable (is of type: {type(setup_func)})")
|
||||
else:
|
||||
try:
|
||||
setup_ok = engine.setup(engine_data)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.exception('exception : {0}'.format(e))
|
||||
logger.exception(f"(PID {os.getpid()}) {engine.name}: engine SETUP failed, exception: {e}")
|
||||
setup_ok = False
|
||||
|
||||
# The evaluation of the return value is analogous to Engine.init
|
||||
if setup_ok is None:
|
||||
setup_ok = True
|
||||
|
||||
if not setup_ok:
|
||||
logger.error("%s: Engine setup was not successful, engine is set to inactive.", engine.name)
|
||||
logger.error(f"(PID {os.getpid()}) {engine.name}: engine setup was not successful")
|
||||
return setup_ok
|
||||
|
||||
|
||||
@@ -311,14 +318,16 @@ def load_engines(engine_list: list[dict[str, t.Any]]):
|
||||
for engine_data in engine_list:
|
||||
if engine_data.get("inactive") is True:
|
||||
continue
|
||||
|
||||
engine = load_engine(engine_data)
|
||||
|
||||
if engine:
|
||||
register_engine(engine)
|
||||
else:
|
||||
# if an engine can't be loaded (if for example the engine is missing
|
||||
# tor or some other requirements) its set to inactive!
|
||||
logger.error(
|
||||
f"(PID {os.getpid()}) loading engine %s failed: set engine to inactive!", engine_data.get("name", "???")
|
||||
f"(PID {os.getpid()}) {engine_data.get('name', '???')}: can't register engine (loading engine failed)"
|
||||
)
|
||||
engine_data["inactive"] = True
|
||||
return engines
|
||||
|
||||
@@ -83,7 +83,7 @@ def extract_video_data(video_block):
|
||||
published_date = None
|
||||
if create_time:
|
||||
try:
|
||||
published_date = datetime.strptime(create_time.strip(), "%Y-%m-%d")
|
||||
published_date = datetime.fromisoformat(create_time.strip())
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ Implementation
|
||||
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
from datetime import datetime, timedelta
|
||||
from urllib.parse import urlencode
|
||||
|
||||
@@ -85,7 +86,7 @@ Additional subcategories:
|
||||
# Do we need support for "free_collection" and "include_stock_enterprise"?
|
||||
|
||||
|
||||
def init(_):
|
||||
def setup(_: dict[str, t.Any]) -> bool | None:
|
||||
if not categories:
|
||||
raise ValueError("adobe_stock engine: categories is unset")
|
||||
|
||||
@@ -100,9 +101,9 @@ def init(_):
|
||||
raise ValueError("adobe_stock engine: adobe_content_types is unset")
|
||||
|
||||
if isinstance(adobe_content_types, list):
|
||||
for t in adobe_content_types:
|
||||
if t not in ADOBE_VALID_TYPES:
|
||||
raise ValueError("adobe_stock engine: adobe_content_types: '%s' is invalid" % t)
|
||||
for content_type in adobe_content_types:
|
||||
if content_type not in ADOBE_VALID_TYPES:
|
||||
raise ValueError("adobe_stock engine: adobe_content_types: '%s' is invalid" % content_type)
|
||||
else:
|
||||
raise ValueError(
|
||||
"adobe_stock engine: adobe_content_types must be a list of strings not %s" % type(adobe_content_types)
|
||||
|
||||
@@ -109,7 +109,7 @@ def response(resp: "SXNG_Response") -> EngineResults:
|
||||
comments_elements = eval_xpath_getindex(entry, xpath_comment, 0, default=None)
|
||||
comments: str = "" if comments_elements is None else comments_elements.text
|
||||
|
||||
publishedDate = datetime.strptime(eval_xpath_getindex(entry, xpath_published, 0).text, "%Y-%m-%dT%H:%M:%SZ")
|
||||
publishedDate = datetime.fromisoformat(eval_xpath_getindex(entry, xpath_published, 0).text.rstrip("Z"))
|
||||
|
||||
res.add(
|
||||
res.types.Paper(
|
||||
|
||||
@@ -25,6 +25,7 @@ To use this engine, add an entry similar to the following to your engine list in
|
||||
https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app
|
||||
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from searx.enginelib import EngineCache
|
||||
|
||||
@@ -49,6 +49,9 @@ CACHE: EngineCache
|
||||
|
||||
|
||||
def setup(engine_settings: dict[str, t.Any]) -> bool:
|
||||
if baidu_category not in ('general', 'images', 'it'):
|
||||
raise SearxEngineAPIException(f"Unsupported category: {baidu_category}")
|
||||
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_settings["name"])
|
||||
return True
|
||||
@@ -65,11 +68,6 @@ def get_image_cookies(headers: dict[str, str]) -> dict[str, str]:
|
||||
return cookies
|
||||
|
||||
|
||||
def init(_):
|
||||
if baidu_category not in ('general', 'images', 'it'):
|
||||
raise SearxEngineAPIException(f"Unsupported category: {baidu_category}")
|
||||
|
||||
|
||||
def request(query, params):
|
||||
page_num = params["pageno"]
|
||||
|
||||
@@ -186,7 +184,7 @@ def parse_images(data):
|
||||
img_date = item.get("bdImgnewsDate")
|
||||
publishedDate = None
|
||||
if img_date:
|
||||
publishedDate = datetime.strptime(img_date, "%Y-%m-%d %H:%M")
|
||||
publishedDate = datetime.fromisoformat(img_date)
|
||||
results.append(
|
||||
{
|
||||
"template": "images.html",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""BASE (Scholar publications)"""
|
||||
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ base_url = "https://api.bilibili.com/x/web-interface/search/type"
|
||||
|
||||
cookie = {
|
||||
"innersign": "0",
|
||||
"buvid3": "".join(random.choice(string.hexdigits) for _ in range(16)) + "infoc",
|
||||
"buvid3": "".join(random.choices(string.hexdigits, k=16)) + "infoc",
|
||||
"i-wanna-go-back": "-1",
|
||||
"b_ut": "7",
|
||||
"FEED_LIVE_VERSION": "V8",
|
||||
|
||||
@@ -40,6 +40,7 @@ about: dict[str, t.Any] = {
|
||||
# engine dependent config
|
||||
categories = ["general", "web"]
|
||||
safesearch = True
|
||||
enable_http3 = True
|
||||
_safesearch_map: dict[int, str] = {
|
||||
0: "off",
|
||||
1: "moderate",
|
||||
@@ -61,8 +62,8 @@ def get_locale_params(engine_region: str | None) -> dict[str, str] | None:
|
||||
|
||||
The ``mkt`` parameter takes a full ``<language>-<country>`` code.
|
||||
|
||||
This function is shared with :py:mod:`searx.engines.bing_images`,
|
||||
:py:mod:`searx.engines.bing_news`, and :py:mod:`searx.engines.bing_videos`.
|
||||
This function is shared with :py:mod:`searx.engines.bing_news`, and
|
||||
:py:mod:`searx.engines.bing_videos`.
|
||||
"""
|
||||
|
||||
if not engine_region or engine_region == "clear":
|
||||
@@ -71,43 +72,21 @@ def get_locale_params(engine_region: str | None) -> dict[str, str] | None:
|
||||
return {"mkt": engine_region}
|
||||
|
||||
|
||||
def override_accept_language(params: "OnlineParams", engine_region: str | None) -> None:
|
||||
"""Override the ``Accept-Language`` header.
|
||||
|
||||
The default header built by :py:class:`~searx.search.processors.online.OnlineProcessor`
|
||||
appends ``en;q=0.3`` as a fallback language::
|
||||
|
||||
Accept-Language: de,de-DE;q=0.7,en;q=0.3
|
||||
|
||||
Bing seems to better select the results locale based on the
|
||||
``Accept-Language`` value header.
|
||||
|
||||
This function is shared with :py:mod:`searx.engines.bing_images`,
|
||||
:py:mod:`searx.engines.bing_news`, and :py:mod:`searx.engines.bing_videos`.
|
||||
"""
|
||||
|
||||
if not engine_region or engine_region == "clear":
|
||||
return
|
||||
|
||||
lang = engine_region.split("-")[0]
|
||||
params["headers"]["Accept-Language"] = f"{engine_region},{lang};q=0.9"
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
"""Assemble a Bing-Web request."""
|
||||
|
||||
engine_region = traits.get_region(params["searxng_locale"], traits.all_locale)
|
||||
|
||||
override_accept_language(params, engine_region)
|
||||
|
||||
query_params: dict[str, str | int] = {
|
||||
"q": query,
|
||||
"adlt": _safesearch_map.get(params.get("safesearch", 0), "off"),
|
||||
}
|
||||
|
||||
locale_params = get_locale_params(engine_region)
|
||||
if locale_params:
|
||||
query_params.update(locale_params)
|
||||
if engine_region and engine_region != "clear":
|
||||
lang, _, cc = engine_region.partition("-")
|
||||
query_params["setlang"] = lang
|
||||
if cc and cc not in ("us", "cn", "ru"): # bing just sends junk for these
|
||||
query_params["cc"] = cc
|
||||
|
||||
params["url"] = f"{base_url}/search?{urlencode(query_params)}"
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Bing-Images: description see :py:obj:`searx.engines.bing`."""
|
||||
|
||||
import typing as t
|
||||
import json
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from lxml import html
|
||||
|
||||
from searx.engines.bing import ( # pylint: disable=unused-import
|
||||
fetch_traits,
|
||||
get_locale_params,
|
||||
override_accept_language,
|
||||
)
|
||||
from searx.engines.bing import fetch_traits # pylint: disable=unused-import
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
# about
|
||||
about = {
|
||||
"website": "https://www.bing.com/images",
|
||||
"wikidata_id": "Q182496",
|
||||
@@ -22,9 +24,9 @@ about = {
|
||||
"results": "HTML",
|
||||
}
|
||||
|
||||
# engine dependent config
|
||||
categories = ["images", "web"]
|
||||
paging = True
|
||||
enable_http3 = True
|
||||
safesearch = True
|
||||
time_range_support = True
|
||||
time_map = {
|
||||
@@ -38,26 +40,27 @@ base_url = "https://www.bing.com"
|
||||
"""Bing-Image search URL"""
|
||||
|
||||
|
||||
def request(query, params):
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
"""Assemble a Bing-Image request."""
|
||||
|
||||
engine_region = traits.get_region(params["searxng_locale"], traits.all_locale)
|
||||
|
||||
override_accept_language(params, engine_region)
|
||||
# build URL query / example:
|
||||
# https://www.bing.com/images/async?q=foo&mmasync=1&first=1&count=35
|
||||
|
||||
# build URL query
|
||||
# - example: https://www.bing.com/images/async?q=foo&async=1&first=1&count=35
|
||||
query_params = {
|
||||
"q": query,
|
||||
"async": "1",
|
||||
"mmasync": "1",
|
||||
# to simplify the page count lets use the default of 35 images per page
|
||||
"first": (int(params.get("pageno", 1)) - 1) * 35 + 1,
|
||||
"count": 35,
|
||||
}
|
||||
|
||||
locale_params = get_locale_params(engine_region)
|
||||
if locale_params:
|
||||
query_params.update(locale_params)
|
||||
if engine_region and engine_region != "clear":
|
||||
lang, _, cc = engine_region.partition("-")
|
||||
query_params["setlang"] = lang
|
||||
if cc:
|
||||
query_params["cc"] = cc
|
||||
|
||||
# time range
|
||||
# - example: one year (525600 minutes) 'qft=filterui:age-lt525600'
|
||||
@@ -67,10 +70,10 @@ def request(query, params):
|
||||
params["url"] = base_url + "/images/async?" + urlencode(query_params)
|
||||
|
||||
|
||||
def response(resp):
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
"""Get response from Bing-Image"""
|
||||
|
||||
results = []
|
||||
res = EngineResults()
|
||||
|
||||
dom = html.fromstring(resp.text)
|
||||
|
||||
@@ -81,19 +84,22 @@ def response(resp):
|
||||
|
||||
metadata = json.loads(result.xpath('.//a[@class="iusc"]/@m')[0])
|
||||
title = " ".join(result.xpath('.//div[@class="infnmpt"]//a/text()')).strip()
|
||||
if not title:
|
||||
title = result.xpath('.//div[@class="infnmpt"]//a/@title')[0]
|
||||
|
||||
img_format = " ".join(result.xpath('.//div[@class="imgpt"]/div/span/text()')).strip().split(" · ")
|
||||
source = " ".join(result.xpath('.//div[@class="imgpt"]//div[@class="lnkw"]//a/text()')).strip()
|
||||
results.append(
|
||||
{
|
||||
"template": "images.html",
|
||||
"url": metadata["purl"],
|
||||
"thumbnail_src": metadata["turl"],
|
||||
"img_src": metadata["murl"],
|
||||
"content": metadata.get("desc"),
|
||||
"title": title,
|
||||
"source": source,
|
||||
"resolution": img_format[0],
|
||||
"img_format": img_format[1] if len(img_format) >= 2 else None,
|
||||
}
|
||||
|
||||
res.add(
|
||||
res.types.Image(
|
||||
title=title,
|
||||
url=metadata["purl"],
|
||||
thumbnail_src=metadata["turl"],
|
||||
img_src=metadata["murl"],
|
||||
content=metadata.get("desc"),
|
||||
source=source,
|
||||
resolution=img_format[0],
|
||||
img_format=img_format[1] if len(img_format) >= 2 else "",
|
||||
)
|
||||
return results
|
||||
)
|
||||
return res
|
||||
|
||||
@@ -12,10 +12,7 @@ from urllib.parse import urlencode
|
||||
from lxml import html
|
||||
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
from searx.engines.bing import (
|
||||
get_locale_params,
|
||||
override_accept_language,
|
||||
)
|
||||
from searx.engines.bing import get_locale_params
|
||||
from searx.utils import eval_xpath, eval_xpath_getindex, eval_xpath_list, extract_text
|
||||
|
||||
# about
|
||||
@@ -33,6 +30,7 @@ categories = ["news"]
|
||||
paging = True
|
||||
"""If go through the pages and there are actually no new results for another
|
||||
page, then bing returns the results from the last page again."""
|
||||
enable_http3 = True
|
||||
|
||||
time_range_support = True
|
||||
time_map = {
|
||||
@@ -53,8 +51,6 @@ def request(query, params):
|
||||
|
||||
engine_region = traits.get_region(params["searxng_locale"], traits.all_locale)
|
||||
|
||||
override_accept_language(params, engine_region)
|
||||
|
||||
# build URL query
|
||||
# - example: https://www.bing.com/news/infinitescrollajax?q=london&first=1
|
||||
page = int(params.get("pageno", 1)) - 1
|
||||
|
||||
@@ -9,7 +9,6 @@ from lxml import html
|
||||
from searx.engines.bing import ( # pylint: disable=unused-import
|
||||
fetch_traits,
|
||||
get_locale_params,
|
||||
override_accept_language,
|
||||
)
|
||||
from searx.engines.bing_images import time_map
|
||||
from searx.utils import eval_xpath, eval_xpath_getindex
|
||||
@@ -26,6 +25,7 @@ about = {
|
||||
# engine dependent config
|
||||
categories = ["videos", "web"]
|
||||
paging = True
|
||||
enable_http3 = True
|
||||
safesearch = True
|
||||
time_range_support = True
|
||||
|
||||
@@ -38,8 +38,6 @@ def request(query, params):
|
||||
|
||||
engine_region = traits.get_region(params["searxng_locale"], traits.all_locale)
|
||||
|
||||
override_accept_language(params, engine_region)
|
||||
|
||||
# build URL query
|
||||
# - example: https://www.bing.com/videos/asyncv2?q=foo&async=content&first=1&count=35
|
||||
query_params = {
|
||||
|
||||
@@ -44,7 +44,7 @@ def response(resp):
|
||||
"url": 'https://www.bitchute.com/video/' + item['video_id'],
|
||||
"content": html_to_text(item['description']),
|
||||
"author": item['channel']['channel_name'],
|
||||
"publishedDate": datetime.strptime(item["date_published"], "%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
"publishedDate": datetime.fromisoformat(item["date_published"].rstrip("Z")),
|
||||
"length": item['duration'],
|
||||
"views": item['view_count'],
|
||||
"thumbnail": item['thumbnail_url'],
|
||||
|
||||
@@ -45,7 +45,7 @@ CACHE_SESSION_ID_KEY = "session_id_key"
|
||||
KEYWORD_RE = re.compile(r"\[\/?Keyword\]")
|
||||
|
||||
|
||||
def init(engine_settings: dict[str, t.Any]) -> bool:
|
||||
def setup(engine_settings: dict[str, t.Any]) -> bool:
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_name=engine_settings["name"])
|
||||
return True
|
||||
@@ -104,7 +104,7 @@ def response(resp: "SXNG_Response") -> EngineResults:
|
||||
title=_remove_keyword_marker(result["Subject"]),
|
||||
content=_remove_keyword_marker(result["Text"]),
|
||||
url=result["Url"],
|
||||
publishedDate=datetime.strptime(result["Published"], "%Y-%m-%d %H:%M:%S"),
|
||||
publishedDate=datetime.fromisoformat(result["Published"]),
|
||||
metadata=gettext.gettext("Posted by {author}").format(author=result["Author"]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -135,7 +135,7 @@ from searx.utils import (
|
||||
eval_xpath_getindex,
|
||||
eval_xpath_list,
|
||||
extract_text,
|
||||
get_embeded_stream_url,
|
||||
get_embedded_stream_url,
|
||||
js_obj_str_to_json_str,
|
||||
js_obj_str_to_python,
|
||||
)
|
||||
@@ -151,6 +151,7 @@ about = {
|
||||
|
||||
base_url = "https://search.brave.com/"
|
||||
categories = []
|
||||
enable_http3 = True
|
||||
brave_category: t.Literal["search", "videos", "images", "news", "goggles"] = "search"
|
||||
"""Brave supports common web-search, videos, images, news, and goggles search.
|
||||
|
||||
@@ -247,13 +248,13 @@ def extract_json_data(text: str) -> dict[str, t.Any]:
|
||||
# node_ids: [0, 19],
|
||||
# data: [{type:"data",data: .... ["q","goggles_id"],route:1,url:1}}]
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
text = text[text.index("<script") : text.index("</script")]
|
||||
if not text:
|
||||
raise ValueError("can't find JS/JSON data in the given text")
|
||||
# form: null,
|
||||
# error: null
|
||||
# });
|
||||
start = text.index("data: [{")
|
||||
end = text.rindex("}}]")
|
||||
js_obj_str = text[start:end]
|
||||
js_obj_str = "{" + js_obj_str + "}}]}"
|
||||
newline = text.index("\n", start)
|
||||
end = text.rindex("}}]", start, newline)
|
||||
js_obj_str = "{" + text[start:end] + "}}]}"
|
||||
# js_obj_str = js_obj_str.replace("\xa0", "") # remove ASCII for
|
||||
# js_obj_str = js_obj_str.replace(r"\u003C", "<").replace(r"\u003c", "<") # fix broken HTML tags in strings
|
||||
json_str = js_obj_str_to_json_str(js_obj_str)
|
||||
@@ -338,7 +339,7 @@ def _parse_search(resp: SXNG_Response) -> EngineResults:
|
||||
if len(video_tag):
|
||||
# In my tests a video tag in the WEB search was most often not a
|
||||
# video, except the ones from youtube ..
|
||||
iframe_src = get_embeded_stream_url(url)
|
||||
iframe_src = get_embedded_stream_url(url)
|
||||
if iframe_src:
|
||||
item["iframe_src"] = iframe_src
|
||||
item["template"] = "videos.html"
|
||||
@@ -353,14 +354,14 @@ def _parse_news(resp: SXNG_Response) -> EngineResults:
|
||||
res = EngineResults()
|
||||
dom = html.fromstring(resp.text)
|
||||
|
||||
for result in eval_xpath_list(dom, "//div[contains(@class, 'results')]//div[@data-type='news']"):
|
||||
url = eval_xpath_getindex(result, ".//a[contains(@class, 'result-header')]/@href", 0, default=None)
|
||||
for result in eval_xpath_list(dom, "//div[@data-type='news']"):
|
||||
url = eval_xpath_getindex(result, ".//a/@href", 0, default=None)
|
||||
if url is None:
|
||||
continue
|
||||
|
||||
title = eval_xpath_list(result, ".//span[contains(@class, 'snippet-title')]")
|
||||
content = eval_xpath_list(result, ".//p[contains(@class, 'desc')]")
|
||||
thumbnail = eval_xpath_getindex(result, ".//div[contains(@class, 'image-wrapper')]//img/@src", 0, default="")
|
||||
title = eval_xpath_list(result, ".//div[contains(@class, 'title')]")
|
||||
content = eval_xpath_list(result, ".//div[contains(@class, 'description')]")
|
||||
thumbnail = eval_xpath_getindex(result, ".//a[contains(@class, 'thumbnail')]//img/@src", 0, default="")
|
||||
|
||||
item = res.types.LegacyResult(
|
||||
template="default.html",
|
||||
@@ -406,9 +407,6 @@ def _parse_videos(json_resp: dict[str, t.Any]) -> EngineResults:
|
||||
)
|
||||
if result["thumbnail"] is not None:
|
||||
item["thumbnail"] = result["thumbnail"]["src"]
|
||||
iframe_src = get_embeded_stream_url(result["url"])
|
||||
if iframe_src:
|
||||
item["iframe_src"] = iframe_src
|
||||
|
||||
res.add(item)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from dateutil import parser
|
||||
|
||||
from searx.exceptions import SearxEngineAPIException
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import html_to_text
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
@@ -39,7 +40,7 @@ if t.TYPE_CHECKING:
|
||||
about = {
|
||||
"website": "https://api.search.brave.com/",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": "https://api-dashboard.search.brave.com/documentation",
|
||||
"official_api_documentation": "https://api-dashboard.search.brave.com/api-reference/web/search/get",
|
||||
"use_official_api": True,
|
||||
"require_api_key": True,
|
||||
"results": "JSON",
|
||||
@@ -62,8 +63,10 @@ base_url = "https://api.search.brave.com/res/v1/web/search"
|
||||
time_range_map = {"day": "past_day", "week": "past_week", "month": "past_month", "year": "past_year"}
|
||||
"""Mapping of SearXNG time ranges to Brave API time ranges."""
|
||||
|
||||
max_page = 10
|
||||
|
||||
def init(_):
|
||||
|
||||
def setup(_: dict[str, t.Any]) -> bool | None:
|
||||
"""Initialize the engine."""
|
||||
if not api_key:
|
||||
raise SearxEngineAPIException("No API key provided")
|
||||
@@ -74,7 +77,8 @@ def request(query: str, params: "OnlineParams") -> None:
|
||||
search_args: dict[str, str | int | None] = {
|
||||
"q": query,
|
||||
"count": results_per_page,
|
||||
"offset": (params["pageno"] - 1) * results_per_page,
|
||||
"offset": params["pageno"] - 1,
|
||||
"text_decorations": False,
|
||||
}
|
||||
|
||||
# Apply time filter if specified
|
||||
@@ -87,6 +91,7 @@ def request(query: str, params: "OnlineParams") -> None:
|
||||
|
||||
params["url"] = f"{base_url}?{urlencode(search_args)}"
|
||||
params["headers"]["X-Subscription-Token"] = api_key
|
||||
params["headers"]["Accept"] = "application/json"
|
||||
|
||||
|
||||
def _extract_published_date(published_date_raw: str):
|
||||
@@ -112,14 +117,19 @@ def response(resp: "SXNG_Response") -> EngineResults:
|
||||
res = EngineResults()
|
||||
data = resp.json()
|
||||
|
||||
for result in data.get("web", {}).get("results", []):
|
||||
for result in (data.get("web") or {}).get("results", []):
|
||||
thumbnail_obj = result.get("thumbnail")
|
||||
thumbnail = ""
|
||||
if thumbnail_obj and not thumbnail_obj.get("logo", False):
|
||||
thumbnail = thumbnail_obj.get("src") or ""
|
||||
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["url"],
|
||||
title=result["title"],
|
||||
content=result.get("description", ""),
|
||||
title=html_to_text(result["title"]),
|
||||
content=html_to_text(result.get("description", "")),
|
||||
publishedDate=_extract_published_date(result.get("age")),
|
||||
thumbnail=result.get("thumbnail", {}).get("src"),
|
||||
thumbnail=thumbnail,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# pylint: disable=invalid-name
|
||||
"""Cara_ is a social media and portfolio-sharing platform for artists and art
|
||||
enthusiasts.
|
||||
|
||||
With the widespread use of generative AI, Cara_ decided to build a place that
|
||||
filters out gen AI images so that people searching for authentic creatives and
|
||||
images can do so easily.
|
||||
|
||||
.. _Cara: https://cara.app/about
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import typing as t
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
about = {
|
||||
"website": "https://cara.app",
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
base_url = "https://cara.app"
|
||||
images_url = "https://images.cara.app"
|
||||
|
||||
categories = ["images"]
|
||||
paging = True
|
||||
results_per_page = 24
|
||||
|
||||
# if using HTTP2, we get blocked immediately
|
||||
enable_http2 = False
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
args = {
|
||||
"q": query,
|
||||
"sortBy": "Top",
|
||||
"take": results_per_page,
|
||||
"skip": (params["pageno"] - 1) * results_per_page,
|
||||
}
|
||||
params["url"] = f"{base_url}/api/search/portfolio-posts?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
json_data: list[dict[str, t.Any]] = resp.json()
|
||||
|
||||
for result in json_data:
|
||||
thumbnail, img = None, None
|
||||
|
||||
i: dict[str, str]
|
||||
for i in result["images"]:
|
||||
if thumbnail is None or i["isCoverImg"]:
|
||||
thumbnail = i
|
||||
|
||||
if img is None or not i["isCoverImg"]:
|
||||
img = i
|
||||
|
||||
if not thumbnail or not img:
|
||||
continue
|
||||
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
{
|
||||
"template": "images.html",
|
||||
"url": f"{base_url}/post/{result['id']}",
|
||||
"thumbnail_src": f"{images_url}/{thumbnail['src']}?height=256",
|
||||
"img_src": f"{images_url}/{img['src']}",
|
||||
"title": result["title"],
|
||||
"content": result["content"],
|
||||
"author": result["name"],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
@@ -41,7 +41,7 @@ search_index = "cw22"
|
||||
<https://www.chatnoir.eu/docs/api-general>`_ for a full list."""
|
||||
|
||||
|
||||
def _obtain_api_key() -> tuple[str, str, str]:
|
||||
def _obtain_api_key() -> tuple[str, str]:
|
||||
home_resp = get(base_url)
|
||||
if not home_resp.ok:
|
||||
raise SearxEngineAPIException("failed to obtain api key")
|
||||
@@ -58,10 +58,9 @@ def _obtain_api_key() -> tuple[str, str, str]:
|
||||
)
|
||||
if not token_resp.ok:
|
||||
raise SearxEngineAPIException("failed to obtain api key")
|
||||
session_id = token_resp.cookies["sessionid"]
|
||||
scraped_api_key = token_resp.json()["token"]["token"]
|
||||
|
||||
return csrf_token, session_id, scraped_api_key
|
||||
return csrf_token, scraped_api_key
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
@@ -73,7 +72,7 @@ def request(query: str, params: "OnlineParams"):
|
||||
|
||||
params["headers"].update(headers)
|
||||
else:
|
||||
csrf_token, session_id, scraped_api_key = _obtain_api_key()
|
||||
csrf_token, scraped_api_key = _obtain_api_key()
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {scraped_api_key}",
|
||||
@@ -81,10 +80,11 @@ def request(query: str, params: "OnlineParams"):
|
||||
}
|
||||
|
||||
params["headers"].update(headers)
|
||||
params["cookies"] = {"csrftoken": session_id, "sessionid": session_id}
|
||||
params["cookies"] = {"csrftoken": csrf_token}
|
||||
|
||||
params["url"] = f"{base_url}/api/v1/_search"
|
||||
params["method"] = "POST"
|
||||
params["impersonate"] = "none"
|
||||
|
||||
json_data = {
|
||||
"query": query,
|
||||
|
||||
@@ -43,7 +43,7 @@ def response(resp):
|
||||
|
||||
publishedDate = None
|
||||
if recipe['submissionDate']:
|
||||
publishedDate = datetime.strptime(result['recipe']['submissionDate'][:19], "%Y-%m-%dT%H:%M:%S")
|
||||
publishedDate = datetime.fromisoformat(result['recipe']['submissionDate'][:19])
|
||||
|
||||
content = [
|
||||
f"Schwierigkeitsstufe (1-3): {recipe['difficulty']}",
|
||||
|
||||
@@ -78,7 +78,7 @@ time_range_dict = {'day': '24h', 'week': '1w', 'month': '1m', 'year': '1y'}
|
||||
base_url = "https://www.chinaso.com"
|
||||
|
||||
|
||||
def init(_):
|
||||
def setup(_: dict[str, t.Any]) -> bool | None:
|
||||
if chinaso_news_source not in t.get_args(ChinasoNewsSourceType):
|
||||
raise ValueError(f"Unsupported news source: {chinaso_news_source}")
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ Implementations
|
||||
===============
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
import re
|
||||
from os.path import expanduser, isabs, realpath, commonprefix
|
||||
from shlex import split as shlex_split
|
||||
@@ -83,7 +84,6 @@ from threading import Thread
|
||||
from searx import logger
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
|
||||
engine_type = 'offline'
|
||||
paging = True
|
||||
command = []
|
||||
@@ -100,7 +100,7 @@ _command_logger = logger.getChild('command')
|
||||
_compiled_parse_regex = {}
|
||||
|
||||
|
||||
def init(engine_settings):
|
||||
def setup(engine_settings: dict[str, t.Any]) -> bool | None:
|
||||
check_parsing_options(engine_settings)
|
||||
|
||||
if 'command' not in engine_settings:
|
||||
|
||||
@@ -141,12 +141,13 @@ def response(resp: "SXNG_Response") -> EngineResults:
|
||||
if name:
|
||||
authors.add(name)
|
||||
|
||||
tag = result.get("fieldOfStudy")
|
||||
res.add(
|
||||
res.types.Paper(
|
||||
title=result.get("title"),
|
||||
url=url,
|
||||
content=result.get("fullText", "") or "",
|
||||
tags=result.get("fieldOfStudy", []),
|
||||
tags=[tag] if tag else [],
|
||||
publishedDate=published_date,
|
||||
type=result.get("documentType", "") or "",
|
||||
authors=authors,
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Deviantart (Images)"""
|
||||
|
||||
import typing as t
|
||||
|
||||
import urllib.parse
|
||||
from lxml import html
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import extract_text, eval_xpath, eval_xpath_list
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
# about
|
||||
about = {
|
||||
"website": 'https://www.deviantart.com/',
|
||||
@@ -23,63 +30,62 @@ paging = True
|
||||
# search-url
|
||||
base_url = 'https://www.deviantart.com'
|
||||
|
||||
results_xpath = '//div[@class="V_S0t_"]/div/div/a'
|
||||
url_xpath = './@href'
|
||||
thumbnail_src_xpath = './div/img/@src'
|
||||
img_src_xpath = './div/img/@srcset'
|
||||
title_xpath = './@aria-label'
|
||||
premium_xpath = '../div/div/div/text()'
|
||||
premium_keytext = 'Watch the artist to view this deviation'
|
||||
cursor_xpath = '(//a[@class="vQ2brP"]/@href)[last()]'
|
||||
results_xpath = '//div[@data-testid="content_row"]//a[.//*[@data-testid="thumb"]]'
|
||||
img_src_xpath = './/img/@srcset'
|
||||
thumbnail_src_xpath = './/img/@src'
|
||||
author_xpath = './/*[@property="schema:name"]/@content'
|
||||
cursor_xpath = '//a[contains(@href, "cursor=") and contains(., "Next")]/@href'
|
||||
|
||||
|
||||
def request(query, params):
|
||||
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
# https://www.deviantart.com/search?q=foo
|
||||
|
||||
nextpage_url = params['engine_data'].get('nextpage')
|
||||
# don't use nextpage when user selected to jump back to page 1
|
||||
if params['pageno'] > 1 and nextpage_url is not None:
|
||||
params['url'] = nextpage_url
|
||||
else:
|
||||
params['url'] = f"{base_url}/search?{urllib.parse.urlencode({'q': query})}"
|
||||
args = {'q': query}
|
||||
if params['pageno'] > 1:
|
||||
cursor = params['engine_data'].get('cursor')
|
||||
if cursor:
|
||||
args['cursor'] = cursor
|
||||
|
||||
return params
|
||||
params['url'] = f"{base_url}/search?{urllib.parse.urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp):
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
|
||||
results = []
|
||||
res = EngineResults()
|
||||
dom = html.fromstring(resp.text)
|
||||
|
||||
for result in eval_xpath_list(dom, results_xpath):
|
||||
# skip images that are blurred
|
||||
_text = extract_text(eval_xpath(result, premium_xpath))
|
||||
if _text and premium_keytext in _text:
|
||||
continue
|
||||
thumbnail_src = extract_text(eval_xpath(result, thumbnail_src_xpath))
|
||||
img_src = extract_text(eval_xpath(result, img_src_xpath))
|
||||
# mature locked thumbs have blur transform (blur_15, blur_30 etc..)
|
||||
if ',blur_' in f'{thumbnail_src}{img_src}':
|
||||
continue
|
||||
if img_src:
|
||||
img_src = img_src.split(' ')[0]
|
||||
parsed_url = urllib.parse.urlparse(img_src)
|
||||
img_src = parsed_url._replace(path=parsed_url.path.split('/v1')[0]).geturl()
|
||||
|
||||
results.append(
|
||||
{
|
||||
'template': 'images.html',
|
||||
'url': extract_text(eval_xpath(result, url_xpath)),
|
||||
'img_src': img_src,
|
||||
'thumbnail_src': extract_text(eval_xpath(result, thumbnail_src_xpath)),
|
||||
'title': extract_text(eval_xpath(result, title_xpath)),
|
||||
}
|
||||
author = extract_text(eval_xpath(result, author_xpath))
|
||||
|
||||
res.add(
|
||||
res.types.Image(
|
||||
template='images.html',
|
||||
url=result.get('href'),
|
||||
img_src=img_src or "",
|
||||
thumbnail_src=thumbnail_src or "",
|
||||
title=result.get('aria-label'),
|
||||
author=author or "",
|
||||
)
|
||||
)
|
||||
|
||||
nextpage_url = extract_text(eval_xpath(dom, cursor_xpath))
|
||||
if nextpage_url:
|
||||
results.append(
|
||||
{
|
||||
'engine_data': nextpage_url.replace("http://", "https://"),
|
||||
'key': 'nextpage',
|
||||
}
|
||||
cursor = urllib.parse.parse_qs(urllib.parse.urlparse(nextpage_url or '').query).get('cursor', [None])[0]
|
||||
if cursor:
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
engine_data=cursor,
|
||||
key='cursor',
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
return res
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Docker Hub (IT)"""
|
||||
|
||||
# pylint: disable=use-dict-literal
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user