<?php

class qwant{
	
	public function __construct(){
		
		include "lib/backend.php";
		$this->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 = []){
		
		$headers = [
			"User-Agent: " . config::USER_AGENT,
			"Accept: application/json, text/plain, */*",
			"Accept-Language: en-US,en;q=0.5",
			"Accept-Encoding: gzip",
			"DNT: 1",
			"Connection: keep-alive",
			"Origin: https://www.qwant.com",
			"Referer: https://www.qwant.com/",
			"Sec-Fetch-Dest: empty",
			"Sec-Fetch-Mode: cors",
			"Sec-Fetch-Site: same-site",
			"TE: trailers"
		];
		
		$curlproc = curl_init();
		
		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);
			
		}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"
			];
			
			switch($get["nsfw"]){
				
				case "yes": $params["safesearch"] = 0; break;
				case "maybe": $params["safesearch"] = 1; break;
				case "no": $params["safesearch"] = 2; break;
			}
		}
		/*
		$handle = fopen("scraper/qwant_web.json", "r");
		$json = fread($handle, filesize("scraper/qwant_web.json"));
		fclose($handle);*/
		
		try{
			$json =
				$this->get(
					$proxy,
					"https://fdn.qwant.com/v3/search/web",
					$params
				);
			
		}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" => []
		];
		
		if(
			$json["status"] != "success" &&
			$json["data"]["error_code"] === 5
		){
			
			// no results
			return $out;
		}
		
		$this->detect_errors($json);
		
		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;
			
			$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);
		}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;
			}
		}
		
		try{
			$json = $this->get(
				$proxy,
				"https://api.qwant.com/v3/search/images",
				$params,
			);
		}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);
	}
}
