Self-Host

To Self-host @ https://link.flossboxin.org.in
This commit is contained in:
vdbhb59 2025-02-21 21:50:36 +05:30
commit c9898cf261
95 changed files with 5217 additions and 0 deletions

View file

@ -0,0 +1,40 @@
<?php
require_once "engines/text/text.php";
class TorSearch extends EngineRequest {
public function get_request_url() {
return "https://ahmia.fi/search/?q=" . urlencode($this->query);
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
foreach($xpath->query("//ol[@class='searchResults']//li[@class='result']") as $result)
{
$url = "http://" . $xpath->evaluate(".//cite", $result)[0]->textContent;
$title = remove_special($xpath->evaluate(".//h4", $result)[0]->textContent);
$description = $xpath->evaluate(".//p", $result)[0]->textContent;
array_push($results,
array (
"title" => $title ? htmlspecialchars($title) : TEXTS["result_no_description"],
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => htmlspecialchars($description)
)
);
}
return $results;
}
public static function print_results($results, $opts) {
TextSearch::print_results($results, $opts);
}
}
?>

View file

@ -0,0 +1,38 @@
<?php
class _1337xRequest extends EngineRequest {
public function get_request_url() {
$query = urlencode($this->query);
return "https://1337x.to/search/$query/1/";
}
public function parse_results($response) {
$xpath = get_xpath($response);
$results = array();
if (!$xpath)
return $results;
foreach($xpath->query("//table/tbody/tr") as $result) {
$name = $xpath->evaluate(".//td[@class='coll-1 name']/a", $result)[1]->textContent;
$magnet = "./engines/bittorrent/get_magnet_1337x.php?url=https://1337x.to" . $xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[1]->textContent;
$size_unformatted = explode(" ", $xpath->evaluate(".//td[contains(@class, 'coll-4 size')]", $result)[0]->textContent);
$size = $size_unformatted[0] . " " . preg_replace("/[0-9]+/", "", $size_unformatted[1]);
$seeders = $xpath->evaluate(".//td[@class='coll-2 seeds']", $result)[0]->textContent;
$leechers = $xpath->evaluate(".//td[@class='coll-3 leeches']", $result)[0]->textContent;
array_push($results,
array (
"name" => htmlspecialchars($name),
"seeders" => (int) $seeders,
"leechers" => (int) $leechers,
"magnet" => htmlspecialchars($magnet),
"size" => htmlspecialchars($size),
"source" => "1337x.to"
)
);
}
return $results;
}
}
?>

View file

@ -0,0 +1,15 @@
<?php
require_once "../../misc/tools.php";
$config = require_once "../../config.php";
$url = $_REQUEST["url"];
$response = request($url, $config->curl_settings);
$xpath = get_xpath($response);
$magnet = $xpath->query("//main/div/div/div/div/div/ul/li/a/@href")[0]->textContent;
$magnet_without_tracker = explode("&tr=", $magnet)[0];
$magnet = $magnet_without_tracker . $config->bittorrent_trackers;
header("Location: $magnet")
?>

View file

@ -0,0 +1,69 @@
<?php
class TorrentSearch extends EngineRequest {
protected $requests;
public function __construct($opts, $mh) {
parent::__construct($opts, $mh);
require_once "engines/bittorrent/thepiratebay.php";
require_once "engines/bittorrent/rutor.php";
require_once "engines/bittorrent/yts.php";
require_once "engines/bittorrent/torrentgalaxy.php";
require_once "engines/bittorrent/1337x.php";
require_once "engines/bittorrent/sukebei.php";
$this->requests = array(
new PirateBayRequest($opts, $mh),
new _1337xRequest($opts, $mh),
new NyaaRequest($opts, $mh),
new RutorRequest($opts, $mh),
new SukebeiRequest($opts, $mh),
new TorrentGalaxyRequest($opts, $mh),
new YTSRequest($opts, $mh),
);
}
public function parse_results($response) {
$results = array();
foreach ($this->requests as $request) {
if ($request->successful())
$results = array_merge($results, $request->get_results());
}
$seeders = array_column($results, "seeders");
array_multisort($seeders, SORT_DESC, $results);
return $results;
}
public static function print_results($results, $opts) {
echo "<div class=\"text-result-container\">";
if (empty($results)) {
echo "<p>" . TEXTS["failure_empty"] . "</p>";
return;
}
foreach($results as $result) {
$source = $result["source"];
$name = $result["name"];
$magnet = $result["magnet"];
$seeders = $result["seeders"];
$leechers = $result["leechers"];
$size = $result["size"];
echo "<div class=\"text-result-wrapper\">";
echo "<a href=\"$magnet\">";
echo "$source";
echo "<h2>$name</h2>";
echo "</a>";
echo "<span>SE: <span class=\"seeders\">$seeders</span> - ";
echo "LE: <span class=\"leechers\">$leechers</span> - ";
echo "$size</span>";
echo "</div>";
}
echo "</div>";
}
}
?>

View file

@ -0,0 +1,52 @@
<?php
class NyaaRequest extends EngineRequest {
public $SOURCE = "nyaa.si";
public function get_request_url() {
return "https://$this->SOURCE/?q=" . urlencode($this->query);
}
public function parse_results($response) {
$xpath = get_xpath($response);
$results = array();
if (!$xpath)
return $results;
foreach($xpath->query("//tbody/tr") as $result)
{
$name_node = $xpath->evaluate(".//td[@colspan='2']//a[not(contains(@class, 'comments'))]/@title", $result);
if ($name_node->length > 0) {
$name = $name_node[0]->textContent;
} else {
$name = "";
}
$centered = $xpath->evaluate(".//td[@class='text-center']", $result);
$magnet_node = $xpath->evaluate(".//a[2]/@href", $centered[0]);
if ($magnet_node->length > 0) {
$magnet = $magnet_node[0]->textContent;
$magnet_without_tracker = explode("&tr=", $magnet)[0];
$magnet = $magnet_without_tracker . $this->opts->bittorrent_trackers;
} else {
$magnet = "";
}
$size = $centered[1]->textContent;
$seeders = $centered[3]->textContent;
$leechers = $centered[4]->textContent;
array_push($results,
array (
"name" => htmlspecialchars($name),
"seeders" => (int) $seeders,
"leechers" => (int) $leechers,
"magnet" => htmlspecialchars($magnet),
"size" => htmlspecialchars($size),
"source" => $this->SOURCE
)
);
}
return $results;
}
}
?>

View file

@ -0,0 +1,40 @@
<?php
class RutorRequest extends EngineRequest {
public function get_request_url() {
return "http://rutor.info/search/" . urlencode($this->query);
}
public function parse_results($response) {
$xpath = get_xpath($response);
$results = array();
if (!$xpath)
return $results;
foreach($xpath->query("//table/tr[@class='gai' or @class='tum']") as $result)
{
$name = $xpath->evaluate(".//td/a", $result)[2]->textContent;
$magnet = $xpath->evaluate(".//td/a/@href", $result)[1]->textContent;
$magnet_without_tracker = explode("&tr=", $magnet)[0];
$magnet = $magnet_without_tracker . $this->opts->bittorrent_trackers;
$td = $xpath->evaluate(".//td", $result);
$size = $td[count($td) == 5 ? 3 : 2]->textContent;
$seeders = $xpath->evaluate(".//span", $result)[0]->textContent;
$leechers = $xpath->evaluate(".//span", $result)[1]->textContent;
array_push($results,
array (
"name" => htmlspecialchars($name),
"seeders" => (int) filter_var($seeders, FILTER_SANITIZE_NUMBER_INT),
"leechers" => (int) filter_var($leechers, FILTER_SANITIZE_NUMBER_INT),
"magnet" => htmlspecialchars($magnet),
"size" => htmlspecialchars($size),
"source" => "rutor.info"
)
);
}
return $results;
}
}
?>

View file

@ -0,0 +1,6 @@
<?php
include_once "engines/bittorrent/nyaa.php";
class SukebeiRequest extends NyaaRequest {
public $SOURCE = "sukebei.nyaa.si";
}
?>

View file

@ -0,0 +1,45 @@
<?php
class PirateBayRequest extends EngineRequest {
public function get_request_url() {
return "https://apibay.org/q.php?q=" . urlencode($this->query);
}
public function parse_results($response) {
$results = array();
$json_response = json_decode($response, true);
if (empty($json_response))
{
return $results;
}
foreach ($json_response as $response)
{
$size = human_filesize($response["size"]);
$hash = $response["info_hash"];
$name = $response["name"];
$seeders = (int) $response["seeders"];
$leechers = (int) $response["leechers"];
$magnet = "magnet:?xt=urn:btih:$hash&dn=$name" . $this->opts->bittorrent_trackers;
if ($name == "No results returned")
break;
array_push($results,
array (
"size" => htmlspecialchars($size),
"name" => htmlspecialchars($name),
"seeders" => (int) htmlspecialchars($seeders),
"leechers" => (int) htmlspecialchars($leechers),
"magnet" => htmlspecialchars($magnet),
"source" => "thepiratebay.org"
)
);
}
return $results;
}
}
?>

View file

@ -0,0 +1,40 @@
<?php
class TorrentGalaxyRequest extends EngineRequest {
public function get_request_url() {
$query = urlencode($this->query);
return "https://torrentgalaxy.to/torrents.php?search=$query#results";
}
public function parse_results($response) {
$xpath = get_xpath($response);
$results = array();
if (!$xpath)
return $results;
foreach($xpath->query("//div[@class='tgxtablerow txlight']") as $result)
{
$name = $xpath->evaluate(".//div[contains(@class, 'clickable-row')]", $result)[0]->textContent;
$magnet = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/a/@href", $result)[1]->textContent;
$magnet_without_tracker = explode("&tr=", $magnet)[0];
$magnet = $magnet_without_tracker . $this->opts->bittorrent_trackers;
$size = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/span", $result)[0]->textContent;
$seeders = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/span/font", $result)[1]->textContent;
$leechers = $xpath->evaluate(".//div[@class='tgxtablecell collapsehide rounded txlight']/span/font", $result)[2]->textContent;
array_push($results,
array (
"name" => htmlspecialchars($name),
"seeders" => (int) $seeders,
"leechers" => (int) $leechers,
"magnet" => htmlspecialchars($magnet),
"size" => htmlspecialchars($size),
"source" => "torrentgalaxy.to"
)
);
}
return $results;
}
}
?>

View file

@ -0,0 +1,46 @@
<?php
class YTSRequest extends EngineRequest {
public function get_request_url() {
return "https://yts.mx/api/v2/list_movies.json?query_term=" . urlencode($this->query);
}
public function parse_results($response) {
$response = curl_multi_getcontent($this->ch);
$results = array();
$json_response = json_decode($response, true);
if ($json_response["status"] != "ok" || $json_response["data"]["movie_count"] == 0)
return $results;
foreach ($json_response["data"]["movies"] as $movie)
{
$name = $movie["title"];
$name_encoded = urlencode($name);
foreach ($movie["torrents"] as $torrent)
{
$hash = $torrent["hash"];
$seeders = $torrent["seeds"];
$leechers = $torrent["peers"];
$size = $torrent["size"];
$magnet = "magnet:?xt=urn:btih:$hash&dn=$name_encoded" . $this->opts->bittorrent_trackers;
array_push($results,
array (
"size" => htmlspecialchars($size),
"name" => htmlspecialchars($name),
"seeders" => htmlspecialchars($seeders),
"leechers" => htmlspecialchars($leechers),
"magnet" => htmlspecialchars($magnet),
"source" => "yts.mx"
)
);
}
}
return $results;
}
}
?>

View file

@ -0,0 +1,77 @@
<?php
require_once "misc/tools.php";
class VideoSearch extends EngineRequest {
protected $instance_url;
public function get_request_url() {
$this->instance_url = $this->opts->invidious_instance_for_video_results;
$query = urlencode($this->query);
return "$this->instance_url/api/v1/search?q=$query";
}
public function parse_results($response) {
$results = array();
$json_response = json_decode($response, true);
foreach ($json_response ?: [] as $response) {
if ($response["type"] == "video") {
$title = $response["title"];
$url = "https://youtube.com/watch?v=" . $response["videoId"];
$uploader = $response["author"];
$views = $response["viewCountText"];
$date = $response["publishedText"];
$lengthSeconds = $response["lengthSeconds"];
$thumbnail = $this->instance_url . "/vi/" . explode("/vi/" ,$response["videoThumbnails"][4]["url"])[1];
array_push($results,
array (
"title" => htmlspecialchars($title),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"uploader" => htmlspecialchars($uploader),
"views" => htmlspecialchars($views),
"date" => htmlspecialchars($date),
"lengthSeconds" => htmlspecialchars($lengthSeconds),
"thumbnail" => htmlspecialchars($thumbnail)
)
);
}
}
return $results;
}
public static function print_results($results, $opts) {
echo "<div class=\"text-result-container\">";
foreach ($results as $key => $result) {
if ($key == "results_source") continue;
$title = $result["title"] ?? '';
$url = $result["url"] ?? '';
$url = check_for_privacy_frontend($url, $opts);
$base_url = get_base_url($url);
$uploader = $result["uploader"] ?? '';
$views = $result["views"] ?? '';
$date = $result["date"] ?? '';
$lengthSeconds = $result["lengthSeconds"] ?? '';
$length = seconds_to_human_readable($lengthSeconds) ?? '';
$thumbnail = "https://i.ytimg.com/vi/" . htmlspecialchars(explode("=", $url)[1]) . "/mqdefault.jpg" ?? '';
echo "<div class=\"text-result-wrapper\">";
echo "<a rel=\"noreferer noopener\" href=\"$url\">";
echo "$base_url";
echo "<h2>$title</h2>";
echo "<img class=\"video-img\" src=\"image_proxy.php?url=$thumbnail\">";
echo "<br>";
echo "<span>$uploader - $date - $views views</span>";
echo "<br>";
echo "<span>$length</span>";
echo "</a>";
echo "</div>";
}
echo "</div>";
}
}
?>

View file

@ -0,0 +1,81 @@
<?php
class LibreXFallback extends EngineRequest {
protected $instance;
public function __construct($instance, $opts, $mh) {
$this->instance = $instance;
parent::__construct($opts, $mh);
}
public function get_request_url() {
return $this->instance . "api.php?" . opts_to_params($this->opts) . "&nfb=1";
}
public function parse_results($response) {
$response = json_decode($response, true);
if (!$response)
return array();
return array_values($response);
}
}
function load_instances($cooldowns) {
$instances_json = json_decode(file_get_contents("instances.json"), true);
if (empty($instances_json["instances"]))
return array();
$instances = array_map(fn($n) => $n['clearnet'], array_filter($instances_json['instances'], fn($n) => !is_null($n['clearnet'])));
$instances = array_filter($instances, fn($n) => !has_cooldown($n, $cooldowns));
shuffle($instances);
return $instances;
}
function get_librex_results($opts) {
if (!$opts->do_fallback)
return array();
$cooldowns = $opts->cooldowns;
$instances = load_instances($cooldowns);
$results = array();
$tries = 0;
do {
$tries++;
$instance = array_pop($instances);
if (!$instance)
break;
if (!(filter_var($instance, FILTER_VALIDATE_URL)))
continue;
if (parse_url($instance)["host"] == $_SERVER['HTTP_HOST'])
continue;
$librex_request = new LibreXFallback($instance, $opts, null);
$results = $librex_request->get_results();
if (!empty($results)) {
$results["results_source"] = parse_url($instance)["host"];
return $results;
}
// on fail then do this
$timeout = ($opts->request_cooldown ?? "1") * 60;
$cooldowns = set_cooldown($instance, $timeout, $cooldowns);
} while (!empty($instances));
return array(
"error" => array(
"message" => TEXTS["failure_fallback"]
)
);
}
?>

View file

@ -0,0 +1,37 @@
<?php
require_once "engines/text/text.php";
class OSMRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results = array();
// TODO allow the nominatim instance to be customised
$url = "https://nominatim.openstreetmap.org/search?q=$query_encoded&format=json";
return $url;
}
public function parse_results($response) {
$json_response = json_decode($response, true);
if (!$json_response)
return array();
$results = array();
foreach ($json_response as $item) {
array_push($results, array(
"title" => $item["name"],
"description" => $item["display_name"],
"url" => "https://www.openstreetmap.org/" . $item["osm_type"] . "/" . $item["osm_id"],
"base_url" => "www.openstreetmap.org"
));
}
return $results;
}
public static function print_results($results, $opts) {
TextSearch::print_results($results, $opts);
}
}
?>

55
engines/qwant/image.php Normal file
View file

@ -0,0 +1,55 @@
<?php
class QwantImageSearch extends EngineRequest {
public function get_request_url() {
$offset = $this->page * 5; // load 50 images per page
$query = urlencode($this->query);
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";
}
public function parse_results($response) {
$json = json_decode($response, true);
$results = array();
if ($json["status"] != "success")
return $results; // no results
$imgs = $json["data"]["result"]["items"];
$imgCount = $json["data"]["result"]["total"];
for ($i = 0; $i < $imgCount; $i++)
{
array_push($results,
array (
"thumbnail" => htmlspecialchars($imgs[$i]["thumbnail"]),
"alt" => htmlspecialchars($imgs[$i]["title"]),
"url" => htmlspecialchars($imgs[$i]["url"])
)
);
}
return $results;
}
public static function print_results($results, $opts) {
echo "<div class=\"image-result-container\">";
foreach($results as $result)
{
if (!$result
|| !array_key_exists("url", $result)
|| !array_key_exists("alt", $result))
continue;
$thumbnail = urlencode($result["thumbnail"]);
$alt = $result["alt"];
$url = $result["url"];
$url = check_for_privacy_frontend($url, $opts);
echo "<a title=\"$alt\" href=\"$url\" rel=\"noreferer noopener\" target=\"_blank\">";
echo "<img src=\"image_proxy.php?url=$thumbnail\">";
echo "</a>";
}
echo "</div>";
}
}
?>

View file

@ -0,0 +1,35 @@
<?php
class CurrencyRequest extends EngineRequest {
public function get_request_url() {
return "https://cdn.moneyconvert.net/api/latest.json";
}
public function parse_results($response) {
$split_query = explode(" ", $this->query);
$base_currency = strtoupper($split_query[1]);
$currency_to_convert = strtoupper($split_query[3]);
$amount_to_convert = floatval($split_query[0]);
$json_response = json_decode($response, true);
$rates = $json_response["rates"];
if (!array_key_exists($base_currency, $rates) || !array_key_exists($currency_to_convert, $rates))
return array();
$base_currency_response = $rates[$base_currency];
$currency_to_convert_response = $rates[$currency_to_convert];
$conversion_result = ($currency_to_convert_response / $base_currency_response) * $amount_to_convert;
$formatted_response = "$amount_to_convert $base_currency = $conversion_result $currency_to_convert";
$source = "https://moneyconvert.net/";
return array(
"special_response" => array(
"response" => htmlspecialchars($formatted_response),
"source" => $source
)
);
}
}
?>

View file

@ -0,0 +1,32 @@
<?php
class DefinitionRequest extends EngineRequest {
public function get_request_url() {
$split_query = explode(" ", $this->query);
$reversed_split_q = array_reverse($split_query);
$word_to_define = $reversed_split_q[1] == "define" ? $reversed_split_q[0] : $reversed_split_q[1];
return "https://api.dictionaryapi.dev/api/v2/entries/en/$word_to_define";
}
public function parse_results($response) {
$json_response = json_decode($response, true);
if (!$json_response)
return array();
if (!array_key_exists("title", $json_response))
{
$definition = $json_response[0]["meanings"][0]["definitions"][0]["definition"];
$source = "https://dictionaryapi.dev";
return array(
"special_response" => array(
"response" => htmlspecialchars($definition),
"source" => $source
)
);
}
}
}
?>

12
engines/special/ip.php Normal file
View file

@ -0,0 +1,12 @@
<?php
class IPRequest extends EngineRequest {
public function parse_results($response) {
return array(
"special_response" => array(
"response" => $_SERVER["REMOTE_ADDR"],
"source" => null
)
);
}
}
?>

View file

@ -0,0 +1,91 @@
<?php
function check_for_special_search($query) {
if (isset($_COOKIE["disable_special"]))
return 0;
$query_lower = strtolower($query);
$split_query = explode(" ", $query);
if (strpos($query_lower, "to") && count($split_query) >= 4) // currency
{
$amount_to_convert = floatval($split_query[0]);
if ($amount_to_convert != 0)
return 1;
}
else if ((strpos($query_lower, "mean") || str_starts_with($query_lower, "define")) && count($split_query) >= 2) // definition
{
return 2;
}
else if (strpos($query_lower, "my") !== false)
{
if (strpos($query_lower, "ip"))
{
return 3;
}
else if (strpos($query_lower, "user agent") || strpos($query_lower, "ua"))
{
return 4;
}
}
else if (strpos($query_lower, "weather") !== false)
{
return 5;
}
else if ($query_lower == "tor")
{
return 6;
}
else if (3 > count(explode(" ", $query))) // wikipedia
{
return 7;
}
return 0;
}
function get_special_search_request($opts, $mh) {
if ($opts->page != 0)
return null;
$special_search = check_for_special_search($opts->query);
$special_request = null;
$url = null;
if ($special_search == 0)
return null;
switch ($special_search) {
case 1:
require_once "engines/special/currency.php";
$special_request = new CurrencyRequest($opts, $mh);
break;
case 2:
require_once "engines/special/definition.php";
$special_request = new DefinitionRequest($opts, $mh);
break;
case 3:
require_once "engines/special/ip.php";
$special_request = new IPRequest($opts, $mh);
break;
case 4:
require_once "engines/special/user_agent.php";
$special_request = new UserAgentRequest($opts, $mh);
break;
case 5:
require_once "engines/special/weather.php";
$special_request = new WeatherRequest($opts, $mh);
break;
case 6:
require_once "engines/special/tor.php";
$special_request = new TorRequest($opts, $mh);
break;
case 7:
require_once "engines/special/wikipedia.php";
$special_request = new WikipediaRequest($opts, $mh);
break;
}
return $special_request;
}
?>

20
engines/special/tor.php Normal file
View file

@ -0,0 +1,20 @@
<?php
class TorRequest extends EngineRequest {
public function get_request_url() {
return "https://check.torproject.org/torbulkexitlist";
}
public function parse_results($response) {
$formatted_response = strpos($response, $_SERVER["REMOTE_ADDR"]) ? "It seems like you are using Tor" : "It seems like you are not using Tor";
$source = "https://check.torproject.org";
return array(
"special_response" => array(
"response" => $formatted_response,
"source" => $source
)
);
}
}
?>

View file

@ -0,0 +1,12 @@
<?php
class UserAgentRequest extends EngineRequest {
public function parse_results($response) {
return array(
"special_response" => array(
"response" => $_SERVER["HTTP_USER_AGENT"],
"source" => null
)
);
}
}
?>

View file

@ -0,0 +1,30 @@
<?php
class WeatherRequest extends EngineRequest {
public function get_request_url () {
return "https://wttr.in/@" . $_SERVER["REMOTE_ADDR"] . "?format=j1";
}
public function parse_results($response) {
$json_response = json_decode($response, true);
if (!$json_response)
return array();
$current_weather = $json_response["current_condition"][0];
$temp_c = $current_weather["temp_C"];
$temp_f = $current_weather["temp_F"];
$description = $current_weather["weatherDesc"][0]["value"];
$formatted_response = "$description - $temp_c °C | $temp_f °F";
$source = "https://wttr.in";
return array(
"special_response" => array(
"response" => htmlspecialchars($formatted_response),
"source" => $source
)
);
}
}
?>

View file

@ -0,0 +1,44 @@
<?php
class WikipediaRequest extends EngineRequest {
protected $wikipedia_domain;
public function get_request_url() {
$this->wikipedia_domain = "wikipedia.org";
$query_encoded = urlencode($this->query);
$languages = json_decode(file_get_contents("static/misc/languages.json"), true);
if (array_key_exists($this->opts->language, $languages))
$this->wikipedia_domain = $languages[$this->opts->language]["wikipedia"] . ".wikipedia.org";
return "https://$this->wikipedia_domain/w/api.php?format=json&action=query&prop=extracts%7Cpageimages&exintro&explaintext&redirects=1&pithumbsize=500&titles=$query_encoded";
}
public function parse_results($response) {
$json_response = json_decode($response, true);
if (!$json_response)
return array();
$first_page = array_values($json_response["query"]["pages"])[0];
if (array_key_exists("missing", $first_page))
return array();
$description = substr($first_page["extract"], 0, 250) . "...";
$source = "https://$this->wikipedia_domain/wiki/$this->query";
$response = array(
"special_response" => array(
"response" => htmlspecialchars($description),
"source" => $source
)
);
if (array_key_exists("thumbnail", $first_page)) {
$image_url = $first_page["thumbnail"]["source"];
$response["special_response"]["image"] = $image_url;
}
return $response;
}
}
?>

71
engines/text/brave.php Normal file
View file

@ -0,0 +1,71 @@
<?php
class BraveSearchRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results_language = $this->opts->language;
$number_of_results = $this->opts->number_of_results;
// TODO find the right parameters for the url
$url = "https://search.brave.com/search?q=$query_encoded&nfpr=1&spellcheck=0&start=$this->page";
if (3 > strlen($results_language) && 0 < strlen($results_language)) {
$url .= "&lr=lang_$results_language";
$url .= "&hl=$results_language";
}
if (3 > strlen($number_of_results) && 0 < strlen($number_of_results))
$url .= "&num=$number_of_results";
if (isset($_COOKIE["safe_search"]))
$url .= "&safe=medium";
return $url;
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
foreach($xpath->query("//div[@id='results']//div[contains(@class, 'snippet')]") as $result) {
$url = $xpath->evaluate(".//a[contains(@class, 'h')]//@href", $result)[0];
if ($url == null)
continue;
$url = $url->textContent;
if (!empty($results) && array_key_exists("url", end($results)) && end($results)["url"] == $url->textContent)
continue;
$title = $xpath->evaluate(".//a[contains(@class, 'h')]//div[contains(@class, 'url')]", $result)[0];
if ($title == null)
continue;
$title = $title->textContent;
$title = end(explode("", $title));
$description = ($xpath->evaluate(".//div[contains(@class, 'snippet-content')]//div[contains(@class, 'snippet-description')]", $result)[0] ?? null) ?->textContent ?? '';
array_push($results,
array (
"title" => htmlspecialchars($title),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => $description == null ?
TEXTS["result_no_description"] :
htmlspecialchars($description)
)
);
}
return $results;
}
}
?>

View file

@ -0,0 +1,64 @@
<?php
class DuckDuckGoRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results = array();
$domain = 'com';
$results_language = $this->opts->language;
$number_of_results = $this->opts->number_of_results;
$url = "https://html.duckduckgo.$domain/html/?q=$query_encoded&kd=-1&s=" . 3 * $this->page;
if (3 > strlen($results_language) && 0 < strlen($results_language))
$url .= "&lr=lang_$results_language";
if (3 > strlen($number_of_results) && 0 < strlen($number_of_results))
$url .= "&num=$number_of_results";
if (isset($_COOKIE["safe_search"]))
$url .= "&safe=medium";
return $url;
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
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) {
$url = $xpath->evaluate(".//h2[@class='result__title']//a/@href", $result)[0];
if ($url == null)
continue;
if (!empty($results)) { // filter duplicate results
if (end($results)["url"] == $url->textContent)
continue;
}
$url = $url->textContent;
$title = $xpath->evaluate(".//h2[@class='result__title']", $result)[0];
$description = $xpath->evaluate(".//a[@class='result__snippet']", $result)[0];
array_push($results,
array (
"title" => htmlspecialchars($title->textContent),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => $description == null ?
TEXTS["result_no_description"] :
htmlspecialchars($description->textContent)
)
);
}
return $results;
}
}
?>

63
engines/text/ecosia.php Normal file
View file

@ -0,0 +1,63 @@
<?php
class EcosiaSearchRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results_language = $this->opts->language;
$number_of_results = $this->opts->number_of_results;
// TODO figure out how to not autocorrect
$url = "https://www.ecosia.org/search?method=index&q=$query_encoded&p=$this->page";
if (!is_null($results_language))
$url .= "&lang=$results_language";
return $url;
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
foreach($xpath->query("//div[contains(@class, 'mainline__result-wrapper')]") as $result) {
$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];
if ($url == null)
continue;
$url = $url->textContent;
if (!empty($results) && array_key_exists("url", end($results)) && end($results)["url"] == $url->textContent)
continue;
$title = $xpath->evaluate(".//article//div[contains(@class, 'result__body')]//div[contains(@class, 'result__header')]//div[contains(@class, 'result__title')]//a//h2", $result)[0];
if ($title == null)
continue;
$title = $title->textContent;
$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;
array_push($results,
array (
"title" => htmlspecialchars($title),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => $description == null ?
TEXTS["result_no_description"] :
htmlspecialchars($description)
)
);
}
return $results;
}
}
?>

79
engines/text/google.php Normal file
View file

@ -0,0 +1,79 @@
<?php
class GoogleRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results = array();
$domain = $this->opts->google_domain;
$results_language = $this->opts->language;
$number_of_results = $this->opts->number_of_results;
$url = "https://www.google.$domain/search?q=$query_encoded&nfpr=1&start=$this->page";
if (3 > strlen($results_language) && 0 < strlen($results_language)) {
$url .= "&lr=lang_$results_language";
$url .= "&hl=$results_language";
}
if (3 > strlen($number_of_results) && 0 < strlen($number_of_results))
$url .= "&num=$number_of_results";
if (isset($_COOKIE["safe_search"]))
$url .= "&safe=medium";
return $url;
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
$didyoumean = $xpath->query(".//a[@class='gL9Hy']")[0];
if (!is_null($didyoumean))
array_push($results, array(
"did_you_mean" => $didyoumean->textContent
));
foreach($xpath->query("//div[@id='search']//div[contains(@class, 'g')]") as $result) {
$url = $xpath->evaluate(".//div[@class='yuRUbf']//a/@href", $result)[0];
if ($url == null)
continue;
if (!empty($results) && array_key_exists("url", end($results)) && end($results)["url"] == $url->textContent)
continue;
$url = $url->textContent;
$title = $xpath->evaluate(".//h3", $result)[0];
$description = $xpath->evaluate(".//div[contains(@class, 'VwiC3b')]", $result)[0];
array_push($results,
array (
"title" => htmlspecialchars($title->textContent),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => $description == null ?
TEXTS["result_no_description"] :
htmlspecialchars($description->textContent)
)
);
}
if (empty($results) && !str_contains($response, "Our systems have detected unusual traffic from your computer network.")) {
$results["error"] = array(
"message" => TEXTS["failure_empty"]
);
}
return $results;
}
}
?>

64
engines/text/mojeek.php Normal file
View file

@ -0,0 +1,64 @@
<?php
class MojeekSearchRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results_language = $this->opts->language;
$number_of_results = $this->opts->number_of_results;
// TODO figure out how to not autocorrect
$url = "https://www.mojeek.com/search?q=$query_encoded&p=$this->page";
// TODO language setting
if (!is_null($results_language))
$url .= "&lang=$results_language";
return $url;
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
foreach($xpath->query("//ul[contains(@class, 'results-standard')]//li") as $result) {
$url = $xpath->evaluate(".//h2//a[contains(@class, 'title')]//@href", $result)[0];
if ($url == null)
continue;
$url = $url->textContent;
if (!empty($results) && array_key_exists("url", $results) && end($results)["url"] == $url->textContent)
continue;
$title = $xpath->evaluate(".//h2//a[contains(@class, 'title')]", $result)[0];
if ($title == null)
continue;
$title = $title->textContent;
$description = ($xpath->evaluate(".//p[contains(@class, 's')]", $result)[0] ?? null) ?->textContent ?? '';
array_push($results,
array (
"title" => htmlspecialchars($title),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => $description == null ?
TEXTS["result_no_description"] :
htmlspecialchars($description)
)
);
}
return $results;
}
}
?>

220
engines/text/text.php Normal file
View file

@ -0,0 +1,220 @@
<?php
function get_engines() {
return array("google", "duckduckgo", "brave", "yandex", "ecosia", "mojeek");
}
class TextSearch extends EngineRequest {
protected $cache_key, $engine, $engines, $engine_request, $special_request;
public function __construct($opts, $mh) {
$this->engines = get_engines();
shuffle($this->engines);
$this->query = $opts->query;
$this->cache_key = "text:" . $this->query . "p" . $opts->page . "l" . $opts->language;
$this->page = $opts->page;
$this->opts = $opts;
$this->engine = $opts->engine;
$query_parts = explode(" ", $this->query);
$last_word_query = end($query_parts);
if (substr($this->query, 0, 1) == "!" || substr($last_word_query, 0, 1) == "!")
check_ddg_bang($this->query, $opts);
if (has_cached_results($this->cache_key))
return;
if ($this->engine == "auto")
$this->engine = $this->select_engine();
// no engine was selected
if (is_null($this->engine))
return;
// this only happens if a specific engine was selected, not if auto is used
if (has_cooldown($this->engine, $this->opts->cooldowns))
return;
$this->engine_request = $this->get_engine_request($this->engine, $opts, $mh);
if (is_null($this->engine_request))
return;
require_once "engines/special/special.php";
$this->special_request = get_special_search_request($opts, $mh);
}
private function select_engine() {
if (sizeof($this->engines) == 0)
return null;
$engine = array_pop($this->engines);
// if this engine is on cooldown, try again
if (!has_cooldown($engine, $this->opts->cooldowns))
return $engine;
return $this->select_engine();
}
private function get_engine_request($engine, $opts, $mh) {
if ($engine == "google") {
require_once "engines/text/google.php";
return new GoogleRequest($opts, $mh);
}
if ($engine == "duckduckgo") {
require_once "engines/text/duckduckgo.php";
return new DuckDuckGoRequest($opts, $mh);
}
if ($engine == "brave") {
require_once "engines/text/brave.php";
return new BraveSearchRequest($opts, $mh);
}
if ($engine == "yandex") {
require_once "engines/text/yandex.php";
return new YandexSearchRequest($opts, $mh);
}
if ($engine == "ecosia") {
require_once "engines/text/ecosia.php";
return new EcosiaSearchRequest($opts, $mh);
}
if ($engine == "mojeek") {
require_once "engines/text/mojeek.php";
return new MojeekSearchRequest($opts, $mh);
}
// if an invalid engine is selected, don't give any results
return null;
}
public function parse_results($response) {
if (has_cached_results($this->cache_key))
return fetch_cached_results($this->cache_key);
if (!isset($this->engine_request))
return array();
$results = $this->engine_request->get_results();
if (empty($results)) {
set_cooldown($this->engine, ($this->opts->request_cooldown ?? "1") * 60, $this->opts->cooldowns);
} else {
if ($this->special_request) {
$special_result = $this->special_request->get_results();
if ($special_result)
$results = array_merge(array($special_result), $results);
}
}
if (!empty($results)) {
$results["results_source"] = parse_url($this->engine_request->url)["host"];
store_cached_results($this->cache_key, $results, $this->opts->cache_time * 60);
}
return $results;
}
public static function print_results($results, $opts) {
if (empty($results)) {
echo "<div class=\"text-result-container\"><p>An error occured fetching results</p></div>";
return;
}
if (array_key_exists("error", $results)) {
echo "<div class=\"text-result-container\"><p>" . $results["error"]["message"] . "</p></div>";
return;
}
$special = $results[0];
if (array_key_exists("did_you_mean", $special)) {
$didyoumean = $special["did_you_mean"];
$new_url = "/search.php?q=" . urlencode($didyoumean);
echo "<p class=\"did-you-mean\">Did you mean ";
echo "<a href=\"$new_url\">$didyoumean</a>";
echo "?</p>";
}
if (array_key_exists("special_response", $special)) {
$response = $special["special_response"]["response"];
$source = $special["special_response"]["source"];
echo "<p class=\"special-result-container\">";
if (array_key_exists("image", $special["special_response"])) {
$image_url = $special["special_response"]["image"];
echo "<img src=\"image_proxy.php?url=$image_url\">";
}
echo $response;
if ($source) {
$source = check_for_privacy_frontend($source, $opts);
echo "<a href=\"$source\" rel=\"noreferer noopener\" target=\"_blank\">$source</a>";
}
echo "</p>";
}
echo "<div class=\"text-result-container\">";
foreach($results as $result) {
if (!is_array($result))
continue;
if (!array_key_exists("title", $result))
continue;
$title = $result["title"];
$url = $result["url"];
$url = check_for_privacy_frontend($url, $opts);
$base_url = get_base_url($url);
$description = $result["description"];
echo "<div class=\"text-result-wrapper\">";
echo "<a rel=\"noreferer noopener\" href=\"$url\">";
echo "$base_url";
echo "<h2>$title</h2>";
echo "</a>";
echo "<span>$description</span>";
echo "</div>";
}
echo "</div>";
}
}
function check_ddg_bang($query, $opts) {
$bangs_json = file_get_contents("static/misc/ddg_bang.json");
$bangs = json_decode($bangs_json, true);
if (substr($query, 0, 1) == "!")
$search_word = substr(explode(" ", $query)[0], 1);
else
$search_word = substr(end(explode(" ", $query)), 1);
$bang_url = null;
foreach($bangs as $bang) {
if ($bang["t"] == $search_word) {
$bang_url = $bang["u"];
break;
}
}
if ($bang_url) {
$bang_query_array = explode("!" . $search_word, $query);
$bang_query = trim(implode("", $bang_query_array));
$request_url = str_replace("{{{s}}}", str_replace('%26quot%3B','%22', urlencode($bang_query)), $bang_url);
header("Location: " . $request_url);
die();
}
}
?>

68
engines/text/yandex.php Normal file
View file

@ -0,0 +1,68 @@
<?php
class YandexSearchRequest extends EngineRequest {
public function get_request_url() {
$query_encoded = str_replace("%22", "\"", urlencode($this->query));
$results_language = $this->opts->language;
$number_of_results = $this->opts->number_of_results;
$url = "https://yandex.com/search?text=$query_encoded&nfpr=1&p=$this->page&noreask=1";
if (!is_null($results_language))
$url .= "&lang=$results_language";
return $url;
}
public function parse_results($response) {
$results = array();
$xpath = get_xpath($response);
if (!$xpath)
return $results;
$r = $xpath->query("//ul[@id='search-result']");
if (empty($r))
return array("error" => array(
"message" => TEXTS["failure_empty"]
));
foreach($xpath->query("//li[contains(@class, 'serp-item')]") as $result) {
$url = $xpath->evaluate(".//div//div//a[contains(@class, 'link')]//@href", $result)[0];
if ($url == null)
continue;
$url = $url->textContent;
if (!empty($results) && array_key_exists("url", $results) && end($results)["url"] == $url->textContent)
continue;
$title = $xpath->evaluate(".//div//div//a[contains(@class, 'link')]//h2[contains(@class, 'OrganicTitle-LinkText')]//span", $result)[0];
if ($title == null)
continue;
$title = $title->textContent;
$description = $xpath->evaluate(".//div[contains(@class, 'Organic-ContentWrapper')]//div[contains(@class, 'text-container')]//span", $result)[0]->textContent;
array_push($results,
array (
"title" => htmlspecialchars($title),
"url" => htmlspecialchars($url),
// base_url is to be removed in the future, see #47
"base_url" => htmlspecialchars(get_base_url($url)),
"description" => $description == null ?
TEXTS["result_no_description"] :
htmlspecialchars($description)
)
);
}
return $results;
}
}
?>