From ea755600ad038309f0980ef97310d13d32130870 Mon Sep 17 00:00:00 2001 From: vdbhb59 Date: Fri, 21 Aug 2026 11:57:23 +0530 Subject: [PATCH] Synced https://git.lolcat.ca/lolcat/4get/commit/bd3f5923fa527f54cb42bffcbf19b1a5df3568f7 https://git.lolcat.ca/lolcat/4get/commit/01d46386871b41a37fdbcbcf205f9915d192c198 https://git.lolcat.ca/lolcat/4get/commit/1af8ad3a2b76341838f08d0167481f5621f7747e https://git.lolcat.ca/lolcat/4get/commit/51b45ba53bb4bfee7b3d5583362d358d3862a4f5 https://git.lolcat.ca/lolcat/4get/commit/89b8be514727b37bda3c7b2acb527335b9bdbfbf --- lib/anubis.php | 13 +- lib/frontend.php | 4 - scraper/brave.php | 34 + scraper/qwant.php | 1102 ----------------------- scraper/startpage.php | 549 ++++++++---- settings.php | 20 - static/themes/Degoog dark.css | 1495 +++++++++++++++++++++++++++++++ static/themes/Degoog light.css | 1501 ++++++++++++++++++++++++++++++++ 8 files changed, 3434 insertions(+), 1284 deletions(-) delete mode 100644 scraper/qwant.php create mode 100644 static/themes/Degoog dark.css create mode 100644 static/themes/Degoog light.css diff --git a/lib/anubis.php b/lib/anubis.php index ec85ec7..7420343 100644 --- a/lib/anubis.php +++ b/lib/anubis.php @@ -45,15 +45,15 @@ class anubis{ if( !isset($script["challenge"]) || + !is_string($script["challenge"]["randomData"]) || !isset($script["rules"]["difficulty"]) || - !is_int($script["rules"]["difficulty"]) || - !is_string($script["challenge"]) + !is_int($script["rules"]["difficulty"]) ){ throw new Exception("Found invalid challenge data"); } - return $this->rape($script["challenge"], $script["rules"]["difficulty"]); + return $this->rape($script["challenge"]["id"], $script["challenge"]["randomData"], $script["rules"]["difficulty"]); } private function is_valid_hash($hash, $difficulty){ @@ -74,9 +74,10 @@ class anubis{ return true; } - public function rape($data, $difficulty = 5){ + public function rape($id, $data, $difficulty = 5){ $nonce = 0; + $microtime = microtime(true); while(true){ @@ -87,10 +88,12 @@ class anubis{ $hash_hex = bin2hex($hash_binary); return [ + "id" => $id, "response" => $hash_hex, //"data" => $data, //"difficulty" => $difficulty, - "nonce" => $nonce + "nonce" => $nonce, + "elapsedTime" => round((microtime(true) - $microtime) * 1000) ]; } diff --git a/lib/frontend.php b/lib/frontend.php index 604c628..4998adf 100644 --- a/lib/frontend.php +++ b/lib/frontend.php @@ -644,7 +644,6 @@ class frontend{ "google_cse" => "Google CSE", "yahoo_japan" => "Yahoo! JAPAN", "startpage" => "Startpage", - "qwant" => "Qwant", "yep" => "Yep", "mwmbl" => "Mwmbl", "mojeek" => "Mojeek", @@ -671,7 +670,6 @@ class frontend{ "google_cse" => "Google CSE", "yahoo_japan" => "Yahoo! JAPAN", "startpage" => "Startpage", - "qwant" => "Qwant", "naver" => "Naver", "baidu" => "Baidu", "solofield" => "Solofield", @@ -705,7 +703,6 @@ class frontend{ "google" => "Google", "yahoo_japan" => "Yahoo! JAPAN", "startpage" => "Startpage", - "qwant" => "Qwant", "naver" => "Naver", "baidu" => "Baidu", "coccoc" => "Cốc Cốc", @@ -724,7 +721,6 @@ class frontend{ "google" => "Google", "yahoo_japan" => "Yahoo! JAPAN", "startpage" => "Startpage", - "qwant" => "Qwant", //"mojeek" => "Mojeek", "baidu" => "Baidu" ] diff --git a/scraper/brave.php b/scraper/brave.php index 5573133..2efaefb 100644 --- a/scraper/brave.php +++ b/scraper/brave.php @@ -383,6 +383,8 @@ class brave{ // load html $this->fuckhtml->load($html); + $this->detect_captcha($html); + /* Get next page "token" */ @@ -1175,6 +1177,8 @@ class brave{ // load html $this->fuckhtml->load($html); + $this->detect_captcha($html); + // get npt $out["npt"] = $this->generatenextpagetoken( @@ -1270,6 +1274,9 @@ class brave{ fclose($handle);*/ $this->fuckhtml->load($html); + + $this->detect_captcha($html); + $json = $this->get_js(); foreach( @@ -1374,6 +1381,8 @@ class brave{ $this->fuckhtml->load($html); + $this->detect_captcha($html); + $out = [ "status" => "ok", "npt" => null, @@ -1853,4 +1862,29 @@ class brave{ ) ); } + + private function detect_captcha(){ + + $title = + $this->fuckhtml + ->getElementsByTagName( + "title" + ); + + if(count($title) === 0){ + + throw new Exception("Brave returned a malformed page"); + } + + $title = + strtolower( + $this->fuckhtml + ->getTextContent($title[0]) + ); + + if($title == "human verification"){ + + throw new Exception("Brave returned a Captcha"); + } + } } diff --git a/scraper/qwant.php b/scraper/qwant.php deleted file mode 100644 index 322182e..0000000 --- a/scraper/qwant.php +++ /dev/null @@ -1,1102 +0,0 @@ -backend = new backend("qwant"); - } - - public function getfilters($page){ - - $base = [ - "nsfw" => [ - "display" => "NSFW", - "option" => [ - "yes" => "Yes", - "maybe" => "Maybe", - "no" => "No" - ] - ], - "country" => [ - "display" => "Country", - "option" => [ - "en_US" => "United States", - "fr_FR" => "France", - "en_GB" => "Great Britain", - "de_DE" => "Germany", - "it_IT" => "Italy", - "es_AR" => "Argentina", - "en_AU" => "Australia", - "es_ES" => "Spain (es)", - "ca_ES" => "Spain (ca)", - "cs_CZ" => "Czech Republic", - "ro_RO" => "Romania", - "el_GR" => "Greece", - "zh_CN" => "China", - "zh_HK" => "Hong Kong", - "en_NZ" => "New Zealand", - "fr_FR" => "France", - "th_TH" => "Thailand", - "ko_KR" => "South Korea", - "sv_SE" => "Sweden", - "nb_NO" => "Norway", - "da_DK" => "Denmark", - "hu_HU" => "Hungary", - "et_EE" => "Estonia", - "es_MX" => "Mexico", - "es_CL" => "Chile", - "en_CA" => "Canada (en)", - "fr_CA" => "Canada (fr)", - "en_MY" => "Malaysia", - "bg_BG" => "Bulgaria", - "fi_FI" => "Finland", - "pl_PL" => "Poland", - "nl_NL" => "Netherlands", - "pt_PT" => "Portugal", - "de_CH" => "Switzerland (de)", - "fr_CH" => "Switzerland (fr)", - "it_CH" => "Switzerland (it)", - "de_AT" => "Austria", - "fr_BE" => "Belgium (fr)", - "nl_BE" => "Belgium (nl)", - "en_IE" => "Ireland", - "he_IL" => "Israel" - ] - ] - ]; - - switch($page){ - - case "web": - $base = array_merge( - $base, - [ - "time" => [ - "display" => "Time posted", - "option" => [ - "any" => "Any time", - "day" => "Past 24 hours", - "week" => "Past week", - "month" => "Past month" - ] - ], - "extendedsearch" => [ - // no display, wont show in interface - "option" => [ - "yes" => "Yes", - "no" => "No" - ] - ] - ] - ); - break; - - case "images": - $base = array_merge( - $base, - [ - "time" => [ - "display" => "Time posted", - "option" => [ - "any" => "Any time", - "day" => "Past 24 hours", - "week" => "Past week", - "month" => "Past month" - ] - ], - "size" => [ - "display" => "Size", - "option" => [ - "any" => "Any size", - "large" => "Large", - "medium" => "Medium", - "small" => "Small" - ] - ], - "color" => [ - "display" => "Color", - "option" => [ - "any" => "Any color", - "coloronly" => "Color only", - "monochrome" => "Monochrome", - "black" => "Black", - "brown" => "Brown", - "gray" => "Gray", - "white" => "White", - "yellow" => "Yellow", - "orange" => "Orange", - "red" => "Red", - "pink" => "Pink", - "purple" => "Purple", - "blue" => "Blue", - "teal" => "Teal", - "green" => "Green" - ] - ], - "imagetype" => [ - "display" => "Type", - "option" => [ - "any" => "Any type", - "animatedgif" => "Animated GIF", - "photo" => "Photograph", - "transparent" => "Transparent" - ] - ], - "license" => [ - "display" => "License", - "option" => [ - "any" => "Any license", - "share" => "Non-commercial reproduction and sharing", - "sharecommercially" => "Reproduction and sharing", - "modify" => "Non-commercial reproduction, sharing and modification", - "modifycommercially" => "Reproduction, sharing and modification", - "public" => "Public domain" - ] - ] - ] - ); - break; - - case "videos": - $base = array_merge( - $base, - [ - "order" => [ - "display" => "Order by", - "option" => [ - "relevance" => "Relevance", - "views" => "Views", - "date" => "Most recent", - ] - ], - "source" => [ - "display" => "Source", - "option" => [ - "any" => "Any source", - "youtube" => "YouTube", - "dailymotion" => "Dailymotion", - ] - ] - ] - ); - break; - - case "news": - $base = array_merge( - $base, - [ - "time" => [ - "display" => "Time posted", - "option" => [ - "any" => "Any time", - "hour" => "Less than 1 hour ago", - "day" => "Past 24 hours", - "week" => "Past week", - "month" => "Past month" - ] - ], - "order" => [ - "display" => "Order by", - "option" => [ - "relevance" => "Relevance", - "date" => "Most recent" - ] - ] - ] - ); - break; - } - - return $base; - } - - private function get($proxy, $url, $get = [], $is_post = false, $cookie = null){ - - $curlproc = curl_init(); - - if($is_post){ - - $headers = [ - "User-Agent: " . config::USER_AGENT, - "Accept: */*", - "Accept-Language: en-US,en;q=0.9", - "Accept-Encoding: gzip, deflate, br, zstd", - "Referer: https://www.qwant.com/", - "Content-type: application/x-www-form-urlencoded", - "Origin: https://www.qwant.com", - "DNT: 1", - "Sec-GPC: 1", - "Connection: keep-alive", - "Sec-Fetch-Dest: empty", - "Sec-Fetch-Mode: cors", - "Sec-Fetch-Site: same-site", - "Pragma: no-cache", - "Cache-Control: no-cache" - ]; - - curl_setopt($curlproc, CURLOPT_POST, true); - curl_setopt($curlproc, CURLOPT_POSTFIELDS, http_build_query($get)); - }else{ - - $headers = [ - "User-Agent: " . config::USER_AGENT, - "Accept: application/json, text/plain, */*", - "Accept-Language: en-US,en;q=0.9", - "Accept-Encoding: gzip, deflate, br, zstd", - "Referer: https://www.qwant.com/", - "Origin: https://www.qwant.com", - "DNT: 1", - ($cookie !== null ? "Cookie: {$cookie}" : ""), - "Sec-GPC: 1", - "Connection: keep-alive", - "Sec-Fetch-Dest: empty", - "Sec-Fetch-Mode: cors", - "Sec-Fetch-Site: same-site", - "Priority: u=4", - "Pragma: no-cache", - "Cache-Control: no-cache", - "TE: trailers" - ]; - - if($get !== []){ - $get = http_build_query($get); - $url .= "?" . $get; - } - } - - curl_setopt($curlproc, CURLOPT_URL, $url); - - curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding - curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers); - - // Bypass HTTP/2 check - curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); - - curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30); - curl_setopt($curlproc, CURLOPT_TIMEOUT, 30); - - $this->backend->assign_proxy($curlproc, $proxy); - - $data = curl_exec($curlproc); - - if(curl_errno($curlproc)){ - throw new Exception(curl_error($curlproc)); - } - - curl_close($curlproc); - - return $data; - } - - public function web($get){ - - if($get["npt"]){ - - // get next page data - [$params, $proxy] = $this->backend->get($get["npt"], "web"); - - $params = json_decode($params, true); - $cookie = $params["cookie"]; - unset($params["cookie"]); - - }else{ - - // get _GET data instead - $search = $get["s"]; - if(strlen($search) === 0){ - - throw new Exception("Search term is empty!"); - } - - if(strlen($search) > 2048){ - - throw new Exception("Search term is too long!"); - } - - $proxy = $this->backend->get_ip(); - - $params = [ - "q" => $search, - "freshness" => $get["time"], - "count" => 10, - "locale" => $get["country"], - "offset" => 0, - "device" => "desktop", - "tgp" => 3, - "safesearch" => 0, - "displayed" => "true", - "llm" => "false" - ]; - - switch($get["nsfw"]){ - - case "yes": $params["safesearch"] = 0; break; - case "maybe": $params["safesearch"] = 1; break; - case "no": $params["safesearch"] = 2; break; - } - - $cookie = null; - } - /* - $handle = fopen("scraper/qwant_web.json", "r"); - $json = fread($handle, filesize("scraper/qwant_web.json")); - fclose($handle);*/ - - $cookie = $this->fuck_datadome($proxy, $search); - - try{ - $json = - $this->get( - $proxy, - "https://api.qwant.com/v3/search/web", - $params, - false, - $cookie - ); - - }catch(Exception $error){ - - throw new Exception("Could not fetch JSON"); - } - - $json = json_decode($json, true); - - if($json === NULL){ - - throw new Exception("Failed to decode JSON"); - } - - if(isset($json["data"]["message"][0])){ - - throw new Exception("Server returned an error:\n" . $json["data"]["message"][0]); - } - - $out = [ - "status" => "ok", - "spelling" => [ - "type" => "no_correction", - "using" => null, - "correction" => null - ], - "npt" => null, - "answer" => [], - "web" => [], - "image" => [], - "video" => [], - "news" => [], - "related" => [] - ]; - - $this->detect_errors($json); - - if( - $json["status"] != "success" && - $json["data"]["error_code"] === 5 - ){ - - // no results - return $out; - } - - if(!isset($json["data"]["result"]["items"]["mainline"])){ - - throw new Exception("Server did not return a result object"); - } - - // data is OK, parse - - // get instant answer - if( - $get["extendedsearch"] == "yes" && - isset($json["data"]["result"]["items"]["sidebar"][0]["endpoint"]) - ){ - - try{ - $answer = - $this->get( - $proxy, - "https://api.qwant.com/v3" . - $json["data"]["result"]["items"]["sidebar"][0]["endpoint"], - [] - ); - - $answer = json_decode($answer, true); - - if( - $answer === null || - $answer["status"] != "success" || - $answer["data"]["result"] === null - ){ - - throw new Exception(); - } - - // parse answer - $out["answer"][] = [ - "title" => $answer["data"]["result"]["title"], - "description" => [ - [ - "type" => "text", - "value" => $this->trimdots($answer["data"]["result"]["description"]) - ] - ], - "url" => $answer["data"]["result"]["url"], - "thumb" => - $answer["data"]["result"]["thumbnail"]["landscape"] == null ? - null : - $this->unshitimage($answer["data"]["result"]["thumbnail"]["landscape"]), - "table" => [], - "sublink" => [] - ]; - - }catch(Exception $error){ - - // do nothing in case of failure - } - - } - - // get word correction - if(isset($json["data"]["query"]["queryContext"]["alteredQuery"])){ - - $out["spelling"] = [ - "type" => "including", - "using" => $json["data"]["query"]["queryContext"]["alteredQuery"], - "correction" => $json["data"]["query"]["queryContext"]["alterationOverrideQuery"] - ]; - } - - // check for next page - if($json["data"]["result"]["lastPage"] === false){ - - $params["offset"] = $params["offset"] + 10; - $params["cookie"] = $cookie; - - $out["npt"] = - $this->backend->store( - json_encode($params), - "web", - $proxy - ); - } - - // parse results - foreach($json["data"]["result"]["items"]["mainline"] as $item){ - - switch($item["type"]){ // ignores ads - - case "web": - - $first_iteration = true; - foreach($item["items"] as $result){ - - if(isset($result["thumbnailUrl"])){ - - $thumb = [ - "url" => $this->unshitimage($result["thumbnailUrl"]), - "ratio" => "16:9" - ]; - }else{ - - $thumb = [ - "url" => null, - "ratio" => null - ]; - } - - $sublinks = []; - if(isset($result["links"])){ - - foreach($result["links"] as $link){ - - $sublinks[] = [ - "title" => $this->trimdots($link["title"]), - "date" => null, - "description" => isset($link["desc"]) ? $this->trimdots($link["desc"]) : null, - "url" => $link["url"] - ]; - } - } - - // detect gibberish results - if( - $first_iteration && - !isset($result["urlPingSuffix"]) - ){ - - throw new Exception("Qwant returned gibberish results"); - } - - $out["web"][] = [ - "title" => $this->trimdots($result["title"]), - "description" => $this->trimdots($result["desc"]), - "url" => $result["url"], - "date" => null, - "type" => "web", - "thumb" => $thumb, - "sublink" => $sublinks, - "table" => [] - ]; - - $first_iteration = false; - } - break; - - case "images": - foreach($item["items"] as $image){ - - $out["image"][] = [ - "title" => $image["title"], - "source" => [ - [ - "url" => $image["media"], - "width" => (int)$image["width"], - "height" => (int)$image["height"] - ], - [ - "url" => $this->unshitimage($image["thumbnail"]), - "width" => $image["thumb_width"], - "height" => $image["thumb_height"] - ] - ], - "url" => $image["url"] - ]; - } - break; - - case "videos": - foreach($item["items"] as $video){ - - $out["video"][] = [ - "title" => $video["title"], - "description" => null, - "date" => (int)$video["date"], - "duration" => $video["duration"] === null ? null : $video["duration"] / 1000, - "views" => null, - "thumb" => - $video["thumbnail"] === null ? - [ - "url" => null, - "ratio" => null, - ] : - [ - "url" => $this->unshitimage($video["thumbnail"]), - "ratio" => "16:9", - ], - "url" => $video["url"] - ]; - } - break; - - case "related_searches": - foreach($item["items"] as $related){ - - $out["related"][] = $related["text"]; - } - break; - } - } - - return $out; - } - - - public function image($get){ - - if($get["npt"]){ - - [$params, $proxy] = - $this->backend->get( - $get["npt"], - "images" - ); - - $params = json_decode($params, true); - $cookie = $params["cookie"]; - unset($params["cookie"]); - - }else{ - - $search = $get["s"]; - - if(strlen($search) === 0){ - - throw new Exception("Search term is empty!"); - } - - $proxy = $this->backend->get_ip(); - - $params = [ - "t" => "images", - "q" => $search, - "count" => 125, - "locale" => $get["country"], - "offset" => 0, // increment by 125 - "device" => "desktop", - "tgp" => 3 - ]; - - if($get["time"] != "any"){ - - $params["freshness"] = $get["time"]; - } - - foreach(["size", "color", "imagetype", "license"] as $p){ - - if($get[$p] != "any"){ - - $params[$p] = $get[$p]; - } - } - - switch($get["nsfw"]){ - - case "yes": $params["safesearch"] = 0; break; - case "maybe": $params["safesearch"] = 1; break; - case "no": $params["safesearch"] = 2; break; - } - - $cookie = null; - } - - $cookie = $this->fuck_datadome($proxy, $search); - - try{ - $json = $this->get( - $proxy, - "https://api.qwant.com/v3/search/images", - $params, - false, - $cookie - ); - }catch(Exception $err){ - - throw new Exception("Failed to get JSON"); - } - - /* - $handle = fopen("scraper/yandex.json", "r"); - $json = fread($handle, filesize("scraper/yandex.json")); - fclose($handle);*/ - - $json = json_decode($json, true); - - if($json === null){ - - throw new Exception("Failed to decode JSON"); - } - - $this->detect_errors($json); - - if(isset($json["data"]["result"]["items"]["mainline"])){ - - throw new Exception("Qwant returned gibberish results"); - } - - $out = [ - "status" => "ok", - "npt" => null, - "image" => [] - ]; - - if($json["data"]["result"]["lastPage"] === false){ - - $params["offset"] = $params["offset"] + 125; - - $out["npt"] = $this->backend->store( - json_encode($params), - "images", - $proxy - ); - } - - foreach($json["data"]["result"]["items"] as $image){ - - $out["image"][] = [ - "title" => $this->trimdots($image["title"]), - "source" => [ - [ - "url" => $image["media"], - "width" => $image["width"], - "height" => $image["height"] - ], - [ - "url" => $this->unshitimage($image["thumbnail"]), - "width" => $image["thumb_width"], - "height" => $image["thumb_height"] - ] - ], - "url" => $image["url"] - ]; - } - - return $out; - } - - public function video($get){ - - $search = $get["s"]; - if(strlen($search) === 0){ - - throw new Exception("Search term is empty!"); - } - - $params = [ - "t" => "videos", - "q" => $search, - "count" => 50, - "locale" => $get["country"], - "offset" => 0, // dont implement pagination - "device" => "desktop", - "tgp" => 3 - ]; - - switch($get["nsfw"]){ - - case "yes": $params["safesearch"] = 0; break; - case "maybe": $params["safesearch"] = 1; break; - case "no": $params["safesearch"] = 2; break; - } - - try{ - $json = - $this->get( - $this->backend->get_ip(), - "https://api.qwant.com/v3/search/videos", - $params - ); - }catch(Exception $error){ - - throw new Exception("Could not fetch JSON"); - } - - /* - $handle = fopen("scraper/yandex-video.json", "r"); - $json = fread($handle, filesize("scraper/yandex-video.json")); - fclose($handle); - */ - - $json = json_decode($json, true); - - if($json === null){ - - throw new Exception("Could not parse JSON"); - } - - $this->detect_errors($json); - - if(isset($json["data"]["result"]["items"]["mainline"])){ - - throw new Exception("Qwant returned gibberish results"); - } - - $out = [ - "status" => "ok", - "npt" => null, - "video" => [], - "author" => [], - "livestream" => [], - "playlist" => [], - "reel" => [] - ]; - - foreach($json["data"]["result"]["items"] as $video){ - - if(empty($video["thumbnail"])){ - - $thumb = [ - "url" => null, - "ratio" => null - ]; - }else{ - - $thumb = [ - "url" => $this->unshitimage($video["thumbnail"]), - "ratio" => "16:9" - ]; - } - - $duration = (int)$video["duration"]; - - $out["video"][] = [ - "title" => $video["title"], - "description" => $this->limitstrlen($video["desc"]), - "author" => [ - "name" => $video["channel"], - "url" => null, - "avatar" => null - ], - "date" => (int)$video["date"], - "duration" => $duration === 0 ? null : $duration, - "views" => null, - "thumb" => $thumb, - "url" => preg_replace("/\?syndication=.+/", "", $video["url"]) - ]; - } - - return $out; - } - - public function news($get){ - - $search = $get["s"]; - if(strlen($search) === 0){ - - throw new Exception("Search term is empty!"); - } - - $params = [ - "t" => "news", - "q" => $search, - "count" => 50, - "locale" => $get["country"], - "offset" => 0, // dont implement pagination - "device" => "desktop", - "tgp" => 3 - ]; - - switch($get["nsfw"]){ - - case "yes": $params["safesearch"] = 0; break; - case "maybe": $params["safesearch"] = 1; break; - case "no": $params["safesearch"] = 2; break; - } - - try{ - $json = - $this->get( - $this->backend->get_ip(), - "https://api.qwant.com/v3/search/news", - $params - ); - }catch(Exception $error){ - - throw new Exception("Could not fetch JSON"); - } - - /* - $handle = fopen("scraper/yandex-video.json", "r"); - $json = fread($handle, filesize("scraper/yandex-video.json")); - fclose($handle); - */ - - $json = json_decode($json, true); - - if($json === null){ - - throw new Exception("Could not parse JSON"); - } - - $this->detect_errors($json); - - if(isset($json["data"]["result"]["items"]["mainline"])){ - - throw new Exception("Qwant returned gibberish results"); - } - - $out = [ - "status" => "ok", - "npt" => null, - "news" => [] - ]; - - foreach($json["data"]["result"]["items"] as $news){ - - if(empty($news["media"][0]["pict_big"]["url"])){ - - $thumb = [ - "url" => null, - "ratio" => null - ]; - }else{ - - $thumb = [ - "url" => $this->unshitimage($news["media"][0]["pict_big"]["url"]), - "ratio" => "16:9" - ]; - } - - $out["news"][] = [ - "title" => $news["title"], - "author" => $news["press_name"], - "description" => $this->trimdots($news["desc"]), - "date" => (int)$news["date"], - "thumb" => $thumb, - "url" => $news["url"] - ]; - } - - return $out; - } - - private function detect_errors($json){ - - if( - isset($json["status"]) && - $json["status"] == "error" - ){ - - if(isset($json["data"]["error_data"]["captchaUrl"])){ - - throw new Exception("Qwant returned a captcha"); - }elseif(isset($json["data"]["error_data"]["error_code"])){ - - throw new Exception( - "Qwant returned an API error: " . - $json["data"]["error_data"]["error_code"] - ); - } - - throw new Exception("Qwant returned an API error"); - } - - if( - isset($json["url"]) && - preg_match( - '/captcha/i', - $json["url"] - ) - ){ - - throw new Exception("Qwant returned a captcha redirect"); - } - } - - private function limitstrlen($text){ - - return explode("\n", wordwrap($text, 300, "\n"))[0]; - } - - private function trimdots($text){ - - return trim($text, ". "); - } - - private function unshitimage($url){ - - // https://s1.qwant.com/thumbr/0x0/8/d/f6de4deb2c2b12f55d8bdcaae576f9f62fd58a05ec0feeac117b354d1bf5c2/th.jpg?u=https%3A%2F%2Fwww.bing.com%2Fth%3Fid%3DOIP.vvDWsagzxjoKKP_rOqhwrQAAAA%26w%3D160%26h%3D160%26c%3D7%26pid%3D5.1&q=0&b=1&p=0&a=0 - // https://s2.qwant.com/thumbr/474x289/7/f/412d13b3fe3a03eb2b89633c8e88b609b7d0b93cdd9a5e52db3c663e41e65e/th.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%3Fid%3DOIP.9Tm_Eo6m7V7ltN19mxduDgHaEh%26pid%3DApi&q=0&b=1&p=0&a=0 - - $image = parse_url($url); - - if( - !isset($image["host"]) || - !isset($image["query"]) - ){ - - // cant do anything - return $url; - } - - $id = null; - - if( - preg_match( - '/s[0-9]+\.qwant\.com$/', - $image["host"] - ) - ){ - - parse_str($image["query"], $str); - - // we're being served a proxy URL - if(isset($str["u"])){ - - $bing_url = $str["u"]; - }else{ - - // give up - return $url; - } - } - - // parse bing URL - $id = null; - $image = parse_url($bing_url); - - if(isset($image["query"])){ - - parse_str($image["query"], $str); - - if(isset($str["id"])){ - - $id = $str["id"]; - } - } - - if($id === null){ - - $id = explode("/th/id/", $image["path"], 2); - - if(count($id) !== 2){ - - // malformed - return $url; - } - - $id = $id[1]; - } - - if(is_array($id)){ - - // fuck off, let proxy.php deal with it - return $url; - } - - return "https://" . $image["host"] . "/th?id=" . rawurlencode($id); - } - - private function fuck_datadome($proxy, $search){ - - if($cookie === null){ - // get datadome bullshit - // yeah go ahead and patch this you code monkey - - // ... you know, instead of fixing this shit, can you make the second page of results return more than 1 result? - // i search shit like "higurashi" and i only get 1 result and then just a bunch of related searches ad finitum - // also, stop pretending you guys have your own index, this is clearly all bing results - try{ - $datadome = - $this->get( - $proxy, - "https://dd.qwant.com/js/", - [ - "jspl" => "-oirHDeP7G3G9LXY3rhxGDYf6t_ZlYPzaiL2dRhQ34uNUc4dO2Td0SmmKu_VJp7-jrAIoOMrTqKgCtV4cPzntMyNuOYN7QQ1q0Fi2c-679vYpJr9ub5dRFXMhCMQmjZsOWXUa8yafIXf85UA5Goct-n4hVT5dFfBGM-dE6gwZEFkHQ1j1195sLybZFTNA1IfX26MBMxlVHoyT-aHtkd6YjGXbvOPMMsObVHDa_Cf4BmApRGo-eehsAttsCqDRwkl0M2E1b0OXCIwMVnCK-qWZ81MGaJGjqxdj_LaxhAjE9JI_4oNedAg4Yj4m_vkQBKdmWJT_bTUsllAZXqX5IxYQHJ8WvlknOTr6sR3wgoSOa_JNVHkUQxqXBbQcoDdy6VciL5rIqwWl4Y2caVYzeXa8YNXxwsfrPDegH3dQ6vOiIiCDT3bexkcS_y7GAioPPv9qDu7M9y4sI8t0VkPgMbWY6MIu5u9WAv6lEiKIYrtlyU-iiXnNdkd9EnpC1TD8_s12PtCuLg5bGgLVmI1dw8VXVZxmVyFvfBtuqXkzM-kZOyv-YIRcf-wPY8DUtnHox1d6t64oYhGv-pYV5dmZ0rGs99LC6ThiifeF_oHz5ZK46Ms68WsiAjc1HLAbFPFxWw8X6lyLlpT5aHgFdDBeCTExKXyItlc9PBxkgK8UnXHQFiWX3HTen8rziwkI31AbkMU7xGP6cvuwuLG7MA7sH3yLF1uimQlhr6EuTrImWQNV7X81DqyZ0OE7I-uT9bp_xISa5it-6AJpFtGcwz04_OuO2zvZazBeiKJI5Fw0X2r3RHTHdA29SXqTIe4y2LRAAp0MSAZUIJAMIbbrz9BG-3D5BHfQUA1Bbuklxv-tNWiuanWbbTmAiuxTAo9P-D2ZMrWHwdN8sCb3nINLur38z1xviSs2qEEjKo9kzQIWoHgwm_amV-jjDtNBXwpCQnoaqt3FfL8e59ecYWVg61Gnl3YCwq6rubFdkIv6UStcWSlj4pRFlFZSjQxzznvGTAc5Nz1JJWVeBHJItei4wqDUYEw2BNTS_fU9keV3LFyKDH_x248AxHLiXt583DhZZwPAKlpmMo0E7odzfOgVQGM5b9l64NL3RQRq8cDxI8KgmOFnPHdI7Myq1A3nW1uoBB8m9_GR_9orboKl5tAQrT0VTMRd6JyNwOOQ71ZoQpMwsD5Xd9YDZd618TNE9dA38h9OyBco_-hwKCYayyEqib1atLLBO_gnO4InR471AklOokDOKx1oY_xgNhlFrZskE4nQQOqeCQ_47oysob9s3kblA8UqDrL2vM3i2KPLWwlQzMAPrQAIv7Amp6rO-wYwBQdbP7XghYympxx9-hNxc1Duq-nS4R3pjPtZg3LoCvG1N_0JLf9jfPBqw_rdPM6DnjEOvpCdvrfv4wBlEpgqbaRQCZBPEz-O5ZlY24OyPvaNX0krPeXkbi-KYuwZSAbfW0zsksXb8cbRmz6tvh25IRYP1XNDPVd8NFrYOdDH9GzhV5MYZziYMurQlVwgumGLz85Y6qTHZU2D5qkMH7o5C5bzptReO96889oxkReS_LDhzdxH0tAQ5a9GgETJTHAXJ9Lt7X037i0qQlatbPLizOl9fo4vUBGJ7w-uSUmIzg1yIwOo8xhLu39WghXfoJ_EX5ptNi7arpAo11UoJIGEgWgq7bS40q2dtnG5HYpp9ef37iFgztM7PV5dlJf_W-n9uXJyCk2nhxZeuGiIGhMTzjP_8oeyiMFelmoyLlqK8wOs6hvWqgDD4NwtZc-XBkEAKsxuPrirb_sszp0z6BWsh3D6fBxAW_lTrNVkxZ1EBtoEN72HvO1aeX8PVuJP3CKwkkUcAhJ5Zvajo8t4Ai8ave2MTZul4QhDIe7ZALJMo71u-hNSj8YG9JLPqF1jcnbkl8ykt_XMMxX8wSZ_AfTsfmAuOWBfEzxv8Fl2Ne-oxcEtfuYnYNJIjQDHXXdtYbWKBezE41wonQu7FWh3wSSF3DDm9EocWMsUZ610nrIviOFdtTqjrY7KnOZp0hS9Hm2V04z-06Db-To71L5O5poYKuOlDAP_ULfW0hgMgYaPnt4Nive9gtue6ZQ5yNFylKM11zCWcF8vmuEPGx4NIZjvNFB-QSkdxZtBTQOgO6r4BqHJNZIDqiD9A6PyMemVtQEo-JhooX6bwHFHx6C84r-kKq1lg0RaDelbu1h6347kocxOczdgHWlagKPFrUvHpPHvCZeFmzLLmPWbDEpUhhfkbEZlNe47svO5WN1TYZMCAlE0g01pe6b_NB9-twQ4FJB1SkUQRA4o5DxiIHSgXBBfw1X98XPEKcJgCU2zRbYuU69mvyPcsxoPsLo8Cl2OAXj2U1tcLUxVhR_vu3I0SG_Ee8hxBp5b6QbpIu2sE0bgpqfi88XqUXf3qvxGbkoZDZ8dPvEvCVmpDZOOGJY5Db4MnpKBByW79lUnpbLCbsVOoxYu8SVmVQ63eGY9yBlACuWxlXA_PhCpnKYwMppjxKvKqalNJGMMg5WQ8cvhsIftE5wgaj21n0A15GwRO1WgUcsO_dLe1MqO88hTHWHV0Q3gNut3sdsmVr_n-0SniLtVAaw4IEtDZWNUfhpaoC7iyKxoJ86t8r697EYPlWUu4nTQhb3TOXAjHF22rFsJJMldKHVxjxjd086ky3OY3L_7cbjVof60-y4gTJdumw5hch-mhaT93nVnaTuPLGCAC48dZSgOitW8Dw62mPS05fTPAfKUY1HpyIWr9ACk7U2KVAijpzFB-bXvqJeCyBeUHr4cB61K2XxTmkNA8Oo9RC32TeYDrThFzoUyYZ_D6zPe1lLtQaFsPRvDBSsYCcr5fcABlST4DyUgnkgqtx-0-D1QEwtWiH3xiHLQGa82ldjqNIXL9LqwxYoCRBKuljHyqEylZD26A9Jp6SL9rk_Ol6TJlYa3zx44PHyMoA3Coy-HIZW5NMwVKdV7egUDDOchvuFnc1Zv2QpSFKc0JY8lYFtSm1HZFwjq7hf2hBqkU0Xneh8AT4Ty9QVb4mJ8rpzc8m_NBZNrpZVE-pIzmv2yGjnRwKSrwsjat7b2iY34UPFqGBlDk5FX6EfRlN9bq-nztWdextLc47ndr1gxsjC-vr6tbdXl5FxMVhbsnLH80wOC6hHubxCysr5C7Ft59s3MLhVQCc3IIOPMBhLOjjjVsu6K8kAJvvamIpj-iYUQj9T8JztAqfobytzbiwPsQ3mywUGkJpZxTjXjqkKdT6yfFdW5ksR8p3VlKGAAKft9TKKcc5IgErmIRtiZF4hGhB45nwIO5XHA59V94QuV3b0oJrleuDCBj0iDFqWYpsUVHyKl3RjrO86Yq1IbNRwXIIMrlsVYos9CS1I75k9kW4yQC6ni3a2ZitDkiEB-SoQuTlBsvfTs892Wt5q3CWH6_7srZO6l2QEAlWqyQp_P4i8j8uiEbZksGsgghVIgOkriblDP9ml7PdkpRX7S3OpY7jjQgTNR8XHaZ-SEw0-zPiqIIt5VioFK8JljPn-nMIDx-ew_vTDKqmKEOF39ezQcc5oQNTQNUvTAe8qCbK4ghS3fagItezoHtB5zm3qwhx9Iu6JMJy0UhywUZBaInY6NokiVZGDgtoqlVjOk7K4299MVZgsOr2P9QHRDo41lYnCeWWFUf50d-ouEOUO_ngteDpgG1DfV64eycrUOWAnpfI8XOUZ7mxS89SqJriw3T3tcljowHiOFw9QjYsjJi0vuHL5jfoBZVdYAUH6KiLfIScO-KgqitdUTpFwks8Qk7xfd2HcEfArlzSiAt3Hmb2Ty-5mhvtQj2RbPehgoI7gqa60b6Qffth-5tbIjXoKlZCQsaKVMAggF9C3w0Rv5uJ3ExCTGsfKO9IVOf--AfxaA8cNoO0W3NTaMH2iWftY0O1-1x3VJEcerp2v9BcZ0iIL3VFJ9eyOPYNHNwTYrI3S6OrVZxuQXFbu2E_Us32wxYqXvnMx-DEOk7bUUFIanL69Cbn_mR_9Wm1sFixBQ15diJoe1Oyl3XWHWL6_YF4XnHzZlMWySt3Awdeynwu99Ter8WMGXl-6K30novKRWC9jDVOfAtxPvXBkkRtpm8nnJT8fwkmIQoWOEPT2MCg3VYzzHzVEH-AueuRiCZj_o7L-fRqbtFrWPyVnGuetUYS5u0D3n7Rr6kCVqWhLfwjqAwV_calzIQs8-J0-AJe6GGaaONPYfXwXjYan0Sl11CS64lXvx7i3MgeOZuH210T6g6hj54dSLw0upO1L8k0FGli3I5OhLMekeGMLT_N2akyHktjbz8Odpk2ms0pJP_PdqvUegjXgH81F5lr40HAXZs7jfC9ytgcWaEjwlmnoFHSCTdlCIgJqEBzItXiz6ilk5zP35VwxdQTNoms7Meqg-ZrgRuLRaqXNtpQT9qWBmiFP6zoYLwymqytZ_P1SV9DCM-KIZQ2Cq-ew8PsYSrwl8GmfuegoKEMBiON1I4mC9ChDgzJmbo5P9te60T0URS5g9ExiQuSo-JXs0MKC57LLiNo3MPZvqR1wRJOTg0_FqS4JFxzPwN5OqhBzfs25f6kK-axf0wWp-LetUx3U1CTNaxfbCTK5tb8KnLOIs3lDwJw5H5wIIRgXnfNLBJT8A2srfXkdpA", - "eventCounters" => [], - "jsType" => "ch", - "cid" => "3i4vPGHCl74uIBumWaxF2a0PjFkyCJxcSvFnn~heX7keA6sGHgRbnzSYcMr_RzJFlfeeBdg9HeufzFPxnvSHbyWowSvjm6mNCSPhkx6MwDqptANoj93ZgGVbrzxGtcYe", - "ddk" => "78B13B7513D180B7AB6D6FF9EB0A51", - "Referer" => "https://www.qwant.com/?q={$search}&t=web", - "request" => "/?q={$search}&t=web", - "responsePage" => "origin", - "ddv" => "5.8.0" - ], - true - ); - }catch(Exception $error){ - - throw new Exception("Failed to fetch JS challenge token"); - } - - $datadome = json_decode($datadome, true); - if($datadome === null){ - - throw new Exception("Failed to decode JS challenge JSON"); - } - - if(!isset($datadome["cookie"])){ - - throw new Exception("Failed to get cookie from JS challenge endpoint"); - } - - $cookie = explode(";", $datadome["cookie"])[0]; - } - - return $cookie; - } -} diff --git a/scraper/startpage.php b/scraper/startpage.php index 2dd3cbc..2d94f3f 100644 --- a/scraper/startpage.php +++ b/scraper/startpage.php @@ -9,6 +9,10 @@ class startpage{ include "lib/fuckhtml.php"; $this->fuckhtml = new fuckhtml(); + + include "lib/anubis.php"; + $this->anubis = new anubis(); + $this->anubis_error = "all gucci"; } public function getfilters($page){ @@ -244,7 +248,7 @@ class startpage{ } } - private function get($proxy, $url, $get = [], $post = false, $is_xhr = false){ + private function get($proxy, $url, $get = [], $post = false, $is_xhr = false, $return_cookies = false, $passed_cookies = []){ $curlproc = curl_init(); @@ -259,6 +263,27 @@ class startpage{ $url .= "?" . $get; } + if($return_cookies){ + + $cookies = []; + curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies){ + + $length = strlen($header); + + $header = explode(":", $header, 2); + + if(trim(strtolower($header[0])) == "set-cookie"){ + + $cookie_tmp = explode("=", trim($header[1]), 2); + + $cookies[trim($cookie_tmp[0])] = + explode(";", $cookie_tmp[1], 2)[0]; + } + + return $length; + }); + } + curl_setopt($curlproc, CURLOPT_URL, $url); // http2 bypass @@ -266,6 +291,15 @@ class startpage{ curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding + $passed_cookies["preferences"] = "date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius"; + + foreach($passed_cookies as $name => $val){ + + $header_cookies[] = "$name=$val"; + } + + $header_cookies = rtrim(implode("; ", $header_cookies)); + if($is_xhr === true){ curl_setopt($curlproc, CURLOPT_HTTPHEADER, @@ -279,7 +313,7 @@ class startpage{ "Origin: https://www.startpage.com/", "DNT: 1", "Connection: keep-alive", - "Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius", + "Cookie: $header_cookies", "Sec-Fetch-Dest: empty", "Sec-Fetch-Mode: cors", "Sec-Fetch-Site: same-origin", @@ -298,7 +332,7 @@ class startpage{ "Content-Length: " . strlen($get), "DNT: 1", "Connection: keep-alive", - "Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius", + "Cookie: $header_cookies", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", @@ -316,7 +350,7 @@ class startpage{ "Accept-Encoding: gzip", "DNT: 1", "Connection: keep-alive", - "Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius", + "Cookie: $header_cookies", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: none", @@ -342,6 +376,15 @@ class startpage{ } curl_close($curlproc); + + if($return_cookies){ + + return [ + "cookies" => $cookies, + "data" => $data + ]; + } + return $data; } @@ -349,14 +392,22 @@ class startpage{ if($get["npt"]){ + $this->anubis_error = "Not ran on pagination"; [$post, $proxy] = $this->backend->get($get["npt"], "web"); + $post = json_decode($post, true); + $query = $post["query"]; + $cookies = $post["cookies"]; + try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $post, - true + $query, + true, + false, + false, + $cookies ); }catch(Exception $error){ @@ -398,7 +449,11 @@ class startpage{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $params + $params, + false, + false, + true, + [] ); }catch(Exception $error){ @@ -406,12 +461,41 @@ class startpage{ } //$html = file_get_contents("scraper/startpage.html"); + + // detect captcha on that page + $this->detect_captcha($html["data"]); + + // solve anubis if need be + $cookies = $this->solve_anubis($proxy, $html); + + if($cookies === false){ + + $cookies = []; + $html = $html["data"]; + }else{ + + try{ + $html = $this->get( + $proxy, + "https://www.startpage.com/sp/search", + $params, + false, + false, + false, + $cookies + ); + }catch(Exception $error){ + + throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}"); + } + } } - - //echo htmlspecialchars($html); + // detect captcha even if we passed anubis challenge $this->detect_captcha($html); + //echo htmlspecialchars($html); + if( preg_match( '/React\.createElement\(UIStartpage\.AppSerpWeb, ?(.+)\),?$/m', @@ -420,14 +504,14 @@ class startpage{ ) === 0 ){ - throw new Exception("Failed to grep JSON object"); + throw new Exception("Failed to grep JSON object. Anubis error: {$this->anubis_error}"); } $json = json_decode($matches[1], true); if($json === null){ - throw new Exception("Failed to decode JSON"); + throw new Exception("Failed to decode JSON. Anubis error: {$this->anubis_error}"); } //print_r($json); @@ -449,7 +533,7 @@ class startpage{ ]; // get npt - $out["npt"] = $this->parse_npt($json, "web", $proxy); + $out["npt"] = $this->parse_npt($json, "web", $proxy, $cookies); foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ @@ -658,14 +742,22 @@ class startpage{ if($get["npt"]){ + $this->anubis_error = "Not ran on pagination"; [$post, $proxy] = $this->backend->get($get["npt"], "images"); + $post = json_decode($post, true); + $query = $post["query"]; + $cookies = $post["cookies"]; + try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $post, - true + $query, + true, + false, + false, + $cookies ); }catch(Exception $error){ @@ -680,76 +772,102 @@ class startpage{ throw new Exception("Search term is empty!"); } + $proxy = $this->backend->get_ip(); + + $params = [ + "query" => $get["s"], + "cat" => "images", + "pl" => "opensearch" + ]; + + if($get["nsfw"] == "no"){ + + $params["qadf"] = "heavy"; + } + + if($get["size"] != "any"){ + + if( + $get["size"] == "Small" || + $get["size"] == "Medium" || + $get["size"] == "Large" || + $get["size"] == "Wallpaper" + ){ + + $params["flimgsize"] = $get["size"]; + }else{ + + $params["image-size-select"] = "isz:lt,islt:" . $get["size"]; + } + } + + if($get["color"] != "any"){ + + if($get["color"] == "color"){ + + $params["flimgcolor"] = "ic:color"; + }elseif($get["color"] == "bnw"){ + + $params["flimgcolor"] = "ic:gray"; + }else{ + + $params["flimgcolor"] = "ic:specific,isc:" . $get["color"]; + } + } + + if($get["type"] != "any"){ + + $params["flimgtype"] = $get["type"]; + } + + if($get["license"] != "any"){ + + $params["flimglicense"] = $get["license"]; + } + try{ + $html = $this->get( + $proxy, + "https://www.startpage.com/sp/search", + $params, + false, + false, + true, + [] + ); + }catch(Exception $error){ - $proxy = $this->backend->get_ip(); + throw new Exception("Failed to fetch search page"); + } + + //$html = file_get_contents("scraper/startpage.html"); + + // detect captcha on that page + $this->detect_captcha($html["data"]); + + // solve anubis if need be + $cookies = $this->solve_anubis($proxy, $html); + + if($cookies === false){ - $params = [ - "query" => $get["s"], - "cat" => "images", - "pl" => "opensearch" - ]; - - if($get["nsfw"] == "no"){ - - $params["qadf"] = "heavy"; - } - - if($get["size"] != "any"){ - - if( - $get["size"] == "Small" || - $get["size"] == "Medium" || - $get["size"] == "Large" || - $get["size"] == "Wallpaper" - ){ - - $params["flimgsize"] = $get["size"]; - }else{ - - $params["image-size-select"] = "isz:lt,islt:" . $get["size"]; - } - } - - if($get["color"] != "any"){ - - if($get["color"] == "color"){ - - $params["flimgcolor"] = "ic:color"; - }elseif($get["color"] == "bnw"){ - - $params["flimgcolor"] = "ic:gray"; - }else{ - - $params["flimgcolor"] = "ic:specific,isc:" . $get["color"]; - } - } - - if($get["type"] != "any"){ - - $params["flimgtype"] = $get["type"]; - } - - if($get["license"] != "any"){ - - $params["flimglicense"] = $get["license"]; - } + $cookies = []; + $html = $html["data"]; + }else{ try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $params + $params, + false, + false, + false, + $cookies ); }catch(Exception $error){ - throw new Exception("Failed to fetch search page"); + throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}"); } - //$html = file_get_contents("scraper/startpage.html"); - - }catch(Exception $error){ - - throw new Exception("Failed to fetch search page"); } } @@ -769,18 +887,18 @@ class startpage{ ) === 0 ){ - throw new Exception("Failed to grep JSON object"); + throw new Exception("Failed to grep JSON object. Anubis error: {$this->anubis_error}"); } $json = json_decode($matches[1], true); if($json === null){ - throw new Exception("Failed to decode JSON object"); + throw new Exception("Failed to decode JSON object. Anubis error: {$this->anubis_error}"); } // get npt - $out["npt"] = $this->parse_npt($json, "images", $proxy); + $out["npt"] = $this->parse_npt($json, "images", $proxy, $cookies); // get images foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ @@ -819,14 +937,22 @@ class startpage{ if($get["npt"]){ + $this->anubis_error = "Not ran on pagination"; [$post, $proxy] = $this->backend->get($get["npt"], "videos"); + $post = json_decode($post, true); + $query = $post["query"]; + $cookies = $post["cookies"]; + try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $post, - true + $query, + true, + false, + false, + $cookies ); }catch(Exception $error){ @@ -841,46 +967,72 @@ class startpage{ throw new Exception("Search term is empty!"); } + $proxy = $this->backend->get_ip(); + + $params = [ + "query" => $get["s"], + "cat" => "video", + "pl" => "opensearch" + ]; + + if($get["nsfw"] == "no"){ + + $params["qadf"] = "heavy"; + } + + if($get["sort"] != "relevance"){ + + $params["sort_by"] = $get["sort"]; + } + + if($get["duration"] != "any"){ + + $params["with_duration"] = $get["duration"]; + } + try{ + $html = $this->get( + $proxy, + "https://www.startpage.com/sp/search", + $params, + false, + false, + true, + [] + ); + }catch(Exception $error){ - $proxy = $this->backend->get_ip(); + throw new Exception("Failed to fetch search page"); + } + + //$html = file_get_contents("scraper/startpage.html"); + + // detect captcha on that page + $this->detect_captcha($html["data"]); + + // solve anubis if need be + $cookies = $this->solve_anubis($proxy, $html); + + if($cookies === false){ - $params = [ - "query" => $get["s"], - "cat" => "video", - "pl" => "opensearch" - ]; - - if($get["nsfw"] == "no"){ - - $params["qadf"] = "heavy"; - } - - if($get["sort"] != "relevance"){ - - $params["sort_by"] = $get["sort"]; - } - - if($get["duration"] != "any"){ - - $params["with_duration"] = $get["duration"]; - } + $cookies = []; + $html = $html["data"]; + }else{ try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $params + $params, + false, + false, + false, + $cookies ); }catch(Exception $error){ - throw new Exception("Failed to fetch search page"); + throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}"); } - //$html = file_get_contents("scraper/startpage.html"); - - }catch(Exception $error){ - - throw new Exception("Failed to fetch search page"); } } @@ -894,14 +1046,14 @@ class startpage{ ) === 0 ){ - throw new Exception("Failed to get JSON object"); + throw new Exception("Failed to get JSON object. Anubis error: {$this->anubis_error}"); } $json = json_decode($matches[1], true); if($json === null){ - throw new Exception("Failed to decode JSON object"); + throw new Exception("Failed to decode JSON object. Anubis error: {$this->anubis_error}"); } $out = [ @@ -915,7 +1067,7 @@ class startpage{ ]; // get npt - $out["npt"] = $this->parse_npt($json, "video", $proxy); + $out["npt"] = $this->parse_npt($json, "video", $proxy, $cookies); // get results foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ @@ -971,14 +1123,22 @@ class startpage{ if($get["npt"]){ + $this->anubis_error = "Not ran on pagination"; [$post, $proxy] = $this->backend->get($get["npt"], "news"); + $post = json_decode($post, true); + $query = $post["query"]; + $cookies = $post["cookies"]; + try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $post, - true + $query, + true, + false, + false, + $cookies ); }catch(Exception $error){ @@ -993,41 +1153,67 @@ class startpage{ throw new Exception("Search term is empty!"); } + $proxy = $this->backend->get_ip(); + + $params = [ + "query" => $get["s"], + "cat" => "news", + "pl" => "opensearch" + ]; + + if($get["nsfw"] == "no"){ + + $params["qadf"] = "heavy"; + } + + if($get["time"] != "any"){ + + $params["with_date"] = $get["time"]; + } + try{ + $html = $this->get( + $proxy, + "https://www.startpage.com/sp/search", + $params, + false, + false, + true, + [] + ); + }catch(Exception $error){ - $proxy = $this->backend->get_ip(); + throw new Exception("Failed to fetch search page. Anubis error: {$this->anubis_error}"); + } + + //$html = file_get_contents("scraper/startpage.html"); + + // detect captcha on that page + $this->detect_captcha($html["data"]); + + // solve anubis if need be + $cookies = $this->solve_anubis($proxy, $html); + + if($cookies === false){ - $params = [ - "query" => $get["s"], - "cat" => "news", - "pl" => "opensearch" - ]; - - if($get["nsfw"] == "no"){ - - $params["qadf"] = "heavy"; - } - - if($get["time"] != "any"){ - - $params["with_date"] = $get["time"]; - } + $cookies = []; + $html = $html["data"]; + }else{ try{ $html = $this->get( $proxy, "https://www.startpage.com/sp/search", - $params + $params, + false, + false, + false, + $cookies ); }catch(Exception $error){ - throw new Exception("Failed to fetch search page"); + throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}"); } - //$html = file_get_contents("scraper/startpage.html"); - - }catch(Exception $error){ - - throw new Exception("Failed to fetch search page"); } } @@ -1041,14 +1227,14 @@ class startpage{ ) === 0 ){ - throw new Exception("Failed to get JSON object"); + throw new Exception("Failed to get JSON object. Anubis error: {$this->anubis_error}"); } $json = json_decode($matches[1], true); if($json === null){ - throw new Exception("Failed to decode JSON object"); + throw new Exception("Failed to decode JSON object. Anubis error: {$this->anubis_error}"); } $out = [ @@ -1058,7 +1244,7 @@ class startpage{ ]; // get npt - $out["npt"] = $this->parse_npt($json, "news", $proxy); + $out["npt"] = $this->parse_npt($json, "news", $proxy, $cookies); foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ @@ -1101,7 +1287,7 @@ class startpage{ return $out; } - private function parse_npt($json, $pagetype, $proxy){ + private function parse_npt($json, $pagetype, $proxy, $cookies){ foreach($json["render"]["presenter"]["pagination"]["pages"] as $page){ @@ -1109,22 +1295,27 @@ class startpage{ return $this->backend->store( - http_build_query( - [ - "lui" => "english", - "language" => "english", - "sc" => $json["render"]["search_sc"], - "t" => "device", - "cat" => $pagetype, - "segment" => "organic", - "abd" => "0", - "abe" => "0", - "query" => rawurldecode($json["render"]["query"]), - "page" => $page["number"], - "qsr" => "all", - "qadf" => "none" // @ todo fix (??) - ] - ), + json_encode([ + "query" => + http_build_query( + [ + "lui" => "english", + "language" => "english", + "sc" => $json["render"]["search_sc"], + "t" => "device", + "cat" => $pagetype, + "segment" => "startpage.udog", // "original" sometimes + "abd" => "0", + "abe" => "0", + "query" => rawurldecode($json["render"]["query"]), + "page" => $page["number"], + "qsr" => "all", + "qadf" => "none", // @ todo fix (??) + ...(isset($page["token"]) ? ["page_token" => $page["token"]] : []) + ] + ), + "cookies" => $cookies + ]), $pagetype, $proxy ); @@ -1270,4 +1461,56 @@ class startpage{ throw new Exception("Startpage redirected the scraper to an unhandled page"); } } + + private function solve_anubis($proxy, $html){ + + // + // solve anubis challenge, if theres one + // + $res = null; + $cookies = []; + + try{ + $res = $this->anubis->scrape($html["data"]); + + }catch(Exception $error){ + + // do nothang + $this->anubis_error = $error->getMessage(); + } + + if($res !== null){ + + // https://www.startpage.com/.within.website/x/cmd/anubis/api/pass-challenge + // ?id=01a00241-da22-7d62-b64f-e7a6a0e8bda8 + // &response=0000460826adc23e0363e97e713e4aa36ca46e1a7cd933184442772f3d6b994c + // &nonce=3098 + // &redir=https%3A%2F%2Fwww.startpage.com%2Fsp%2Fsearch + // &elapsedTime=111 + + $anubis_response = + $this->get( + $proxy, + "https://www.startpage.com/.within.website/x/cmd/anubis/api/pass-challenge", + [ + "id" => $res["id"], + "response" => $res["response"], + "nonce" => $res["nonce"], + "redir" => "https://www.startpage.com/sp/search", + "elapsedTime" => $res["elapsedTime"] + ], + false, + false, + true, + $html["cookies"] + ); + + // re-fetch search page AGAIN with the anubis bullshit + $cookies = array_merge($anubis_response["cookies"], $html["cookies"]); + + return $cookies; + } + + return false; + } } \ No newline at end of file diff --git a/settings.php b/settings.php index 323bfd3..55c7f78 100644 --- a/settings.php +++ b/settings.php @@ -87,10 +87,6 @@ $settings = [ "value" => "kagi", "text" => "Kagi" ], - [ - "value" => "qwant", - "text" => "Qwant" - ], [ "value" => "yep", "text" => "Yep" @@ -145,10 +141,6 @@ $settings = [ "value" => "startpage", "text" => "Startpage" ], - [ - "value" => "qwant", - "text" => "Qwant" - ], [ "value" => "ghostery", "text" => "Ghostery" @@ -231,10 +223,6 @@ $settings = [ "value" => "startpage", "text" => "Startpage" ], - [ - "value" => "qwant", - "text" => "Qwant" - ], [ "value" => "naver", "text" => "Naver" @@ -329,10 +317,6 @@ $settings = [ "value" => "startpage", "text" => "Startpage" ], - [ - "value" => "qwant", - "text" => "Qwant" - ], [ "value" => "naver", "text" => "Naver" @@ -379,10 +363,6 @@ $settings = [ "value" => "startpage", "text" => "Startpage" ], - [ - "value" => "qwant", - "text" => "Qwant" - ], /*[ "value" => "mojeek", "text" => "Mojeek" diff --git a/static/themes/Degoog dark.css b/static/themes/Degoog dark.css new file mode 100644 index 0000000..96c7cab --- /dev/null +++ b/static/themes/Degoog dark.css @@ -0,0 +1,1495 @@ +@charset "UTF-8"; + +:root{ + --warning: #fbbc05; + --bg: #25272e; + --bg-light: #303134; + --bg-hover: #3c4043; + --border: #5f6368; + --border-light: #303134; + --text-primary: #dddddd; + --text-secondary: #9aa0a6; + --text-link: #8ab4f8; + --text-link-visited: #c58af9; + --text-cite: #bdc1c6; + --text-snippet: #bdc1c6; + --search-bar-bg: #303134; + --search-bar-bg-hover: #303134; + --btn-bg: #303134; + --overlay-bg: rgba(0, 0, 0, 0.6); + --white: #fff3f3; +} + +:root { + --gutter: clamp( + 1rem, + calc(1rem + max(0px, (100vw - 48rem) * 0.31)), + 9.375rem + ); + --main-width: 40.75rem; + --sidebar-width: 21.25rem; + --column-gap: 2.5rem; + --bar-height: 2.75rem; + --bar-radius: 1.5rem; + --radius-xs: 0.1875rem; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + --fast: 0.2s ease; + --icon-search: url("data:image/svg+xml;utf8,"); + --icon-chevron: url("data:image/svg+xml;utf8,"); + + --1d2021: var(--bg); + --282828: var(--bg-light); + --3c3836: var(--bg-hover); + --504945: var(--border-light); + --928374: var(--text-secondary); + --a89984: var(--text-snippet); + --bdae93: var(--text-primary); + --8ec07c: var(--success); + --ebdbb2: var(--text-primary); + --comment: var(--text-secondary); + --default: var(--text-primary); + --keyword: var(--warning); + --string: var(--text-link); + --green: var(--success); + --yellow: var(--warning); + --red: var(--danger); +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-size: 100%; + font-family: sans-serif; +} + +body { + margin: 0; + padding: 0; + max-width: none; + min-height: 100vh; + background: var(--bg); + color: var(--text-primary); + font-family: var(--font); + font-size: 0.875rem; + line-height: 1.5; + word-wrap: anywhere; +} + +input, +select, +button, +textarea { + font-family: var(--font); +} + +a { + color: var(--text-link); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +::selection { + background: rgba(var(--primary-rgb), 0.3); +} + +::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: var(--radius-sm); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--text-primary); + font-weight: 500; + line-height: 1.3; + margin: 0 0 0.75rem; +} + +h1 { + font-size: 1.75rem; +} + +h2 { + font-size: 1.25rem; +} + +h3 { + font-size: 1.125rem; +} + +h4, +h5, +h6 { + font-size: 1rem; +} + +b, +strong { + font-weight: 600; +} + +hr { + border: 0; + border-top: 1px solid var(--border-light); + margin: 1.5rem 0; +} + +ul, +ol { + padding-left: 1.25rem; + margin: 0.5rem 0; +} + +.quote { + font-style: normal; + color: var(--text-secondary); + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + margin: 1rem 0; + padding: 0.75rem 1rem; +} + +.navigation { + position: absolute; + top: 1.15rem; + right: var(--gutter); + left: auto; + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 0.8125rem; + line-height: 1.5; + z-index: 2; +} + +.navigation a { + color: var(--text-secondary); + text-decoration: none; +} + +.navigation a:hover { + color: var(--text-primary); + text-decoration: none; +} + +.navigation a:not(:last-child)::after { + content: none; +} + +.logo a { + display: inline-block; + text-decoration: none; +} + +.home .logo { + width: auto; + max-width: none; + height: auto; + margin: 0 0 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.home .logo img { + display: block; + width: auto; + max-width: min(100%, 22rem); + height: auto; +} + +body.home { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + margin: 0; + padding: 2rem 1rem 4rem; + text-align: center; +} + +.home #center { + display: block; + width: min(36.5rem, 100%); + vertical-align: baseline; +} + +.home form { + display: block; + margin-bottom: 1.75rem; +} + +.home .searchbox { + width: 100%; + margin: 0 auto; + text-align: left; +} + +.home #center > a { + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.home #center > a:hover { + color: var(--text-primary); + text-decoration: none; +} + +.home .subtext { + margin-top: 1.25rem; + color: var(--text-secondary); + font-size: 0.75rem; + line-height: 1.6; +} + +.home .subtext a { + color: var(--text-secondary); +} + +.searchbox { + display: flex; + align-items: center; + position: relative; + width: 100%; + max-width: 43.25rem; + height: var(--bar-height); + margin: 0; + padding: 0 0.75rem; + background: var(--search-bar-bg); + border: 0; + border-radius: var(--bar-radius); + box-shadow: var(--shadow); + transition: + background var(--fast), + box-shadow var(--fast); +} + +.searchbox::before { + content: ""; + flex-shrink: 0; + width: 1.125rem; + height: 1.125rem; + margin-right: 0.75rem; + background-color: var(--text-secondary); + -webkit-mask: var(--icon-search) center/contain no-repeat; + mask: var(--icon-search) center/contain no-repeat; +} + +.searchbox:hover, +.searchbox:focus-within { + background: var(--search-bar-bg-hover); + border: 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.16); +} + +.searchbox .wrapper { + order: 1; + flex: 1; + min-width: 0; + overflow: visible; +} + +.searchbox input { + all: unset; + box-sizing: border-box; + height: var(--bar-height); + line-height: var(--bar-height); + color: var(--text-primary); + font-family: var(--font); +} + +.searchbox input[type="text"] { + width: 100%; + padding-left: 0; + font-size: 1rem; +} + +.searchbox input[type="text"]::placeholder { + color: var(--text-secondary); +} + +.searchbox input[type="submit"] { + order: 2; + float: none; + flex-shrink: 0; + height: 2rem; + line-height: 2rem; + margin-left: 0.5rem; + padding: 0 0.75rem; + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 500; + cursor: pointer; + transition: + background var(--fast), + color var(--fast); +} + +.searchbox input[type="submit"]:hover { + background: var(--bg-hover); + color: var(--text-primary); + text-decoration: none; +} + +.searchbox:has(.autocomplete[style*="block"]) { + border-radius: var(--bar-radius) var(--bar-radius) 0 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.16); +} + +.autocomplete { + top: 100%; + left: 0; + right: 0; + padding: 0.25rem 0 0.5rem; + background: var(--search-bar-bg-hover); + border: 0; + border-radius: 0 0 var(--bar-radius) var(--bar-radius); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.16); + overflow: hidden; + z-index: 100; +} + +.autocomplete .entry { + padding: 0.45rem 1rem 0.45rem 2.875rem; + color: var(--text-primary); + font-size: 1rem; + line-height: 1.5; +} + +.autocomplete .entry:hover, +.autocomplete .entry:focus { + background: var(--bg-hover); +} + +body > form { + display: grid; + grid-template-columns: minmax(0, 1fr); + align-items: center; + column-gap: 1.25rem; + padding: 1rem var(--gutter) 0; + margin: 0; +} + +body > form .searchbox { + grid-column: 1; + grid-row: 1; +} + +body > form .tabs { + grid-column: 1 / -1; + grid-row: 2; +} + +body > form .filters { + grid-column: 1 / -1; + grid-row: 3; +} + +.tabs { + display: flex; + align-items: center; + gap: 0.25rem; + margin-top: 1rem; + padding: 0 0 0 0.25rem; + border-bottom: 1px solid var(--border-light); + overflow-x: auto; + scrollbar-width: none; +} + +.tabs::-webkit-scrollbar { + display: none; +} + +.tabs .tab { + display: inline-flex; + align-items: center; + padding: 0.75rem 1rem; + color: var(--text-secondary); + font-size: 0.8125rem; + white-space: nowrap; + border-bottom: 3px solid transparent; + transition: + color var(--fast), + border-color var(--fast); +} + +.tabs .tab:hover { + color: var(--text-primary); + text-decoration: none; +} + +.tabs .tab.selected { + color: var(--text-primary); + border-bottom: 3px solid var(--text-primary); +} + +.filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + margin: 0; + padding: 0.75rem 0 0; + overflow: visible; + white-space: normal; +} + +.filters .filter { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin: 0; + padding: 0.375rem 0.75rem; + background: var(--bg-light); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + vertical-align: middle; +} + +.filters .filter .title { + margin: 0; + color: var(--text-secondary); + font-size: 0.8125rem; + white-space: nowrap; +} + +.filters .filter input, +.filters .filter select { + all: unset; + display: block; + width: auto; + max-width: 11rem; + height: 1.5rem; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--text-primary); + font-family: var(--font); + font-size: 0.8125rem; + font-weight: 500; + line-height: 1.5rem; + cursor: pointer; +} + +.filters .filter select { + padding-right: 1.125rem; + background-color: transparent; + background-image: var(--icon-chevron); + background-repeat: no-repeat; + background-position: right center; + background-size: 0.875rem; +} + +.timetaken { + margin: 0; + padding: 1.25rem var(--gutter) 0.75rem; + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 400; +} + +#overflow { + overflow: visible; +} + +#overflow.web { + display: flex; + align-items: flex-start; + gap: var(--column-gap); + padding: 0 var(--gutter) 3rem; +} + +body > #overflow:first-child { + padding-top: 2rem; +} + +.web .left { + float: none; + order: 1; + flex: 1; + min-width: 0; + width: auto; + max-width: var(--main-width); +} + +.web .right-wrapper { + float: none; + order: 2; + flex-shrink: 0; + width: var(--sidebar-width); + padding-left: 0; + overflow: visible; +} + +.web .right-wrapper:not(:has(> div:not(:empty))) { + display: none; +} + +.web .right-right, +.web .right-left { + float: none; + width: 100%; + padding: 0; + overflow: visible; +} + +.web .text-result { + margin: 0 0 2rem; + padding: 0; +} + +.web .url { + display: flow-root; + position: relative; + line-height: 1.625rem; +} + +.favicon { + all: unset; + float: left; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 1.625rem; + height: 1.625rem; + margin-right: 0.625rem; + border-radius: 50%; + background: var(--bg-hover); + cursor: pointer; +} + +.web .favicon img { + display: block; + width: 1rem; + height: 1rem; + margin: 0; + object-fit: contain; + font-size: 0.625rem; + line-height: 1; + text-align: center; +} + +.web .url .part { + color: var(--text-cite); + font-size: 0.875rem; +} + +.web .url .part:hover { + text-decoration: none; +} + +.web .separator::before { + content: "›"; + padding: 0 0.375rem; + color: var(--text-secondary); + font-size: 0.875rem; +} + +.favicon-dropdown { + top: 2rem; + left: 0; + padding: 0.25rem 0; + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + z-index: 20; +} + +.favicon-dropdown::before { + content: none; +} + +.favicon-dropdown a { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.875rem; + color: var(--text-primary); + font-size: 0.8125rem; + line-height: 1.4; + white-space: nowrap; +} + +.favicon-dropdown a:hover { + background: var(--bg-hover); + text-decoration: none; +} + +.favicon-dropdown img { + float: none; + width: 1rem; + height: 1rem; + margin: 0; + flex-shrink: 0; +} + +.web .hover { + display: flow-root; + clear: none; + padding-top: 0; + color: var(--text-snippet); +} + +.web .text-result .title { + margin: 0.125rem 0 0; + color: var(--text-link); + font-size: 1.25rem; + font-weight: 400; + line-height: 1.3; +} + +.web .text-result a:visited .title { + color: var(--text-link-visited) !important; +} + +.web .text-result .hover:hover .title { + text-decoration: underline; +} + +.web .description { + margin-top: 0.25rem; + color: var(--text-snippet); + font-size: 0.875rem; + line-height: 1.58; + white-space: pre-line; +} + +.web .type { + float: left; + margin: 0.3rem 0.5rem 0 0; + padding: 0 0.375rem; + background: var(--bg-hover); + border: 0; + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: 0.6875rem; + font-weight: 500; + line-height: 1.4; +} + +.web .text-result .greentext { + margin-top: 0.25rem; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.web .text-result .author { + font-style: normal; + color: var(--text-secondary); +} + +.thumb-wrap { + float: right; + width: 7.5rem; + height: 5.625rem; + margin: 0.25rem 0 0.5rem 0.75rem; + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + overflow: hidden; +} + +.thumb-wrap.portrait { + width: 4.25rem; +} + +.thumb-wrap.square { + width: 5.625rem; +} + +.thumb-wrap .thumb { + object-fit: cover; +} + +.duration { + right: 0.375rem; + bottom: 0.375rem; + padding: 1px 0.25rem; + background: rgba(0, 0, 0, 0.75); + border: 0; + border-radius: var(--radius-xs); + color: #fff; + font-family: var(--font); + font-size: 0.6875rem; + line-height: 1.3; +} + +.web .sublinks { + padding: 0.75rem 0 0; + font-size: 0.8125rem; + color: var(--text-secondary); +} + +.web .sublinks table td { + padding-bottom: 0.75rem; +} + +.web .sublinks .title { + color: var(--text-link); + font-size: 0.875rem; +} + +.web .sublinks .description { + font-size: 0.8125rem; + line-height: 1.5; +} + +.infobox, +.web .answer-wrapper, +.web .info-table, +.web .wiki-head table, +.about table, +.related, +.web .settings, +.code, +.nextpage, +.web .spoiler-button { + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: var(--shadow); +} + +.infobox { + margin-bottom: 1rem; + padding: 1rem; + color: var(--text-primary); + overflow: hidden; +} + +.infobox h1 { + font-size: 1.25rem; +} + +.infobox h2 { + font-size: 1.125rem; +} + +.infobox a { + color: var(--text-link); +} + +.infobox ul { + padding-left: 1.25rem; + margin-bottom: 0; +} + +.web .answer-wrapper { + margin-bottom: 1rem; + padding: 1rem; + overflow: hidden; +} + +.web .answer { + padding-bottom: 0; + max-height: 34rem; +} + +.web .answer::after { + height: 2.5rem; + background: linear-gradient(transparent, var(--bg-light)); +} + +.web .answer-title { + color: var(--text-primary); + text-decoration: none; +} + +.web .answer-title h1 { + font-size: 1.375rem; + margin-bottom: 0.5rem; +} + +.web .spoiler-button { + margin-top: 0.75rem; + padding: 0 0.75rem; + line-height: 2.25rem; + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 500; + box-shadow: none; + background: var(--bg-hover); +} + +.web .spoiler-button:hover { + color: var(--text-primary); + text-decoration: none; +} + +.web .wiki-head .photo { + margin: 0 0 0.75rem 0.75rem; +} + +.web .wiki-head .photo img { + max-width: 9rem; + max-height: 9rem; + border-radius: var(--radius-lg); + filter: none; +} + +.web .wiki-head .description { + padding-top: 0.5rem; + color: var(--text-snippet); + line-height: 1.58; +} + +.web .wiki-head h2 { + font-size: 1.125rem; + margin: 1.25rem 0 0.5rem; +} + +.web .wiki-head h2:first-child { + margin-top: 0.5rem; +} + +.web .wiki-head a { + color: var(--text-link); +} + +.web .wiki-head .socials { + margin-top: 1rem; + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + overflow: visible; +} + +.web .wiki-head .socials a { + float: none; + display: flex; + align-items: center; + justify-content: center; + width: 4.25rem; + height: auto; + padding: 0.625rem 0.25rem; + border-radius: var(--radius-lg); + color: var(--text-secondary); + transition: background var(--fast); +} + +.web .wiki-head .socials a:hover { + background: var(--bg-hover); +} + +.web .wiki-head .socials a:hover .title { + text-decoration: none; + color: var(--text-primary); +} + +.web .wiki-head .socials img { + width: 1.5rem; + height: 1.5rem; + line-height: 1.5rem; +} + +.web .wiki-head .socials .title { + margin-top: 0.5rem; + font-size: 0.6875rem; + line-height: 1.3; +} + +.web .fullimg { + margin: 0.5rem 0 1rem; + border: 0; + border-radius: var(--radius-lg); + max-height: 11rem; +} + +table { + width: 100%; + text-align: left; + border-collapse: collapse; +} + +table a { + color: var(--text-link); +} + +.web .info-table, +.web .wiki-head table, +.about table { + margin: 0.75rem 0 0; + overflow: hidden; + color: var(--text-snippet); + font-size: 0.8125rem; +} + +.web .info-table td, +.web .wiki-head td, +.about table td { + padding: 0.5rem 0.875rem; + vertical-align: top; +} + +.web .info-table tr td:first-child, +.web .wiki-head tr td:first-child, +.about table tr td:first-child { + width: 34%; + min-width: 0; + padding-right: 0.875rem; + color: var(--text-secondary); + white-space: normal; +} + +.web .info-table tr:nth-child(even), +.web .wiki-head tr:nth-child(odd), +.about table tr:nth-child(odd) { + background: var(--bg-hover); +} + +.related { + margin: 0 0 2rem; + padding: 0.25rem 0; + overflow: hidden; +} + +.related td { + padding: 0; + vertical-align: middle; +} + +.related tr td:first-child { + padding-right: 0; +} + +.related a { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.6rem 0.875rem; + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.4; +} + +.related a::before { + content: ""; + flex-shrink: 0; + width: 1rem; + height: 1rem; + background-color: var(--text-secondary); + -webkit-mask: var(--icon-search) center/contain no-repeat; + mask: var(--icon-search) center/contain no-repeat; +} + +.related a:hover { + background: var(--bg-hover); + text-decoration: none; +} + +.nextpage { + display: inline-flex; + align-items: center; + gap: 0.5rem; + width: auto; + margin: 0 0 2rem; + padding: 0 1.125rem; + min-height: 2.25rem; + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 500; + text-align: left; + transition: + background var(--fast), + color var(--fast); +} + +.nextpage:hover { + background: var(--bg-hover); + color: var(--text-primary); + text-decoration: none; +} + +.code { + margin: 0.5rem 0 0.875rem; + padding: 0.75rem 0.875rem; + background: var(--bg-hover); + color: var(--text-primary); + font-family: var(--mono); + font-size: 0.8125rem; + box-shadow: none; + overflow-x: auto; +} + +.code-inline { + display: inline; + padding: 0.05rem 0.375rem; + background: var(--bg-hover); + border: 0; + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: var(--mono); + font-size: 0.8125rem; +} + +.web .images { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin: 0.5rem 0 0; + overflow: visible; + font-size: 0; +} + +.web .images .image { + width: 5.5rem; + height: 5.5rem; + padding: 0; + line-height: 0; + border-radius: var(--radius-lg); + background: var(--bg-hover); + overflow: hidden; + transition: opacity var(--fast); +} + +.web .images .image:hover { + opacity: 0.85; +} + +.web .images .image img { + width: 100%; + height: 100%; + max-width: none; + max-height: none; + object-fit: cover; + vertical-align: top; +} + +.web .images .duration { + border: 0; + right: 0.375rem; + bottom: 0.375rem; +} + +#images { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); + gap: 1rem 0.75rem; + margin: 0; + padding: 0.5rem var(--gutter) 1.5rem; + overflow: visible; +} + +#images .infobox { + grid-column: 1 / -1; + width: auto; + max-width: var(--main-width); + margin-bottom: 0.5rem; +} + +#images .image-wrapper { + float: none; + width: auto; + line-height: 1.4; +} + +#images .image { + width: auto; + max-width: none; + margin: 0; + padding: 0; + font-size: 0.8125rem; +} + +#images .thumb { + height: 11.25rem; + margin-bottom: 0.5rem; + border-radius: var(--radius-lg); + background: var(--bg-hover); + overflow: hidden; + transition: opacity var(--fast); +} + +#images .image:hover .thumb { + opacity: 0.85; +} + +#images img { + object-fit: cover; +} + +#images .duration { + border: 0; +} + +#images .image .title, +#images .image .i-title-wrapper { + margin-bottom: 0.125rem; + color: var(--text-primary); + font-size: 0.8125rem; + font-weight: 400; + text-overflow: ellipsis; +} + +#images .image .description { + height: auto; + color: var(--text-secondary); + font-size: 0.6875rem; + white-space: nowrap; + text-overflow: ellipsis; +} + +.nextpage.img { + width: auto; + margin: 0 var(--gutter) 3rem; +} + +#popup-bg { + background: #000; + opacity: 0.92; +} + +#popup-image { + background: transparent; + border: 0; +} + +#popup-status { + height: 2.75rem; + background: var(--bg-light); + border: 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2); +} + +#popup-num, +#popup-title { + padding: 0 0.875rem; + line-height: 2.75rem; + color: var(--text-primary); + font-size: 0.8125rem; +} + +#popup-num { + font-weight: 600; + color: var(--text-secondary); +} + +#popup-status select { + all: unset; + display: block; + width: 15rem; + height: 2rem; + margin-right: 0.75rem; + padding: 0 1.75rem 0 0.75rem; + background-color: var(--bg-hover); + background-image: var(--icon-chevron); + background-repeat: no-repeat; + background-position: right 0.625rem center; + background-size: 0.875rem; + border-radius: var(--radius-md); + color: var(--text-primary); + font-family: var(--font); + font-size: 0.8125rem; + line-height: 2rem; + cursor: pointer; +} + +.captcha-wrapper { + border: 0; + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow); +} + +.captcha-wrapper img { + background: var(--bg-light); +} + +.captcha-wrapper input:checked + label::after { + border-right-color: var(--white); + border-bottom-color: var(--white); +} + +.web .settings { + margin-top: 1rem; + padding: 1rem 1rem 0.25rem; +} + +.web .settings h2 { + font-size: 1.125rem; + margin-bottom: 1rem; +} + +.web .setting { + margin-bottom: 1.25rem; +} + +.web .setting .title { + margin-bottom: 0.375rem; + color: var(--text-secondary); + font-size: 0.8125rem; + line-height: 1.5; +} + +.web .setting .title i { + font-style: normal; + color: var(--text-secondary); +} + +.web .settings select { + all: unset; + display: block; + box-sizing: border-box; + width: 100%; + max-width: 22rem; + height: 2.25rem; + padding: 0 2rem 0 0.75rem; + background-color: var(--bg); + background-image: var(--icon-chevron); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 0.875rem; + border: 0; + border-radius: var(--radius-md); + color: var(--text-primary); + font-family: var(--font); + font-size: 0.875rem; + line-height: 2.25rem; + cursor: pointer; +} + +.web .settings-submit { + display: flex; + align-items: center; + gap: 1rem; + margin: 1.25rem 0 0; +} + +.web .settings-submit input { + all: unset; + order: 1; + height: 2.25rem; + padding: 0 1.25rem; + background: var(--primary); + border-radius: var(--radius-sm); + box-shadow: var(--shadow); + color: var(--white); + font-family: var(--font); + font-size: 0.875rem; + font-weight: 500; + line-height: 2.25rem; + cursor: pointer; + transition: background var(--fast); +} + +.web .settings-submit input:hover { + background: var(--primary-hover); +} + +.web .settings-submit a { + order: 2; + margin-right: 0; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +body.about { + margin: 0; + padding: 2rem var(--gutter) 4rem; + max-width: calc(var(--main-width) + var(--gutter) * 2); +} + +.about a { + color: var(--text-link); +} + +.about h1, +.about h2 { + margin-top: 1.75rem; +} + +.about table { + margin-bottom: 1rem; +} + +body.instances { + margin: 0; + padding: 2rem var(--gutter) 4rem; +} + +.instances a { + color: var(--text-link); +} + +.instances table { + margin-top: 1rem; + background: var(--bg-light); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + overflow: hidden; +} + +.instances thead { + outline: 0; + background: var(--bg-hover); + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.instances tbody { + outline: 0; + top: 0; +} + +.instances th, +.instances td { + padding: 0.5rem 0.875rem; +} + +.instances th:hover { + background: var(--border-light); +} + +.instances tbody tr:nth-child(even) { + background: var(--bg-hover); +} + +.instances tbody tr:hover { + background: var(--border-light); +} + +.instances .arrow { + border-top-color: var(--text-secondary); +} + +.instances .arrow.up { + border-top-color: transparent; + border-bottom-color: var(--text-secondary); +} + +.instances .popup { + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + padding: 1rem 1.25rem; +} + +.instances .go-back { + margin-top: 1rem; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +@media only screen and (max-width: 1550px) { + .web .right-right, + .web .right-left { + padding: 0; + } + + .web .left, + .searchbox, + #images .infobox { + width: auto; + } + + .web .right-wrapper { + width: var(--sidebar-width); + } +} + +@media only screen and (max-width: 1000px) { + #overflow.web { + flex-direction: column; + gap: 0; + } + + .web .left, + .web .right-wrapper { + width: 100%; + max-width: none; + } + + .web .right-wrapper { + order: 0; + margin-bottom: 1.5rem; + } + + .web.has-answer .left::before { + display: none; + } + + body:not(.instances) table td { + display: table-cell; + width: auto; + } + + .related td { + display: block; + width: 100%; + } + + .web .info-table tr td:first-child, + .web .wiki-head tr td:first-child { + text-decoration: none; + } +} + +@media only screen and (max-width: 767px) { + .navigation { + position: static; + justify-content: flex-end; + padding: 1rem var(--gutter) 0; + } + + body > form { + grid-template-columns: minmax(0, 1fr); + padding-top: 0.5rem; + } + + body > form .searchbox { + grid-column: 1; + grid-row: 1; + max-width: none; + } + + body > form .tabs { + grid-row: 2; + } + + body > form .filters { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + padding-bottom: 0.5rem; + scrollbar-width: thin; + } + + .web .answer { + max-height: 16rem; + } + + .thumb-wrap { + width: 5rem; + height: 3.75rem; + } +} diff --git a/static/themes/Degoog light.css b/static/themes/Degoog light.css new file mode 100644 index 0000000..1861e67 --- /dev/null +++ b/static/themes/Degoog light.css @@ -0,0 +1,1501 @@ +@charset "UTF-8"; + +:root { + --primary: #5673ac; + --primary-hover: #3b547c; + --primary-rgb: 66, 133, 244; + --danger: #ea4335; + --warning: #c17d00; + --success: #34a853; + + --bg: #f1f1f1; + --bg-light: #fafafa; + --bg-hover: #fafafa; + --border: #c8d0da; + --border-light: #dedede; + --text-primary: #343536; + --text-secondary: #4b5563; + --text-link: #1a0dab; + --text-link-visited: #681da8; + --text-cite: #111827; + --text-snippet: #374151; + --search-bar-bg: #f7f8fb; + --search-bar-bg-hover: #f7f8fb; + --btn-bg: #f7f8fb; + --overlay-bg: rgba(0, 0, 0, 0.4); + --white: #fff; +} + +:root { + --gutter: clamp( + 1rem, + calc(1rem + max(0px, (100vw - 48rem) * 0.31)), + 9.375rem + ); + --main-width: 40.75rem; + --sidebar-width: 21.25rem; + --column-gap: 2.5rem; + --bar-height: 2.75rem; + --bar-radius: 1.5rem; + --radius-xs: 0.1875rem; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + --fast: 0.2s ease; + --icon-search: url("data:image/svg+xml;utf8,"); + --icon-chevron: url("data:image/svg+xml;utf8,"); + + --1d2021: var(--bg); + --282828: var(--bg-light); + --3c3836: var(--bg-hover); + --504945: var(--border-light); + --928374: var(--text-secondary); + --a89984: var(--text-snippet); + --bdae93: var(--text-primary); + --8ec07c: var(--success); + --ebdbb2: var(--text-primary); + --comment: var(--text-secondary); + --default: var(--text-primary); + --keyword: var(--warning); + --string: var(--text-link); + --green: var(--success); + --yellow: var(--warning); + --red: var(--danger); +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-size: 100%; + font-family: sans-serif; +} + +body { + margin: 0; + padding: 0; + max-width: none; + min-height: 100vh; + background: var(--bg); + color: var(--text-primary); + font-family: var(--font); + font-size: 0.875rem; + line-height: 1.5; + word-wrap: anywhere; +} + +input, +select, +button, +textarea { + font-family: var(--font); +} + +a { + color: var(--text-link); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +::selection { + background: rgba(var(--primary-rgb), 0.3); +} + +::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: var(--radius-sm); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--text-primary); + font-weight: 500; + line-height: 1.3; + margin: 0 0 0.75rem; +} + +h1 { + font-size: 1.75rem; +} + +h2 { + font-size: 1.25rem; +} + +h3 { + font-size: 1.125rem; +} + +h4, +h5, +h6 { + font-size: 1rem; +} + +b, +strong { + font-weight: 600; +} + +hr { + border: 0; + border-top: 1px solid var(--border-light); + margin: 1.5rem 0; +} + +ul, +ol { + padding-left: 1.25rem; + margin: 0.5rem 0; +} + +.quote { + font-style: normal; + color: var(--text-secondary); + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + margin: 1rem 0; + padding: 0.75rem 1rem; +} + +.navigation { + position: absolute; + top: 1.15rem; + right: var(--gutter); + left: auto; + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 0.8125rem; + line-height: 1.5; + z-index: 2; +} + +.navigation a { + color: var(--text-secondary); + text-decoration: none; +} + +.navigation a:hover { + color: var(--text-primary); + text-decoration: none; +} + +.navigation a:not(:last-child)::after { + content: none; +} + +.logo a { + display: inline-block; + text-decoration: none; +} + +.home .logo { + width: auto; + max-width: none; + height: auto; + margin: 0 0 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.home .logo img { + display: block; + width: auto; + max-width: min(100%, 22rem); + height: auto; +} + +body.home { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + margin: 0; + padding: 2rem 1rem 4rem; + text-align: center; +} + +.home #center { + display: block; + width: min(36.5rem, 100%); + vertical-align: baseline; +} + +.home form { + display: block; + margin-bottom: 1.75rem; +} + +.home .searchbox { + width: 100%; + margin: 0 auto; + text-align: left; +} + +.home #center > a { + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.home #center > a:hover { + color: var(--text-primary); + text-decoration: none; +} + +.home .subtext { + margin-top: 1.25rem; + color: var(--text-secondary); + font-size: 0.75rem; + line-height: 1.6; +} + +.home .subtext a { + color: var(--text-secondary); +} + +.searchbox { + display: flex; + align-items: center; + position: relative; + width: 100%; + max-width: 43.25rem; + height: var(--bar-height); + margin: 0; + padding: 0 0.75rem; + background: var(--search-bar-bg); + border: 0; + border-radius: var(--bar-radius); + box-shadow: var(--shadow); + transition: + background var(--fast), + box-shadow var(--fast); +} + +.searchbox::before { + content: ""; + flex-shrink: 0; + width: 1.125rem; + height: 1.125rem; + margin-right: 0.75rem; + background-color: var(--text-secondary); + -webkit-mask: var(--icon-search) center/contain no-repeat; + mask: var(--icon-search) center/contain no-repeat; +} + +.searchbox:hover, +.searchbox:focus-within { + background: var(--search-bar-bg-hover); + border: 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.16); +} + +.searchbox .wrapper { + order: 1; + flex: 1; + min-width: 0; + overflow: visible; +} + +.searchbox input { + all: unset; + box-sizing: border-box; + height: var(--bar-height); + line-height: var(--bar-height); + color: var(--text-primary); + font-family: var(--font); +} + +.searchbox input[type="text"] { + width: 100%; + padding-left: 0; + font-size: 1rem; +} + +.searchbox input[type="text"]::placeholder { + color: var(--text-secondary); +} + +.searchbox input[type="submit"] { + order: 2; + float: none; + flex-shrink: 0; + height: 2rem; + line-height: 2rem; + margin-left: 0.5rem; + padding: 0 0.75rem; + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 500; + cursor: pointer; + transition: + background var(--fast), + color var(--fast); +} + +.searchbox input[type="submit"]:hover { + background: var(--bg-hover); + color: var(--text-primary); + text-decoration: none; +} + +.searchbox:has(.autocomplete[style*="block"]) { + border-radius: var(--bar-radius) var(--bar-radius) 0 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.16); +} + +.autocomplete { + top: 100%; + left: 0; + right: 0; + padding: 0.25rem 0 0.5rem; + background: var(--search-bar-bg-hover); + border: 0; + border-radius: 0 0 var(--bar-radius) var(--bar-radius); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.16); + overflow: hidden; + z-index: 100; +} + +.autocomplete .entry { + padding: 0.45rem 1rem 0.45rem 2.875rem; + color: var(--text-primary); + font-size: 1rem; + line-height: 1.5; +} + +.autocomplete .entry:hover, +.autocomplete .entry:focus { + background: var(--bg-hover); +} + +body > form { + display: grid; + grid-template-columns: minmax(0, 1fr); + align-items: center; + column-gap: 1.25rem; + padding: 1rem var(--gutter) 0; + margin: 0; +} + +body > form .searchbox { + grid-column: 1; + grid-row: 1; +} + +body > form .tabs { + grid-column: 1 / -1; + grid-row: 2; +} + +body > form .filters { + grid-column: 1 / -1; + grid-row: 3; +} + +.tabs { + display: flex; + align-items: center; + gap: 0.25rem; + margin-top: 1rem; + padding: 0 0 0 0.25rem; + border-bottom: 1px solid var(--border-light); + overflow-x: auto; + scrollbar-width: none; +} + +.tabs::-webkit-scrollbar { + display: none; +} + +.tabs .tab { + display: inline-flex; + align-items: center; + padding: 0.75rem 1rem; + color: var(--text-secondary); + font-size: 0.8125rem; + white-space: nowrap; + border-bottom: 3px solid transparent; + transition: + color var(--fast), + border-color var(--fast); +} + +.tabs .tab:hover { + color: var(--text-primary); + text-decoration: none; +} + +.tabs .tab.selected { + color: var(--text-primary); + border-bottom: 3px solid var(--text-primary); +} + +.filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + margin: 0; + padding: 0.75rem 0 0; + overflow: visible; + white-space: normal; +} + +.filters .filter { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin: 0; + padding: 0.375rem 0.75rem; + background: var(--bg-light); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + vertical-align: middle; +} + +.filters .filter .title { + margin: 0; + color: var(--text-secondary); + font-size: 0.8125rem; + white-space: nowrap; +} + +.filters .filter input, +.filters .filter select { + all: unset; + display: block; + width: auto; + max-width: 11rem; + height: 1.5rem; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--text-primary); + font-family: var(--font); + font-size: 0.8125rem; + font-weight: 500; + line-height: 1.5rem; + cursor: pointer; +} + +.filters .filter select { + padding-right: 1.125rem; + background-color: transparent; + background-image: var(--icon-chevron); + background-repeat: no-repeat; + background-position: right center; + background-size: 0.875rem; +} + +.timetaken { + margin: 0; + padding: 1.25rem var(--gutter) 0.75rem; + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 400; +} + +#overflow { + overflow: visible; +} + +#overflow.web { + display: flex; + align-items: flex-start; + gap: var(--column-gap); + padding: 0 var(--gutter) 3rem; +} + +body > #overflow:first-child { + padding-top: 2rem; +} + +.web .left { + float: none; + order: 1; + flex: 1; + min-width: 0; + width: auto; + max-width: var(--main-width); +} + +.web .right-wrapper { + float: none; + order: 2; + flex-shrink: 0; + width: var(--sidebar-width); + padding-left: 0; + overflow: visible; +} + +.web .right-wrapper:not(:has(> div:not(:empty))) { + display: none; +} + +.web .right-right, +.web .right-left { + float: none; + width: 100%; + padding: 0; + overflow: visible; +} + +.web .text-result { + margin: 0 0 2rem; + padding: 0; +} + +.web .url { + display: flow-root; + position: relative; + line-height: 1.625rem; +} + +.favicon { + all: unset; + float: left; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 1.625rem; + height: 1.625rem; + margin-right: 0.625rem; + border-radius: 50%; + background: var(--bg-hover); + cursor: pointer; +} + +.web .favicon img { + display: block; + width: 1rem; + height: 1rem; + margin: 0; + object-fit: contain; + font-size: 0.625rem; + line-height: 1; + text-align: center; +} + +.web .url .part { + color: var(--text-cite); + font-size: 0.875rem; +} + +.web .url .part:hover { + text-decoration: none; +} + +.web .separator::before { + content: "›"; + padding: 0 0.375rem; + color: var(--text-secondary); + font-size: 0.875rem; +} + +.favicon-dropdown { + top: 2rem; + left: 0; + padding: 0.25rem 0; + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + z-index: 20; +} + +.favicon-dropdown::before { + content: none; +} + +.favicon-dropdown a { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.875rem; + color: var(--text-primary); + font-size: 0.8125rem; + line-height: 1.4; + white-space: nowrap; +} + +.favicon-dropdown a:hover { + background: var(--bg-hover); + text-decoration: none; +} + +.favicon-dropdown img { + float: none; + width: 1rem; + height: 1rem; + margin: 0; + flex-shrink: 0; +} + +.web .hover { + display: flow-root; + clear: none; + padding-top: 0; + color: var(--text-snippet); +} + +.web .text-result .title { + margin: 0.125rem 0 0; + color: var(--text-link); + font-size: 1.25rem; + font-weight: 400; + line-height: 1.3; +} + +.web .text-result a:visited .title { + color: var(--text-link-visited) !important; +} + +.web .text-result .hover:hover .title { + text-decoration: underline; +} + +.web .description { + margin-top: 0.25rem; + color: var(--text-snippet); + font-size: 0.875rem; + line-height: 1.58; + white-space: pre-line; +} + +.web .type { + float: left; + margin: 0.3rem 0.5rem 0 0; + padding: 0 0.375rem; + background: var(--bg-hover); + border: 0; + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: 0.6875rem; + font-weight: 500; + line-height: 1.4; +} + +.web .text-result .greentext { + margin-top: 0.25rem; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.web .text-result .author { + font-style: normal; + color: var(--text-secondary); +} + +.thumb-wrap { + float: right; + width: 7.5rem; + height: 5.625rem; + margin: 0.25rem 0 0.5rem 0.75rem; + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + overflow: hidden; +} + +.thumb-wrap.portrait { + width: 4.25rem; +} + +.thumb-wrap.square { + width: 5.625rem; +} + +.thumb-wrap .thumb { + object-fit: cover; +} + +.duration { + right: 0.375rem; + bottom: 0.375rem; + padding: 1px 0.25rem; + background: rgba(0, 0, 0, 0.75); + border: 0; + border-radius: var(--radius-xs); + color: #fff; + font-family: var(--font); + font-size: 0.6875rem; + line-height: 1.3; +} + +.web .sublinks { + padding: 0.75rem 0 0; + font-size: 0.8125rem; + color: var(--text-secondary); +} + +.web .sublinks table td { + padding-bottom: 0.75rem; +} + +.web .sublinks .title { + color: var(--text-link); + font-size: 0.875rem; +} + +.web .sublinks .description { + font-size: 0.8125rem; + line-height: 1.5; +} + +.infobox, +.web .answer-wrapper, +.web .info-table, +.web .wiki-head table, +.about table, +.related, +.web .settings, +.code, +.nextpage, +.web .spoiler-button { + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: var(--shadow); +} + +.infobox { + margin-bottom: 1rem; + padding: 1rem; + color: var(--text-primary); + overflow: hidden; +} + +.infobox h1 { + font-size: 1.25rem; +} + +.infobox h2 { + font-size: 1.125rem; +} + +.infobox a { + color: var(--text-link); +} + +.infobox ul { + padding-left: 1.25rem; + margin-bottom: 0; +} + +.web .answer-wrapper { + margin-bottom: 1rem; + padding: 1rem; + overflow: hidden; +} + +.web .answer { + padding-bottom: 0; + max-height: 34rem; +} + +.web .answer::after { + height: 2.5rem; + background: linear-gradient(transparent, var(--bg-light)); +} + +.web .answer-title { + color: var(--text-primary); + text-decoration: none; +} + +.web .answer-title h1 { + font-size: 1.375rem; + margin-bottom: 0.5rem; +} + +.web .spoiler-button { + margin-top: 0.75rem; + padding: 0 0.75rem; + line-height: 2.25rem; + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 500; + box-shadow: none; + background: var(--bg-hover); +} + +.web .spoiler-button:hover { + color: var(--text-primary); + text-decoration: none; +} + +.web .wiki-head .photo { + margin: 0 0 0.75rem 0.75rem; +} + +.web .wiki-head .photo img { + max-width: 9rem; + max-height: 9rem; + border-radius: var(--radius-lg); + filter: none; +} + +.web .wiki-head .description { + padding-top: 0.5rem; + color: var(--text-snippet); + line-height: 1.58; +} + +.web .wiki-head h2 { + font-size: 1.125rem; + margin: 1.25rem 0 0.5rem; +} + +.web .wiki-head h2:first-child { + margin-top: 0.5rem; +} + +.web .wiki-head a { + color: var(--text-link); +} + +.web .wiki-head .socials { + margin-top: 1rem; + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + overflow: visible; +} + +.web .wiki-head .socials a { + float: none; + display: flex; + align-items: center; + justify-content: center; + width: 4.25rem; + height: auto; + padding: 0.625rem 0.25rem; + border-radius: var(--radius-lg); + color: var(--text-secondary); + transition: background var(--fast); +} + +.web .wiki-head .socials a:hover { + background: var(--bg-hover); +} + +.web .wiki-head .socials a:hover .title { + text-decoration: none; + color: var(--text-primary); +} + +.web .wiki-head .socials img { + width: 1.5rem; + height: 1.5rem; + line-height: 1.5rem; +} + +.web .wiki-head .socials .title { + margin-top: 0.5rem; + font-size: 0.6875rem; + line-height: 1.3; +} + +.web .fullimg { + margin: 0.5rem 0 1rem; + border: 0; + border-radius: var(--radius-lg); + max-height: 11rem; +} + +table { + width: 100%; + text-align: left; + border-collapse: collapse; +} + +table a { + color: var(--text-link); +} + +.web .info-table, +.web .wiki-head table, +.about table { + margin: 0.75rem 0 0; + overflow: hidden; + color: var(--text-snippet); + font-size: 0.8125rem; +} + +.web .info-table td, +.web .wiki-head td, +.about table td { + padding: 0.5rem 0.875rem; + vertical-align: top; +} + +.web .info-table tr td:first-child, +.web .wiki-head tr td:first-child, +.about table tr td:first-child { + width: 34%; + min-width: 0; + padding-right: 0.875rem; + color: var(--text-secondary); + white-space: normal; +} + +.web .info-table tr:nth-child(even), +.web .wiki-head tr:nth-child(odd), +.about table tr:nth-child(odd) { + background: var(--bg-hover); +} + +.related { + margin: 0 0 2rem; + padding: 0.25rem 0; + overflow: hidden; +} + +.related td { + padding: 0; + vertical-align: middle; +} + +.related tr td:first-child { + padding-right: 0; +} + +.related a { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.6rem 0.875rem; + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.4; +} + +.related a::before { + content: ""; + flex-shrink: 0; + width: 1rem; + height: 1rem; + background-color: var(--text-secondary); + -webkit-mask: var(--icon-search) center/contain no-repeat; + mask: var(--icon-search) center/contain no-repeat; +} + +.related a:hover { + background: var(--bg-hover); + text-decoration: none; +} + +.nextpage { + display: inline-flex; + align-items: center; + gap: 0.5rem; + width: auto; + margin: 0 0 2rem; + padding: 0 1.125rem; + min-height: 2.25rem; + color: var(--text-secondary); + font-size: 0.8125rem; + font-weight: 500; + text-align: left; + transition: + background var(--fast), + color var(--fast); +} + +.nextpage:hover { + background: var(--bg-hover); + color: var(--text-primary); + text-decoration: none; +} + +.code { + margin: 0.5rem 0 0.875rem; + padding: 0.75rem 0.875rem; + background: var(--bg-hover); + color: var(--text-primary); + font-family: var(--mono); + font-size: 0.8125rem; + box-shadow: none; + overflow-x: auto; +} + +.code-inline { + display: inline; + padding: 0.05rem 0.375rem; + background: var(--bg-hover); + border: 0; + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: var(--mono); + font-size: 0.8125rem; +} + +.web .images { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin: 0.5rem 0 0; + overflow: visible; + font-size: 0; +} + +.web .images .image { + width: 5.5rem; + height: 5.5rem; + padding: 0; + line-height: 0; + border-radius: var(--radius-lg); + background: var(--bg-hover); + overflow: hidden; + transition: opacity var(--fast); +} + +.web .images .image:hover { + opacity: 0.85; +} + +.web .images .image img { + width: 100%; + height: 100%; + max-width: none; + max-height: none; + object-fit: cover; + vertical-align: top; +} + +.web .images .duration { + border: 0; + right: 0.375rem; + bottom: 0.375rem; +} + +#images { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); + gap: 1rem 0.75rem; + margin: 0; + padding: 0.5rem var(--gutter) 1.5rem; + overflow: visible; +} + +#images .infobox { + grid-column: 1 / -1; + width: auto; + max-width: var(--main-width); + margin-bottom: 0.5rem; +} + +#images .image-wrapper { + float: none; + width: auto; + line-height: 1.4; +} + +#images .image { + width: auto; + max-width: none; + margin: 0; + padding: 0; + font-size: 0.8125rem; +} + +#images .thumb { + height: 11.25rem; + margin-bottom: 0.5rem; + border-radius: var(--radius-lg); + background: var(--bg-hover); + overflow: hidden; + transition: opacity var(--fast); +} + +#images .image:hover .thumb { + opacity: 0.85; +} + +#images img { + object-fit: cover; +} + +#images .duration { + border: 0; +} + +#images .image .title, +#images .image .i-title-wrapper { + margin-bottom: 0.125rem; + color: var(--text-primary); + font-size: 0.8125rem; + font-weight: 400; + text-overflow: ellipsis; +} + +#images .image .description { + height: auto; + color: var(--text-secondary); + font-size: 0.6875rem; + white-space: nowrap; + text-overflow: ellipsis; +} + +.nextpage.img { + width: auto; + margin: 0 var(--gutter) 3rem; +} + +#popup-bg { + background: #000; + opacity: 0.92; +} + +#popup-image { + background: transparent; + border: 0; +} + +#popup-status { + height: 2.75rem; + background: var(--bg-light); + border: 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2); +} + +#popup-num, +#popup-title { + padding: 0 0.875rem; + line-height: 2.75rem; + color: var(--text-primary); + font-size: 0.8125rem; +} + +#popup-num { + font-weight: 600; + color: var(--text-secondary); +} + +#popup-status select { + all: unset; + display: block; + width: 15rem; + height: 2rem; + margin-right: 0.75rem; + padding: 0 1.75rem 0 0.75rem; + background-color: var(--bg-hover); + background-image: var(--icon-chevron); + background-repeat: no-repeat; + background-position: right 0.625rem center; + background-size: 0.875rem; + border-radius: var(--radius-md); + color: var(--text-primary); + font-family: var(--font); + font-size: 0.8125rem; + line-height: 2rem; + cursor: pointer; +} + +.captcha-wrapper { + border: 0; + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow); +} + +.captcha-wrapper img { + background: var(--bg-light); +} + +.captcha-wrapper input:checked + label::after { + border-right-color: var(--white); + border-bottom-color: var(--white); +} + +.web .settings { + margin-top: 1rem; + padding: 1rem 1rem 0.25rem; +} + +.web .settings h2 { + font-size: 1.125rem; + margin-bottom: 1rem; +} + +.web .setting { + margin-bottom: 1.25rem; +} + +.web .setting .title { + margin-bottom: 0.375rem; + color: var(--text-secondary); + font-size: 0.8125rem; + line-height: 1.5; +} + +.web .setting .title i { + font-style: normal; + color: var(--text-secondary); +} + +.web .settings select { + all: unset; + display: block; + box-sizing: border-box; + width: 100%; + max-width: 22rem; + height: 2.25rem; + padding: 0 2rem 0 0.75rem; + background-color: var(--bg); + background-image: var(--icon-chevron); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 0.875rem; + border: 0; + border-radius: var(--radius-md); + color: var(--text-primary); + font-family: var(--font); + font-size: 0.875rem; + line-height: 2.25rem; + cursor: pointer; +} + +.web .settings-submit { + display: flex; + align-items: center; + gap: 1rem; + margin: 1.25rem 0 0; +} + +.web .settings-submit input { + all: unset; + order: 1; + height: 2.25rem; + padding: 0 1.25rem; + background: var(--primary); + border-radius: var(--radius-sm); + box-shadow: var(--shadow); + color: var(--white); + font-family: var(--font); + font-size: 0.875rem; + font-weight: 500; + line-height: 2.25rem; + cursor: pointer; + transition: background var(--fast); +} + +.web .settings-submit input:hover { + background: var(--primary-hover); +} + +.web .settings-submit a { + order: 2; + margin-right: 0; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +body.about { + margin: 0; + padding: 2rem var(--gutter) 4rem; + max-width: calc(var(--main-width) + var(--gutter) * 2); +} + +.about a { + color: var(--text-link); +} + +.about h1, +.about h2 { + margin-top: 1.75rem; +} + +.about table { + margin-bottom: 1rem; +} + +body.instances { + margin: 0; + padding: 2rem var(--gutter) 4rem; +} + +.instances a { + color: var(--text-link); +} + +.instances table { + margin-top: 1rem; + background: var(--bg-light); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + overflow: hidden; +} + +.instances thead { + outline: 0; + background: var(--bg-hover); + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.instances tbody { + outline: 0; + top: 0; +} + +.instances th, +.instances td { + padding: 0.5rem 0.875rem; +} + +.instances th:hover { + background: var(--border-light); +} + +.instances tbody tr:nth-child(even) { + background: var(--bg-hover); +} + +.instances tbody tr:hover { + background: var(--border-light); +} + +.instances .arrow { + border-top-color: var(--text-secondary); +} + +.instances .arrow.up { + border-top-color: transparent; + border-bottom-color: var(--text-secondary); +} + +.instances .popup { + background: var(--bg-light); + border: 0; + border-radius: var(--radius-lg); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + padding: 1rem 1.25rem; +} + +.instances .go-back { + margin-top: 1rem; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +@media only screen and (max-width: 1550px) { + .web .right-right, + .web .right-left { + padding: 0; + } + + .web .left, + .searchbox, + #images .infobox { + width: auto; + } + + .web .right-wrapper { + width: var(--sidebar-width); + } +} + +@media only screen and (max-width: 1000px) { + #overflow.web { + flex-direction: column; + gap: 0; + } + + .web .left, + .web .right-wrapper { + width: 100%; + max-width: none; + } + + .web .right-wrapper { + order: 0; + margin-bottom: 1.5rem; + } + + .web.has-answer .left::before { + display: none; + } + + body:not(.instances) table td { + display: table-cell; + width: auto; + } + + .related td { + display: block; + width: 100%; + } + + .web .info-table tr td:first-child, + .web .wiki-head tr td:first-child { + text-decoration: none; + } +} + +@media only screen and (max-width: 767px) { + .navigation { + position: static; + justify-content: flex-end; + padding: 1rem var(--gutter) 0; + } + + body > form { + grid-template-columns: minmax(0, 1fr); + padding-top: 0.5rem; + } + + body > form .searchbox { + grid-column: 1; + grid-row: 1; + max-width: none; + } + + body > form .tabs { + grid-row: 2; + } + + body > form .filters { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + padding-bottom: 0.5rem; + scrollbar-width: thin; + } + + .web .answer { + max-height: 16rem; + } + + .thumb-wrap { + width: 5rem; + height: 3.75rem; + } +}