├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ └── docker-image.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── api.php ├── auto_updater.sh ├── config.php.example ├── docker-compose.yml ├── docker ├── README.md ├── attributes.sh ├── config.php ├── entrypoint.sh ├── env-substitution.sh ├── fastcgi.conf ├── nginx.conf └── opensearch.xml ├── docs ├── README.md ├── docker-compose-prebuilt-tor-proxy.md ├── docker-run-manually-built-tor-proxy.md ├── docker-run-prebuilt-tor-proxy.md └── system-tor-proxy.md ├── donate.php ├── engines ├── ahmia │ └── hidden_service.php ├── bittorrent │ ├── 1337x.php │ ├── get_magnet_1337x.php │ ├── merge.php │ ├── nyaa.php │ ├── rutor.php │ ├── sukebei.php │ ├── thepiratebay.php │ ├── torrentgalaxy.php │ └── yts.php ├── invidious │ └── video.php ├── librex │ └── fallback.php ├── maps │ └── openstreetmap.php ├── qwant │ └── image.php ├── special │ ├── currency.php │ ├── definition.php │ ├── ip.php │ ├── special.php │ ├── tor.php │ ├── user_agent.php │ ├── weather.php │ └── wikipedia.php └── text │ ├── bing.php │ ├── brave.php │ ├── duckduckgo.php │ ├── ecosia.php │ ├── google.php │ ├── mojeek.php │ ├── text.php │ └── yandex.php ├── favicon.ico ├── image_proxy.php ├── index.php ├── instances.json ├── instances.php ├── locale ├── bn.php ├── en.php ├── fr.php ├── hr.php ├── it.php ├── ko.php ├── localization.php ├── nl.php ├── sr.php ├── sv.php └── uk.php ├── misc ├── cooldowns.php ├── footer.php ├── header.php ├── search_engine.php └── tools.php ├── opensearch.xml.example ├── search.php ├── settings.php └── static ├── css ├── amoled.css ├── auto.css ├── catppuccin_frappe.css ├── catppuccin_latte.css ├── catppuccin_macchiato.css ├── catppuccin_mocha.css ├── dark.css ├── darker.css ├── discord.css ├── dracula.css ├── github_night.css ├── google.css ├── gruvbox.css ├── light.css ├── night_owl.css ├── nord.css ├── startpage.css ├── styles.css ├── tokyo_night.css └── ubuntu.css ├── images ├── btc.png ├── buy-me-a-coffee.png ├── general_result.png ├── images_result.png ├── kofi.png ├── librex.png ├── maps_result.png ├── tor_result.png ├── torrents_result.png ├── videos_result.png ├── xmr-ahwx.png └── xmr.png └── misc ├── ddg_bang.json └── languages.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .gitignore 3 | .dockerignore 4 | config.php.example 5 | docker-compose.yml 6 | Dockerfile 7 | opensearch.xml.example 8 | README.md 9 | docker-old/ 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Ahwxorg 2 | buy_me_a_coffee: Ahwx 3 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | workflow_dispatch: 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | permissions: write-all 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: docker/login-action@v2.1.0 13 | with: 14 | registry: ghcr.io 15 | username: ${{ github.repository_owner }} 16 | password: ${{ secrets.GITHUB_TOKEN }} 17 | - id: owner 18 | uses: ASzc/change-string-case-action@v5 19 | with: 20 | string: ${{ github.repository_owner }} 21 | - id: repo 22 | uses: ASzc/change-string-case-action@v5 23 | with: 24 | string: ${{ github.event.repository.name }} 25 | 26 | - uses: docker/setup-qemu-action@v2 27 | - uses: docker/setup-buildx-action@v2 28 | - uses: docker/build-push-action@v4 29 | with: 30 | context: . 31 | file: Dockerfile 32 | platforms: linux/amd64,linux/arm64 33 | push: true 34 | tags: | 35 | ghcr.io/${{ steps.owner.outputs.lowercase }}/${{ steps.repo.outputs.lowercase }}:latest 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /config.php 2 | /opensearch.xml 3 | /nginx_logs 4 | /php_logs 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.21 2 | 3 | WORKDIR "/var/www/html" 4 | 5 | ADD "." "." 6 | 7 | # Docker metadata contains information about the maintainer, such as the name, repository, and support email 8 | # See more: https://docs.docker.com/config/labels-custom-metadata/ 9 | LABEL name="LibreY" \ 10 | description="Framework and javascript free privacy respecting meta search engine" \ 11 | version="1.0" \ 12 | vendor="Ahwx " \ 13 | maintainer="Ahwx " \ 14 | url="https://github.com/Ahwxorg/LibreY" \ 15 | authors="https://github.com/Ahwxorg/LibreY/contributors" 16 | 17 | # Include arguments as temporary environment variables to be handled by Docker during the image build process 18 | # Change or add new arguments to customize the image generated by 'docker build' command 19 | ARG DOCKER_SCRIPTS="docker" 20 | ARG NGINX_PORT=8080 21 | # Set this argument during build time to indicate that the path is for php's www.conf 22 | ARG WWW_CONFIG="/etc/php84/php-fpm.d/www.conf" 23 | 24 | # Customize the environment during both execution and build time by modifying the environment variables added to the container's shell 25 | # When building your image, make sure to set the 'TZ' environment variable to your desired time zone location, for example 'America/Sao_Paulo' 26 | # See more: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List 27 | ENV TZ="America/New_York" 28 | 29 | # Install required packages 30 | RUN apk add gettext php84 php84-fpm php84-dom php84-curl php84-pecl-apcu nginx --no-cache 31 | 32 | # Configure PHP-FPM to listen on a Unix socket instead of a TCP port, which is more secure and efficient 33 | RUN touch /run/php-fpm84.sock && chown nginx:nginx "/run/php-fpm84.sock" 34 | RUN sed -i 's/^\s*listen = 127.0.0.1:9000/listen = \/run\/php-fpm84.sock/' ${WWW_CONFIG} &&\ 35 | sed -i 's/^\s*;\s*listen.owner = nobody/listen.owner = nginx/' ${WWW_CONFIG} &&\ 36 | sed -i 's/^\s*;\s*listen.group = nobody/listen.group = nginx/' ${WWW_CONFIG} &&\ 37 | sed -i 's/^\s*;\s*listen.mode = 0660/listen.mode = 0660/' ${WWW_CONFIG} 38 | 39 | EXPOSE ${NGINX_PORT} 40 | 41 | # Configures the container to be run as an executable. 42 | ENTRYPOINT ["/bin/sh", "-c", "docker/entrypoint.sh"] 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

LibreY

2 | 3 | [![Docker Image CI](https://github.com/Ahwxorg/LibreY/actions/workflows/docker-image.yml/badge.svg)](https://github.com/Ahwxorg/LibreY/actions/workflows/docker-image.yml) 4 | 5 | > LibreY is a fork of LibreX, a framework-less and javascript-free privacy respecting meta search engine, made by [hnhx](https://github.com/hnhx). LibreY changed some features like automatic redirection (now instance fallback), added scrapers for more search engines and various other changes visible in the [commit log](https://github.com/hnhx/librex/compare/main...Ahwxorg:LibreY:main). The original code is written by [hnhx and contributors](https://github.com/hnhx/LibreX/contributors) 6 | 7 |

8 | 9 | 10 |

11 | 12 |

13 | 14 |
15 | 16 | ## Matrix 17 | 18 | If there's an important announcement, we do have a Matrix chatroom which you can join over at [#librey:ahwx.org](https://matrix.to/#/#librey:ahwx.org). 19 | 20 | ### Instances 21 | 22 | You can find a list of instances on any LibreY instance by accessing /instances.php.
23 | Alternatively look at `instances.json` where the list is generated from.

24 | While the official instances may be more updated and have better uptime, please consider using another person's instances as these are heavily overloaded.
25 | Support the community. ❤️

26 | [@Ahwxorg](https://github.com/Ahwxorg)'s instance:
27 | [search.liv.town](https://search.liv.town/instances.php)
28 |
29 | [@codedipper](https://github.com/codedipper)'s instance:
30 | [search.revvy.de](https://search.revvy.de/instances.php)
31 | [Tor](http://search.revvybrr6pvbx4n3j4475h4ghw4elqr4t5xo2vtd3gfpu2nrsnhh57id.onion/instances.php)
32 | [I2P](http://revekebotog64xrrammtsmjwtwlg3vqyzwdurzt2pu6botg4bejq.b32.i2p/instances.php)
33 |
34 | [@davidovski](https://github.com/davidovski)'s instance:
35 | [search.davidovski.xyz](https://search.davidovski.xyz/instances.php)
36 |
37 | 38 | ### How to host LibreY 39 | 40 | [This section has been moved](https://github.com/Ahwxorg/LibreY/tree/main/docs) to clear up some README space. 41 | 42 | ### About LibreY 43 | 44 | LibreY gives you text results from Google, DuckDuckGo, Brave Search, Ecosia, Yandex Search and Mojeek. LibreY provides images from Qwant, and torrents from i.e. Ahmia and popular torrent sites. LibreY does this without spying on you. 45 |
LibreY doesn't save **any** type of data about the user, there are no logs (except NGINX logs if the host sets them). 46 | 47 | ### LibreY compared to other metasearch engines 48 | 49 | | Name | Works without JS | Privacy frontend redirect | Torrent results | API | No 3rd party libs used | 50 | | ------- | -------------------- | ------------------------- | --------------- | --- | ---------------------- | 51 | | LibreY | ✅ | ✅ | ✅ | ✅ | ✅ | 52 | | SearXNG | ❓ Not user friendly | ❓ Only host can set it | ✅ | ✅ | ❌ | 53 | | Whoogle | ✅ | ❓ Only host can set it | ❌ | ❌ | ❌ | 54 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Basically the latest commit. We don't really do versioning with LibreY. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please join #librey:ahwx.org on Matrix and please DM me (@ahwx:ahwx.org) or if Matrix is absolutely impossible, email me; ahwx *at* ahwx *dot* org 10 | -------------------------------------------------------------------------------- /api.php: -------------------------------------------------------------------------------- 1 | disable_api) { 7 | echo "

" . TEXTS["api_unavailable"] . "

"; 8 | die(); 9 | } 10 | 11 | require_once "misc/tools.php"; 12 | 13 | if (!$opts->query) { 14 | echo "

Example API request: ./api.php?q=gentoo&p=2&t=0

15 |
16 |

\"q\" is the keyword

17 |

\"p\" is the result page (the first page is 0)

18 |

\"t\" is the search type (0=text, 1=image, 2=video, 3=torrent, 4=tor)

19 |
20 |

The results are going to be in JSON format.

21 |

The API supports both POST and GET requests.

"; 22 | 23 | die(); 24 | } 25 | 26 | $results = fetch_search_results($opts, false); 27 | if (array_key_exists("error", $results)) { 28 | http_response_code(500); 29 | } 30 | header("Content-Type: application/json"); 31 | echo json_encode($results); 32 | ?> 33 | -------------------------------------------------------------------------------- /auto_updater.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | while true; do 3 | git stash 4 | git pull 5 | sleep 60 6 | done 7 | -------------------------------------------------------------------------------- /config.php.example: -------------------------------------------------------------------------------- 1 | https://google.fr/ 5 | "google_domain" => "com", 6 | 7 | // Results will be in this language 8 | "language" => "", 9 | "number_of_results" => 10, 10 | 11 | // The default theme css to use 12 | // use `ls --ignore={styles,auto}.css static/css | cut -d. -f1` to list valid themes 13 | "default_theme" => "dark", 14 | 15 | // You can use any Invidious instance here 16 | "invidious_instance_for_video_results" => "https://invidious.snopyta.org", 17 | 18 | "disable_bittorrent_search" => false, 19 | "bittorrent_trackers" => "&tr=http://nyaa.tracker.wf:7777/announce&tr=udp://open.stealth.si:80/announce&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://exodus.desync.com:6969/announce&tr=udp://tracker.torrent.eu.org:451/announce", 20 | 21 | "disable_hidden_service_search" => false, 22 | 23 | // Fallback to another librex instance if google search fails 24 | // This may greatly increase the time it takes to get a result, if a direct search is not possible 25 | "instance_fallback" => true, 26 | 27 | // how long in minutes to put google/other instances on cooldown if they aren't responding 28 | "request_cooldown" => 25, 29 | 30 | // how long in minutes to store results for in the cache, set -1 to disable cache 31 | "cache_time" => 20, 32 | 33 | // Disable requests to /api.php - HIGHLY recommended to keep this at false, this is what allows LibreY to still show results when Google/DuckDuckGo blocks the requests. 34 | "disable_api" => false, 35 | 36 | // whether to show where the result is from on the results page 37 | "show_result_source" => true, 38 | 39 | /* 40 | Preset privacy friendly frontends for users, these can be overwritten by users in the settings 41 | e.g.: Preset the invidious instance URL: "instance_url" => "https://yewtu.be", 42 | */ 43 | 44 | "frontends" => array( 45 | "invidious" => array( 46 | "instance_url" => "", 47 | "project_url" => "https://docs.invidious.io/instances/", 48 | "original_name" => "YouTube", 49 | "original_url" => "youtube.com" 50 | ), 51 | "rimgo" => array( 52 | "instance_url" => "", 53 | "project_url" => "https://codeberg.org/video-prize-ranch/rimgo#instances", 54 | "original_name" => "Imgur", 55 | "original_url" => "imgur.com" 56 | ), 57 | "scribe" => array( 58 | "instance_url" => "", 59 | "project_url" => "https://git.sr.ht/~edwardloveall/scribe/tree/main/docs/instances.md", 60 | "original_name" => "Medium", 61 | "original_url" => "medium.com" 62 | ), 63 | "gothub" => array( 64 | "instance_url" => "", 65 | "project_url" => "https://codeberg.org/gothub/gothub#instances", 66 | "original_name" => "GitHub", 67 | "original_url" => "github.com" 68 | ), 69 | "nitter" => array( 70 | "instance_url" => "", 71 | "project_url" => "https://github.com/zedeus/nitter/wiki/Instances", 72 | "original_name" => "Twitter", 73 | "original_url" => "twitter.com" 74 | ), 75 | 76 | "redlib" => array( 77 | "instance_url" => "", 78 | "project_url" => "https://github.com/redlib-org/redlib-instances/blob/main/instances.md", 79 | "original_name" => "Reddit", 80 | "original_url" => "reddit.com" 81 | ), 82 | "proxitok" => array( 83 | "instance_url" => "", 84 | "project_url" => "https://github.com/pablouser1/ProxiTok/wiki/Public-instances", 85 | "original_name" => "TikTok", 86 | "original_url" => "tiktok.com" 87 | ), 88 | "wikiless" => array( 89 | "instance_url" => "", 90 | "project_url" => "https://github.com/Metastem/wikiless#instances", 91 | "original_name" => "Wikipedia", 92 | "original_url" => "wikipedia.org" 93 | ), 94 | "quetre" => array( 95 | "instance_url" => "", 96 | "project_url" => "https://github.com/zyachel/quetre#instances", 97 | "original_name" => "Quora", 98 | "original_url" => "quora.com" 99 | ), 100 | "libremdb" => array( 101 | "instance_url" => "", 102 | "project_url" => "https://github.com/zyachel/libremdb#instances", 103 | "original_name" => "IMDb", 104 | "original_url" => "imdb.com" 105 | ), 106 | "breezewiki" => array( 107 | "instance_url" => "", 108 | "project_url" => "https://docs.breezewiki.com/Links.html", 109 | "original_name" => "Fandom", 110 | "original_url" => "fandom.com" 111 | ), 112 | "anonymousoverflow" => array( 113 | "instance_url" => "", 114 | "project_url" => "https://github.com/httpjamesm/AnonymousOverflow#clearnet-instances", 115 | "original_name" => "StackOverflow", 116 | "original_url" => "stackoverflow.com" 117 | ), 118 | "suds" => array( 119 | "instance_url" => "", 120 | "project_url" => "https://git.vern.cc/cobra/Suds/src/branch/main/instances.json", 121 | "original_name" => "Snopes", 122 | "original_url" => "snopes.com" 123 | ), 124 | "biblioreads" => array( 125 | "instance_url" => "", 126 | "project_url" => "https://github.com/nesaku/BiblioReads#instances", 127 | "original_name" => "Goodreads", 128 | "original_url" => "goodreads.com" 129 | ) 130 | ), 131 | 132 | 133 | "preferred_engines" => array( 134 | /* search engine to use to search, auto will automatically balance between all scrapers, which is recommended. */ 135 | "text" => "auto" 136 | // "text" => "google" 137 | // "text" => "duckduckgo" 138 | ), 139 | 140 | 141 | /* 142 | To send requests through a proxy uncomment CURLOPT_PROXY and CURLOPT_PROXYTYPE: 143 | 144 | CURLOPT_PROXYTYPE options: 145 | 146 | CURLPROXY_HTTP 147 | CURLPROXY_SOCKS4 148 | CURLPROXY_SOCKS4A 149 | CURLPROXY_SOCKS5 150 | CURLPROXY_SOCKS5_HOSTNAME 151 | 152 | !!! ONLY CHANGE THE OTHER OPTIONS IF YOU KNOW WHAT YOU ARE DOING !!! 153 | */ 154 | "curl_settings" => array( 155 | // CURLOPT_PROXY => "ip:port", 156 | // CURLOPT_PROXYTYPE => CURLPROXY_HTTP, 157 | CURLOPT_RETURNTRANSFER => true, 158 | CURLOPT_ENCODING => "", 159 | CURLOPT_USERAGENT => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0", // For a normal Windows 10 PC running Firefox x64 160 | CURLOPT_IPRESOLVE => CURL_IPRESOLVE_WHATEVER, 161 | CURLOPT_CUSTOMREQUEST => "GET", 162 | CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP, 163 | CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP, 164 | CURLOPT_MAXREDIRS => 5, 165 | CURLOPT_TIMEOUT => 3, 166 | CURLOPT_VERBOSE => false, 167 | CURLOPT_FOLLOWLOCATION => true 168 | ) 169 | ); 170 | ?> 171 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | librey: 3 | image: ghcr.io/ahwxorg/librey:latest 4 | container_name: librey 5 | network_mode: bridge 6 | ports: 7 | - 8080:8080 8 | environment: 9 | - CONFIG_GOOGLE_DOMAIN=com 10 | - CONFIG_LANGUAGE=en 11 | - CONFIG_NUMBER_OF_RESULTS=10 12 | - CONFIG_INVIDIOUS_INSTANCE=https://inv.nadeko.net 13 | - CONFIG_DISABLE_BITTORRENT_SEARCH=false 14 | - CONFIG_HIDDEN_SERVICE_SEARCH=false 15 | - CONFIG_INSTANCE_FALLBACK=true 16 | - CONFIG_RATE_LIMIT_COOLDOWN=25 17 | - CONFIG_CACHE_TIME=20 18 | - CONFIG_DISABLE_API=false 19 | - CONFIG_TEXT_SEARCH_ENGINE=auto 20 | - CURLOPT_PROXY_ENABLED=false 21 | - CURLOPT_PROXY=192.0.2.53:8388 22 | - CURLOPT_PROXYTYPE=CURLPROXY_HTTP 23 | - CURLOPT_USERAGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0 24 | - CURLOPT_FOLLOWLOCATION=true 25 | volumes: 26 | # - ./nginx_logs:/var/log/nginx # Disabled by default. These are the NGINX request logs. 27 | - ./php_logs:/var/log/php84 # Enabled by default. These are the PHP error logs. 28 | restart: unless-stopped 29 | watchtower: # Watchtower is not required but highly recommended, since Watchtower will re-pull and restart the LibreY container automatically whenever there's an update. 30 | image: containrrr/watchtower 31 | volumes: 32 | - /var/run/docker.sock:/var/run/docker.sock 33 | -------------------------------------------------------------------------------- /docker/attributes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # YOU DON'T NEED TO EDIT THIS FILE. IF YOU WANT TO SET CUSTOM ENVIRONMENT VARIABLES, 4 | # USE THE 'DOCKERFILE IMAGE' FROM ROOT DIRECTORY AND PASS THE ENVIRONMENT PARAMETERS 5 | 6 | # These templates will be used to create configuration files that incorporate values from environment variables 7 | # If these locations do not already exist within the Docker container, they will be created 8 | export CONFIG_PHP_TEMPLATE="$(pwd)/config.php" 9 | export CONFIG_OPEN_SEARCH_TEMPLATE="$(pwd)/opensearch.xml" 10 | export CONFIG_NGINX_TEMPLATE="/etc/nginx/http.d/librey.conf" 11 | 12 | # Configure 'opensearch.xml' with librey configuration metadata, such as the encoding and the host that stores the site 13 | # These configurations will replace the 'opensearch.xml' inside '.dockers/templates' for the best setup for your instance 14 | export OPEN_SEARCH_TITLE=${OPEN_SEARCH_TITLE:-"LibreY"} 15 | export OPEN_SEARCH_DESCRIPTION=${OPEN_SEARCH_DESCRIPTION:-"Framework and javascript free privacy respecting meta search engine"} 16 | export OPEN_SEARCH_ENCODING=${OPEN_SEARCH_ENCODING:-"UTF-8"} 17 | export OPEN_SEARCH_LONG_NAME=${OPEN_SEARCH_LONG_NAME:-"LibreY Search"} 18 | export OPEN_SEARCH_HOST=${OPEN_SEARCH_HOST:-"127.0.0.1"} 19 | 20 | # Replace the 'config.php' script, which contains the most common search engine configurations, with these environment setups 21 | # These environment setups can be found in 'config.php', and the default configurations can be useful for most use cases 22 | export CONFIG_GOOGLE_DOMAIN=${CONFIG_GOOGLE_DOMAIN:-"com"} 23 | export CONFIG_LANGUAGE=${CONFIG_LANGUAGE:-"en"} 24 | export CONFIG_NUMBER_OF_RESULTS=${CONFIG_NUMBER_OF_RESULTS:-10} 25 | export CONFIG_INVIDIOUS_INSTANCE=${CONFIG_INVIDIOUS_INSTANCE:-"https://invidious.snopyta.org"} 26 | export CONFIG_DISABLE_BITTORRENT_SEARCH=${CONFIG_DISABLE_BITTORRENT_SEARCH:-false} 27 | export CONFIG_BITTORRENT_TRACKERS=${CONFIG_BITTORRENT_TRACKERS:-"&tr=http://nyaa.tracker.wf:7777/announce&tr=udp://open.stealth.si:80/announce&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://exodus.desync.com:6969/announce&tr=udp://tracker.torrent.eu.org:451/announce"} 28 | export CONFIG_HIDDEN_SERVICE_SEARCH=${CONFIG_HIDDEN_SERVICE_SEARCH:-false} 29 | export CONFIG_INSTANCE_FALLBACK="${CONFIG_INSTANCE_FALLBACK:-true}" 30 | export CONFIG_RATE_LIMIT_COOLDOWN="${CONFIG_RATE_LIMIT_COOLDOWN:-25}" 31 | export CONFIG_CACHE_TIME="${CONFIG_CACHE_TIME:-20}" 32 | export CONFIG_DISABLE_API="${CONFIG_DISABLE_API:-false}" 33 | export CONFIG_SHOW_RESULT_SOURCE="${CONFIG_SHOW_RESULT_SOURCE:-true}" 34 | export CONFIG_DEFAULT_THEME=${CONFIG_DEFAULT_THEME:-"dark"} 35 | 36 | # Supported apps integration configuration. These empty spaces can be set up using free hosts as pointers 37 | # A particular example is using the "https://yewtu.be" or a self-hosted host to integrate the invidious app to librey 38 | export APP_INVIDIOUS=${APP_INVIDIOUS:-""} 39 | export APP_RIMGO=${APP_RIMGO:-""} 40 | export APP_SCRIBE=${APP_SCRIBE:-""} 41 | export APP_GOTHUB=${APP_GOTHUB:-""} 42 | export APP_NITTER=${APP_NITTER:-""} 43 | export APP_REDLIB=${APP_REDLIB:-""} 44 | export APP_PROXITOK=${APP_PROXITOK:-""} 45 | export APP_WIKILESS=${APP_WIKILESS:-""} 46 | export APP_QUETRE=${APP_QUETRE:-""} 47 | export APP_LIBREMDB=${APP_LIBREMDB:-""} 48 | export APP_BREEZEWIKI=${APP_BREEZEWIKI:-""} 49 | export APP_ANONYMOUS_OVERFLOW=${APP_ANONYMOUS_OVERFLOW:-""} 50 | export APP_SUDS=${APP_SUDS:-""} 51 | export APP_BIBLIOREADS=${APP_BIBLIOREADS:-""} 52 | 53 | export CONFIG_TEXT_SEARCH_ENGINE=${CONFIG_TEXT_SEARCH_ENGINE:-"auto"} 54 | 55 | # GNU/Curl configurations. Leave 'CURLOPT_PROXY' blank whether you don't need to use a proxy for requests 56 | # Generally, a proxy is needed when your IP address is blocked by search engines in response to multiple requests within a short time frame. In these cases, it is recommended to use rotating proxies 57 | export CURLOPT_PROXY_ENABLED=${CURLOPT_PROXY_ENABLED:-false} 58 | export CURLOPT_PROXY=${CURLOPT_PROXY:-""} 59 | export CURLOPT_PROXYTYPE=${CURLOPT_PROXYTYPE:-"CURLPROXY_HTTP"} 60 | export CURLOPT_RETURNTRANSFER=${CURLOPT_RETURNTRANSFER:-true} 61 | export CURLOPT_ENCODING=${CURLOPT_ENCODING:-""} 62 | export CURLOPT_USERAGENT=${CURLOPT_USERAGENT:-"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0"} 63 | export CURLOPT_IPRESOLVE=${CURLOPT_IPRESOLVE:-"CURL_IPRESOLVE_WHATEVER"} 64 | export CURLOPT_CUSTOMREQUEST="${CURLOPT_CUSTOMREQUEST:-"GET"}" 65 | export CURLOPT_MAXREDIRS=${CURLOPT_MAXREDIRS:-5} 66 | export CURLOPT_TIMEOUT=${CURLOPT_TIMEOUT:-3} 67 | export CURLOPT_VERBOSE=${CURLOPT_VERBOSE:-true} 68 | export CURLOPT_FOLLOWLOCATION=${CURLOPT_FOLLOWLOCATION:-true} 69 | 70 | # These shell functions will be available for use by any function calls 71 | function AwkTrim() { awk '{$1=$1};1'; } 72 | -------------------------------------------------------------------------------- /docker/config.php: -------------------------------------------------------------------------------- 1 | "${CONFIG_GOOGLE_DOMAIN}", 5 | "language" => "${CONFIG_LANGUAGE}", 6 | "number_of_results" => ${CONFIG_NUMBER_OF_RESULTS}, 7 | "invidious_instance_for_video_results" => "${CONFIG_INVIDIOUS_INSTANCE}", 8 | "disable_bittorrent_search" => ${CONFIG_DISABLE_BITTORRENT_SEARCH}, 9 | "bittorrent_trackers" => "${CONFIG_BITTORRENT_TRACKERS}", 10 | "disable_hidden_service_search" => ${CONFIG_HIDDEN_SERVICE_SEARCH}, 11 | "instance_fallback" => ${CONFIG_INSTANCE_FALLBACK}, 12 | "request_cooldown" => ${CONFIG_RATE_LIMIT_COOLDOWN}, 13 | "cache_time" => ${CONFIG_CACHE_TIME}, 14 | "disable_api" => ${CONFIG_DISABLE_API}, 15 | "show_result_source" => ${CONFIG_SHOW_RESULT_SOURCE}, 16 | "default_theme" => "${CONFIG_DEFAULT_THEME}", 17 | 18 | "frontends" => array( 19 | "invidious" => array( 20 | "instance_url" => "${APP_INVIDIOUS}", 21 | "project_url" => "https://docs.invidious.io/instances/", 22 | "original_name" => "YouTube", 23 | "original_url" => "youtube.com" 24 | ), 25 | "rimgo" => array( 26 | "instance_url" => "${APP_RIMGO}", 27 | "project_url" => "https://codeberg.org/video-prize-ranch/rimgo#instances", 28 | "original_name" => "Imgur", 29 | "original_url" => "imgur.com" 30 | ), 31 | "scribe" => array( 32 | "instance_url" => "${APP_SCRIBE}", 33 | "project_url" => "https://git.sr.ht/~edwardloveall/scribe/tree/main/docs/instances.md", 34 | "original_name" => "Medium", 35 | "original_url" => "medium.com" 36 | ), 37 | "gothub" => array( 38 | "instance_url" => "${APP_GOTHUB}", 39 | "project_url" => "https://codeberg.org/gothub/gothub#instances", 40 | "original_name" => "GitHub", 41 | "original_url" => "github.com" 42 | ), 43 | "nitter" => array( 44 | "instance_url" => "${APP_NITTER}", 45 | "project_url" => "https://github.com/zedeus/nitter/wiki/Instances", 46 | "original_name" => "Twitter", 47 | "original_url" => "twitter.com" 48 | ), 49 | "redlib" => array( 50 | "instance_url" => "${APP_REDLIB}", 51 | "project_url" => "https://github.com/redlib-org/redlib-instances/blob/main/instances.md", 52 | "original_name" => "Reddit", 53 | "original_url" => "reddit.com" 54 | ), 55 | "proxitok" => array( 56 | "instance_url" => "${APP_PROXITOK}", 57 | "project_url" => "https://github.com/pablouser1/ProxiTok/wiki/Public-instances", 58 | "original_name" => "TikTok", 59 | "original_url" => "tiktok.com" 60 | ), 61 | "wikiless" => array( 62 | "instance_url" => "${APP_WIKILESS}", 63 | "project_url" => "https://github.com/Metastem/wikiless#instances", 64 | "original_name" => "Wikipedia", 65 | "original_url" => "wikipedia.org" 66 | ), 67 | "quetre" => array( 68 | "instance_url" => "${APP_QUETRE}", 69 | "project_url" => "https://github.com/zyachel/quetre#instances", 70 | "original_name" => "Quora", 71 | "original_url" => "quora.com" 72 | ), 73 | "libremdb" => array( 74 | "instance_url" => "${APP_LIBREMDB}", 75 | "project_url" => "https://github.com/zyachel/libremdb#instances", 76 | "original_name" => "IMDb", 77 | "original_url" => "imdb.com" 78 | ), 79 | "breezewiki" => array( 80 | "instance_url" => "${APP_BREEZEWIKI}", 81 | "project_url" => "https://docs.breezewiki.com/Links.html", 82 | "original_name" => "Fandom", 83 | "original_url" => "fandom.com" 84 | ), 85 | "anonymousoverflow" => array( 86 | "instance_url" => "${APP_ANONYMOUS_OVERFLOW}", 87 | "project_url" => "https://github.com/httpjamesm/AnonymousOverflow#clearnet-instances", 88 | "original_name" => "StackOverflow", 89 | "original_url" => "stackoverflow.com" 90 | ), 91 | "suds" => array( 92 | "instance_url" => "${APP_SUDS}", 93 | "project_url" => "https://git.vern.cc/cobra/Suds/src/branch/main/instances.json", 94 | "original_name" => "Snopes", 95 | "original_url" => "snopes.com" 96 | ), 97 | "biblioreads" => array( 98 | "instance_url" => "${APP_BIBLIOREADS}", 99 | "project_url" => "https://github.com/nesaku/BiblioReads#instances", 100 | "original_name" => "Goodreads", 101 | "original_url" => "goodreads.com" 102 | ) 103 | ), 104 | 105 | 106 | "preferred_engines" => array( 107 | "text" => "${CONFIG_TEXT_SEARCH_ENGINE}" 108 | ), 109 | 110 | "curl_settings" => array( 111 | CURLOPT_PROXY => "${CURLOPT_PROXY}", 112 | CURLOPT_PROXYTYPE => ${CURLOPT_PROXYTYPE}, 113 | CURLOPT_RETURNTRANSFER => ${CURLOPT_RETURNTRANSFER}, 114 | CURLOPT_ENCODING => "${CURLOPT_ENCODING}", 115 | CURLOPT_USERAGENT => "${CURLOPT_USERAGENT}", 116 | CURLOPT_IPRESOLVE => ${CURLOPT_IPRESOLVE}, 117 | CURLOPT_CUSTOMREQUEST => "${CURLOPT_CUSTOMREQUEST}", 118 | CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP, 119 | CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP, 120 | CURLOPT_MAXREDIRS => ${CURLOPT_MAXREDIRS}, 121 | CURLOPT_TIMEOUT => ${CURLOPT_TIMEOUT}, 122 | CURLOPT_VERBOSE => ${CURLOPT_VERBOSE}, 123 | CURLOPT_FOLLOWLOCATION => ${CURLOPT_FOLLOWLOCATION} 124 | ) 125 | ); 126 | ?> 127 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /bin/sh -c docker/env-substitution.sh 4 | 5 | /bin/sh -c /usr/sbin/php-fpm84 6 | 7 | exec nginx -g "daemon off;" 8 | -------------------------------------------------------------------------------- /docker/env-substitution.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Load all environment variables from 'attributes.sh' using the command 'source /path/attributes.sh' 4 | source "docker/attributes.sh" 5 | 6 | # The lines below will replace the environment variables in the templates with the corresponding variables listed above if the config file is not yet provided. To accomplish this, the GNU 'envsubst' package will be used 7 | # Although not recommended (if you do not know what you are doing), you still have the option to add new substitution file templates using any required environment variables 8 | 9 | [[ ! -s ${CONFIG_PHP_TEMPLATE} ]] && cat 'docker/config.php' | envsubst > ${CONFIG_PHP_TEMPLATE}; 10 | [[ ! -s ${CONFIG_OPEN_SEARCH_TEMPLATE} ]] && cat 'docker/opensearch.xml' | envsubst > ${CONFIG_OPEN_SEARCH_TEMPLATE}; 11 | 12 | export OPEN_SEARCH_HOST_FOR_NGINX="$(echo "${OPEN_SEARCH_HOST}" | cut -d "/" -f 3 | cut -d ":" -f 1)" 13 | if [[ ! -s ${CONFIG_NGINX_TEMPLATE} ]]; then 14 | 15 | cat 'docker/nginx.conf' | envsubst '${OPEN_SEARCH_HOST_FOR_NGINX}' > ${CONFIG_NGINX_TEMPLATE}; 16 | mv "docker/fastcgi.conf" /etc/nginx/fastcgi.conf 17 | 18 | chown nginx:nginx "/etc/nginx/fastcgi.conf" 19 | chown nginx:nginx "/etc/nginx/http.d/librey.conf" 20 | 21 | fi 22 | 23 | # If it is empty or proxy is not enabled, we are using sed to delete 24 | # any line that contains the string 'CURLOPT_PROXY' or 'CURLOPT_PROXYTYPE' 25 | # from the file 'config.php' defined on top of 'attributes.sh' 26 | if [[ -z "${CURLOPT_PROXY}" || "${CURLOPT_PROXY_ENABLED}" = false ]]; then 27 | sed -i "/CURLOPT_PROXY/d" ${CONFIG_PHP_TEMPLATE}; 28 | sed -i "/CURLOPT_PROXYTYPE/d" ${CONFIG_PHP_TEMPLATE}; 29 | fi 30 | -------------------------------------------------------------------------------- /docker/fastcgi.conf: -------------------------------------------------------------------------------- 1 | # These settings should work well for serving as a front-end of many search engines 2 | # on our PHP website. However, we may need to adjust them based on our specific requirements. 3 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 4 | fastcgi_param QUERY_STRING $query_string; 5 | fastcgi_param REQUEST_METHOD $request_method; 6 | fastcgi_param CONTENT_TYPE $content_type; 7 | fastcgi_param CONTENT_LENGTH $content_length; 8 | 9 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 10 | fastcgi_param REQUEST_URI $request_uri; 11 | fastcgi_param DOCUMENT_URI $document_uri; 12 | fastcgi_param DOCUMENT_ROOT $document_root; 13 | fastcgi_param SERVER_PROTOCOL $server_protocol; 14 | 15 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 16 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 17 | 18 | fastcgi_param REMOTE_ADDR $remote_addr; 19 | fastcgi_param REMOTE_PORT $remote_port; 20 | fastcgi_param SERVER_ADDR $server_addr; 21 | fastcgi_param SERVER_PORT $server_port; 22 | fastcgi_param SERVER_NAME $server_name; 23 | 24 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 25 | fastcgi_param HTTPS $https if_not_empty; 26 | fastcgi_param REDIRECT_STATUS 200; 27 | -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name ${OPEN_SEARCH_HOST_FOR_NGINX} localhost; 4 | 5 | add_header Content-Security-Policy "default-src 'none'; style-src 'self'; img-src 'self'"; 6 | add_header X-Frame-Options "DENY" always; 7 | add_header X-Content-Type-Options "nosniff"; 8 | add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), attribution-reporting=(), autoplay=(), bluetooth=(), browsing-topics=(), camera=(), compute-pressure=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), indentity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()"; 9 | 10 | root /var/www/html; 11 | index index.php; 12 | 13 | location / { 14 | try_files $uri $uri/ /index.php?$args; 15 | } 16 | 17 | location ~ \.php$ { 18 | fastcgi_pass unix:/run/php-fpm84.sock; 19 | fastcgi_index index.php; 20 | include fastcgi.conf; 21 | } 22 | 23 | location ~ /\. { 24 | deny all; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docker/opensearch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${OPEN_SEARCH_TITLE} 4 | ${OPEN_SEARCH_DESCRIPTION} 5 | ${OPEN_SEARCH_ENCODING} 6 | ${OPEN_SEARCH_LONG_NAME} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Installing 2 | 3 | To host LibreY using Docker, you can follow the instructions in the [Docker directory](https://github.com/Ahwxorg/LibreY/tree/main/docker). 4 | 5 | To host LibreY using your OS's native package manager and PHP+NGINX, you can use the following steps as a guide. 6 | 7 | These instructions are specific to Debian GNU/Linux but should be similar on most *nix variants (differences may include commands for package management, init system, php-fpm.sock location and availability of fastcgi-php.conf) 8 | 9 | Install the packages (Debian GNU/Linux): 10 | 11 | ```sh 12 | apt install php php-fpm php-dom php-curl php-apcu nginx git 13 | ``` 14 | 15 | Install the packages (Arch Linux): 16 | ```sh 17 | pacman -S php php-fpm php-apcu nginx git 18 | ``` 19 | 20 | Clone LibreY: 21 | 22 | ```sh 23 | mkdir -p /var/www/html 24 | git clone https://github.com/Ahwxorg/LibreY.git /var/www/html/LibreY 25 | ``` 26 | 27 | Rename the config and opensearch files, edit manually if needed: 28 | 29 | ```sh 30 | cd /var/www/html/LibreY/ 31 | mv config.php.example config.php 32 | mv opensearch.xml.example opensearch.xml 33 | ``` 34 | 35 | Change opensearch.xml to point to your domain: 36 | 37 | ```sh 38 | sed -i 's/http:\/\/localhost:80/https:\/\/your.domain/g' opensearch.xml 39 | ``` 40 | 41 | An nginx configuration similar to the one below should be placed in your `http { ... }` block or a file that is automatically detected as such. 42 | 43 | ```sh 44 | server { 45 | listen 80; 46 | 47 | server_name your.domain; 48 | 49 | root /var/www/html/LibreY; 50 | index index.php; 51 | 52 | location ~ \.php$ { 53 | include snippets/fastcgi-php.conf; 54 | fastcgi_pass unix:/run/php/php-fpm.sock; 55 | } 56 | } 57 | ``` 58 | 59 | You can optionally remove the .php extension in URLs by adding this code your `server { ... }` block. 60 | 61 | ```sh 62 | location / { 63 | try_files $uri $uri/ @extensionless-php; 64 | } 65 | 66 | location @extensionless-php { 67 | rewrite ^(.*)$ $1.php last; 68 | } 69 | ``` 70 | 71 | Start php-fpm and nginx immediately and on every boot: 72 | 73 | ```sh 74 | systemctl enable --now php-fpm nginx 75 | ``` 76 | 77 | Now LibreY should be running at the port you specified! 78 | -------------------------------------------------------------------------------- /docs/docker-compose-prebuilt-tor-proxy.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Please note that this heavily relies on [@codedipper](https://github.com/codedipper)'s [work and is basically a copy of his wiki](https://github.com/codedipper/LibreY-Tor/wiki/LibreY-Tor-Proxy-%E2%80%90-Prebuilt-images-with-compose). 3 | 4 | 5 | Step 0: Install and configure Docker.\ 6 | Step 1: Enter a root shell on the host machine. 7 | 8 | Step 2: Clone the required git repositories to the host machine. 9 | 10 | ```sh 11 | git clone https://github.com/Ahwxorg/LibreY.git LibreY/ 12 | git clone https://github.com/codedipper/LibreY-Tor.git LibreY-Tor/ 13 | ``` 14 | 15 | Step 3: Apply the docker-compose.yml patch. 16 | 17 | ```sh 18 | cd LibreY/ 19 | git apply ../LibreY-Tor/LibreY_docker-compose-yml.diff 20 | ``` 21 | 22 | Step 4: Manually edit `LibreY/docker-compose.yml` and `LibreY-Tor/torrc` to configure LibreY and Tor to your liking. 23 | 24 | Step 5: Run it! `docker compose up -d` 25 | 26 | To update containers: 27 | 28 | ```sh 29 | cd LibreY/ 30 | docker compose down 31 | docker compose pull 32 | docker compose up -d 33 | ``` 34 | 35 | It is recommended to build the images yourself to avoid an outdated Tor version. 36 | -------------------------------------------------------------------------------- /docs/docker-run-manually-built-tor-proxy.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Please note that this heavily relies on [@codedipper](https://github.com/codedipper)'s [work and is basically a copy of his wiki](https://github.com/codedipper/LibreY-Tor/wiki/LibreY-Tor-Proxy-‐-Manually-built-images-with-compose). 3 | 4 | Step 0: Install and configure Docker.\ 5 | Step 1: Enter a root shell on the host machine. 6 | 7 | Step 2: Clone the required git repositories to the host machine. 8 | ```sh 9 | git clone https://github.com/Ahwxorg/LibreY.git LibreY/ 10 | git clone https://github.com/codedipper/LibreY-Tor.git LibreY-Tor/ 11 | docker network create --driver=bridge --subnet=172.4.0.0/16 librey_tor_net 12 | ``` 13 | 14 | Step 3: Manually edit `LibreY-Tor/torrc` to configure Tor to your liking. 15 | 16 | Step 4: Build LibreY + LibreY-Tor image. 17 | ```sh 18 | cd LibreY/ 19 | docker buildx build --rm --no-cache --pull -t librey:latest . 20 | cd ../LibreY-Tor/ 21 | docker buildx build --rm --no-cache --pull -t librey-tor:latest . 22 | ``` 23 | 24 | Step 5: Start your desired containers with the options you want. 25 | 26 | ```sh 27 | cd ../LibreY/ 28 | docker run -d --name librey --ip=172.4.0.6 --network librey_tor_net -p 8080:8080 -e TZ="America/New_York" -e CONFIG_GOOGLE_DOMAIN="com" -e CONFIG_LANGUAGE="en" -e CONFIG_NUMBER_OF_RESULTS="10" -e CONFIG_INVIDIOUS_INSTANCE="https://inv.nadeko.net" -e CONFIG_DISABLE_BITTORRENT_SEARCH=false -e CONFIG_HIDDEN_SERVICE_SEARCH=false -e CONFIG_INSTANCE_FALLBACK=true -e CONFIG_RATE_LIMIT_COOLDOWN=25 -e CONFIG_CACHE_TIME=20 -e CONFIG_DISABLE_API=false -e CONFIG_TEXT_SEARCH_ENGINE="auto" -e CURLOPT_PROXY_ENABLED=true -e CURLOPT_PROXY="172.4.0.5:9050" -e CURLOPT_PROXYTYPE="CURLPROXY_SOCKS5_HOSTNAME" -e CURLOPT_USERAGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0" -e CURLOPT_FOLLOWLOCATION=true -v ./nginx_logs:/var/log/nginx -v ./php_logs:/var/log/php84 --restart unless-stopped librey:latest 29 | docker run -d --name librey-librey_tor-1 --network librey_tor_net --ip=172.4.0.5 -v $PWD/../LibreY-Tor/torrc:/etc/tor/torrc librey-tor:latest 30 | 31 | ``` 32 | 33 | To update containers: 34 | ```sh 35 | cd LibreY/ 36 | docker stop librey librey-librey_tor-1 37 | docker rm librey librey-librey_tor-1 38 | docker buildx build --rm --no-cache --pull -t librey:latest . 39 | cd ../LibreY-Tor/ 40 | docker buildx build --rm --no-cache --pull -t librey-tor:latest . 41 | # Repeat run commands 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/docker-run-prebuilt-tor-proxy.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Please note that this heavily relies on [@codedipper](https://github.com/codedipper)'s [work and is basically a copy of his wiki](https://github.com/codedipper/LibreY-Tor/wiki/LibreY-Tor-Proxy-‐-Prebuilt-images-with-run). 3 | 4 | 5 | Step 0: Install and configure Docker.\ 6 | Step 1: Enter a root shell on the host machine. 7 | 8 | Step 2: Clone the required git repositories to the host machine. 9 | 10 | ```sh 11 | git clone https://github.com/Ahwxorg/LibreY.git LibreY/ 12 | git clone https://github.com/codedipper/LibreY-Tor.git LibreY-Tor/ 13 | ``` 14 | 15 | Step 3: Configure Docker network. 16 | 17 | ```sh 18 | docker network create --driver=bridge --subnet=172.4.0.0/16 librey_tor_net 19 | ``` 20 | 21 | Step 4: Manually edit `LibreY-Tor/torrc` to configure Tor to your liking. 22 | 23 | Step 5: Run it! 24 | ```sh 25 | cd LibreY/ 26 | docker run -d --name librey --ip=172.4.0.6 --network librey_tor_net -p 8080:8080 -e TZ="America/New_York" -e CONFIG_GOOGLE_DOMAIN="com" -e CONFIG_LANGUAGE="en" -e CONFIG_NUMBER_OF_RESULTS="10" -e CONFIG_INVIDIOUS_INSTANCE="https://inv.nadeko.net" -e CONFIG_DISABLE_BITTORRENT_SEARCH=false -e CONFIG_HIDDEN_SERVICE_SEARCH=false -e CONFIG_INSTANCE_FALLBACK=true -e CONFIG_RATE_LIMIT_COOLDOWN=25 -e CONFIG_CACHE_TIME=20 -e CONFIG_DISABLE_API=false -e CONFIG_TEXT_SEARCH_ENGINE="auto" -e CURLOPT_PROXY_ENABLED=true -e CURLOPT_PROXY="172.4.0.5:9050" -e CURLOPT_PROXYTYPE="CURLPROXY_SOCKS5_HOSTNAME" -e CURLOPT_USERAGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0" -e CURLOPT_FOLLOWLOCATION=true -v ./nginx_logs:/var/log/nginx -v ./php_logs:/var/log/php84 --restart unless-stopped ghcr.io/ahwxorg/librey:latest 27 | docker run -d --name librey-watchtower-1 -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower 28 | docker run -d --name librey-librey_tor-1 --network librey_tor_net --ip=172.4.0.5 -v $PWD/../LibreY-Tor/torrc:/etc/tor/torrc ghcr.io/codedipper/librey-tor:latest 29 | ``` 30 | 31 | To update containers: 32 | 33 | ```sh 34 | cd LibreY/ 35 | docker stop librey librey-watchtower-1 librey-librey_tor-1 36 | docker rm librey librey-watchtower-1 librey-librey_tor-1 37 | docker pull ghcr.io/ahwxorg/librey:latest 38 | docker pull containrrr/watchtower 39 | docker pull ghcr.io/codedipper/librey-tor:latest 40 | # Repeat run commands 41 | ``` 42 | 43 | It is recommended to build the images yourself to avoid an outdated Tor version. 44 | -------------------------------------------------------------------------------- /docs/system-tor-proxy.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Please note that this heavily relies on [@codedipper](https://github.com/codedipper)'s [work and is basically a copy of his wiki](https://github.com/codedipper/LibreY-Tor/wiki/LibreY-Tor-Proxy-‐-System-tor). 3 | 4 | 0. This was tested on an Arch Linux setup, packages `base` and `tor` must be installed to get the proxy working.\ 5 | LibreY has other requirements as well.\ 6 | Slight differences in Tor configurations may be needed across different systems. 7 | 8 | 1. Create `/etc/tor/torrc` and edit it to your liking 9 | ``` 10 | SOCKSPort 127.0.0.1:9050 11 | SOCKSPolicy accept 127.0.0.1/32 12 | SOCKSPolicy reject * 13 | 14 | DataDirectory /var/lib/tor 15 | User tor 16 | 17 | Log notice stdout 18 | RunAsDaemon 0 19 | 20 | # UseBridges 1 21 | # Bridge xx.xxx.xxx.xxx:xx 22 | ``` 23 | 24 | 2. Secure directory permissions 25 | ``` 26 | chown -R tor: /usr/share/tor 27 | chown -R tor: /var/lib/tor 28 | chown -R tor: /etc/tor 29 | chmod -R 0700 /usr/share/tor 30 | chmod -R 0700 /var/lib/tor 31 | chmod -R 0700 /etc/tor 32 | ``` 33 | 34 | 3. Start Tor and enable it on boot 35 | ``` 36 | systemctl enable --now tor.service 37 | ``` 38 | 39 | 4. Install LibreY 40 | https://github.com/Ahwxorg/LibreY/tree/main/docs#readme 41 | 42 | 5. Edit configuration files 43 | In your `config.php`, uncomment and change the following options to match the port you're running Tor on. 44 | ``` 45 | // CURLOPT_PROXY => "ip:port", 46 | // CURLOPT_PROXYTYPE => CURLPROXY_HTTP, 47 | ``` 48 | 49 | For our configuration, they would be changed to: 50 | ``` 51 | CURLOPT_PROXY => "127.0.0.1", 52 | CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME, 53 | ``` 54 | 55 | 56 | 6. Start LibreY! You can use a tool like [iftop](https://archlinux.org/packages/extra/x86_64/iftop/) to make sure your LibreY instance is connecting to guard nodes or a bridge instead of Google servers. 57 | 58 | To update Tor and LibreY: 59 | ``` 60 | pacman -Syu 61 | git pull 62 | ``` 63 | -------------------------------------------------------------------------------- /donate.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | LibreY - Donate 8 | 9 | 10 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /engines/ahmia/hidden_service.php: -------------------------------------------------------------------------------- 1 | query); 7 | } 8 | 9 | public function parse_results($response) { 10 | $results = array(); 11 | $xpath = get_xpath($response); 12 | 13 | if (!$xpath) 14 | return $results; 15 | 16 | foreach($xpath->query("//ol[@class='searchResults']//li[@class='result']") as $result) 17 | { 18 | $url = "http://" . $xpath->evaluate(".//cite", $result)[0]->textContent; 19 | $title = remove_special($xpath->evaluate(".//h4", $result)[0]->textContent); 20 | $description = $xpath->evaluate(".//p", $result)[0]->textContent; 21 | 22 | array_push($results, 23 | array ( 24 | "title" => $title ? htmlspecialchars($title) : TEXTS["result_no_description"], 25 | "url" => htmlspecialchars($url), 26 | // base_url is to be removed in the future, see #47 27 | "base_url" => htmlspecialchars(get_base_url($url)), 28 | "description" => htmlspecialchars($description) 29 | ) 30 | ); 31 | } 32 | 33 | return $results; 34 | } 35 | 36 | public static function print_results($results, $opts) { 37 | TextSearch::print_results($results, $opts); 38 | } 39 | } 40 | ?> 41 | -------------------------------------------------------------------------------- /engines/bittorrent/1337x.php: -------------------------------------------------------------------------------- 1 | query); 5 | return "https://1337x.to/search/$query/1/"; 6 | } 7 | 8 | public function parse_results($response) { 9 | $xpath = get_xpath($response); 10 | $results = array(); 11 | 12 | if (!$xpath) 13 | return $results; 14 | 15 | foreach($xpath->query("//table/tbody/tr") as $result) { 16 | $name = $xpath->evaluate(".//td[@class='coll-1 name']/a", $result)[1]->textContent; 17 | $magnet = "./engines/bittorrent/get_magnet_1337x.php?url=https://1337x.to" . $xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[1]->textContent; 18 | $size_unformatted = explode(" ", $xpath->evaluate(".//td[contains(@class, 'coll-4 size')]", $result)[0]->textContent); 19 | $size = $size_unformatted[0] . " " . preg_replace("/[0-9]+/", "", $size_unformatted[1]); 20 | $seeders = $xpath->evaluate(".//td[@class='coll-2 seeds']", $result)[0]->textContent; 21 | $leechers = $xpath->evaluate(".//td[@class='coll-3 leeches']", $result)[0]->textContent; 22 | 23 | array_push($results, 24 | array ( 25 | "name" => htmlspecialchars($name), 26 | "seeders" => (int) $seeders, 27 | "leechers" => (int) $leechers, 28 | "magnet" => htmlspecialchars($magnet), 29 | "size" => htmlspecialchars($size), 30 | "source" => "1337x.to" 31 | ) 32 | ); 33 | } 34 | 35 | return $results; 36 | } 37 | } 38 | ?> 39 | -------------------------------------------------------------------------------- /engines/bittorrent/get_magnet_1337x.php: -------------------------------------------------------------------------------- 1 | curl_settings); 8 | $xpath = get_xpath($response); 9 | 10 | $magnet = $xpath->query("//main/div/div/div/div/div/ul/li/a/@href")[0]->textContent; 11 | $magnet_without_tracker = explode("&tr=", $magnet)[0]; 12 | $magnet = $magnet_without_tracker . $config->bittorrent_trackers; 13 | 14 | header("Location: $magnet") 15 | ?> 16 | -------------------------------------------------------------------------------- /engines/bittorrent/merge.php: -------------------------------------------------------------------------------- 1 | requests = array( 15 | new PirateBayRequest($opts, $mh), 16 | new _1337xRequest($opts, $mh), 17 | new NyaaRequest($opts, $mh), 18 | new RutorRequest($opts, $mh), 19 | new SukebeiRequest($opts, $mh), 20 | new TorrentGalaxyRequest($opts, $mh), 21 | new YTSRequest($opts, $mh), 22 | ); 23 | } 24 | 25 | public function parse_results($response) { 26 | $results = array(); 27 | foreach ($this->requests as $request) { 28 | if ($request->successful()) 29 | $results = array_merge($results, $request->get_results()); 30 | } 31 | 32 | $seeders = array_column($results, "seeders"); 33 | array_multisort($seeders, SORT_DESC, $results); 34 | 35 | return $results; 36 | } 37 | 38 | public static function print_results($results, $opts) { 39 | echo "
"; 40 | 41 | if (empty($results)) { 42 | echo "

" . TEXTS["failure_empty"] . "

"; 43 | return; 44 | } 45 | 46 | foreach($results as $result) { 47 | $source = $result["source"]; 48 | $name = $result["name"]; 49 | $magnet = $result["magnet"]; 50 | $seeders = $result["seeders"]; 51 | $leechers = $result["leechers"]; 52 | $size = $result["size"]; 53 | 54 | echo "
"; 55 | echo ""; 56 | echo "$source"; 57 | echo "

$name

"; 58 | echo "
"; 59 | echo "SE: $seeders - "; 60 | echo "LE: $leechers - "; 61 | echo "$size"; 62 | echo "
"; 63 | } 64 | 65 | echo "
"; 66 | } 67 | } 68 | 69 | ?> 70 | -------------------------------------------------------------------------------- /engines/bittorrent/nyaa.php: -------------------------------------------------------------------------------- 1 | SOURCE/?q=" . urlencode($this->query); 7 | } 8 | 9 | public function parse_results($response) { 10 | $xpath = get_xpath($response); 11 | $results = array(); 12 | 13 | if (!$xpath) 14 | return $results; 15 | 16 | foreach($xpath->query("//tbody/tr") as $result) 17 | { 18 | $name_node = $xpath->evaluate(".//td[@colspan='2']//a[not(contains(@class, 'comments'))]/@title", $result); 19 | if ($name_node->length > 0) { 20 | $name = $name_node[0]->textContent; 21 | } else { 22 | $name = ""; 23 | } 24 | $centered = $xpath->evaluate(".//td[@class='text-center']", $result); 25 | $magnet_node = $xpath->evaluate(".//a[2]/@href", $centered[0]); 26 | if ($magnet_node->length > 0) { 27 | $magnet = $magnet_node[0]->textContent; 28 | $magnet_without_tracker = explode("&tr=", $magnet)[0]; 29 | $magnet = $magnet_without_tracker . $this->opts->bittorrent_trackers; 30 | } else { 31 | $magnet = ""; 32 | } 33 | $size = $centered[1]->textContent; 34 | $seeders = $centered[3]->textContent; 35 | $leechers = $centered[4]->textContent; 36 | 37 | array_push($results, 38 | array ( 39 | "name" => htmlspecialchars($name), 40 | "seeders" => (int) $seeders, 41 | "leechers" => (int) $leechers, 42 | "magnet" => htmlspecialchars($magnet), 43 | "size" => htmlspecialchars($size), 44 | "source" => $this->SOURCE 45 | ) 46 | ); 47 | } 48 | 49 | return $results; 50 | } 51 | } 52 | ?> 53 | -------------------------------------------------------------------------------- /engines/bittorrent/rutor.php: -------------------------------------------------------------------------------- 1 | query); 5 | } 6 | 7 | public function parse_results($response) { 8 | $xpath = get_xpath($response); 9 | $results = array(); 10 | 11 | if (!$xpath) 12 | return $results; 13 | 14 | foreach($xpath->query("//table/tr[@class='gai' or @class='tum']") as $result) 15 | { 16 | $name = $xpath->evaluate(".//td/a", $result)[2]->textContent; 17 | $magnet = $xpath->evaluate(".//td/a/@href", $result)[1]->textContent; 18 | $magnet_without_tracker = explode("&tr=", $magnet)[0]; 19 | $magnet = $magnet_without_tracker . $this->opts->bittorrent_trackers; 20 | $td = $xpath->evaluate(".//td", $result); 21 | $size = $td[count($td) == 5 ? 3 : 2]->textContent; 22 | $seeders = $xpath->evaluate(".//span", $result)[0]->textContent; 23 | $leechers = $xpath->evaluate(".//span", $result)[1]->textContent; 24 | 25 | array_push($results, 26 | array ( 27 | "name" => htmlspecialchars($name), 28 | "seeders" => (int) filter_var($seeders, FILTER_SANITIZE_NUMBER_INT), 29 | "leechers" => (int) filter_var($leechers, FILTER_SANITIZE_NUMBER_INT), 30 | "magnet" => htmlspecialchars($magnet), 31 | "size" => htmlspecialchars($size), 32 | "source" => "rutor.info" 33 | ) 34 | ); 35 | } 36 | 37 | return $results; 38 | } 39 | } 40 | ?> 41 | -------------------------------------------------------------------------------- /engines/bittorrent/sukebei.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /engines/bittorrent/thepiratebay.php: -------------------------------------------------------------------------------- 1 | query); 5 | } 6 | 7 | public function parse_results($response) { 8 | $results = array(); 9 | $json_response = json_decode($response, true); 10 | 11 | if (empty($json_response)) 12 | { 13 | return $results; 14 | } 15 | 16 | foreach ($json_response as $response) 17 | { 18 | $size = human_filesize($response["size"]); 19 | $hash = $response["info_hash"]; 20 | $name = $response["name"]; 21 | $seeders = (int) $response["seeders"]; 22 | $leechers = (int) $response["leechers"]; 23 | 24 | $magnet = "magnet:?xt=urn:btih:$hash&dn=$name" . $this->opts->bittorrent_trackers; 25 | 26 | if ($name == "No results returned") 27 | break; 28 | 29 | array_push($results, 30 | array ( 31 | "size" => htmlspecialchars($size), 32 | "name" => htmlspecialchars($name), 33 | "seeders" => (int) htmlspecialchars($seeders), 34 | "leechers" => (int) htmlspecialchars($leechers), 35 | "magnet" => htmlspecialchars($magnet), 36 | "source" => "thepiratebay.org" 37 | ) 38 | ); 39 | } 40 | 41 | return $results; 42 | 43 | } 44 | } 45 | ?> 46 | -------------------------------------------------------------------------------- /engines/bittorrent/torrentgalaxy.php: -------------------------------------------------------------------------------- 1 | query); 5 | return "https://torrentgalaxy.to/torrents.php?search=$query#results"; 6 | } 7 | 8 | public function parse_results($response) { 9 | $xpath = get_xpath($response); 10 | $results = array(); 11 | 12 | if (!$xpath) 13 | return $results; 14 | 15 | foreach($xpath->query("//div[@class='tgxtablerow txlight']") as $result) 16 | { 17 | $name = $xpath->evaluate(".//div[contains(@class, 'clickable-row')]", $result)[0]->textContent; 18 | $magnet = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/a/@href", $result)[1]->textContent; 19 | $magnet_without_tracker = explode("&tr=", $magnet)[0]; 20 | $magnet = $magnet_without_tracker . $this->opts->bittorrent_trackers; 21 | $size = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/span", $result)[0]->textContent; 22 | $seeders = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/span/font", $result)[1]->textContent; 23 | $leechers = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/span/font", $result)[2]->textContent; 24 | 25 | array_push($results, 26 | array ( 27 | "name" => htmlspecialchars($name), 28 | "seeders" => (int) $seeders, 29 | "leechers" => (int) $leechers, 30 | "magnet" => htmlspecialchars($magnet), 31 | "size" => htmlspecialchars($size), 32 | "source" => "torrentgalaxy.to" 33 | ) 34 | ); 35 | } 36 | 37 | return $results; 38 | } 39 | } 40 | ?> 41 | -------------------------------------------------------------------------------- /engines/bittorrent/yts.php: -------------------------------------------------------------------------------- 1 | query); 5 | } 6 | 7 | public function parse_results($response) { 8 | $response = curl_multi_getcontent($this->ch); 9 | $results = array(); 10 | $json_response = json_decode($response, true); 11 | 12 | if ($json_response["status"] != "ok" || $json_response["data"]["movie_count"] == 0) 13 | return $results; 14 | 15 | foreach ($json_response["data"]["movies"] as $movie) 16 | { 17 | $name = $movie["title"]; 18 | $name_encoded = urlencode($name); 19 | 20 | foreach ($movie["torrents"] as $torrent) 21 | { 22 | 23 | $hash = $torrent["hash"]; 24 | $seeders = $torrent["seeds"]; 25 | $leechers = $torrent["peers"]; 26 | $size = $torrent["size"]; 27 | 28 | $magnet = "magnet:?xt=urn:btih:$hash&dn=$name_encoded" . $this->opts->bittorrent_trackers; 29 | 30 | array_push($results, 31 | array ( 32 | "size" => htmlspecialchars($size), 33 | "name" => htmlspecialchars($name), 34 | "seeders" => htmlspecialchars($seeders), 35 | "leechers" => htmlspecialchars($leechers), 36 | "magnet" => htmlspecialchars($magnet), 37 | "source" => "yts.mx" 38 | ) 39 | ); 40 | } 41 | } 42 | 43 | return $results; 44 | } 45 | } 46 | ?> 47 | -------------------------------------------------------------------------------- /engines/invidious/video.php: -------------------------------------------------------------------------------- 1 | instance_url = $this->opts->invidious_instance_for_video_results; 7 | $query = urlencode($this->query); 8 | return "$this->instance_url/api/v1/search?q=$query"; 9 | } 10 | 11 | public function parse_results($response) { 12 | $results = array(); 13 | $json_response = json_decode($response, true); 14 | 15 | foreach ($json_response ?: [] as $response) { 16 | if ($response["type"] == "video") { 17 | $title = $response["title"]; 18 | $url = "https://youtube.com/watch?v=" . $response["videoId"]; 19 | $uploader = $response["author"]; 20 | $views = $response["viewCountText"]; 21 | $date = $response["publishedText"]; 22 | $lengthSeconds = $response["lengthSeconds"]; 23 | $thumbnail = $this->instance_url . "/vi/" . explode("/vi/" ,$response["videoThumbnails"][4]["url"])[1]; 24 | 25 | array_push($results, 26 | array ( 27 | "title" => htmlspecialchars($title), 28 | "url" => htmlspecialchars($url), 29 | // base_url is to be removed in the future, see #47 30 | "base_url" => htmlspecialchars(get_base_url($url)), 31 | "uploader" => htmlspecialchars($uploader), 32 | "views" => htmlspecialchars($views), 33 | "date" => htmlspecialchars($date), 34 | "lengthSeconds" => htmlspecialchars($lengthSeconds), 35 | "thumbnail" => htmlspecialchars($thumbnail) 36 | ) 37 | ); 38 | } 39 | } 40 | 41 | return $results; 42 | } 43 | 44 | public static function print_results($results, $opts) { 45 | echo "
"; 46 | 47 | foreach ($results as $key => $result) { 48 | if ($key == "results_source") continue; 49 | $title = $result["title"] ?? ''; 50 | $url = $result["url"] ?? ''; 51 | $url = check_for_privacy_frontend($url, $opts); 52 | $base_url = get_base_url($url); 53 | $uploader = $result["uploader"] ?? ''; 54 | $views = $result["views"] ?? ''; 55 | $date = $result["date"] ?? ''; 56 | $lengthSeconds = $result["lengthSeconds"] ?? ''; 57 | $length = seconds_to_human_readable($lengthSeconds) ?? ''; 58 | $thumbnail = "https://i.ytimg.com/vi/" . htmlspecialchars(explode("=", $url)[1]) . "/mqdefault.jpg" ?? ''; 59 | 60 | echo ""; 71 | } 72 | 73 | 74 | echo "
"; 75 | } 76 | } 77 | ?> 78 | -------------------------------------------------------------------------------- /engines/librex/fallback.php: -------------------------------------------------------------------------------- 1 | instance = $instance; 7 | parent::__construct($opts, $mh); 8 | } 9 | 10 | public function get_request_url() { 11 | return $this->instance . "api.php?" . opts_to_params($this->opts) . "&nfb=1"; 12 | } 13 | 14 | public function parse_results($response) { 15 | $response = json_decode($response, true); 16 | if (!$response) 17 | return array(); 18 | 19 | return array_values($response); 20 | } 21 | } 22 | 23 | function load_instances($cooldowns) { 24 | $instances_json = json_decode(file_get_contents("instances.json"), true); 25 | 26 | if (empty($instances_json["instances"])) 27 | return array(); 28 | 29 | $instances = array_map(fn($n) => $n['clearnet'], array_filter($instances_json['instances'], fn($n) => !is_null($n['clearnet']))); 30 | $instances = array_filter($instances, fn($n) => !has_cooldown($n, $cooldowns)); 31 | shuffle($instances); 32 | return $instances; 33 | } 34 | 35 | function get_librex_results($opts) { 36 | if (!$opts->do_fallback) 37 | return array(); 38 | 39 | $cooldowns = $opts->cooldowns; 40 | $instances = load_instances($cooldowns); 41 | 42 | $results = array(); 43 | $tries = 0; 44 | 45 | do { 46 | $tries++; 47 | 48 | $instance = array_pop($instances); 49 | 50 | if (!$instance) 51 | break; 52 | 53 | if (!(filter_var($instance, FILTER_VALIDATE_URL))) 54 | continue; 55 | 56 | if (parse_url($instance)["host"] == $_SERVER['HTTP_HOST']) 57 | continue; 58 | 59 | $librex_request = new LibreXFallback($instance, $opts, null); 60 | 61 | $results = $librex_request->get_results(); 62 | 63 | if (!empty($results)) { 64 | $results["results_source"] = parse_url($instance)["host"]; 65 | return $results; 66 | } 67 | 68 | // on fail then do this 69 | $timeout = ($opts->request_cooldown ?? "1") * 60; 70 | $cooldowns = set_cooldown($instance, $timeout, $cooldowns); 71 | 72 | } while (!empty($instances)); 73 | 74 | return array( 75 | "error" => array( 76 | "message" => TEXTS["failure_fallback"] 77 | ) 78 | ); 79 | } 80 | 81 | ?> 82 | -------------------------------------------------------------------------------- /engines/maps/openstreetmap.php: -------------------------------------------------------------------------------- 1 | query)); 7 | $results = array(); 8 | 9 | // TODO allow the nominatim instance to be customised 10 | $url = "https://nominatim.openstreetmap.org/search?q=$query_encoded&format=json"; 11 | 12 | return $url; 13 | } 14 | 15 | 16 | public function parse_results($response) { 17 | $json_response = json_decode($response, true); 18 | if (!$json_response) 19 | return array(); 20 | 21 | $results = array(); 22 | foreach ($json_response as $item) { 23 | array_push($results, array( 24 | "title" => $item["name"], 25 | "description" => $item["display_name"], 26 | "url" => "https://www.openstreetmap.org/" . $item["osm_type"] . "/" . $item["osm_id"], 27 | "base_url" => "www.openstreetmap.org" 28 | )); 29 | } 30 | return $results; 31 | } 32 | 33 | public static function print_results($results, $opts) { 34 | TextSearch::print_results($results, $opts); 35 | } 36 | } 37 | ?> 38 | -------------------------------------------------------------------------------- /engines/qwant/image.php: -------------------------------------------------------------------------------- 1 | page * 5; // load 50 images per page 5 | $query = urlencode($this->query); 6 | return "https://api.qwant.com/v3/search/images?q=$query&t=images&count=50&locale=en_us&offset=$offset&device=desktop&tgp=3&safesearch=1"; 7 | } 8 | 9 | public function parse_results($response) { 10 | $json = json_decode($response, true); 11 | $results = array(); 12 | 13 | if ($json["status"] != "success") 14 | return $results; // no results 15 | 16 | $imgs = $json["data"]["result"]["items"]; 17 | $imgCount = $json["data"]["result"]["total"]; 18 | 19 | for ($i = 0; $i < $imgCount; $i++) 20 | { 21 | array_push($results, 22 | array ( 23 | "thumbnail" => htmlspecialchars($imgs[$i]["thumbnail"]), 24 | "alt" => htmlspecialchars($imgs[$i]["title"]), 25 | "url" => htmlspecialchars($imgs[$i]["url"]) 26 | ) 27 | ); 28 | } 29 | 30 | return $results; 31 | } 32 | 33 | public static function print_results($results, $opts) { 34 | echo "
"; 35 | 36 | foreach($results as $result) 37 | { 38 | if (!$result 39 | || !array_key_exists("url", $result) 40 | || !array_key_exists("alt", $result)) 41 | continue; 42 | $thumbnail = urlencode($result["thumbnail"]); 43 | $alt = $result["alt"]; 44 | $url = $result["url"]; 45 | $url = check_for_privacy_frontend($url, $opts); 46 | 47 | echo ""; 48 | echo ""; 49 | echo ""; 50 | } 51 | 52 | echo "
"; 53 | } 54 | } 55 | ?> 56 | -------------------------------------------------------------------------------- /engines/special/currency.php: -------------------------------------------------------------------------------- 1 | query); 9 | 10 | $base_currency = strtoupper($split_query[1]); 11 | $currency_to_convert = strtoupper($split_query[3]); 12 | $amount_to_convert = floatval($split_query[0]); 13 | 14 | $json_response = json_decode($response, true); 15 | 16 | $rates = $json_response["rates"]; 17 | 18 | if (!array_key_exists($base_currency, $rates) || !array_key_exists($currency_to_convert, $rates)) 19 | return array(); 20 | $base_currency_response = $rates[$base_currency]; 21 | $currency_to_convert_response = $rates[$currency_to_convert]; 22 | 23 | $conversion_result = ($currency_to_convert_response / $base_currency_response) * $amount_to_convert; 24 | 25 | $formatted_response = "$amount_to_convert $base_currency = $conversion_result $currency_to_convert"; 26 | $source = "https://moneyconvert.net/"; 27 | return array( 28 | "special_response" => array( 29 | "response" => htmlspecialchars($formatted_response), 30 | "source" => $source 31 | ) 32 | ); 33 | } 34 | } 35 | ?> 36 | -------------------------------------------------------------------------------- /engines/special/definition.php: -------------------------------------------------------------------------------- 1 | query); 6 | $reversed_split_q = array_reverse($split_query); 7 | $word_to_define = $reversed_split_q[1] == "define" ? $reversed_split_q[0] : $reversed_split_q[1]; 8 | return "https://api.dictionaryapi.dev/api/v2/entries/en/$word_to_define"; 9 | } 10 | 11 | public function parse_results($response) { 12 | $json_response = json_decode($response, true); 13 | 14 | if (!$json_response) 15 | return array(); 16 | 17 | if (!array_key_exists("title", $json_response)) 18 | { 19 | $definition = $json_response[0]["meanings"][0]["definitions"][0]["definition"]; 20 | 21 | $source = "https://dictionaryapi.dev"; 22 | return array( 23 | "special_response" => array( 24 | "response" => htmlspecialchars($definition), 25 | "source" => $source 26 | ) 27 | ); 28 | } 29 | 30 | } 31 | } 32 | ?> 33 | -------------------------------------------------------------------------------- /engines/special/ip.php: -------------------------------------------------------------------------------- 1 | array( 6 | "response" => $_SERVER["REMOTE_ADDR"], 7 | "source" => null 8 | ) 9 | ); 10 | } 11 | } 12 | ?> 13 | -------------------------------------------------------------------------------- /engines/special/special.php: -------------------------------------------------------------------------------- 1 | = 4) // currency 11 | { 12 | $amount_to_convert = floatval($split_query[0]); 13 | if ($amount_to_convert != 0) 14 | return 1; 15 | } 16 | else if ((strpos($query_lower, "mean") || str_starts_with($query_lower, "define")) && count($split_query) >= 2) // definition 17 | { 18 | return 2; 19 | } 20 | else if (strpos($query_lower, "my") !== false) 21 | { 22 | if (strpos($query_lower, "ip")) 23 | { 24 | return 3; 25 | } 26 | else if (strpos($query_lower, "user agent") || strpos($query_lower, "ua")) 27 | { 28 | return 4; 29 | } 30 | } 31 | else if (strpos($query_lower, "weather") !== false) 32 | { 33 | return 5; 34 | } 35 | else if ($query_lower == "tor") 36 | { 37 | return 6; 38 | } 39 | else if (3 > count(explode(" ", $query))) // wikipedia 40 | { 41 | return 7; 42 | } 43 | 44 | return 0; 45 | } 46 | 47 | function get_special_search_request($opts, $mh) { 48 | if ($opts->page != 0) 49 | return null; 50 | 51 | $special_search = check_for_special_search($opts->query); 52 | $special_request = null; 53 | $url = null; 54 | 55 | if ($special_search == 0) 56 | return null; 57 | 58 | switch ($special_search) { 59 | case 1: 60 | require_once "engines/special/currency.php"; 61 | $special_request = new CurrencyRequest($opts, $mh); 62 | break; 63 | case 2: 64 | require_once "engines/special/definition.php"; 65 | $special_request = new DefinitionRequest($opts, $mh); 66 | break; 67 | case 3: 68 | require_once "engines/special/ip.php"; 69 | $special_request = new IPRequest($opts, $mh); 70 | break; 71 | case 4: 72 | require_once "engines/special/user_agent.php"; 73 | $special_request = new UserAgentRequest($opts, $mh); 74 | break; 75 | case 5: 76 | require_once "engines/special/weather.php"; 77 | $special_request = new WeatherRequest($opts, $mh); 78 | break; 79 | case 6: 80 | require_once "engines/special/tor.php"; 81 | $special_request = new TorRequest($opts, $mh); 82 | break; 83 | case 7: 84 | require_once "engines/special/wikipedia.php"; 85 | $special_request = new WikipediaRequest($opts, $mh); 86 | break; 87 | } 88 | 89 | return $special_request; 90 | } 91 | ?> 92 | -------------------------------------------------------------------------------- /engines/special/tor.php: -------------------------------------------------------------------------------- 1 | array( 14 | "response" => $formatted_response, 15 | "source" => $source 16 | ) 17 | ); 18 | } 19 | } 20 | ?> 21 | -------------------------------------------------------------------------------- /engines/special/user_agent.php: -------------------------------------------------------------------------------- 1 | array( 6 | "response" => $_SERVER["HTTP_USER_AGENT"], 7 | "source" => null 8 | ) 9 | ); 10 | } 11 | } 12 | ?> 13 | -------------------------------------------------------------------------------- /engines/special/weather.php: -------------------------------------------------------------------------------- 1 | array( 24 | "response" => htmlspecialchars($formatted_response), 25 | "source" => $source 26 | ) 27 | ); 28 | } 29 | } 30 | ?> 31 | -------------------------------------------------------------------------------- /engines/special/wikipedia.php: -------------------------------------------------------------------------------- 1 | wikipedia_domain = "wikipedia.org"; 6 | $query_encoded = urlencode($this->query); 7 | 8 | $languages = json_decode(file_get_contents("static/misc/languages.json"), true); 9 | 10 | if (array_key_exists($this->opts->language, $languages)) 11 | $this->wikipedia_domain = $languages[$this->opts->language]["wikipedia"] . ".wikipedia.org"; 12 | 13 | return "https://$this->wikipedia_domain/w/api.php?format=json&action=query&prop=extracts%7Cpageimages&exintro&explaintext&redirects=1&pithumbsize=500&titles=$query_encoded"; 14 | } 15 | 16 | public function parse_results($response) { 17 | $json_response = json_decode($response, true); 18 | if (!$json_response) 19 | return array(); 20 | 21 | $first_page = array_values($json_response["query"]["pages"])[0]; 22 | 23 | if (array_key_exists("missing", $first_page)) 24 | return array(); 25 | 26 | $description = substr($first_page["extract"], 0, 250) . "..."; 27 | 28 | $source = "https://$this->wikipedia_domain/wiki/$this->query"; 29 | $response = array( 30 | "special_response" => array( 31 | "response" => htmlspecialchars($description), 32 | "source" => $source 33 | ) 34 | ); 35 | 36 | if (array_key_exists("thumbnail", $first_page)) { 37 | $image_url = $first_page["thumbnail"]["source"]; 38 | $response["special_response"]["image"] = $image_url; 39 | } 40 | 41 | return $response; 42 | } 43 | } 44 | ?> 45 | -------------------------------------------------------------------------------- /engines/text/bing.php: -------------------------------------------------------------------------------- 1 | query)); 5 | 6 | $results_language = $this->opts->language; 7 | $number_of_results = $this->opts->number_of_results; 8 | 9 | // NOTE Page(0,1)=1, Page(2)=9, Page(3+)=23..37..51.. 10 | if ($this->page <= 1) 11 | $page = 1; 12 | elseif($this->page == 2) 13 | $page = 9; 14 | else 15 | $page = 9 + (($this->page - 2) * 14); 16 | $url = "https://www.bing.com/search?q=$query_encoded&first=$page&rdr=1"; 17 | 18 | $randomBytes = strtoupper(bin2hex(random_bytes(16))); 19 | $url = "https://www.bing.com/search?q=$query_encoded&first=$page&rdr=1&rdrig=$randomBytes"; 20 | 21 | if (!is_null($results_language)) 22 | $url .= "&srchlang=$results_language"; 23 | 24 | // TODO Reconsider current safe-search implementation for granularity 25 | // NOTE Possible values are strict, demote (moderate, default), off 26 | if (isset($_COOKIE["safe_search"])) 27 | $url .= "&adlt=demote"; 28 | 29 | return $url; 30 | } 31 | 32 | public function parse_results($response) { 33 | $results = array(); 34 | $xpath = get_xpath($response); 35 | 36 | if (!$xpath) 37 | return $results; 38 | 39 | foreach($xpath->query("//ol[@id='b_results']/li") as $result) { 40 | $href_url = $xpath->evaluate(".//h2/a/@href", $result)[0]; 41 | 42 | if ($href_url == null) 43 | continue; 44 | 45 | $possible_url = $href_url->textContent; 46 | 47 | $possible_url_query = parse_url($possible_url, PHP_URL_QUERY); 48 | 49 | if ($possible_url_query == false) 50 | continue; 51 | 52 | parse_str($possible_url_query, $possible_url); 53 | 54 | if (!array_key_exists('u', $possible_url)) 55 | continue; 56 | 57 | $possible_url = $possible_url['u']; 58 | 59 | if (str_starts_with($possible_url, "a1aHR0c")) 60 | { 61 | // First two characters are irrelevant, strip for later 62 | $possible_url = substr($possible_url, 2); 63 | } 64 | if (str_starts_with($possible_url, "aHR0c")) 65 | { 66 | // Base64 "coded", extract and decode 67 | $possible_url = str_replace('-', '+', $possible_url); 68 | $possible_url = str_replace('_', '/', $possible_url); 69 | $url = urldecode(base64_decode($possible_url, true)); 70 | } else 71 | $url = $possible_url; 72 | 73 | if (str_starts_with($url, "a1")) 74 | continue; // It's probably a Bing-relative link such as for video, skip it. 75 | 76 | if (!empty($results) && array_key_exists("url", $results) && end($results)["url"] == $url->textContent) 77 | continue; 78 | 79 | $title = $xpath->evaluate("./h2/a", $result)[0]; 80 | 81 | if ($title == null) 82 | continue; 83 | 84 | $title = $title->textContent; 85 | 86 | $description = ($xpath->evaluate("./div[contains(@class, 'b_caption')]/p", $result)[0] ?? null) ?->textContent ?? ''; 87 | 88 | array_push($results, 89 | array ( 90 | "title" => htmlspecialchars($title), 91 | "url" => htmlspecialchars($url), 92 | // base_url is to be removed in the future, see #47 93 | "base_url" => htmlspecialchars(get_base_url($url)), 94 | "description" => $description == null ? 95 | TEXTS["result_no_description"] : 96 | htmlspecialchars($description) 97 | ) 98 | ); 99 | } 100 | 101 | $didyoumean = $xpath->evaluate("//ol[@id='b_results']/li/div[contains(@class, 'sp_requery')]/a/strong")[0] ?? null; 102 | 103 | if (!is_null($didyoumean)) 104 | array_push($results, array( 105 | "did_you_mean" => $didyoumean->textContent 106 | )); 107 | 108 | return $results; 109 | } 110 | } 111 | ?> 112 | -------------------------------------------------------------------------------- /engines/text/brave.php: -------------------------------------------------------------------------------- 1 | query)); 5 | 6 | $results_language = $this->opts->language; 7 | $number_of_results = $this->opts->number_of_results; 8 | 9 | // TODO find the right parameters for the url 10 | $url = "https://search.brave.com/search?q=$query_encoded&nfpr=1&spellcheck=0&start=$this->page"; 11 | 12 | if (3 > strlen($results_language) && 0 < strlen($results_language)) { 13 | $url .= "&lr=lang_$results_language"; 14 | $url .= "&hl=$results_language"; 15 | } 16 | 17 | if (3 > strlen($number_of_results) && 0 < strlen($number_of_results)) 18 | $url .= "&num=$number_of_results"; 19 | 20 | if (isset($_COOKIE["safe_search"])) 21 | $url .= "&safe=medium"; 22 | 23 | return $url; 24 | } 25 | 26 | public function parse_results($response) { 27 | $results = array(); 28 | $xpath = get_xpath($response); 29 | 30 | if (!$xpath) 31 | return $results; 32 | 33 | foreach($xpath->query("//div[@id='results']//div[contains(@class, 'snippet')]") as $result) { 34 | $url = $xpath->evaluate(".//a[contains(@class, 'h')]//@href", $result)[0]; 35 | 36 | if ($url == null) 37 | continue; 38 | 39 | $url = $url->textContent; 40 | 41 | if (!empty($results) && array_key_exists("url", end($results)) && end($results)["url"] == $url->textContent) 42 | continue; 43 | 44 | $title = $xpath->evaluate(".//a[contains(@class, 'h')]//div[contains(@class, 'url')]", $result)[0]; 45 | 46 | if ($title == null) 47 | continue; 48 | $title = $title->textContent; 49 | 50 | $title = end(explode("›", $title)); 51 | 52 | $description = ($xpath->evaluate(".//div[contains(@class, 'snippet-content')]//div[contains(@class, 'snippet-description')]", $result)[0] ?? null) ?->textContent ?? ''; 53 | 54 | array_push($results, 55 | array ( 56 | "title" => htmlspecialchars($title), 57 | "url" => htmlspecialchars($url), 58 | // base_url is to be removed in the future, see #47 59 | "base_url" => htmlspecialchars(get_base_url($url)), 60 | "description" => $description == null ? 61 | TEXTS["result_no_description"] : 62 | htmlspecialchars($description) 63 | ) 64 | ); 65 | 66 | } 67 | return $results; 68 | } 69 | 70 | } 71 | ?> 72 | -------------------------------------------------------------------------------- /engines/text/duckduckgo.php: -------------------------------------------------------------------------------- 1 | query)); 5 | $results = array(); 6 | 7 | $domain = 'com'; 8 | $results_language = $this->opts->language; 9 | $number_of_results = $this->opts->number_of_results; 10 | 11 | $url = "https://html.duckduckgo.$domain/html/?q=$query_encoded&kd=-1&s=" . 3 * $this->page; 12 | 13 | if (3 > strlen($results_language) && 0 < strlen($results_language)) 14 | $url .= "&lr=lang_$results_language"; 15 | 16 | if (3 > strlen($number_of_results) && 0 < strlen($number_of_results)) 17 | $url .= "&num=$number_of_results"; 18 | 19 | if (isset($_COOKIE["safe_search"])) 20 | $url .= "&safe=medium"; 21 | 22 | return $url; 23 | } 24 | 25 | public function parse_results($response) { 26 | $results = array(); 27 | $xpath = get_xpath($response); 28 | 29 | if (!$xpath) 30 | return $results; 31 | 32 | foreach($xpath->query("/html/body/div[1]/div[". count($xpath->query('/html/body/div[1]/div')) ."]/div/div/div[contains(@class, 'web-result')]/div") as $result) { 33 | $url = $xpath->evaluate(".//h2[@class='result__title']//a/@href", $result)[0]; 34 | 35 | if ($url == null) 36 | continue; 37 | 38 | if (!empty($results)) { // filter duplicate results 39 | if (end($results)["url"] == $url->textContent) 40 | continue; 41 | } 42 | 43 | $url = $url->textContent; 44 | 45 | $title = $xpath->evaluate(".//h2[@class='result__title']", $result)[0]; 46 | $description = $xpath->evaluate(".//a[@class='result__snippet']", $result)[0]; 47 | 48 | array_push($results, 49 | array ( 50 | "title" => htmlspecialchars($title->textContent), 51 | "url" => htmlspecialchars($url), 52 | // base_url is to be removed in the future, see #47 53 | "base_url" => htmlspecialchars(get_base_url($url)), 54 | "description" => $description == null ? 55 | TEXTS["result_no_description"] : 56 | htmlspecialchars($description->textContent) 57 | ) 58 | ); 59 | } 60 | return $results; 61 | } 62 | 63 | } 64 | ?> 65 | -------------------------------------------------------------------------------- /engines/text/ecosia.php: -------------------------------------------------------------------------------- 1 | query)); 5 | 6 | $results_language = $this->opts->language; 7 | $number_of_results = $this->opts->number_of_results; 8 | 9 | // TODO figure out how to not autocorrect 10 | $url = "https://www.ecosia.org/search?method=index&q=$query_encoded&p=$this->page"; 11 | 12 | if (!is_null($results_language)) 13 | $url .= "&lang=$results_language"; 14 | 15 | return $url; 16 | } 17 | 18 | public function parse_results($response) { 19 | $results = array(); 20 | $xpath = get_xpath($response); 21 | 22 | if (!$xpath) 23 | return $results; 24 | 25 | 26 | foreach($xpath->query("//div[contains(@class, 'mainline__result-wrapper')]") as $result) { 27 | $url = $xpath->evaluate(".//article//div[contains(@class, 'result__body')]//div[contains(@class, 'result__header')]//div[contains(@class, 'result__info')]//a[contains(@class, 'result__link')]//@href", $result)[0]; 28 | 29 | if ($url == null) 30 | continue; 31 | 32 | $url = $url->textContent; 33 | 34 | if (!empty($results) && array_key_exists("url", end($results)) && end($results)["url"] == $url->textContent) 35 | continue; 36 | 37 | $title = $xpath->evaluate(".//article//div[contains(@class, 'result__body')]//div[contains(@class, 'result__header')]//div[contains(@class, 'result__title')]//a//h2", $result)[0]; 38 | 39 | if ($title == null) 40 | continue; 41 | 42 | $title = $title->textContent; 43 | 44 | $description = $xpath->evaluate(".//article//div[contains(@class, 'result__body')]//div[contains(@class, 'result__columns')]//div[contains(@class, 'result__columns-start')]//div//div//div/p", $result)[0]->textContent; 45 | 46 | array_push($results, 47 | array ( 48 | "title" => htmlspecialchars($title), 49 | "url" => htmlspecialchars($url), 50 | // base_url is to be removed in the future, see #47 51 | "base_url" => htmlspecialchars(get_base_url($url)), 52 | "description" => $description == null ? 53 | TEXTS["result_no_description"] : 54 | htmlspecialchars($description) 55 | ) 56 | ); 57 | 58 | } 59 | return $results; 60 | } 61 | 62 | } 63 | ?> 64 | -------------------------------------------------------------------------------- /engines/text/google.php: -------------------------------------------------------------------------------- 1 | arc_id = "srp_"; 9 | 10 | for ($i = 0; $i < 24; $i++) { 11 | $c = random_int(0, strlen($charset) - 1); 12 | $this->arc_id .= $charset[$c]; 13 | } 14 | 15 | $this->arc_id .= "_1"; 16 | $this->arc_timestamp = time(); 17 | } 18 | 19 | public function get_request_url() { 20 | if ($this->arc_timestamp + 3600 < time()) 21 | $this->generate_arc_id(); 22 | 23 | $query_encoded = str_replace("%22", "\"", urlencode($this->query)); 24 | $results = array(); 25 | 26 | $domain = $this->opts->google_domain; 27 | $results_language = $this->opts->language; 28 | $number_of_results = $this->opts->number_of_results; 29 | $arc_page = sprintf("%02d", $this->page * 10); 30 | 31 | $url = "https://www.google.$domain/search?q=$query_encoded&nfpr=1&start=$this->page"; 32 | 33 | if (3 > strlen($results_language) && 0 < strlen($results_language)) { 34 | $url .= "&lr=lang_$results_language"; 35 | $url .= "&hl=$results_language"; 36 | } 37 | 38 | if (3 > strlen($number_of_results) && 0 < strlen($number_of_results)) 39 | $url .= "&num=$number_of_results"; 40 | 41 | if (isset($_COOKIE["safe_search"])) 42 | $url .= "&safe=medium"; 43 | 44 | $url .= "&asearch=arc&async=arc_id:$this->arc_id$arc_page,use_ac:true,_fmt:html"; 45 | 46 | return $url; 47 | } 48 | 49 | 50 | public function parse_results($response) { 51 | $results = array(); 52 | $xpath = get_xpath($response); 53 | 54 | if (!$xpath) 55 | return $results; 56 | 57 | $didyoumean = $xpath->query(".//p[@class='QRYxYe NNMgCf']/a/b/i")[0]; 58 | 59 | if (!is_null($didyoumean)) 60 | array_push($results, array( 61 | "did_you_mean" => $didyoumean->textContent 62 | )); 63 | 64 | foreach($xpath->query("//div[@class='MjjYud']") as $result) { 65 | $url = $xpath->evaluate(".//a[@class='zReHs']/@href", $result)[0]; 66 | 67 | if ($url == null) 68 | continue; 69 | 70 | if (!empty($results) && array_key_exists("url", end($results)) && end($results)["url"] == $url->textContent) 71 | continue; 72 | 73 | $url = $url->textContent; 74 | 75 | $title = $xpath->evaluate(".//h3", $result)[0]; 76 | $description = $xpath->evaluate(".//div[contains(@class, 'VwiC3b')]", $result)[0]; 77 | 78 | array_push($results, 79 | array ( 80 | "title" => htmlspecialchars($title->textContent), 81 | "url" => htmlspecialchars($url), 82 | // base_url is to be removed in the future, see #47 83 | "base_url" => htmlspecialchars(get_base_url($url)), 84 | "description" => $description == null ? 85 | TEXTS["result_no_description"] : 86 | htmlspecialchars($description->textContent) 87 | ) 88 | ); 89 | } 90 | 91 | if (empty($results) && !str_contains($response, "Our systems have detected unusual traffic from your computer network.")) { 92 | $results["error"] = array( 93 | "message" => TEXTS["failure_empty"] 94 | ); 95 | } 96 | 97 | return $results; 98 | } 99 | } 100 | ?> 101 | -------------------------------------------------------------------------------- /engines/text/mojeek.php: -------------------------------------------------------------------------------- 1 | query)); 5 | 6 | $results_language = $this->opts->language; 7 | $number_of_results = $this->opts->number_of_results; 8 | 9 | // TODO figure out how to not autocorrect 10 | $url = "https://www.mojeek.com/search?q=$query_encoded&p=$this->page"; 11 | 12 | // TODO language setting 13 | if (!is_null($results_language)) 14 | $url .= "&lang=$results_language"; 15 | 16 | return $url; 17 | } 18 | 19 | public function parse_results($response) { 20 | $results = array(); 21 | $xpath = get_xpath($response); 22 | 23 | if (!$xpath) 24 | return $results; 25 | 26 | 27 | foreach($xpath->query("//ul[contains(@class, 'results-standard')]//li") as $result) { 28 | $url = $xpath->evaluate(".//h2//a[contains(@class, 'title')]//@href", $result)[0]; 29 | 30 | if ($url == null) 31 | continue; 32 | 33 | $url = $url->textContent; 34 | 35 | if (!empty($results) && array_key_exists("url", $results) && end($results)["url"] == $url->textContent) 36 | continue; 37 | 38 | $title = $xpath->evaluate(".//h2//a[contains(@class, 'title')]", $result)[0]; 39 | 40 | if ($title == null) 41 | continue; 42 | 43 | $title = $title->textContent; 44 | 45 | $description = ($xpath->evaluate(".//p[contains(@class, 's')]", $result)[0] ?? null) ?->textContent ?? ''; 46 | 47 | array_push($results, 48 | array ( 49 | "title" => htmlspecialchars($title), 50 | "url" => htmlspecialchars($url), 51 | // base_url is to be removed in the future, see #47 52 | "base_url" => htmlspecialchars(get_base_url($url)), 53 | "description" => $description == null ? 54 | TEXTS["result_no_description"] : 55 | htmlspecialchars($description) 56 | ) 57 | ); 58 | 59 | } 60 | return $results; 61 | } 62 | 63 | } 64 | ?> 65 | -------------------------------------------------------------------------------- /engines/text/text.php: -------------------------------------------------------------------------------- 1 | engines = get_engines(); 10 | shuffle($this->engines); 11 | 12 | $this->query = $opts->query; 13 | $this->cache_key = "text:" . $this->query . "p" . $opts->page . "l" . $opts->language; 14 | 15 | $this->page = $opts->page; 16 | $this->opts = $opts; 17 | 18 | $this->engine = $opts->engine; 19 | 20 | $query_parts = explode(" ", $this->query); 21 | $last_word_query = end($query_parts); 22 | if (substr($this->query, 0, 1) == "!" || substr($last_word_query, 0, 1) == "!") 23 | check_ddg_bang($this->query, $opts); 24 | 25 | if (has_cached_results($this->cache_key)) 26 | return; 27 | 28 | if ($this->engine == "auto") 29 | $this->engine = $this->select_engine(); 30 | 31 | // no engine was selected 32 | if (is_null($this->engine)) 33 | return; 34 | 35 | // this only happens if a specific engine was selected, not if auto is used 36 | if (has_cooldown($this->engine, $this->opts->cooldowns)) 37 | return; 38 | 39 | $this->engine_request = $this->get_engine_request($this->engine, $opts, $mh); 40 | 41 | if (is_null($this->engine_request)) 42 | return; 43 | 44 | require_once "engines/special/special.php"; 45 | $this->special_request = get_special_search_request($opts, $mh); 46 | } 47 | private function select_engine() { 48 | if (sizeof($this->engines) == 0) 49 | return null; 50 | 51 | $engine = array_pop($this->engines); 52 | 53 | // if this engine is on cooldown, try again 54 | if (!has_cooldown($engine, $this->opts->cooldowns)) 55 | return $engine; 56 | 57 | return $this->select_engine(); 58 | } 59 | 60 | private function get_engine_request($engine, $opts, $mh) { 61 | if ($engine == "google") { 62 | require_once "engines/text/google.php"; 63 | return new GoogleRequest($opts, $mh); 64 | } 65 | 66 | if ($engine == "duckduckgo") { 67 | require_once "engines/text/duckduckgo.php"; 68 | return new DuckDuckGoRequest($opts, $mh); 69 | } 70 | 71 | if ($engine == "brave") { 72 | require_once "engines/text/brave.php"; 73 | return new BraveSearchRequest($opts, $mh); 74 | } 75 | 76 | if ($engine == "yandex") { 77 | require_once "engines/text/yandex.php"; 78 | return new YandexSearchRequest($opts, $mh); 79 | } 80 | 81 | if ($engine == "ecosia") { 82 | require_once "engines/text/ecosia.php"; 83 | return new EcosiaSearchRequest($opts, $mh); 84 | } 85 | 86 | if ($engine == "mojeek") { 87 | require_once "engines/text/mojeek.php"; 88 | return new MojeekSearchRequest($opts, $mh); 89 | } 90 | 91 | if ($engine == "bing") { 92 | require_once "engines/text/bing.php"; 93 | return new BingSearchRequest($opts, $mh); 94 | } 95 | 96 | // if an invalid engine is selected, don't give any results 97 | return null; 98 | } 99 | 100 | public function parse_results($response) { 101 | if (has_cached_results($this->cache_key)) 102 | return fetch_cached_results($this->cache_key); 103 | 104 | if (!isset($this->engine_request)) 105 | return array(); 106 | 107 | $results = $this->engine_request->get_results(); 108 | 109 | if (empty($results)) { 110 | set_cooldown($this->engine, ($this->opts->request_cooldown ?? "1") * 60, $this->opts->cooldowns); 111 | } else { 112 | if ($this->special_request) { 113 | $special_result = $this->special_request->get_results(); 114 | 115 | if ($special_result) 116 | $results = array_merge(array($special_result), $results); 117 | } 118 | } 119 | 120 | if (!empty($results)) { 121 | $results["results_source"] = parse_url($this->engine_request->url)["host"]; 122 | store_cached_results($this->cache_key, $results, $this->opts->cache_time * 60); 123 | } 124 | 125 | return $results; 126 | } 127 | 128 | public static function print_results($results, $opts) { 129 | 130 | if (empty($results)) { 131 | echo "

An error occured fetching results

"; 132 | return; 133 | } 134 | 135 | if (array_key_exists("error", $results)) { 136 | echo "

" . $results["error"]["message"] . "

"; 137 | return; 138 | } 139 | 140 | $special = $results[0]; 141 | 142 | if (array_key_exists("did_you_mean", $special)) { 143 | $didyoumean = $special["did_you_mean"]; 144 | $new_url = "/search.php?q=" . urlencode($didyoumean); 145 | echo "

Did you mean "; 146 | echo "$didyoumean"; 147 | echo "?

"; 148 | } 149 | 150 | if (array_key_exists("special_response", $special)) { 151 | $response = $special["special_response"]["response"]; 152 | $source = $special["special_response"]["source"]; 153 | 154 | echo "

"; 155 | if (array_key_exists("image", $special["special_response"])) { 156 | $image_url = $special["special_response"]["image"]; 157 | echo ""; 158 | } 159 | echo $response; 160 | if ($source) { 161 | $source = check_for_privacy_frontend($source, $opts); 162 | echo "$source"; 163 | } 164 | echo "

"; 165 | } 166 | 167 | echo "
"; 168 | 169 | foreach($results as $result) { 170 | if (!is_array($result)) 171 | continue; 172 | if (!array_key_exists("title", $result)) 173 | continue; 174 | 175 | $title = $result["title"]; 176 | $url = $result["url"]; 177 | $url = check_for_privacy_frontend($url, $opts); 178 | 179 | $base_url = get_base_url($url); 180 | $description = $result["description"]; 181 | 182 | echo "
"; 183 | echo ""; 184 | echo "$base_url"; 185 | echo "

$title

"; 186 | echo "
"; 187 | echo "$description"; 188 | echo "
"; 189 | } 190 | 191 | echo "
"; 192 | } 193 | } 194 | 195 | function check_ddg_bang($query, $opts) { 196 | 197 | $bangs_json = file_get_contents("static/misc/ddg_bang.json"); 198 | $bangs = json_decode($bangs_json, true); 199 | 200 | if (substr($query, 0, 1) == "!") 201 | $search_word = substr(explode(" ", $query)[0], 1); 202 | else 203 | $search_word = substr(end(explode(" ", $query)), 1); 204 | 205 | $bang_url = null; 206 | 207 | foreach($bangs as $bang) { 208 | if ($bang["t"] == $search_word) { 209 | $bang_url = $bang["u"]; 210 | break; 211 | } 212 | } 213 | 214 | if ($bang_url) { 215 | $bang_query_array = explode("!" . $search_word, $query); 216 | $bang_query = trim(implode("", $bang_query_array)); 217 | 218 | $request_url = str_replace("{{{s}}}", str_replace('%26quot%3B','%22', urlencode($bang_query)), $bang_url); 219 | 220 | header("Location: " . $request_url); 221 | die(); 222 | } 223 | } 224 | 225 | ?> 226 | -------------------------------------------------------------------------------- /engines/text/yandex.php: -------------------------------------------------------------------------------- 1 | query)); 5 | 6 | $results_language = $this->opts->language; 7 | $number_of_results = $this->opts->number_of_results; 8 | 9 | $url = "https://yandex.com/search?text=$query_encoded&nfpr=1&p=$this->page&noreask=1"; 10 | 11 | if (!is_null($results_language)) 12 | $url .= "&lang=$results_language"; 13 | 14 | return $url; 15 | } 16 | 17 | public function parse_results($response) { 18 | $results = array(); 19 | $xpath = get_xpath($response); 20 | 21 | if (!$xpath) 22 | return $results; 23 | 24 | $r = $xpath->query("//ul[@id='search-result']"); 25 | if (empty($r)) 26 | return array("error" => array( 27 | "message" => TEXTS["failure_empty"] 28 | )); 29 | 30 | foreach($xpath->query("//li[contains(@class, 'serp-item')]") as $result) { 31 | $url = $xpath->evaluate(".//div//div//a[contains(@class, 'link')]//@href", $result)[0]; 32 | 33 | if ($url == null) 34 | continue; 35 | 36 | $url = $url->textContent; 37 | 38 | if (!empty($results) && array_key_exists("url", $results) && end($results)["url"] == $url->textContent) 39 | continue; 40 | 41 | $title = $xpath->evaluate(".//div//div//a[contains(@class, 'link')]//h2[contains(@class, 'OrganicTitle-LinkText')]//span", $result)[0]; 42 | 43 | if ($title == null) 44 | continue; 45 | 46 | $title = $title->textContent; 47 | 48 | $description = $xpath->evaluate(".//div[contains(@class, 'Organic-ContentWrapper')]//div[contains(@class, 'text-container')]//span", $result)[0]->textContent; 49 | 50 | array_push($results, 51 | array ( 52 | "title" => htmlspecialchars($title), 53 | "url" => htmlspecialchars($url), 54 | // base_url is to be removed in the future, see #47 55 | "base_url" => htmlspecialchars(get_base_url($url)), 56 | "description" => $description == null ? 57 | TEXTS["result_no_description"] : 58 | htmlspecialchars($description) 59 | ) 60 | ); 61 | 62 | } 63 | 64 | return $results; 65 | } 66 | 67 | } 68 | ?> 69 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/favicon.ico -------------------------------------------------------------------------------- /image_proxy.php: -------------------------------------------------------------------------------- 1 | curl_settings); 15 | 16 | header("Content-Type: image/png"); 17 | echo $image_src; 18 | } 19 | ?> 20 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | LibreY 4 | 5 | 6 |
7 |

LibreY

8 | 9 | 10 | 11 | 12 |
13 | 14 | disable_bittorrent_search) { 15 | echo ''; 16 | } ?> 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /instances.json: -------------------------------------------------------------------------------- 1 | { 2 | "instances": [ 3 | { 4 | "clearnet": "https://search.revvy.de/", 5 | "tor": "http://search.revvybrr6pvbx4n3j4475h4ghw4elqr4t5xo2vtd3gfpu2nrsnhh57id.onion/", 6 | "i2p": "http://revekebotog64xrrammtsmjwtwlg3vqyzwdurzt2pu6botg4bejq.b32.i2p/", 7 | "country": "FI", 8 | "librey": true 9 | }, 10 | { 11 | "clearnet": "https://search.liv.town/", 12 | "tor": null, 13 | "i2p": null, 14 | "country": "NL", 15 | "librey": true 16 | }, 17 | { 18 | "clearnet": "https://search.davidovski.xyz/", 19 | "tor": null, 20 | "i2p": null, 21 | "country": "GB", 22 | "librey": true 23 | }, 24 | { 25 | "clearnet": "https://ly.owo.si/", 26 | "tor": "http://ly.pk47sgwhncn5cgidm7bofngmh7lc7ukjdpk5bjwfemmyp27ovl25ikyd.onion/", 27 | "i2p": null, 28 | "country": "DE", 29 | "librey": true 30 | }, 31 | { 32 | "clearnet": "https://librey.nube-gran.de/", 33 | "tor": "http://2ceiaa37hsmmk3japczvc4b6wnralj64qpf7ywfheircq3ht3rwus6ad.onion/", 34 | "i2p": null, 35 | "country": "AT", 36 | "librey": true 37 | }, 38 | { 39 | "clearnet": "https://librey.darkness.services/", 40 | "tor": "http://librey.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion/", 41 | "i2p": null, 42 | "country": "US", 43 | "librey": true 44 | }, 45 | { 46 | "clearnet": "https://librey.org/", 47 | "tor": null, 48 | "i2p": null, 49 | "country": "US", 50 | "librey": true 51 | }, 52 | { 53 | "clearnet": "https://search.technicalvoid.dev/", 54 | "tor": null, 55 | "i2p": null, 56 | "country": "RO", 57 | "librey": true 58 | }, 59 | { 60 | "clearnet": "https://search.uwabaki.party/", 61 | "tor": null, 62 | "i2p": null, 63 | "country": "DE", 64 | "librey": true 65 | }, 66 | { 67 | "clearnet": "https://librey.4o1x5.dev/", 68 | "tor": null, 69 | "i2p": null, 70 | "country": "HU", 71 | "librey": true 72 | }, 73 | { 74 | "clearnet": "https://search.funami.tech/", 75 | "tor": null, 76 | "i2p": null, 77 | "country": "KR", 78 | "librey": true 79 | }, 80 | { 81 | "clearnet": "https://librex.nohost.network/", 82 | "tor": null, 83 | "i2p": null, 84 | "country": "MX", 85 | "librey": true 86 | }, 87 | { 88 | "clearnet": "https://librey.baczek.me/", 89 | "tor": null, 90 | "i2p": null, 91 | "country": "PL", 92 | "librey": true 93 | }, 94 | { 95 | "clearnet": "https://libre.blitzw.in/", 96 | "tor": null, 97 | "i2p": null, 98 | "country": "ZA", 99 | "librey": true 100 | }, 101 | { 102 | "clearnet": "https://serp.catswords.net/", 103 | "tor": null, 104 | "i2p": null, 105 | "country": "KR", 106 | "librey": true 107 | }, 108 | { 109 | "clearnet": "https://librey.sny.sh/", 110 | "tor": null, 111 | "i2p": null, 112 | "country": "DE", 113 | "librey": true 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /instances.php: -------------------------------------------------------------------------------- 1 | $n['librey']); 8 | $librex_instances = array_filter($instances_json['instances'], fn($n) => !$n['librey']); 9 | 10 | 11 | function list_instances($instances) 12 | { 13 | echo ""; 14 | echo ""; 15 | echo ""; 16 | echo ""; 17 | echo ""; 18 | echo ""; 19 | 20 | foreach($instances as $instance) { 21 | $hostname = parse_url($instance["clearnet"])["host"]; 22 | $country = get_country_emote($instance["country"]) . $instance["country"]; 23 | 24 | $is_tor = !is_null($instance["tor"]); 25 | $is_i2p = !is_null($instance["i2p"]); 26 | 27 | echo ""; 28 | echo ""; 29 | 30 | echo $is_tor 31 | ? "" 32 | : ""; 33 | 34 | echo $is_i2p 35 | ? "" 36 | :""; 37 | 38 | echo ""; 39 | echo ""; 40 | } 41 | echo "
ClearnetTorI2PCountry
" . $hostname . "\u{2705}\u{274C}\u{2705}\u{274C}$country
"; 42 | } 43 | ?> 44 | LibreY - instances 45 | 46 | 47 |
48 |
49 |

LibreY instances

50 | 53 | 54 | 0){ 56 | echo "

"; printftext("instances_librex", "LibreX:

"); 57 | list_instances($librex_instances); 58 | } 59 | ?> 60 |
61 |
62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /locale/bn.php: -------------------------------------------------------------------------------- 1 | "LibreY দিয়ে অনুসন্ধান", 5 | "search_button" => "LibreY দিয়ে অনুসন্ধান করুন", 6 | "torrent_search_button" => "LibreY দিয়ে টরেন্ট সার্চ অনুসন্ধান করুন", 7 | 8 | "source_code_link" => "কোডের উৎসস্থল", 9 | "instances_link" => "আলাদা মেসিনগুলি", 10 | "settings_link" => "সেটিংস", 11 | "api_link" => "API", 12 | "donate_link" => "ডোনেট করুন ❤️", 13 | 14 | "latest_commit" => "সর্বশেষ অবদান: %s", 15 | 16 | "category_general" => "জেনার‍্যাল", 17 | "category_images" => "ছবি", 18 | "category_videos" => "ভিডিও", 19 | "category_torrents" => "টরেন্ট", 20 | "category_tor" => "টর", 21 | "category_maps" => "ম্যাপ", 22 | 23 | "feature_disabled" => "হোস্ট এই ফিছারসগুলি নিষ্ক্রিয় করেছে :C", 24 | 25 | "settings_title" => "সেটিংস", 26 | "settings_theme" => "থিম", 27 | "settings_special_warning" => "এই চেকবক্সটি 'রিসেট' বোতাম বা আপনার cookies সাফ না হওয়া পর্যন্ত বজায় থাকবে।", 28 | "settings_special_disabled" => "বিশেষ অনুসন্ধানগুলি নিষ্ক্রিয় করুন (যেমন: মুদ্রা রূপান্তর)", 29 | 30 | "settings_frontends" => "গোপনীয়তা-বান্ধব ফ্রন্টএন্ড", 31 | "settings_frontends_description" => "যদি আপনি ইউটিউব দেখতে চান প্রাইভেসি সমেত, তাহলে \"Invidious\" এ ক্লিক করুন, আপনার জন্য সবচেয়ে উপযুক্ত ইনস্ট্যান্সটি খুঁজুন এবং এটি এখানে পেস্ট করুন (সঠিক ফর্ম্যাট: https://example.com)", 32 | "settings_frontends_disable" => "ফ্রন্টএন্ড নিষ্ক্রিয় করুন", 33 | 34 | "settings_search_settings" => "অনুসন্ধান সেটিংস", 35 | "settings_language" => "ভাষা", 36 | "settings_preferred_engine" => "পছন্দের ইঞ্জিনটি", 37 | 38 | "settings_number_of_results" => "প্রতি পৃষ্ঠায় ফলাফলের সংখ্যা", 39 | 40 | "settings_safe_search" => "নিরাপদ অনুসন্ধান", 41 | "settings_save" => "সেটিংস সেভ করুন", 42 | "settings_reset" => "সেটিংস রিসেট করুন", 43 | 44 | 45 | "failure_fallback" => "কোনও ফলাফল পাওয়া যায়নি। অন্যান্য ইনস্ট্যান্সগুলিতে ফেরত যেতে ব্যর্থ হয়েছে।", 46 | "failure_empty" => "কোনও ফলাফল পাওয়া যায়নি। দয়া করে ভিন্ন কীওয়ার্ড চেষ্টা করুন!", 47 | "result_no_description" => "এই সাইটটির জন্য কোনও বর্ণনা প্রদান করা হয়নি।", 48 | 49 | "instances_librex" => "নিম্নলিখিত ইনস্ট্যান্সগুলি পুরানো %s চালাচ্ছে", 50 | 51 | "donate_original_developer" => "%s এর আসল ডেভেলপারকে দান করুন, একটি প্রকল্প যা LibreY উন্নত করার চেষ্টা করে।", 52 | "donate_fork" => "LibreY তে %s কে ফর্ক করে দান করুন", 53 | 54 | "api_unavailable" => "এই LibreY API বর্তমানে উপলব্ধ নয়" 55 | ); 56 | 57 | ?> -------------------------------------------------------------------------------- /locale/en.php: -------------------------------------------------------------------------------- 1 | "LibreY search", 5 | "search_button" => "Search with LibreY", 6 | "torrent_search_button" => "Search torrents with LibreY", 7 | 8 | "source_code_link" => "Source", 9 | "instances_link" => "Instances", 10 | "settings_link" => "Settings", 11 | "api_link" => "API", 12 | "donate_link" => "Donate ❤️", 13 | 14 | "latest_commit" => "Latest commit: %s", 15 | 16 | "category_general" => "General", 17 | "category_images" => "Images", 18 | "category_videos" => "Videos", 19 | "category_torrents" => "Torrents", 20 | "category_tor" => "Tor", 21 | "category_maps" => "Maps", 22 | 23 | "feature_disabled" => "The host has disabled this feature :C", 24 | 25 | "settings_title" => "Settings", 26 | "settings_theme" => "Theme", 27 | "settings_special_warning" => "This checkbox will persist until the 'Reset' button is clicked or your cookies are cleared.", 28 | "settings_special_disabled" => "Disable special queries (e.g.: currency conversion)", 29 | 30 | "settings_frontends" => "Privacy friendly frontends", 31 | "settings_frontends_description" => "For an example if you want to view YouTube without getting spied on, click on \"Invidious\", find the instance that is most suitable for you then paste it in (correct format: https://example.com)", 32 | "settings_frontends_disable" => "Disable frontends", 33 | 34 | "settings_search_settings" => "Search settings", 35 | "settings_language" => "Language", 36 | "settings_preferred_engine" => "Preferred Engine", 37 | 38 | "settings_number_of_results" => "Number of results per page", 39 | 40 | "settings_safe_search" => "Safe search", 41 | "settings_save" => "Save", 42 | "settings_reset" => "Reset", 43 | 44 | 45 | "failure_fallback" => "No results found. Unable to fallback to other instances.", 46 | "failure_empty" => "No results found. Please try different keywords!", 47 | "result_no_description" => "No description was provided for this site.", 48 | 49 | "instances_librex" => "The following instances are running the older %s", 50 | 51 | "donate_original_developer" => "Donate to the original developer of %s, a project LibreY tries to improve.", 52 | "donate_fork" => "Donate to the person that forked %s into LibreY", 53 | 54 | "api_unavailable" => "This LibreY API is unavailable at the moment" 55 | ); 56 | 57 | ?> 58 | -------------------------------------------------------------------------------- /locale/fr.php: -------------------------------------------------------------------------------- 1 | "Recherche de LibreY", 7 | "search_button" => "Chercher avec LibreY", 8 | "torrent_search_button" => "Chercher les bit-torrents avec LibreY", 9 | 10 | "source_code_link" => "Code source", 11 | "instances_link" => "Instances", 12 | "settings_link" => "Paramètres", 13 | "api_link" => "API", 14 | "donate_link" => "Donner ❤️", 15 | 16 | "latest_commit" => "Commit plus récent : %s", 17 | 18 | "category_general" => "Général", 19 | "category_images" => "Images", 20 | "category_videos" => "Vidéos", 21 | "category_torrents" => "Bit-torrents", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Cartes", 24 | 25 | "feature_disabled" => "L'hôt(e) a désactivé cette fonction :C", 26 | 27 | "settings_title" => "Paramètres", 28 | "settings_theme" => "Thème", 29 | "settings_special_warning" => "Cette case à cocher va durer jusqu'à le bouton de 'Réinitialiser' c'est cliqué ou tes cookies sont dégagés.", 30 | "settings_special_disabled" => "Désactiver les requêtes spéciaux (par ex. : conversion de la monnaie)", 31 | 32 | "settings_frontends" => "Front-ends d'utilisateurs font pour intimité", 33 | "settings_frontends_description" => "Par exemple, si vous voulez voir YouTube sans de s'être espionné, cliquer sur \"Invidious\", trouver l'instance qu'est le plus approprié pour vous et le coller (format correcte : https://example.com)", 34 | "settings_frontends_disable" => "Désactiver front-ends de utilisateurs", 35 | 36 | "settings_search_settings" => "Paramètres de la recherche", 37 | "settings_language" => "Langue", 38 | "settings_preferred_engine" => "Moteur de recherche préféré", 39 | 40 | "settings_number_of_results" => "Nombre des résultats pour chaque page", 41 | 42 | "settings_safe_search" => "SafeSearch", 43 | "settings_save" => "Enregistrer", 44 | "settings_reset" => "Réinitialiser", 45 | 46 | 47 | "failure_fallback" => "Pas de résultats trouvés. Ne peux pas faire de rechange sur les autres instances.", 48 | "failure_empty" => "Pas de résultats trouvés. S'il vous plaît essayez des mot-clés différents!", 49 | "result_no_description" => "Une description n'était pas fouri pour ce site web.", 50 | 51 | "instances_librex" => "Les suivantes instances utiliser le vieux %s", 52 | 53 | "donate_original_developer" => "Donner au développeur original de %s, un projet LibreY essaye d'améliorer.", 54 | "donate_fork" => "Donner à la personne qui a dérivé LibreY de %s", 55 | 56 | "api_unavailable" => "Ce API de LibreY est indisponible en ce moment" 57 | ); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /locale/hr.php: -------------------------------------------------------------------------------- 1 | "LibreY Pretraživanje", 7 | "search_button" => "LibreY Pretraživanje", 8 | "torrent_search_button" => "Pretraži torrente sa LibreY-om", 9 | 10 | "source_code_link" => "Izvorni kod", 11 | "instances_link" => "Instancije", 12 | "settings_link" => "Postavke", 13 | "api_link" => "API", 14 | "donate_link" => "Donirajte ❤️", 15 | 16 | "latest_commit" => "Posljednji commit: %s", 17 | 18 | "category_general" => "Općenito", 19 | "category_images" => "Slike", 20 | "category_videos" => "Video zapisi", 21 | "category_torrents" => "Torrenti", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Karte", 24 | 25 | "feature_disabled" => "Operater je onemogućio ovu funkciju :C", 26 | 27 | "settings_title" => "Postavke", 28 | "settings_theme" => "Tema", 29 | "settings_special_disabled" => "Onemogući posebne upite (npr.: konverzija valuta)", 30 | 31 | "settings_frontends" => "Privatni frontendi", 32 | "settings_frontends_description" => "Na primjer, ako želite gledati YouTube s privatnošću, kliknite na „Invidious“, pronađite instancu koja vam najviše odgovara, a zatim je zalijepite (ispravan format: https://example.hr)", 33 | "settings_frontends_disable" => "Onemogući frontende", 34 | 35 | "settings_search_settings" => "Postavke pretraživanja", 36 | "settings_language" => "Jezik", 37 | "settings_preferred_engine" => "Preferirani pretraživač", 38 | 39 | "settings_number_of_results" => "Broj rezultata po stranici", 40 | 41 | "settings_safe_search" => "Sigurna pretraga", 42 | "settings_save" => "Spremi", 43 | "settings_reset" => "Poništi", 44 | 45 | 46 | "failure_fallback" => "Nema rezultata. Pokušaj prebacivanja na drugu instancu nije uspio.", 47 | "failure_empty" => "Nema rezultata. Probajte sa drugim ključnim riječima!", 48 | "result_no_description" => "Nisu date dovoljno informacije o ovoj web stranici.", 49 | 50 | "instances_librex" => "Sljedeće instance rade na starijoj verziji %s", 51 | 52 | "donate_original_developer" => "Donirajte programeru koji je razvio projekt %s, koji LibreY pokušava poboljšati", 53 | "donate_fork" => "Donirajte osobi koja je napravila fork %s u LibreY", 54 | 55 | "api_unavailable" => "Ovaj LibreY API trenutno nije dostupan" 56 | ); 57 | 58 | ?> 59 | -------------------------------------------------------------------------------- /locale/it.php: -------------------------------------------------------------------------------- 1 | "LibreY Ricerca", 7 | "search_button" => "Cerca con LibreY", 8 | "torrent_search_button" => "Cerca torrent con LibreY", 9 | 10 | "source_code_link" => "Sorgente", 11 | "instances_link" => "Instanze", 12 | "settings_link" => "Impostazioni", 13 | "api_link" => "API", 14 | "donate_link" => "Dona ❤️", 15 | 16 | "latest_commit" => "Ultimo aggiornamento: %s", 17 | 18 | "category_general" => "Generale", 19 | "category_images" => "Immagini", 20 | "category_videos" => "Video", 21 | "category_torrents" => "Torrent", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Mappe", 24 | 25 | "feature_disabled" => "L'istanza ha disabilitato questa funzione :C", 26 | 27 | "settings_title" => "Impostazioni", 28 | "settings_theme" => "Tema", 29 | "settings_special_warning" => "Questa casella di spunta persisterà fino a il bottone di 'Ripristina' è cliccato o i tui cookies sono sgombrati.", 30 | "settings_special_disabled" => "Disabilitare le ricerche speciali (ad es.: conversione di valuta)", 31 | 32 | "settings_frontends" => "Intermediari che rispettano la privacy", 33 | "settings_frontends_description" => "Per esempio, se volete vedere YouTube senza essere spiati, cliccate su “Invidious”, trovate l'istanza più adatta a voi e incollatela (formato corretto: https://example.com).", 34 | "settings_frontends_disable" => "Disabilita gli intermediari", 35 | 36 | "settings_search_settings" => "Impostazioni di ricerca", 37 | "settings_language" => "Lingua", 38 | "settings_preferred_engine" => "Motore preferito", 39 | 40 | "settings_number_of_results" => "Numero di risultati per pagina", 41 | 42 | "settings_safe_search" => "Ricerca sicura", 43 | "settings_save" => "Salva", 44 | "settings_reset" => "Ripristina", 45 | 46 | 47 | "failure_fallback" => "Nessun risultato trovato. Impossibile passare ad altre istanze", 48 | "failure_empty" => "Nessun risultato trovato. Provare con altri termini di ricerca!", 49 | "result_no_description" => "Non è stata fornita alcuna descrizione per questo sito.", 50 | 51 | "instances_librex" => "Le seguenti istanze stanno eseguendo la vecchia %s", 52 | 53 | "donate_original_developer" => "Fate una donazione allo sviluppatore originale di %s, un progetto che LibreY cerca di migliorare.", 54 | "donate_fork" => "Donare alla persona che ha fatto il fork di %s in LibreY", 55 | 56 | "api_unavailable" => "Questa API di LibreY non è al momento disponibile." 57 | ); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /locale/ko.php: -------------------------------------------------------------------------------- 1 | "LibreY 검색", 7 | "search_button" => "LibreY로 검색하기", 8 | "torrent_search_button" => "LibreY로 토렌트 검색하기", 9 | 10 | "source_code_link" => "소스 코드", 11 | "instances_link" => "인스턴스", 12 | "settings_link" => "설정", 13 | "api_link" => "API", 14 | "donate_link" => "후원하기 ❤️", 15 | 16 | "latest_commit" => "마지막 커밋: %s", 17 | 18 | "category_general" => "일반", 19 | "category_images" => "이미지", 20 | "category_videos" => "영상", 21 | "category_torrents" => "토렌트", 22 | "category_tor" => "Tor", 23 | "category_maps" => "지도", 24 | 25 | "feature_disabled" => "운영자가 이 기능을 비활성화 했습니다 :C", 26 | 27 | "settings_title" => "설정", 28 | "settings_theme" => "테마", 29 | "settings_special_warning" => "이 체크박스는 '초기화' 버튼을 누르거나 쿠키를 제거하기 전까지 선택이 유지됩니다.", 30 | "settings_special_disabled" => "특수 검색어 비활성화 (예: 환전 등)", 31 | 32 | "settings_frontends" => "프라이버시 친화적 프론트엔드", 33 | "settings_frontends_description" => "예를 들어 YouTube를 감시 없이 보고 싶다면, \"Invidious\"를 클릭하고, 당신에게 가장 맞는 인스턴스를 고른 다음 링크를 붙여넣으세요. (링크는 https://example.com 처럼 생겨야 합니다)", 34 | "settings_frontends_disable" => "프론트엔드 비활성화", 35 | 36 | "settings_search_settings" => "검색 설정", 37 | "settings_language" => "언어", 38 | "settings_preferred_engine" => "선호하는 검색 엔진", 39 | 40 | "settings_number_of_results" => "페이지 당 표시할 결과 수", 41 | 42 | "settings_safe_search" => "세이프서치", 43 | "settings_save" => "저장", 44 | "settings_reset" => "초기화", 45 | 46 | 47 | "failure_fallback" => "결과 없음. 다른 인스턴스로 대체 시도 실패.", 48 | "failure_empty" => "결과 없음. 다른 검색어로 시도 해 보세요!", 49 | "result_no_description" => "이 사이트에 대한 정보가 없습니다.", 50 | 51 | "instances_librex" => "다음 인스턴스들은 예전 %s을(를) 구동 중입니다", 52 | 53 | "donate_original_developer" => "LibreY가 개선하고자 하는 프로젝트인 %s을(를) 개발한 사람에게 기부하기", 54 | "donate_fork" => "%s을(를) LibreY로 포크 한 사람에게 기부하기", 55 | 56 | "api_unavailable" => "이 LibreY API는 현재 사용할 수 없습니다" 57 | ); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /locale/localization.php: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /locale/nl.php: -------------------------------------------------------------------------------- 1 | "LibreY search", 7 | "search_button" => "Zoek met LibreY", 8 | "torrent_search_button" => "Zoek naar torrents met LibreY", 9 | 10 | "source_code_link" => "Broncode", 11 | "instances_link" => "Instances", 12 | "settings_link" => "Instellingen", 13 | "api_link" => "API", 14 | "donate_link" => "Doneer ❤️", 15 | 16 | "latest_commit" => "Laatste commit: %s", 17 | 18 | "category_general" => "Algemeen", 19 | "category_images" => "Foto's", 20 | "category_videos" => "Videos", 21 | "category_torrents" => "Torrents", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Kaarten", 24 | 25 | "feature_disabled" => "De host heeft deze functie uitgeschakeld :C", 26 | 27 | "settings_title" => "Instellingen", 28 | "settings_theme" => "Thema", 29 | "settings_special_warning" => "Als u dit selectievakje inschakelt, blijft de instelling actief totdat u op de Reset knop klikt of uw cookies op andere wijze aangepast worden.", 30 | "settings_special_disabled" => "Schakel speciale zoekopdrachten uit (bijvoorbeeld: valutaconversie)", 31 | 32 | "settings_frontends" => "Privacyvriendelijke frontends", 33 | "settings_frontends_description" => "Als je - bijvoorbeeld - naar YouTube wil kijken zonder bespioneerd te worden, klik dan op \"Invidious\", zoek een instance die het best voor jou is en plak de URL hier in (correct formaat: https://example.com)", 34 | "settings_frontends_disable" => "Frontends uitschakelen", 35 | 36 | "settings_search_settings" => "Zoek-gerelateerde instellingen", 37 | "settings_language" => "Taal", 38 | "settings_preferred_engine" => "Favoriete zoekmachine", 39 | 40 | "settings_number_of_results" => "Resulaten per pagina (in nummers)", 41 | 42 | "settings_safe_search" => "Safe search", 43 | "settings_save" => "Opslaan", 44 | "settings_reset" => "Reset", 45 | 46 | 47 | "failure_fallback" => "Geen resultaten gevonden. Kan niet terugvallen op andere instances.", 48 | "failure_empty" => "Geen resultaten gevonden. Probeer een andere zoekterm/andere keywords!", 49 | "result_no_description" => "Voor deze website is geen omschrijving gegeven.", 50 | 51 | "instances_librex" => "De volgende instances draaien de oudere versie; %s", 52 | 53 | "donate_original_developer" => "Doneer aan de originele ontwikkelaar van %s, een project dat LibreY probeert te verbeteren.", 54 | "donate_fork" => "Doneer aan de persoon die %s heeft geforkt naar LibreY", 55 | 56 | "api_unavailable" => "De LibreY API is op dit moment niet beschikbaar" 57 | ); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /locale/sr.php: -------------------------------------------------------------------------------- 1 | "LibreY Претрага", 7 | "search_button" => "Претражи са LibreY-ем", 8 | "torrent_search_button" => "Претражи торенте са LibreY-ем", 9 | 10 | "source_code_link" => "Изворни код", 11 | "instances_link" => "Инстанце", 12 | "settings_link" => "Подешавања", 13 | "api_link" => "API", 14 | "donate_link" => "Донирајте ❤️", 15 | 16 | "latest_commit" => "Последњи комит: %s", 17 | 18 | "category_general" => "Опште", 19 | "category_images" => "Слике", 20 | "category_videos" => "Видео снимци", 21 | "category_torrents" => "Торенти", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Мапе", 24 | 25 | "feature_disabled" => "Оператор је онемогућио ову функцију :C", 26 | 27 | "settings_title" => "Подешавања", 28 | "settings_theme" => "Тема", 29 | "settings_special_disabled" => "Онемогући посебне упите (нпр.: конверзија валута)", 30 | 31 | "settings_frontends" => "Приватни фронтендови", 32 | "settings_frontends_description" => "На пример, ако желите да гледате YouTube са приватношћу, кликните на „Invidious“, пронађите инстанцу која вам највише одговара, а затим је налепите (исправан формат: https://example.rs)", 33 | "settings_frontends_disable" => "Онемогући фронтендове", 34 | 35 | "settings_search_settings" => "Подешавања претраге", 36 | "settings_language" => "Језик", 37 | "settings_preferred_engine" => "Преферирани претраживач", 38 | 39 | "settings_number_of_results" => "Број резултата по страници", 40 | 41 | "settings_safe_search" => "Безбедна претрага", 42 | "settings_save" => "Сачувај", 43 | "settings_reset" => "Ресетуј", 44 | 45 | 46 | "failure_fallback" => "Нема резултата. Покушај пребацивања на другу инстанцију није успео.", 47 | "failure_empty" => "Нема резултата. Пробајте са другачијим кључним речима!", 48 | "result_no_description" => "Нису дате довољно информације о овој Веб страници.", 49 | 50 | "instances_librex" => "Следеће инстанце раде на старијој верзији %s", 51 | 52 | "donate_original_developer" => "Донирајте програмеру који је развио пројекат %s, који LibreY покушава да побољша", 53 | "donate_fork" => "Донирајте особи која је направила форк %s у LibreY", 54 | 55 | "api_unavailable" => "Овај LibreY API тренутно није доступан" 56 | ); 57 | 58 | ?> 59 | -------------------------------------------------------------------------------- /locale/sv.php: -------------------------------------------------------------------------------- 1 | "LibreY search", 7 | "search_button" => "Sök med LibreY", 8 | "torrent_search_button" => "Sök torrents med LibreY", 9 | 10 | "source_code_link" => "Källkod", 11 | "instances_link" => "Instanser", 12 | "settings_link" => "Inställningar", 13 | "api_link" => "API", 14 | "donate_link" => "Donera ❤️", 15 | 16 | "latest_commit" => "Senaste commit: %s", 17 | 18 | "category_general" => "Allmänt", 19 | "category_images" => "Bilder", 20 | "category_videos" => "Videor", 21 | "category_torrents" => "Torrents", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Kartor", 24 | 25 | "feature_disabled" => "Funktionen är inaktiverad av serverägaren :C", 26 | 27 | "settings_title" => "Inställningar", 28 | "settings_theme" => "Tema", 29 | "settings_special_warning" => "Rutorna sparas tills du klickar på 'Återställ' eller rensar cookies.", 30 | "settings_special_disabled" => "Inaktivera specialförfrågningar (t.ex. valutaomvandlare)", 31 | 32 | "settings_frontends" => "Privata frontends", 33 | "settings_frontends_description" => "För om du vill titta på YouTube utan att bli spionerad på, klicka på \"Invidious\", hitta den instans som passar dig bäst och klistra in (korrekt format: https://example.com)", 34 | "settings_frontends_disable" => "Stäng av frontends", 35 | 36 | "settings_search_settings" => "Sökinställningar", 37 | "settings_language" => "Språk", 38 | "settings_preferred_engine" => "Föredragen sökmotor", 39 | 40 | 41 | "settings_number_of_results" => "Antal resultat per sida", 42 | 43 | "settings_safe_search" => "Safe search", //typically not translated 44 | "settings_save" => "Spara", 45 | "settings_reset" => "Återställ", 46 | 47 | 48 | "failure_fallback" => "Inga resultat hittades. Gick inte att använda andra instanser som reservlösning.", 49 | "failure_empty" => "Inga resultat hittades. Var god försök med andra nyckelord!", 50 | "result_no_description" => "Ingen beskrivning finns för denna webbplats.", 51 | 52 | "instances_librex" => "Följande instanser kör den äldre %s", 53 | 54 | "donate_original_developer" => "Donera till orginalutvecklaren av %s, ett 55 | projekt som LibreY försöker förbättra.", 56 | "donate_fork" => "Donera till personen som forkade %s till LibreY", 57 | 58 | "api_unavailable" => "Denna LibreY API är för närvarande otillgänglig." 59 | ); 60 | 61 | ?> 62 | -------------------------------------------------------------------------------- /locale/uk.php: -------------------------------------------------------------------------------- 1 | "Пошук LibreY", 7 | "search_button" => "Шукати за допомогою LibreY", 8 | "torrent_search_button" => "Шукати торренти за допомогою LibreY", 9 | 10 | "source_code_link" => "Код", 11 | "instances_link" => "Інстанси", 12 | "settings_link" => "Налаштування", 13 | "api_link" => "API", 14 | "donate_link" => "Пожертвувати ❤️", 15 | 16 | "latest_commit" => "Останній комміт: %s", 17 | 18 | "category_general" => "Загальне", 19 | "category_images" => "Зображення", 20 | "category_videos" => "Відео", 21 | "category_torrents" => "Торренти", 22 | "category_tor" => "Tor", 23 | "category_maps" => "Карти", 24 | 25 | "feature_disabled" => "Хост вимкнув цю функцію :C", 26 | 27 | "settings_title" => "Налаштування", 28 | "settings_theme" => "Тема", 29 | "settings_special_warning" => "Цей прапорець залишатиметься, доки не буде натиснута кнопка «Скинути» або не буде очищено кукі.", 30 | "settings_special_disabled" => "Вимкнути спеціальні запити (наприклад: конвертація валют)", 31 | 32 | "settings_frontends" => "Приватні фронтенди", 33 | "settings_frontends_description" => "Наприклад, якщо ви хочете переглядати YouTube без стеження, натисніть на \"Invidious\", знайдіть інстанс, який вам підходить, і вставте його (правильний формат: https://example.com)", 34 | "settings_frontends_disable" => "Вимкнути фронтенди", 35 | 36 | "settings_search_settings" => "Налаштування пошуку", 37 | "settings_language" => "Мова", 38 | "settings_preferred_engine" => "Бажаний рушій", 39 | 40 | "settings_number_of_results" => "Кількість результатів на сторінці", 41 | 42 | "settings_safe_search" => "Безпечний пошук", 43 | "settings_save" => "Зберегти", 44 | "settings_reset" => "Скинути", 45 | 46 | 47 | "failure_fallback" => "Результатів не знайдено. Неможливо використати резервні інстанси.", 48 | "failure_empty" => "Результатів не знайдено. Спробуйте інші ключові слова!", 49 | "result_no_description" => "Опис цього сайту не надано.", 50 | 51 | "instances_librex" => "Наступні інстанси працюють на старішій версії %s", 52 | 53 | "donate_original_developer" => "Пожертвувати оригінальному розробнику %s, проєкт, який LibreY намагається поліпшити.", 54 | "donate_fork" => "Пожертвувати людині, яка форкнула %s у LibreY", 55 | 56 | "api_unavailable" => "API LibreY наразі недоступне" 57 | ); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /misc/cooldowns.php: -------------------------------------------------------------------------------- 1 | time(); 25 | } 26 | 27 | function has_cached_results($url) { 28 | if (function_exists("apcu_exists")) 29 | return apcu_exists("cached:$url"); 30 | 31 | return false; 32 | } 33 | 34 | function store_cached_results($url, $results, $ttl = 0) { 35 | if (function_exists("apcu_store") && !empty($results) && $ttl >= 0) 36 | return apcu_store("cached:$url", $results, $ttl); 37 | } 38 | 39 | function fetch_cached_results($url) { 40 | if (function_exists("apcu_fetch")) 41 | return apcu_fetch("cached:$url"); 42 | 43 | return array(); 44 | } 45 | ?> 46 | -------------------------------------------------------------------------------- /misc/footer.php: -------------------------------------------------------------------------------- 1 | 11 |
12 | " . printftext("latest_commit", $hash) . ""; 18 | ?> 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /misc/header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | " type="application/opensearchdescription+xml" href="opensearch.xml?method=POST" rel="search"/> 13 | default_theme ?? "dark")); 15 | echo "static/css/" . $theme . ".css"; 16 | ?>"/> 17 | -------------------------------------------------------------------------------- /misc/search_engine.php: -------------------------------------------------------------------------------- 1 | query = $opts->query; 9 | $this->page = $opts->page; 10 | $this->mh = $mh; 11 | $this->opts = $opts; 12 | 13 | $this->url = $this->get_request_url(); 14 | if (!$this->url) 15 | return; 16 | 17 | if (has_cached_results($this->url)) 18 | return; 19 | 20 | $this->ch = curl_init($this->url); 21 | 22 | if ($opts->curl_settings) 23 | curl_setopt_array($this->ch, $opts->curl_settings); 24 | 25 | if ($mh) 26 | curl_multi_add_handle($mh, $this->ch); 27 | } 28 | 29 | public function get_request_url() { 30 | return ""; 31 | } 32 | 33 | public function successful() { 34 | return (isset($this->ch) && curl_getinfo($this->ch)['http_code'] == '200') 35 | || has_cached_results($this->url); 36 | } 37 | 38 | abstract function parse_results($response); 39 | 40 | public function get_results() { 41 | if (!isset($this->url)) 42 | return $this->parse_results(null); 43 | 44 | if ($this->DO_CACHING && has_cached_results($this->url)) 45 | return fetch_cached_results($this->url); 46 | 47 | if (!isset($this->ch)) 48 | return $this->parse_results(null); 49 | 50 | $response = $this->mh ? curl_multi_getcontent($this->ch) : curl_exec($this->ch); 51 | $results = $this->parse_results($response) ?? array(); 52 | 53 | if ($this->DO_CACHING && !empty($results)) 54 | store_cached_results($this->url, $results, $this->opts->cache_time * 60); 55 | 56 | return $results; 57 | } 58 | 59 | public static function print_results($results, $opts) {} 60 | } 61 | 62 | function load_opts() { 63 | if (isset($GLOBALS["opts"])) 64 | $opts = $GLOBALS["opts"]; 65 | else 66 | $opts = require_once "config.php"; 67 | 68 | # account for the old, misspelled options 69 | if (isset($opts->disable_bittorent_search)) 70 | $opts->disable_bittorrent_search = $opts->disable_bittorent_search; 71 | 72 | if (isset($opts->bittorent_trackers)) 73 | $opts->bittorrent_trackers = $opts->bittorent_trackers; 74 | 75 | $opts->request_cooldown ??= 25; 76 | $opts->cache_time ??= 25; 77 | 78 | $opts->query = trim($_REQUEST["q"] ?? ""); 79 | $opts->type = (int) ($_REQUEST["t"] ?? 0); 80 | $opts->page = (int) ($_REQUEST["p"] ?? 0); 81 | 82 | $opts->theme = $_REQUEST["theme"] ?? trim(htmlspecialchars($_COOKIE["theme"] ?? $opts->default_theme ?? "dark")); 83 | 84 | $opts->safe_search = (int) ($_REQUEST["safe"] ?? 0) == 1 || isset($_COOKIE["safe_search"]); 85 | 86 | $opts->disable_special = (int) ($_REQUEST["ns"] ?? 0) == 1 || isset($_COOKIE["disable_special"]); 87 | 88 | $opts->disable_frontends = (int) ($_REQUEST["nf"] ?? 0) == 1 || isset($_COOKIE["disable_frontends"]); 89 | 90 | $opts->language = $_REQUEST["lang"] ?? trim(htmlspecialchars($_COOKIE["language"] ?? $opts->language ?? "en")); 91 | 92 | $opts->do_fallback = (int) ($_REQUEST["nfb"] ?? 0) == 0; 93 | if (!$opts->instance_fallback) { 94 | $opts->do_fallback = false; 95 | } 96 | 97 | $opts->number_of_results ??= trim(htmlspecialchars($_COOKIE["number_of_results"])); 98 | 99 | foreach (array_keys($opts->frontends ?? array()) as $frontend) { 100 | $opts->frontends[$frontend]["instance_url"] = $_COOKIE[$frontend] ?? $opts->frontends[$frontend]["instance_url"]; 101 | } 102 | 103 | $opts->curl_settings[CURLOPT_FOLLOWLOCATION] ??= true; 104 | 105 | $opts->engine = $_REQUEST["engine"] ?? $_COOKIE["engine"] ?? $opts->preferred_engines["text"] ?? "auto"; 106 | 107 | return $opts; 108 | } 109 | 110 | function opts_to_params($opts) { 111 | $query = urlencode($opts->query); 112 | 113 | $params = ""; 114 | $params .= "p=$opts->page"; 115 | $params .= "&q=$query"; 116 | $params .= "&t=$opts->type"; 117 | $params .= "&safe=" . ($opts->safe_search ? 1 : 0); 118 | $params .= "&nf=" . ($opts->disable_frontends ? 1 : 0); 119 | $params .= "&ns=" . ($opts->disable_special ? 1 : 0); 120 | $params .= "&engine=" . ($opts->engine ?? "auto"); 121 | 122 | return $params; 123 | } 124 | 125 | function init_search($opts, $mh) { 126 | switch ($opts->type) 127 | { 128 | case 1: 129 | require_once "engines/qwant/image.php"; 130 | return new QwantImageSearch($opts, $mh); 131 | 132 | case 2: 133 | require_once "engines/invidious/video.php"; 134 | return new VideoSearch($opts, $mh); 135 | 136 | case 3: 137 | if ($opts->disable_bittorrent_search) { 138 | echo "

" . TEXTS["feature_disabled"] . "

"; 139 | break; 140 | } 141 | 142 | require_once "engines/bittorrent/merge.php"; 143 | return new TorrentSearch($opts, $mh); 144 | 145 | case 4: 146 | if ($opts->disable_hidden_service_search) { 147 | echo "

" . TEXTS["feature_disabled"] . "

"; 148 | break; 149 | } 150 | require_once "engines/ahmia/hidden_service.php"; 151 | return new TorSearch($opts, $mh); 152 | 153 | case 5: 154 | require_once "engines/maps/openstreetmap.php"; 155 | return new OSMRequest($opts, $mh); 156 | 157 | default: 158 | require_once "engines/text/text.php"; 159 | return new TextSearch($opts, $mh); 160 | } 161 | } 162 | 163 | function fetch_search_results($opts, $do_print) { 164 | $opts->cooldowns = load_cooldowns(); 165 | 166 | $start_time = microtime(true); 167 | $mh = curl_multi_init(); 168 | $search_category = init_search($opts, $mh); 169 | 170 | $running = null; 171 | 172 | do { 173 | curl_multi_exec($mh, $running); 174 | } while ($running); 175 | 176 | $results = $search_category->get_results(); 177 | 178 | if (empty($results)) { 179 | require_once "engines/librex/fallback.php"; 180 | $results = get_librex_results($opts); 181 | } 182 | 183 | if (!$do_print || empty($results)) 184 | return $results; 185 | 186 | print_elapsed_time($start_time, $results, $opts); 187 | $search_category->print_results($results, $opts); 188 | 189 | return $results; 190 | } 191 | ?> 192 | -------------------------------------------------------------------------------- /misc/tools.php: -------------------------------------------------------------------------------- 1 | (int)$days, 28 | 'hour' => (int)$hours, 29 | 'minute' => (int)$minutes, 30 | 'second' => (int)$seconds, 31 | ]; 32 | 33 | foreach ($sections as $name => $value){ 34 | if ($value > 0){ 35 | $timeParts[] = $value. ' '.$name.($value == 1 ? '' : 's'); 36 | } 37 | } 38 | 39 | return implode(', ', $timeParts); 40 | } 41 | 42 | function get_root_domain($url) { 43 | return parse_url($url, PHP_URL_HOST); 44 | } 45 | 46 | function try_replace_with_frontend($url, $frontend, $original, $opts) { 47 | $frontends = $opts->frontends; 48 | 49 | if (array_key_exists($frontend, $opts->frontends)) { 50 | $frontend = $frontends[$frontend]["instance_url"]; 51 | 52 | if (empty(trim($frontend))) 53 | return $url; 54 | 55 | if (strpos($url, "wikipedia.org") !== false) { 56 | $wiki_split = explode(".", $url); 57 | if (count($wiki_split) > 1) { 58 | $lang = explode("://", $wiki_split[0])[1]; 59 | $url = $frontend . explode($original, $url)[1] . (strpos($url, "?") !== false ? "&" : "?") . "lang=" . $lang; 60 | } 61 | } else if (strpos($url, "fandom.com") !== false) { 62 | $fandom_split = explode(".", $url); 63 | if (count($fandom_split) > 1) { 64 | $wiki_name = explode("://", $fandom_split[0])[1]; 65 | $url = $frontend . "/" . $wiki_name . explode($original, $url)[1]; 66 | } 67 | } else if (strpos($url, "gist.github.com") !== false) { 68 | $gist_path = explode("gist.github.com", $url)[1]; 69 | $url = $frontend . "/gist" . $gist_path; 70 | } else if (strpos($url, "stackexchange.com") !== false) { 71 | $se_domain = explode(".", explode("://", $url)[1])[0]; 72 | $se_path = explode("stackexchange.com", $url)[1]; 73 | $url = $frontend . "/exchange" . "/" . $se_domain . $se_path; 74 | } else { 75 | $url = $frontend . explode($original, $url)[1]; 76 | } 77 | 78 | 79 | return $url; 80 | } 81 | 82 | return $url; 83 | } 84 | 85 | function check_for_privacy_frontend($url, $opts) { 86 | if ($opts->disable_frontends) 87 | return $url; 88 | 89 | foreach($opts->frontends as $frontend => $data) { 90 | $original = $data["original_url"]; 91 | 92 | if (strpos($url, $original)) { 93 | $url = try_replace_with_frontend($url, $frontend, $original, $opts); 94 | break; 95 | } else if (strpos($url, "stackexchange.com")) { 96 | $url = try_replace_with_frontend($url, "anonymousoverflow", "stackexchange.com", $opts); 97 | break; 98 | } 99 | } 100 | 101 | return $url; 102 | } 103 | 104 | function get_xpath($response) { 105 | if (!$response) 106 | return null; 107 | 108 | $htmlDom = new DOMDocument; 109 | @$htmlDom->loadHTML($response); 110 | $xpath = new DOMXPath($htmlDom); 111 | 112 | return $xpath; 113 | } 114 | 115 | function request($url, $conf) { 116 | $ch = curl_init($url); 117 | curl_setopt_array($ch, $conf); 118 | $response = curl_exec($ch); 119 | 120 | return $response; 121 | } 122 | 123 | function human_filesize($bytes, $dec = 2) { 124 | $size = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); 125 | $factor = floor((strlen($bytes) - 1) / 3); 126 | 127 | return sprintf("%.{$dec}f ", $bytes / pow(1024, $factor)) . @$size[$factor]; 128 | } 129 | 130 | function remove_special($string) { 131 | $string = preg_replace("/[\r\n]+/", "\n", $string); 132 | return trim(preg_replace("/\s+/", ' ', $string)); 133 | } 134 | 135 | function print_elapsed_time($start_time, $results, $opts) { 136 | $source = ""; 137 | if (array_key_exists("results_source", $results)) { 138 | $source = " from " . $results["results_source"]; 139 | unset($results["results_source"]); 140 | } 141 | 142 | $end_time = number_format(microtime(true) - $start_time, 2, '.', ''); 143 | echo "

Fetched the results in $end_time seconds$source

"; 144 | } 145 | 146 | function print_next_page_button($text, $page, $query, $type) { 147 | echo "
"; 148 | echo ""; 149 | echo ""; 150 | echo ""; 151 | echo ""; 152 | echo "
"; 153 | } 154 | 155 | function copy_cookies($curl) { 156 | if (array_key_exists("HTTP_COOKIE", $_SERVER)) 157 | curl_setopt( $curl, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE'] ); 158 | } 159 | 160 | 161 | function get_country_emote($code) { 162 | $emoji = []; 163 | foreach(str_split($code) as $c) { 164 | if(($o = ord($c)) > 64 && $o % 32 < 27) { 165 | $emoji[] = hex2bin("f09f87" . dechex($o % 32 + 165)); 166 | continue; 167 | } 168 | 169 | $emoji[] = $c; 170 | } 171 | 172 | return join($emoji); 173 | } 174 | 175 | function logStackTrace() { 176 | // Get the stack trace 177 | $stackTrace = debug_backtrace(); 178 | 179 | // Format the stack trace for logging 180 | $logMessage = "Stack Trace: "; 181 | foreach ($stackTrace as $index => $trace) { 182 | // Skip the first entry as it's the current function call 183 | if ($index === 0) { 184 | continue; 185 | } 186 | 187 | // Build the log message for each stack frame 188 | $logMessage .= "#{$index} "; 189 | if (isset($trace['file'])) { 190 | $logMessage .= "File: {$trace['file']} "; 191 | } 192 | if (isset($trace['line'])) { 193 | $logMessage .= "Line: {$trace['line']} "; 194 | } 195 | if (isset($trace['function'])) { 196 | $logMessage .= "Function: {$trace['function']} "; 197 | } 198 | $logMessage .= "\n"; 199 | } 200 | 201 | // Log the stack trace to the error log 202 | error_log($logMessage); 203 | } 204 | 205 | ?> 206 | -------------------------------------------------------------------------------- /opensearch.xml.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | LibreY 4 | Framework and javascript free privacy respecting meta search engine 5 | UTF-8 6 | LibreY search 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /search.php: -------------------------------------------------------------------------------- 1 | 1) 11 | return; 12 | echo "
"; 13 | 14 | if ($page != 0) 15 | { 16 | print_next_page_button("<<", 0, $query, $type); 17 | print_next_page_button("<", $page - 10, $query, $type); 18 | } 19 | 20 | for ($i=$page / 10; $page / 10 + 10 > $i; $i++) 21 | print_next_page_button($i + 1, $i * 10, $query, $type); 22 | 23 | print_next_page_button(">", $page + 10, $query, $type); 24 | 25 | echo "
"; 26 | } 27 | ?> 28 | 29 | 30 | <?php 31 | echo $opts->query; 32 | ?> - <?php printtext("page_title");?> 33 | 34 | 35 |
36 |

LibreY

37 | strlen($opts->query) || strlen($opts->query) > 256) 40 | { 41 | header("Location: ./"); 42 | die(); 43 | } 44 | 45 | echo "value=\"" . htmlspecialchars($opts->query) . "\""; 46 | ?> 47 | > 48 |
49 | type\"/>"; 51 | ?> 52 | 53 | 54 |
55 | disable_bittorrent_search && $category_index == 3) || 63 | ($opts->disable_hidden_service_search && $category_index ==4)) 64 | { 65 | continue; 66 | } 67 | 68 | echo "type) ? "class=\"active\" " : "") . "href=\"./search.php?q=" . urlencode($opts->query) . "&p=0&t=" . $category_index . "\">\""" . TEXTS["category_$category"] . ""; 69 | } 70 | ?> 71 |
72 |
73 | 74 | type, $opts->query, $opts->page); 77 | ?> 78 | 79 | 80 | -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | $value) { 23 | if (!empty($value)) { 24 | setcookie($key, $value, [ 25 | "expires" => time() + (86400 * 90), // Sets cookie to expire in 90 days 26 | "path" => "/", 27 | "domain" => "$domain", 28 | "secure" => true, // Ensure cookies are only sent over HTTPS 29 | "httponly" => true, // Prevent client-side JavaScript access to cookies 30 | "samesite" => "Strict" // Strict SameSite policy for better protection against CSRF attacks 31 | ]); 32 | } else { 33 | setcookie($key, "", time() - 1000); 34 | } 35 | } 36 | } 37 | 38 | if (isset($_REQUEST["save"]) || isset($_REQUEST["reset"])) { 39 | header("Location: ./"); 40 | die(); 41 | } 42 | 43 | 44 | require_once "misc/header.php"; 45 | $opts = load_opts(); 46 | ?> 47 | 48 | LibreY - <?php printtext("settings_title");?> 49 | 50 | 51 |
52 |

53 |
54 |
55 | 56 | 89 |
90 |
91 |

92 | 93 | disable_special ? "checked" : ""; ?> >
94 | 95 | disable_frontends ? "checked" : ""; ?> >
96 | 97 | safe_search ? "checked" : ""; ?> >
98 |
99 | 100 |

101 |

102 |
103 | frontends as $frontend => $data) 105 | { 106 | echo "
"; 107 | echo "" . ucfirst($frontend) . ""; 108 | echo ""; 111 | echo "
"; 112 | } 113 | ?> 114 |
115 | 116 |

117 |
118 |
119 | 120 | 137 |
138 |
139 | 140 | " > 141 |
142 |
143 |
144 |
145 | 146 | 162 |
163 |
164 | 165 |
166 | 167 | 168 |
169 |
170 |
171 | 172 | 173 | -------------------------------------------------------------------------------- /static/css/amoled.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #000000; 3 | --main-fg: #F1F3F4; 4 | 5 | --result-link-fg: #8AB4F8; 6 | --result-fg: #BABCBE; 7 | 8 | --button-bg: #141414; 9 | 10 | --special-result-border: #202124; 11 | --special-text-background: #080808; 12 | --special-text-color: #F1F3F4; 13 | 14 | --search-container-text-color: #F1F3F4; 15 | --search-container-background-color: #303134; 16 | --search-container-background-border: #3C4043; 17 | 18 | --search-form-background-color: #080808; 19 | 20 | --border: #202124; 21 | 22 | --footer-fg: #BABCBE; 23 | --footer-bg: #080808; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/auto.css: -------------------------------------------------------------------------------- 1 | @import "dark.css" (prefers-color-scheme: dark); 2 | @import "light.css" (prefers-color-scheme: light); 3 | -------------------------------------------------------------------------------- /static/css/catppuccin_frappe.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #303446; 3 | --main-fg: #c6d0f5; 4 | 5 | --result-link-fg: #8caaee; 6 | --result-fg: #babbf1; 7 | 8 | --button-bg: #414559; 9 | 10 | --special-result-border: opacity 50; 11 | --special-text-background: #51576d; 12 | --special-text-color: #c6d0f5; 13 | 14 | --search-container-text-color: #c6d0f5; 15 | --search-container-background-color: #414559; 16 | --search-container-background-border: #babbf1; 17 | 18 | --search-form-background-color: #292c3c; 19 | 20 | --border: #babbf1; 21 | 22 | --footer-fg: #a5adce; 23 | --footer-bg: #414559; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/catppuccin_latte.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #eff1f5; 3 | --main-fg: #4c4f69; 4 | 5 | --result-link-fg: #1e66f5; 6 | --result-fg: #7287fd; 7 | 8 | --button-bg: #ccd0da; 9 | 10 | --special-result-border: opacity 50; 11 | --special-text-background: #bcc0cc; 12 | --special-text-color: #4c4f69; 13 | 14 | --search-container-text-color: #4c4f69; 15 | --search-container-background-color: #ccd0da; 16 | --search-container-background-border: #7287fd; 17 | 18 | --search-form-background-color: #e6e9ef; 19 | 20 | --border: #7287fd; 21 | 22 | --footer-fg: #6c6f85; 23 | --footer-bg: #ccd0da; 24 | } 25 | -------------------------------------------------------------------------------- /static/css/catppuccin_macchiato.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #24273a; 3 | --main-fg: #cad3f5; 4 | 5 | --result-link-fg: #8aadf4; 6 | --result-fg: #b7bdf8; 7 | 8 | --button-bg: #363a4f; 9 | 10 | --special-result-border: opacity 50; 11 | --special-text-background: #494d64; 12 | --special-text-color: #cad3f5; 13 | 14 | --search-container-text-color: #cad3f5; 15 | --search-container-background-color: #363a4f; 16 | --search-container-background-border: #b7bdf8; 17 | 18 | --search-form-background-color: #1e2030; 19 | 20 | --border: #b7bdf8; 21 | 22 | --footer-fg: #a5adcb; 23 | --footer-bg: #363a4f; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/catppuccin_mocha.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #1e1e2e; 3 | --main-fg: #cdd6f4; 4 | 5 | --result-link-fg: #89b4fa; 6 | --result-fg: #b4b3fe; 7 | 8 | --button-bg: #313244; 9 | 10 | --special-result-border: opacity 50; 11 | --special-text-background: #45475a; 12 | --special-text-color: #cdd6f4; 13 | 14 | --search-container-text-color: #cdd6f4; 15 | --search-container-background-color: #313244; 16 | --search-container-background-border: #b4b3fe; 17 | 18 | --search-form-background-color: #181825; 19 | 20 | --border: #b4b3fe; 21 | 22 | --footer-fg: #a6adc8; 23 | --footer-bg: #313244; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #1c1c1c; 3 | --main-fg: #F1F3F4; 4 | 5 | --result-link-fg: #8AB4F8; 6 | --result-fg: #999da2; 7 | 8 | --button-bg: #333333; 9 | 10 | --special-result-border: opacity 0; 11 | --special-text-background: #282828; 12 | --special-text-color: #F1F3F4; 13 | 14 | --search-container-text-color: #F1F3F4; 15 | --search-container-background-color: #333333; 16 | --search-container-background-border: #3C4043; 17 | 18 | --search-form-background-color: #161616; 19 | 20 | --border: #303134; 21 | 22 | --footer-fg: #999da2; 23 | --footer-bg: #161616; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/darker.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #0a0b0d; 3 | --main-fg: #F1F3F4; 4 | 5 | --result-link-fg: #8AB4F8; 6 | --result-fg: #BABCBE; 7 | 8 | --button-bg: #303134; 9 | 10 | --special-result-border: #202124; 11 | --special-text-background: #0a0b0d; 12 | --special-text-color: #F1F3F4; 13 | 14 | --search-container-text-color: #F1F3F4; 15 | --search-container-background-color: #303134; 16 | --search-container-background-border: #3C4043; 17 | 18 | --search-form-background-color: #0a0b0d; 19 | 20 | --border: #202124; 21 | 22 | --footer-fg: #BABCBE; 23 | --footer-bg: #080808; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/discord.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #2f3136; 3 | --main-fg: #dcddde; 4 | 5 | --result-link-fg: #747ff4; 6 | --result-fg: #dcddde; 7 | 8 | --button-bg: #36393f; 9 | 10 | --special-result-border: opacity 0; 11 | --special-text-background: #36393f; 12 | --special-text-color: #dcddde; 13 | 14 | --search-container-text-color: #dcddde; 15 | --search-container-background-color: #282b30; 16 | --search-container-background-border: opacity 0; 17 | 18 | --search-form-background-color: #36393f; 19 | 20 | --border: opacity 0; 21 | 22 | --footer-fg: #dcddde; 23 | --footer-bg: #36393f; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/dracula.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #282A36; 3 | --main-fg: #F8F8F2; 4 | 5 | --result-link-fg: #8BE9FD; 6 | --result-fg: #6272A4; 7 | 8 | --button-bg: #44475A; 9 | 10 | --special-result-border: opacity 50; 11 | --special-text-background: #44475A; 12 | --special-text-color: #F8F8F2; 13 | 14 | --search-container-text-color: #F8F8F2; 15 | --search-container-background-color: #44475A; 16 | --search-container-background-border: #6272A4; 17 | 18 | --search-form-background-color: #21222C; 19 | 20 | --border: #6272A4; 21 | 22 | --footer-fg: #C2C2C2; 23 | --footer-bg: #44475A; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/github_night.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #0d1117; 3 | --main-fg: #f0f6fc; 4 | 5 | --result-link-fg: #58a6ff; 6 | --result-fg: #8b949e; 7 | 8 | --button-bg: #1f242b; 9 | 10 | --special-result-border: opacity 0; 11 | --special-text-background: #161b22; 12 | --special-text-color: #f0f6fc; 13 | 14 | --search-container-text-color: #f0f6fc; 15 | --search-container-background-color: #1f242b; 16 | --search-container-background-border: #303842; 17 | 18 | --search-form-background-color: #161b22; 19 | 20 | --border: #303842; 21 | 22 | --footer-fg: #8b949e; 23 | --footer-bg: #161b22; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/google.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #202124; 3 | --main-fg: #F1F3F4; 4 | 5 | --result-link-fg: #8AB4F8; 6 | --result-fg: #BABCBE; 7 | 8 | --button-bg: #303134; 9 | 10 | --special-result-border: #3c4043; 11 | --special-text-background: #202124; 12 | --special-text-color: #F1F3F4; 13 | 14 | --search-container-text-color: #F1F3F4; 15 | --search-container-background-color: #303134; 16 | --search-container-background-border: #3C4043; 17 | 18 | --search-form-background-color: #202124; 19 | 20 | --border: #3c4043; 21 | 22 | --footer-fg: #BABCBE; 23 | --footer-bg: #171717; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/gruvbox.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #262622; 3 | --main-fg: #f2efd3; 4 | 5 | --result-link-fg: #8AB4F8; 6 | --result-fg: #a2a08e; 7 | 8 | --button-bg: #1b1b18; 9 | 10 | --special-result-border: rgba(0,0,0,.2); 11 | --special-text-background: #1b1b18; 12 | --special-text-color: #f2efd3; 13 | 14 | --search-container-text-color: #f2efd3; 15 | --search-container-background-color: #262622; 16 | --search-container-background-border: #3C4043; 17 | 18 | --search-form-background-color: #151613; 19 | 20 | --border: rgba(0,0,0,.2); 21 | 22 | --footer-fg: #a2a08e; 23 | --footer-bg: #151613; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #ffffff; 3 | --main-fg: #000000; 4 | 5 | --result-link-fg: #1a0dab; 6 | --result-fg: #202124; 7 | 8 | --button-bg: #f6f6f6; 9 | 10 | --special-result-border: #dadce0; 11 | --special-text-background: #ffffff; 12 | --special-text-color: #000000; 13 | 14 | --search-container-text-color: #000000; 15 | --search-container-background-color: #f6f6f6; 16 | --search-container-background-border: #dadce0; 17 | 18 | --search-form-background-color: #ffffff; 19 | 20 | --border: #dadce0; 21 | 22 | --footer-fg: #353535; 23 | --footer-bg: #f6f6f6; 24 | } 25 | -------------------------------------------------------------------------------- /static/css/night_owl.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #011627; 3 | --main-fg: #d6deeb; 4 | 5 | --result-link-fg: #5f7e97; 6 | --result-fg: #d6deeb; 7 | 8 | --button-bg: #122d42; 9 | 10 | --special-result-border: opacity 0; 11 | --special-text-background: #122d42; 12 | --special-text-color: #d6deeb; 13 | 14 | --search-container-text-color: #d6deeb; 15 | --search-container-background-color: #535470; 16 | --search-container-background-border: opacity 0; 17 | 18 | --search-form-background-color: #122d42; 19 | 20 | --border: opacity 0; 21 | 22 | --footer-fg: #d6deeb; 23 | --footer-bg: #535470; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/nord.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #3B4252; 3 | --main-fg: #E5E9F0; 4 | 5 | --result-link-fg: #88C0D0; 6 | --result-fg: #D8DEE9; 7 | 8 | --button-bg: #4C566A; 9 | 10 | --special-result-border: opacity 0; 11 | --special-text-background: #4C566A; 12 | --special-text-color: #E5E9F0; 13 | 14 | --search-container-text-color: #E5E9F0; 15 | --search-container-background-color: #2E3440; 16 | --search-container-background-border: opacity 0; 17 | 18 | --search-form-background-color: #4C566A; 19 | 20 | --border: opacity 0; 21 | 22 | --footer-fg: #D8DEE9; 23 | --footer-bg: #2E3440; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/startpage.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #171b25; 3 | --main-fg: #e8eaed; 4 | 5 | --result-link-fg: #a7b1fc; 6 | --result-fg: #ebecf7; 7 | 8 | --button-bg: #0c0d0f; 9 | 10 | --special-result-border: #3a445c; 11 | --special-text-background: #1e222d; 12 | --special-text-color: #e8eaed; 13 | 14 | --search-container-text-color: #ebecf7; 15 | --search-container-background-color: #2e3443; 16 | --search-container-background-border: #2e3443; 17 | 18 | --search-form-background-color: #0c0d0f; 19 | 20 | --border: #0c0d0f; 21 | 22 | --footer-fg: #999da2; 23 | --footer-bg: #0c0d0f; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | color: var(--main-fg); 3 | background-color: var(--main-bg); 4 | font-family: Arial, Helvetica, sans-serif; 5 | font-size: 16px; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | } 11 | 12 | input, 13 | button { 14 | outline: none; 15 | } 16 | 17 | button { 18 | cursor: pointer; 19 | } 20 | 21 | p { 22 | font-size: 18px; 23 | color: var(--result-fg); 24 | } 25 | 26 | a, 27 | .text-result-wrapper a:hover { 28 | text-decoration: none; 29 | } 30 | 31 | .text-result-wrapper a:visited h2, 32 | .special-result-container a, 33 | .did-you-mean a, 34 | .sub-search-button-wrapper a, 35 | .sub-search-button-wrapper a:visited { 36 | color: #bd93f9; 37 | } 38 | 39 | .sub-search-button-wrapper .active { 40 | border-bottom: 2px #bd93f9 solid; 41 | padding-bottom: 10px; 42 | } 43 | 44 | a:hover, 45 | .text-result-wrapper h2:hover { 46 | text-decoration: underline; 47 | } 48 | 49 | .search-container { 50 | text-align: center; 51 | margin-top: 10%; 52 | } 53 | 54 | .search-container h1 { 55 | font-size: 70px; 56 | } 57 | 58 | .search-container input, 59 | .sub-search-container input { 60 | width: 500px; 61 | color: var(--search-container-text-color); 62 | background-color: var(--search-container-background-color); 63 | padding: 10px; 64 | font-size: inherit; 65 | font-family: sans-serif; 66 | border: 1px solid var(--search-container-background-border); 67 | border-radius: 25px; 68 | } 69 | 70 | .search-button-wrapper button, 71 | .misc-container button, 72 | .misc-container select { 73 | color: inherit; 74 | background-color: var(--button-bg); 75 | font-size: 14px; 76 | border: 1px solid var(--main-bg); 77 | border-radius: 4px; 78 | padding: 13px 10px 13px 10px; 79 | } 80 | 81 | .search-button-wrapper button { 82 | 83 | margin: 30px 60px 0px 60px; 84 | } 85 | 86 | .sub-search-container { 87 | background-color: var(--search-form-background-color); 88 | width: 100%; 89 | border-bottom: 1px solid var(--border); 90 | } 91 | 92 | .sub-search-container hr { 93 | opacity: 0; 94 | } 95 | 96 | .sub-search-container input { 97 | width: 580px; 98 | position: relative; 99 | left: 140px; 100 | margin: 18px; 101 | } 102 | 103 | .logomobile { 104 | position: absolute; 105 | margin-top: 0px; 106 | top: 25px; 107 | left: 20px; 108 | } 109 | 110 | .no-decoration { 111 | text-decoration: none; 112 | color: var(--main-fg); 113 | } 114 | 115 | .no-decoration:hover { 116 | text-decoration: none; 117 | } 118 | 119 | .sub-search-button-wrapper { 120 | margin-left: 165px; 121 | margin-bottom: 10px; 122 | } 123 | 124 | .search-button-wrapper button:hover, 125 | .misc-container button:hover { 126 | border: 1px solid #5f6368; 127 | cursor: pointer; 128 | } 129 | 130 | .sub-search-button-wrapper i { 131 | vertical-align: middle; 132 | margin-right: 5px; 133 | } 134 | 135 | .sub-search-button-wrapper a { 136 | border: none; 137 | background-color: inherit; 138 | font-size: 15px; 139 | margin-right: 20px; 140 | cursor: pointer; 141 | text-decoration: none; 142 | } 143 | 144 | .sub-search-button-wrapper img { 145 | vertical-align: middle; 146 | margin-right: 5px; 147 | width: 20px; 148 | } 149 | 150 | .misc-container { 151 | text-align: center; 152 | word-wrap: break-word; 153 | width: 460px; 154 | margin-left: auto; 155 | margin-right: auto; 156 | margin-bottom: 100px; 157 | } 158 | 159 | .misc-container div { 160 | margin-bottom: 30px; 161 | } 162 | 163 | .misc-container button { 164 | margin-right: 10px; 165 | margin-left: 10px; 166 | } 167 | 168 | .misc-container a { 169 | color: inherit; 170 | text-decoration: underline; 171 | } 172 | 173 | .settings-textbox-container div { 174 | text-align: left; 175 | margin-bottom: 25px; 176 | } 177 | 178 | .settings-textbox-container input { 179 | color: inherit; 180 | background-color: inherit; 181 | padding: 5px; 182 | font-size: inherit; 183 | font-family: inherit; 184 | border: 1px solid #5f6368; 185 | border-radius: 5px; 186 | float: right; 187 | } 188 | 189 | .text-result-container, 190 | #time, 191 | .next-page-button-wrapper, 192 | .did-you-mean { 193 | margin-left: 170px; 194 | } 195 | 196 | .text-result-container { 197 | margin-bottom: 100px; 198 | } 199 | 200 | .special-result-container { 201 | padding: 10px; 202 | border: 1px solid var(--special-result-border); 203 | width: 500px; 204 | border-radius: 8px; 205 | background: var(--special-text-background); 206 | color: var(--special-text-color); 207 | margin-left: 840px; 208 | margin-top: 0px; 209 | position: absolute; 210 | } 211 | 212 | .text-result-wrapper { 213 | max-width: 550px; 214 | word-wrap: break-word; 215 | margin-bottom: 35px; 216 | } 217 | 218 | .text-result-wrapper a { 219 | font-size: 14px; 220 | color: var(--result-fg); 221 | } 222 | 223 | .video-img { 224 | height: 115px; 225 | border-radius: 12px; 226 | } 227 | 228 | .text-result-wrapper h2 { 229 | font-size: 20px; 230 | color: var(--result-link-fg); 231 | padding-top: 5px; 232 | margin-top: 1px; 233 | } 234 | 235 | .special-result-container a { 236 | display: flex; 237 | margin-top: 10px; 238 | font-size: 14px; 239 | } 240 | 241 | .special-result-container img { 242 | display: flex; 243 | max-width: 60%; 244 | max-height: 200px; 245 | padding-bottom: 10px; 246 | margin-left: auto; 247 | margin-right: auto; 248 | } 249 | 250 | .next-page-button-wrapper { 251 | margin-top: -40px; 252 | margin-bottom: 100px; 253 | } 254 | 255 | .next-page-button-wrapper button { 256 | border: none; 257 | background-color: inherit; 258 | color: var(--result-link-fg); 259 | font-size: 18px; 260 | margin-right: 8px; 261 | } 262 | 263 | .next-page-button-wrapper .page { 264 | display: inline; 265 | } 266 | 267 | .image-result-container { 268 | display: flex; 269 | flex-wrap: wrap; 270 | grid-gap: 1.5rem; 271 | justify-items: center; 272 | margin-left: 9%; 273 | margin-right: 9%; 274 | padding: 0; 275 | margin-bottom: 50px; 276 | } 277 | 278 | a[title] { 279 | flex-grow: 1; 280 | height: 12rem; 281 | } 282 | 283 | .image-result-container img { 284 | margin: 0px; 285 | height: 100%; 286 | width: 100%; 287 | object-fit: cover; 288 | vertical-align: bottom; 289 | } 290 | 291 | 292 | .git-container { 293 | right: 0; 294 | } 295 | 296 | .git-container, 297 | .footer-container { 298 | position: fixed; 299 | bottom: 0; 300 | } 301 | 302 | .footer-container { 303 | width: 100vw; 304 | left: 0; 305 | background-color: var(--footer-bg); 306 | padding: 10px; 307 | border-top: 1px solid var(--border); 308 | text-align: center; 309 | } 310 | 311 | .git-container a { 312 | font-size: 10px; 313 | } 314 | 315 | .git-container a, 316 | .footer-container a { 317 | color: var(--footer-fg); 318 | } 319 | 320 | .footer-container a { 321 | margin-right: 30px; 322 | } 323 | 324 | .hide { 325 | display: none; 326 | } 327 | 328 | .Y { 329 | color: #bd93f9; 330 | } 331 | 332 | .seeders { 333 | color: #50fa7b; 334 | } 335 | 336 | .leechers { 337 | color: #ff79c6; 338 | } 339 | 340 | 341 | /* donate css start */ 342 | 343 | .donate-container { 344 | width: 700px; 345 | margin-left: auto; 346 | margin-right: auto; 347 | margin-bottom: 100px; 348 | } 349 | 350 | .flexbox-column { 351 | display: flex; 352 | flex-direction: column; 353 | justify-content: space-between; 354 | align-items: center; 355 | gap: 10px; 356 | } 357 | 358 | .inner-wrap { 359 | width: 500px; 360 | padding: 20px; 361 | } 362 | 363 | .qr-box { 364 | background-color: var(--search-container-background-color); 365 | border: 1px solid var(--search-container-background-border); 366 | border-radius: 10px 0px 0px 10px; 367 | width: 100%; 368 | 369 | display: flex; 370 | word-wrap: break-word; 371 | align-items: center; 372 | justify-content: space-between; 373 | } 374 | 375 | .flex-row { 376 | width: 700px; 377 | height: auto; 378 | display: flex; 379 | flex-direction: row; 380 | justify-content: space-evenly; 381 | } 382 | 383 | hr.small-line { 384 | /* background-color: #f1f3f4; */ 385 | border: 2px solid var(--main-fg); 386 | height: 0px; 387 | width: 100px; 388 | margin: 30px; 389 | border-radius: 2px; 390 | } 391 | /* donate css end */ 392 | 393 | 394 | @media only screen and (max-width: 1320px) { 395 | .special-result-container { 396 | position: relative; 397 | float: none; 398 | margin-left: 165px; 399 | } 400 | } 401 | 402 | /* mobile view */ 403 | @media only screen and (max-width: 750px) { 404 | p { 405 | font-size: 16px; 406 | } 407 | 408 | html { 409 | font-size: 14px; 410 | } 411 | 412 | .search-container input { 413 | width: 80%; 414 | } 415 | 416 | .search-button-wrapper button { 417 | display: table-row; 418 | margin: 30px 0px 0px 0px; 419 | width: 80%; 420 | } 421 | 422 | .image-result-container { 423 | display: unset; 424 | margin: 0; 425 | padding: 0; 426 | } 427 | 428 | .image-result-container img { 429 | margin: 0 5% 5% 5%; 430 | padding: 0; 431 | width: 90%; 432 | border: none; 433 | } 434 | 435 | .git-container { 436 | display: none; 437 | } 438 | 439 | .footer-container a { 440 | margin: 10px; 441 | } 442 | 443 | .sub-search-container { 444 | margin-left: auto; 445 | margin-right: auto; 446 | text-align: center; 447 | } 448 | 449 | .sub-search-container .logo { 450 | display: none; 451 | 452 | } 453 | 454 | .logomobile { 455 | position: relative; 456 | float: none; 457 | margin-top: 0px; 458 | margin-bottom: 0px; 459 | margin-left: auto; 460 | margin-right: auto; 461 | padding: 10px; 462 | font-size: 28px; 463 | display: block; 464 | top: 0px; 465 | left: 0px; 466 | } 467 | 468 | .sub-search-container input { 469 | width: 80%; 470 | position: relative; 471 | left: 0px; 472 | margin-top: 0px; 473 | } 474 | 475 | .sub-search-button-wrapper { 476 | margin: 0; 477 | padding: 0; 478 | display: flex; 479 | align-items: baseline; 480 | } 481 | 482 | .sub-search-button-wrapper img { 483 | margin: 0; 484 | padding: 0; 485 | } 486 | 487 | .sub-search-button-wrapper a { 488 | margin-left: auto; 489 | margin-right: auto; 490 | padding: 0; 491 | display: flex; 492 | flex-direction: column; 493 | align-items: center; 494 | } 495 | 496 | .special-result-container { 497 | position: relative; 498 | float: none; 499 | max-width: 90%; 500 | margin-left: 10px; 501 | width: auto; 502 | } 503 | 504 | .special-result-container img { 505 | max-width: 80%; 506 | } 507 | 508 | .misc-container { 509 | margin-bottom: 200px; 510 | width: 95%; 511 | } 512 | 513 | .search-container h1 { 514 | font-size: 55px; 515 | } 516 | 517 | .search-container { 518 | margin-top: 10%; 519 | } 520 | 521 | .text-result-container, 522 | #time, 523 | .next-page-button-wrapper { 524 | margin-left: 20px; 525 | max-width: 90%; 526 | } 527 | 528 | .next-page-button-wrapper { 529 | margin-top: 30px; 530 | } 531 | 532 | .text-result-container, 533 | #time, 534 | .next-page-button-wrapper, 535 | .did-you-mean { 536 | margin-left: 20px; 537 | } 538 | 539 | /* dontate css start*/ 540 | 541 | .donate-container { 542 | margin-bottom: 100px; 543 | width: 95%; 544 | text-align: center; 545 | } 546 | 547 | .qr-box { 548 | display: flex; 549 | flex-direction: column; 550 | word-wrap: break-word; 551 | align-items: center; 552 | justify-content: space-between; 553 | height: auto; 554 | } 555 | 556 | .qr-box { 557 | border-radius: 10px; 558 | flex-direction: column; 559 | align-items: center; 560 | } 561 | 562 | .inner-wrap { 563 | width: 80%; 564 | text-align: center; 565 | } 566 | 567 | .qr-box img { 568 | width: 40%; 569 | height: auto; 570 | padding: 20px; 571 | } 572 | 573 | .flex-row { 574 | flex-direction: column; 575 | align-items: center; 576 | gap: 5px; 577 | width: 0; 578 | } 579 | 580 | .flex-row a img { 581 | width: 220px; 582 | height: auto; 583 | } 584 | /* donate css end */ 585 | 586 | 587 | } 588 | -------------------------------------------------------------------------------- /static/css/tokyo_night.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #24283b; 3 | --main-fg: #cfc9c2; 4 | 5 | --result-link-fg: #bb9af7; 6 | --result-fg: #a2a08e; 7 | 8 | --button-bg: #343b58; 9 | 10 | --special-result-border: border 0; 11 | --special-text-background: #1a1b26; 12 | --special-text-color: #c0caf5; 13 | 14 | --search-container-text-color: #f2efd3; 15 | --search-container-background-color: #565f89; 16 | --search-container-background-border: #bb9af7; 17 | 18 | --search-form-background-color: #1a1b26; 19 | 20 | --border: #565f89; 21 | 22 | --footer-fg: #f2efd3; 23 | --footer-bg: #1a1b26; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/css/ubuntu.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg: #2C001E; 3 | --main-fg: #FFFFFF; 4 | 5 | --result-link-fg: #84de32; 6 | --result-fg: #a2a08e; 7 | 8 | --button-bg: #5E2750; 9 | 10 | --special-result-border: border 0; 11 | --special-text-background: #333333; 12 | --special-text-color: #FFFFFF; 13 | 14 | --search-container-text-color: #f2efd3; 15 | --search-container-background-color: #77216F; 16 | --search-container-background-border: #E95420; 17 | 18 | --search-form-background-color: #333333; 19 | 20 | --border: #77216F; 21 | 22 | --footer-fg: #f2efd3; 23 | --footer-bg: #5E2750; 24 | 25 | color-scheme: dark; 26 | } 27 | -------------------------------------------------------------------------------- /static/images/btc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/btc.png -------------------------------------------------------------------------------- /static/images/buy-me-a-coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/buy-me-a-coffee.png -------------------------------------------------------------------------------- /static/images/general_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/general_result.png -------------------------------------------------------------------------------- /static/images/images_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/images_result.png -------------------------------------------------------------------------------- /static/images/kofi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/kofi.png -------------------------------------------------------------------------------- /static/images/librex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/librex.png -------------------------------------------------------------------------------- /static/images/maps_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/maps_result.png -------------------------------------------------------------------------------- /static/images/tor_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/tor_result.png -------------------------------------------------------------------------------- /static/images/torrents_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/torrents_result.png -------------------------------------------------------------------------------- /static/images/videos_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/videos_result.png -------------------------------------------------------------------------------- /static/images/xmr-ahwx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/xmr-ahwx.png -------------------------------------------------------------------------------- /static/images/xmr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ahwxorg/LibreY/524c287a7ec0ca3323f3fa06b03b9aa78b977826/static/images/xmr.png -------------------------------------------------------------------------------- /static/misc/languages.json: -------------------------------------------------------------------------------- 1 | {"af": {"name": "Afrikaans", "wikipedia": "af"}, "ak": {"name": "Akan", "wikipedia": "ak"}, "sq": {"name": "Albanian", "wikipedia": "sq"}, "ws": {"name": "Samoa", "wikipedia": "ws"}, "am": {"name": "Amharic", "wikipedia": "am"}, "ar": {"name": "Arabic", "wikipedia": "ar"}, "hy": {"name": "Armenian", "wikipedia": "hy"}, "az": {"name": "Azerbaijani", "wikipedia": "az"}, "eu": {"name": "Basque", "wikipedia": "eu"}, "be": {"name": "Belarusian", "wikipedia": "be"}, "bem": {"name": "Bemba", "wikipedia": ""}, "bn": {"name": "Bengali", "wikipedia": "bn"}, "bh": {"name": "Bihari", "wikipedia": "bh"}, "xx-bork": {"name": "Bork, bork, bork!", "wikipedia": ""}, "bs": {"name": "Bosnian", "wikipedia": "bs"}, "br": {"name": "Breton", "wikipedia": "br"}, "bg": {"name": "Bulgarian", "wikipedia": "bg"}, "bt": {"name": "Bhutanese", "wikipedia": ""}, "km": {"name": "Cambodian", "wikipedia": ""}, "ca": {"name": "Catalan", "wikipedia": "ca"}, "chr": {"name": "Cherokee", "wikipedia": "chr"}, "ny": {"name": "Chichewa", "wikipedia": "ny"}, "zh-cn": {"name": "Chinese (Simplified)", "wikipedia": "zh"}, "zh-tw": {"name": "Chinese (Traditional)", "wikipedia": "zh-classical"}, "co": {"name": "Corsican", "wikipedia": "co"}, "hr": {"name": "Croatian", "wikipedia": "hr"}, "cs": {"name": "Czech", "wikipedia": "cs"}, "da": {"name": "Danish", "wikipedia": "da"}, "nl": {"name": "Dutch", "wikipedia": "nl"}, "xx-elmer": {"name": "Elmer Fudd", "wikipedia": ""}, "en": {"name": "English", "wikipedia": "en"}, "eo": {"name": "Esperanto", "wikipedia": "eo"}, "et": {"name": "Estonian", "wikipedia": "et"}, "ee": {"name": "Ewe", "wikipedia": "ee"}, "fo": {"name": "Faroese", "wikipedia": "fo"}, "tl": {"name": "Filipino", "wikipedia": "tl"}, "fi": {"name": "Finnish", "wikipedia": "fi"}, "fr": {"name": "French", "wikipedia": "fr"}, "fy": {"name": "Frisian", "wikipedia": "fy"}, "gaa": {"name": "Ga", "wikipedia": ""}, "gl": {"name": "Galician", "wikipedia": "gl"}, "ka": {"name": "Georgian", "wikipedia": "ka"}, "de": {"name": "German", "wikipedia": "de"}, "el": {"name": "Greek", "wikipedia": "el"}, "kl": {"name": "Greenlandic", "wikipedia": "kl"}, "gn": {"name": "Guarani", "wikipedia": "gn"}, "gu": {"name": "Gujarati", "wikipedia": "gu"}, "xx-hacker": {"name": "Hacker", "wikipedia": ""}, "ht": {"name": "Haitian Creole", "wikipedia": "ht"}, "ha": {"name": "Hausa", "wikipedia": "ha"}, "haw": {"name": "Hawaiian", "wikipedia": "haw"}, "iw": {"name": "Hebrew", "wikipedia": "he"}, "hi": {"name": "Hindi", "wikipedia": "hi"}, "hu": {"name": "Hungarian", "wikipedia": "hu"}, "is": {"name": "Icelandic", "wikipedia": "is"}, "ig": {"name": "Igbo", "wikipedia": "ig"}, "id": {"name": "Indonesian", "wikipedia": "id"}, "ia": {"name": "Interlingua", "wikipedia": "ia"}, "ga": {"name": "Irish", "wikipedia": "ga"}, "it": {"name": "Italian", "wikipedia": "it"}, "ja": {"name": "Japanese", "wikipedia": "ja"}, "jw": {"name": "Javanese", "wikipedia": "jv"}, "kn": {"name": "Kannada", "wikipedia": "kn"}, "kk": {"name": "Kazakh", "wikipedia": "kk"}, "rw": {"name": "Kinyarwanda", "wikipedia": "rw"}, "rn": {"name": "Kirundi", "wikipedia": "rn"}, "xx-klingon": {"name": "Klingon", "wikipedia": "tlh"}, "kg": {"name": "Kongo", "wikipedia": "kg"}, "ko": {"name": "Korean", "wikipedia": "ko"}, "kri": {"name": "Krio (Sierra Leone)", "wikipedia": ""}, "ku": {"name": "Kurdish", "wikipedia": "ku"}, "ckb": {"name": "Kurdish (Soran\u00ee)", "wikipedia": "ckb"}, "ky": {"name": "Kyrgyz", "wikipedia": "ky"}, "lo": {"name": "Laothian", "wikipedia": "lo"}, "la": {"name": "Latin", "wikipedia": "la"}, "lv": {"name": "Latvian", "wikipedia": "lv"}, "ln": {"name": "Lingala", "wikipedia": "ln"}, "lt": {"name": "Lithuanian", "wikipedia": "lt"}, "loz": {"name": "Lozi", "wikipedia": ""}, "lg": {"name": "Luganda", "wikipedia": "lg"}, "ach": {"name": "Luo", "wikipedia": ""}, "mk": {"name": "Macedonian", "wikipedia": "mk"}, "mg": {"name": "Malagasy", "wikipedia": "mg"}, "my": {"name": "Malay", "wikipedia": "ms"}, "ml": {"name": "Malayalam", "wikipedia": "ml"}, "mt": {"name": "Maltese", "wikipedia": "mt"}, "mv": {"name": "Maldives", "wikipedia": "dv"}, "mi": {"name": "Maori", "wikipedia": "mi"}, "mr": {"name": "Marathi", "wikipedia": "mr"}, "mfe": {"name": "Mauritian Creole", "wikipedia": ""}, "mo": {"name": "Moldavian", "wikipedia": "ro"}, "mn": {"name": "Mongolian", "wikipedia": "mn"}, "sr-me": {"name": "Montenegrin", "wikipedia": "sr"}, "ne": {"name": "Nepali", "wikipedia": "ne"}, "pcm": {"name": "Nigerian Pidgin", "wikipedia": "pcm"}, "nso": {"name": "Northern Sotho", "wikipedia": "nso"}, "no": {"name": "Norwegian", "wikipedia": "no"}, "nn": {"name": "Norwegian (Nynorsk)", "wikipedia": "nn"}, "oc": {"name": "Occitan", "wikipedia": "oc"}, "or": {"name": "Oriya", "wikipedia": ""}, "om": {"name": "Oromo", "wikipedia": "om"}, "ps": {"name": "Pashto", "wikipedia": "ps"}, "fa": {"name": "Persian", "wikipedia": "fa"}, "xx-pirate": {"name": "Pirate", "wikipedia": ""}, "pl": {"name": "Polish", "wikipedia": "pl"}, "pt": {"name": "Portuguese", "wikipedia": "pt"}, "pt-br": {"name": "Portuguese (Brazil)", "wikipedia": "pt"}, "pt-pt": {"name": "Portuguese (Portugal)", "wikipedia": "pt"}, "pa": {"name": "Punjabi", "wikipedia": "pa"}, "qu": {"name": "Quechua", "wikipedia": "qu"}, "ro": {"name": "Romanian", "wikipedia": "ro"}, "rm": {"name": "Romansh", "wikipedia": "rm"}, "nyn": {"name": "Runyakitara", "wikipedia": ""}, "ru": {"name": "Russian", "wikipedia": "ru"}, "gd": {"name": "Scots Gaelic", "wikipedia": "gd"}, "sr": {"name": "Serbian", "wikipedia": "sr"}, "sh": {"name": "Serbo-Croatian", "wikipedia": "sh"}, "st": {"name": "Sesotho", "wikipedia": "st"}, "tn": {"name": "Setswana", "wikipedia": "tn"}, "crs": {"name": "Seychellois Creole", "wikipedia": ""}, "sn": {"name": "Shona", "wikipedia": "sn"}, "sd": {"name": "Sindhi", "wikipedia": "sd"}, "si": {"name": "Sinhalese", "wikipedia": "si"}, "sk": {"name": "Slovak", "wikipedia": "sk"}, "sl": {"name": "Slovenian", "wikipedia": "sl"}, "so": {"name": "Somali", "wikipedia": "so"}, "es": {"name": "Spanish", "wikipedia": "es"}, "es-419": {"name": "Spanish (Latin American)", "wikipedia": "es"}, "su": {"name": "Sundanese", "wikipedia": "su"}, "sw": {"name": "Swahili", "wikipedia": "sw"}, "sv": {"name": "Swedish", "wikipedia": "sv"}, "tg": {"name": "Tajik", "wikipedia": "tg"}, "ta": {"name": "Tamil", "wikipedia": "ta"}, "tt": {"name": "Tatar", "wikipedia": "tt"}, "te": {"name": "Telugu", "wikipedia": "te"}, "th": {"name": "Thai", "wikipedia": "th"}, "ti": {"name": "Tigrinya", "wikipedia": "ti"}, "to": {"name": "Tonga", "wikipedia": "to"}, "lua": {"name": "Tshiluba", "wikipedia": ""}, "tum": {"name": "Tumbuka", "wikipedia": "tum"}, "tr": {"name": "Turkish", "wikipedia": "tr"}, "tk": {"name": "Turkmen", "wikipedia": "tk"}, "tw": {"name": "Twi", "wikipedia": "tw"}, "ug": {"name": "Uighur", "wikipedia": "ug"}, "uk": {"name": "Ukrainian", "wikipedia": "uk"}, "ur": {"name": "Urdu", "wikipedia": "ur"}, "uz": {"name": "Uzbek", "wikipedia": "uz"}, "vu": {"name": "Vanuatu", "wikipedia": ""}, "vi": {"name": "Vietnamese", "wikipedia": "vi"}, "cy": {"name": "Welsh", "wikipedia": "cy"}, "wo": {"name": "Wolof", "wikipedia": "wo"}, "xh": {"name": "Xhosa", "wikipedia": "xh"}, "yi": {"name": "Yiddish", "wikipedia": "yi"}, "yo": {"name": "Yoruba", "wikipedia": "yo"}, "zu": {"name": "Zulu", "wikipedia": "zu"}} 2 | --------------------------------------------------------------------------------