1946 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1946 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| class ddg{
 | |
| 	
 | |
| 	public function __construct(){
 | |
| 		
 | |
| 		include "lib/backend.php";
 | |
| 		$this->backend = new backend("ddg");
 | |
| 		
 | |
| 		include "lib/fuckhtml.php";
 | |
| 		$this->fuckhtml = new fuckhtml();
 | |
| 	}
 | |
| 	
 | |
| 	/*
 | |
| 		curl functions
 | |
| 	*/
 | |
| 	private const req_web = 0;
 | |
| 	private const req_xhr = 1;
 | |
| 	
 | |
| 	private function get($proxy, $url, $get = [], $reqtype = self::req_web){
 | |
| 		
 | |
| 		$curlproc = curl_init();
 | |
| 		
 | |
| 		if($get !== []){
 | |
| 			$get = http_build_query($get);
 | |
| 			$url .= "?" . $get;
 | |
| 		}
 | |
| 		
 | |
| 		curl_setopt($curlproc, CURLOPT_URL, $url);
 | |
| 		
 | |
| 		// http2 bypass
 | |
| 		curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
 | |
| 		
 | |
| 		switch($reqtype){
 | |
| 			case self::req_web:
 | |
| 				$headers =
 | |
| 					["User-Agent: " . config::USER_AGENT,
 | |
| 					"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
 | |
| 					"Accept-Language: en-US,en;q=0.5",
 | |
| 					"Accept-Encoding: gzip",
 | |
| 					"DNT: 1",
 | |
| 					"Sec-GPC: 1",
 | |
| 					"Connection: keep-alive",
 | |
| 					"Upgrade-Insecure-Requests: 1",
 | |
| 					"Sec-Fetch-Dest: document",
 | |
| 					"Sec-Fetch-Mode: navigate",
 | |
| 					"Sec-Fetch-Site: same-origin",
 | |
| 					"Sec-Fetch-User: ?1",
 | |
| 					"Priority: u=0, i",
 | |
| 					"TE: trailers"];
 | |
| 				break;
 | |
| 			
 | |
| 			case self::req_xhr:
 | |
| 				$headers =
 | |
| 					["User-Agent: " . config::USER_AGENT,
 | |
| 					"Accept: */*",
 | |
| 					"Accept-Language: en-US,en;q=0.5",
 | |
| 					"Accept-Encoding: gzip",
 | |
| 					"Referer: https://duckduckgo.com/",
 | |
| 					"DNT: 1",
 | |
| 					"Sec-GPC: 1",
 | |
| 					"Connection: keep-alive",
 | |
| 					"Sec-Fetch-Dest: script",
 | |
| 					"Sec-Fetch-Mode: no-cors",
 | |
| 					"Sec-Fetch-Site: same-site",
 | |
| 					"Priority: u=1"];
 | |
| 				break;
 | |
| 		}
 | |
| 		
 | |
| 		$this->backend->assign_proxy($curlproc, $proxy);
 | |
| 		
 | |
| 		curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
 | |
| 		curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
 | |
| 		
 | |
| 		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);
 | |
| 		
 | |
| 		$data = curl_exec($curlproc);
 | |
| 		
 | |
| 		if(curl_errno($curlproc)){
 | |
| 			throw new Exception(curl_error($curlproc));
 | |
| 		}
 | |
| 		
 | |
| 		curl_close($curlproc);
 | |
| 		return $data;
 | |
| 	}
 | |
| 	
 | |
| 	public function getfilters($pagetype){
 | |
| 		
 | |
| 		$base = [
 | |
| 			"country" => [
 | |
| 				"display" => "Country",
 | |
| 				"option" => [
 | |
| 					"us-en" => "US (English)",
 | |
| 					"ar-es" => "Argentina",
 | |
| 					"au-en" => "Australia",
 | |
| 					"at-de" => "Austria",
 | |
| 					"be-fr" => "Belgium (fr)",
 | |
| 					"be-nl" => "Belgium (nl)",
 | |
| 					"br-pt" => "Brazil",
 | |
| 					"bg-bg" => "Bulgaria",
 | |
| 					"ca-en" => "Canada (en)",
 | |
| 					"ca-fr" => "Canada (fr)",
 | |
| 					"ct-ca" => "Catalonia",
 | |
| 					"cl-es" => "Chile",
 | |
| 					"cn-zh" => "China",
 | |
| 					"co-es" => "Colombia",
 | |
| 					"hr-hr" => "Croatia",
 | |
| 					"cz-cs" => "Czech Republic",
 | |
| 					"dk-da" => "Denmark",
 | |
| 					"ee-et" => "Estonia",
 | |
| 					"fi-fi" => "Finland",
 | |
| 					"fr-fr" => "France",
 | |
| 					"de-de" => "Germany",
 | |
| 					"gr-el" => "Greece",
 | |
| 					"hk-tzh" => "Hong Kong",
 | |
| 					"hu-hu" => "Hungary",
 | |
| 					"in-en" => "India (en)",
 | |
| 					"id-en" => "Indonesia (en)",
 | |
| 					"ie-en" => "Ireland",
 | |
| 					"il-en" => "Israel (en)",
 | |
| 					"it-it" => "Italy",
 | |
| 					"jp-jp" => "Japan",
 | |
| 					"kr-kr" => "Korea",
 | |
| 					"lv-lv" => "Latvia",
 | |
| 					"lt-lt" => "Lithuania",
 | |
| 					"my-en" => "Malaysia (en)",
 | |
| 					"mx-es" => "Mexico",
 | |
| 					"nl-nl" => "Netherlands",
 | |
| 					"nz-en" => "New Zealand",
 | |
| 					"no-no" => "Norway",
 | |
| 					"pk-en" => "Pakistan (en)",
 | |
| 					"pe-es" => "Peru",
 | |
| 					"ph-en" => "Philippines (en)",
 | |
| 					"pl-pl" => "Poland",
 | |
| 					"pt-pt" => "Portugal",
 | |
| 					"ro-ro" => "Romania",
 | |
| 					"ru-ru" => "Russia",
 | |
| 					"xa-ar" => "Saudi Arabia",
 | |
| 					"sg-en" => "Singapore",
 | |
| 					"sk-sk" => "Slovakia",
 | |
| 					"sl-sl" => "Slovenia",
 | |
| 					"za-en" => "South Africa",
 | |
| 					"es-ca" => "Spain (ca)",
 | |
| 					"es-es" => "Spain (es)",
 | |
| 					"se-sv" => "Sweden",
 | |
| 					"ch-de" => "Switzerland (de)",
 | |
| 					"ch-fr" => "Switzerland (fr)",
 | |
| 					"tw-tzh" => "Taiwan",
 | |
| 					"th-en" => "Thailand (en)",
 | |
| 					"tr-tr" => "Turkey",
 | |
| 					"us-es" => "US (Spanish)",
 | |
| 					"ua-uk" => "Ukraine",
 | |
| 					"uk-en" => "United Kingdom",
 | |
| 					"vn-en" => "Vietnam (en)"
 | |
| 				]
 | |
| 			]
 | |
| 		];
 | |
| 		
 | |
| 		switch($pagetype){
 | |
| 			
 | |
| 			case "web":
 | |
| 				$base["country"]["option"] =
 | |
| 					array_merge(["any" => "All Regions"], $base["country"]["option"]);
 | |
| 				
 | |
| 				return array_merge($base,
 | |
| 					[
 | |
| 						"nsfw" => [
 | |
| 							"display" => "NSFW",
 | |
| 							"option" => [
 | |
| 								"yes" => "Yes",
 | |
| 								"maybe" => "Maybe",
 | |
| 								"no" => "No"
 | |
| 							]
 | |
| 						],
 | |
| 						"newer" => [
 | |
| 							"display" => "Newer than",
 | |
| 							"option" => "_DATE"
 | |
| 						],
 | |
| 						"older" => [
 | |
| 							"display" => "Older than",
 | |
| 							"option" => "_DATE"
 | |
| 						],
 | |
| 						"extendedsearch" => [
 | |
| 							// undefined display
 | |
| 							"option" => [
 | |
| 								"yes" => "Yes",
 | |
| 								"no" => "No",
 | |
| 							]
 | |
| 						]
 | |
| 					]
 | |
| 				);
 | |
| 				break;
 | |
| 			
 | |
| 			case "images":
 | |
| 				return array_merge($base,
 | |
| 					[
 | |
| 						"nsfw" => [
 | |
| 							"display" => "NSFW",
 | |
| 							"option" => [
 | |
| 								"yes" => "Yes",
 | |
| 								"no" => "No"
 | |
| 							]
 | |
| 						],
 | |
| 						"date" => [
 | |
| 							"display" => "Time posted",
 | |
| 							"option" => [
 | |
| 								"any" => "Any time",
 | |
| 								"Day" => "Past day",
 | |
| 								"Week" => "Past week",
 | |
| 								"Month" => "Past month"
 | |
| 							]
 | |
| 						],
 | |
| 						"size" => [
 | |
| 							"display" => "Size",
 | |
| 							"option" => [
 | |
| 								"any" => "Any size",
 | |
| 								"Small" => "Small",
 | |
| 								"Medium" => "Medium",
 | |
| 								"Large" => "Large",
 | |
| 								"Wallpaper" => "Wallpaper"
 | |
| 							]
 | |
| 						],
 | |
| 						"color" => [
 | |
| 							"display" => "Colors",
 | |
| 							"option" => [
 | |
| 								"any" => "All colors",
 | |
| 								"Monochrome" => "Black and white",
 | |
| 								"Red" => "Red",
 | |
| 								"Orange" => "Orange",
 | |
| 								"Yellow" => "Yellow",
 | |
| 								"Green" => "Green",
 | |
| 								"Blue" => "Blue",
 | |
| 								"Purple" => "Purple",
 | |
| 								"Pink" => "Pink",
 | |
| 								"Brown" => "Brown",
 | |
| 								"Black" => "Black",
 | |
| 								"Gray" => "Gray",
 | |
| 								"Teal" => "Teal",
 | |
| 								"White" => "White"
 | |
| 							]
 | |
| 						],
 | |
| 						"type" => [
 | |
| 							"display" => "Type",
 | |
| 							"option" => [
 | |
| 								"any" => "All types",
 | |
| 								"photo" => "Photograph",
 | |
| 								"clipart" => "Clipart",
 | |
| 								"gif" => "Animated GIF",
 | |
| 								"transparent" => "Transparent"
 | |
| 							]
 | |
| 						],
 | |
| 						"layout" => [
 | |
| 							"display" => "Layout",
 | |
| 							"option" => [
 | |
| 								"any" => "All layouts",
 | |
| 								"Square" => "Square",
 | |
| 								"Tall" => "Tall",
 | |
| 								"Wide" => "Wide"
 | |
| 							]
 | |
| 						],
 | |
| 						"license" => [
 | |
| 							"display" => "License",
 | |
| 							"option" => [
 | |
| 								"any" => "All licenses",
 | |
| 								"Any" => "All Creative Commons",
 | |
| 								"Public" => "Public domain",
 | |
| 								"Share" => "Free to Share and Use",
 | |
| 								"ShareCommercially" => "Free to Share and Use Commercially",
 | |
| 								"Modify" => "Free to Modify, Share, and Use",
 | |
| 								"ModifyCommercially" => "Free to Modify, Share, and Use Commercially"
 | |
| 							]
 | |
| 						]
 | |
| 					]
 | |
| 				);
 | |
| 				break;
 | |
| 			
 | |
| 			case "videos":
 | |
| 				return array_merge($base,
 | |
| 					[
 | |
| 						"nsfw" => [
 | |
| 							"display" => "NSFW",
 | |
| 							"option" => [
 | |
| 								"yes" => "Yes",
 | |
| 								"no" => "No"
 | |
| 							]
 | |
| 						],
 | |
| 						"date" => [
 | |
| 							"display" => "Time fetched",
 | |
| 							"option" => [
 | |
| 								"any" => "Any time",
 | |
| 								"d" => "Past day",
 | |
| 								"w" => "Past week",
 | |
| 								"m" => "Past month"
 | |
| 							]
 | |
| 						],
 | |
| 						"resolution" => [ //videoDefinition
 | |
| 							"display" => "Resolution",
 | |
| 							"option" => [
 | |
| 								"any" => "Any resolution",
 | |
| 								"high" => "High definition",
 | |
| 								"standard" => "Standard definition"
 | |
| 							]
 | |
| 						],
 | |
| 						"duration" => [ // videoDuration
 | |
| 							"display" => "Duration",
 | |
| 							"option" => [
 | |
| 								"any" => "Any duration",
 | |
| 								"short" => "Short (>5min)",
 | |
| 								"medium" => "Medium (5-20min)",
 | |
| 								"long" => "Long (<20min)"
 | |
| 							]
 | |
| 						],
 | |
| 						"license" => [
 | |
| 							"display" => "License",
 | |
| 							"option" => [
 | |
| 								"any" => "Any license",
 | |
| 								"creativeCommon" => "Creative Commons",
 | |
| 								"youtube" => "YouTube Standard"
 | |
| 							]
 | |
| 						]
 | |
| 					]
 | |
| 				);
 | |
| 				break;
 | |
| 				
 | |
| 			case "news":
 | |
| 				return array_merge($base,
 | |
| 					[
 | |
| 						"nsfw" => [
 | |
| 							"display" => "NSFW",
 | |
| 							"option" => [
 | |
| 								"yes" => "Yes",
 | |
| 								"maybe" => "Maybe",
 | |
| 								"no" => "No"
 | |
| 							]
 | |
| 						],
 | |
| 						"date" => [
 | |
| 							"display" => "Time posted",
 | |
| 							"option" => [
 | |
| 								"any" => "Any time",
 | |
| 								"d" => "Past day",
 | |
| 								"w" => "Past week",
 | |
| 								"m" => "Past month"
 | |
| 							]
 | |
| 						]
 | |
| 					]
 | |
| 				);
 | |
| 				break;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	public function web($get){
 | |
| 		
 | |
| 		$out = [
 | |
| 			"status" => "ok",
 | |
| 			"spelling" => [
 | |
| 				"type" => "no_correction",
 | |
| 				"using" => null,
 | |
| 				"correction" => null
 | |
| 			],
 | |
| 			"npt" => null,
 | |
| 			"answer" => [],
 | |
| 			"web" => [],
 | |
| 			"image" => [],
 | |
| 			"video" => [],
 | |
| 			"news" => [],
 | |
| 			"related" => []
 | |
| 		];
 | |
| 		
 | |
| 		if($get["npt"]){
 | |
| 			
 | |
| 			[$js_link, $proxy] = $this->backend->get($get["npt"], "web");
 | |
| 			$js_link = "https://links.duckduckgo.com" . $js_link;
 | |
| 			
 | |
| 			$html = "";
 | |
| 			$get["extendedsearch"] = "no";
 | |
| 			
 | |
| 		}else{
 | |
| 			if(strlen($get["s"]) === 0){
 | |
| 				
 | |
| 				throw new Exception("Search term is empty!");
 | |
| 			}
 | |
| 			
 | |
| 			$proxy = $this->backend->get_ip();
 | |
| 			
 | |
| 			// generate filters
 | |
| 			$get_filters = [
 | |
| 				"q" => $get["s"]
 | |
| 			];
 | |
| 			
 | |
| 			if($get["country"] == "any"){
 | |
| 				
 | |
| 				$get_filters["kl"] = "wt-wt";
 | |
| 			}else{
 | |
| 				
 | |
| 				$get_filters["kl"] = $get["country"];
 | |
| 			}
 | |
| 			
 | |
| 			switch($get["nsfw"]){
 | |
| 				
 | |
| 				case "yes": $get_filters["kp"] = "-2"; break;
 | |
| 				case "maybe": $get_filters["kp"] = "-1"; break;
 | |
| 				case "no": $get_filters["kp"] = "1"; break;
 | |
| 			}
 | |
| 			
 | |
| 			$df = true;
 | |
| 			
 | |
| 			if($get["newer"] === false){
 | |
| 				
 | |
| 				if($get["older"] !== false){
 | |
| 					
 | |
| 					$start = 36000;
 | |
| 					$end = $get["older"];
 | |
| 				}else{
 | |
| 					
 | |
| 					$df = false;
 | |
| 				}
 | |
| 			}else{
 | |
| 				
 | |
| 				$start = $get["newer"];
 | |
| 				
 | |
| 				if($get["older"] !== false){
 | |
| 					
 | |
| 					$end = $get["older"];
 | |
| 				}else{
 | |
| 					
 | |
| 					$end = time();
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			if($df === true){
 | |
| 				$get_filters["df"] = date("Y-m-d", $start) . ".." . date("Y-m-d", $end);
 | |
| 			}
 | |
| 			
 | |
| 			//
 | |
| 			// Get HTML
 | |
| 			//
 | |
| 			try{
 | |
| 				$html = $this->get(
 | |
| 					$proxy,
 | |
| 					"https://duckduckgo.com/",
 | |
| 					$get_filters
 | |
| 				);
 | |
| 			}catch(Exception $e){
 | |
| 				
 | |
| 				throw new Exception("Failed to fetch search page");
 | |
| 			}
 | |
| 			
 | |
| 			$this->fuckhtml->load($html);
 | |
| 			
 | |
| 			$script =
 | |
| 				$this->fuckhtml
 | |
| 				->getElementById(
 | |
| 					"deep_preload_link",
 | |
| 					"link"
 | |
| 				);
 | |
| 			
 | |
| 			if(
 | |
| 				$script === null ||
 | |
| 				!isset($script["attributes"]["href"])
 | |
| 			){
 | |
| 				
 | |
| 				throw new Exception("Failed to grep d.js");
 | |
| 			}
 | |
| 			
 | |
| 			$js_link =
 | |
| 				$this->fuckhtml
 | |
| 				->getTextContent(
 | |
| 					$script["attributes"]["href"]
 | |
| 				);
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get d.js
 | |
| 		//
 | |
| 		try{
 | |
| 			$js = $this->get(
 | |
| 				$proxy,
 | |
| 				$js_link,
 | |
| 				[],
 | |
| 				ddg::req_xhr
 | |
| 			);
 | |
| 			
 | |
| 		}catch(Exception $e){
 | |
| 			
 | |
| 			throw new Exception("Failed to fetch d.js");
 | |
| 		}
 | |
| 		
 | |
| 		//echo htmlspecialchars($js);
 | |
| 		
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.pageLayout\.load\(\s*\'d\'\s*,\s*/',
 | |
| 				$js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) <= 1){
 | |
| 			
 | |
| 			throw new Exception("Failed to grep pageLayout(d)");
 | |
| 		}
 | |
| 		
 | |
| 		$json =
 | |
| 			json_decode(
 | |
| 				$this->fuckhtml
 | |
| 				->extract_json(
 | |
| 					$js_tmp[1]
 | |
| 				),
 | |
| 				true
 | |
| 			);
 | |
| 		
 | |
| 		if($json === null){
 | |
| 			
 | |
| 			throw new Exception("Failed to decode JSON");
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get search results + NPT token
 | |
| 		//
 | |
| 		foreach($json as $item){
 | |
| 			
 | |
| 			if(isset($item["c"])){
 | |
| 				
 | |
| 				$table = [];
 | |
| 				
 | |
| 				// get youtube video information
 | |
| 				if(isset($item["video"]["thumbnail_url_template"])){
 | |
| 					
 | |
| 					$thumb =
 | |
| 						[
 | |
| 							"ratio" => "16:9",
 | |
| 							"url" => $this->bingimg($item["video"]["thumbnail_url_template"])
 | |
| 						];
 | |
| 				}else{
 | |
| 					
 | |
| 					$thumb =
 | |
| 						[
 | |
| 							"ratio" => null,
 | |
| 							"url" => null
 | |
| 						];
 | |
| 				}
 | |
| 				
 | |
| 				// get table items
 | |
| 				if(isset($item["rf"])){
 | |
| 					
 | |
| 					foreach($item["rf"] as $hint){
 | |
| 						
 | |
| 						if(
 | |
| 							!isset($hint["label"]["text"]) ||
 | |
| 							!isset($hint["items"][0]["text"])
 | |
| 						){
 | |
| 							
 | |
| 							continue;
 | |
| 						}
 | |
| 						
 | |
| 						$text = [];
 | |
| 						
 | |
| 						foreach($hint["items"] as $text_part){
 | |
| 							
 | |
| 							$text[] = $text_part["text"];
 | |
| 						}
 | |
| 						
 | |
| 						$text = implode(", ", $text);
 | |
| 						
 | |
| 						if(is_numeric($text)){
 | |
| 							
 | |
| 							$text = number_format((string)$text);
 | |
| 						}
 | |
| 						
 | |
| 						$table[$hint["label"]["text"]] = $text;
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 				// get ratings
 | |
| 				if(isset($item["ar"])){
 | |
| 					
 | |
| 					foreach($item["ar"] as $rating){
 | |
| 						
 | |
| 						if(
 | |
| 							isset($rating["aggregateRating"]["bestRating"]) &&
 | |
| 							isset($rating["aggregateRating"]["ratingValue"])
 | |
| 						){
 | |
| 							
 | |
| 							$text = $rating["aggregateRating"]["ratingValue"] . "/" . $rating["aggregateRating"]["bestRating"];
 | |
| 							
 | |
| 							if(isset($rating["aggregateRating"]["reviewCount"])){
 | |
| 								
 | |
| 								$text .= " (" . number_format($rating["aggregateRating"]["reviewCount"]) . " votes)";
 | |
| 							}
 | |
| 							
 | |
| 							$table["Rating"] = $text;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 				// get sublinks
 | |
| 				$sublinks = [];
 | |
| 				
 | |
| 				if(isset($item["l"])){
 | |
| 					
 | |
| 					foreach($item["l"] as $sublink){
 | |
| 						
 | |
| 						$sublinks[] = [
 | |
| 							"title" => $this->titledots($sublink["text"]),
 | |
| 							"description" => $this->titledots($sublink["snippet"]),
 | |
| 							"url" => $sublink["targetUrl"],
 | |
| 							"date" => null
 | |
| 						];
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 				// parse search result
 | |
| 				$out["web"][] = [
 | |
| 					"title" =>
 | |
| 						$this->titledots(
 | |
| 							$this->fuckhtml
 | |
| 							->getTextContent(
 | |
| 								$item["t"]
 | |
| 							)
 | |
| 						),
 | |
| 					"description" =>
 | |
| 						isset($item["a"]) ?
 | |
| 						$this->titledots(
 | |
| 							$this->fuckhtml
 | |
| 							->getTextContent(
 | |
| 								$item["a"]
 | |
| 							)
 | |
| 						) : null,
 | |
| 					"url" => $this->unshiturl($item["c"]),
 | |
| 					"date" =>
 | |
| 						isset($item["e"]) ?
 | |
| 						strtotime($item["e"]) : null,
 | |
| 					"type" => "web",
 | |
| 					"thumb" => $thumb,
 | |
| 					"sublink" => $sublinks,
 | |
| 					"table" => $table
 | |
| 				];
 | |
| 				continue;
 | |
| 			}
 | |
| 			
 | |
| 			if(isset($item["n"])){
 | |
| 				
 | |
| 				// get NPT
 | |
| 				$out["npt"] =
 | |
| 					$this->backend->store(
 | |
| 						$item["n"],
 | |
| 						"web",
 | |
| 						$proxy
 | |
| 					);
 | |
| 				continue;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get spelling
 | |
| 		//
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.page\.showMessage\(\s*\'spelling\'\s*,\s*/',
 | |
| 				$js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) > 1){
 | |
| 			
 | |
| 			$json =
 | |
| 				json_decode(
 | |
| 					$this->fuckhtml
 | |
| 					->extract_json(
 | |
| 						$js_tmp[1]
 | |
| 					),
 | |
| 					true
 | |
| 				);
 | |
| 			
 | |
| 			if($json !== null){
 | |
| 				
 | |
| 				// parse spelling
 | |
| 				// qc=2: including
 | |
| 				
 | |
| 				switch((int)$json["qc"]){
 | |
| 					
 | |
| 					case 2:
 | |
| 						$type = "including";
 | |
| 						break;
 | |
| 					
 | |
| 					default:
 | |
| 						$type = "not_many";
 | |
| 						break;
 | |
| 				}
 | |
| 				
 | |
| 				$out["spelling"] = [
 | |
| 					"type" => $type,
 | |
| 					"using" =>
 | |
| 						$this->fuckhtml
 | |
| 						->getTextContent(
 | |
| 							$json["suggestion"]
 | |
| 						),
 | |
| 					"correction" => $json["recourseText"]
 | |
| 				];
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get images
 | |
| 		//
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.duckbar\.load\(\s*\'images\'\s*,\s*/',
 | |
| 				$js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) > 1){
 | |
| 			
 | |
| 			$json =
 | |
| 				json_decode(
 | |
| 					$this->fuckhtml
 | |
| 					->extract_json(
 | |
| 						$js_tmp[1]
 | |
| 					),
 | |
| 					true
 | |
| 				);
 | |
| 			
 | |
| 			if($json !== null){
 | |
| 				
 | |
| 				foreach($json["results"] as $image){
 | |
| 					
 | |
| 					$ratio = $this->bingratio((int)$image["width"], (int)$image["height"]);
 | |
| 					
 | |
| 					$out["image"][] = [
 | |
| 						"title" => $image["title"],
 | |
| 						"source" =>	[
 | |
| 							[
 | |
| 								"url" => $image["image"],
 | |
| 								"width" => (int)$image["width"],
 | |
| 								"height" => (int)$image["height"]
 | |
| 							],
 | |
| 							[
 | |
| 								"url" => $this->bingimg($image["thumbnail"]),
 | |
| 								"width" => $ratio[0],
 | |
| 								"height" => $ratio[1]
 | |
| 							]
 | |
| 						],
 | |
| 						"url" => $this->unshiturl($image["url"])
 | |
| 					];
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get videos
 | |
| 		//
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.duckbar\.load\(\s*\'videos\'\s*,\s*/',
 | |
| 				$js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) > 1){
 | |
| 			
 | |
| 			$json =
 | |
| 				json_decode(
 | |
| 					$this->fuckhtml
 | |
| 					->extract_json(
 | |
| 						$js_tmp[1]
 | |
| 					),
 | |
| 					true
 | |
| 				);
 | |
| 			
 | |
| 			if($json !== null){
 | |
| 				
 | |
| 				foreach($json["results"] as $video){
 | |
| 					
 | |
| 					$thumb = [
 | |
| 						"ratio" => null,
 | |
| 						"url" => null
 | |
| 					];
 | |
| 					
 | |
| 					foreach(["large", "medium", "small"] as $contender){
 | |
| 						
 | |
| 						if(isset($video["images"][$contender])){
 | |
| 							
 | |
| 							$thumb = [
 | |
| 								"ratio" => "16:9",
 | |
| 								"url" => $this->bingimg($video["images"][$contender])
 | |
| 							];
 | |
| 							break;
 | |
| 						}
 | |
| 					}
 | |
| 					
 | |
| 					$out["video"][] = [
 | |
| 						"title" => $this->titledots($video["title"]),
 | |
| 						"description" =>
 | |
| 							$video["description"] != "" ?
 | |
| 							$this->titledots($video["description"]) : null,
 | |
| 						"date" => 
 | |
| 							isset($video["published"]) ?
 | |
| 							strtotime($video["published"]) : null,
 | |
| 						"duration" =>
 | |
| 							$video["duration"] != "" ?
 | |
| 							$this->hms2int($video["duration"]) : null,
 | |
| 						"views" =>
 | |
| 							isset($video["statistics"]["viewCount"]) ?
 | |
| 							(int)$video["statistics"]["viewCount"] : null,
 | |
| 						"thumb" => $thumb,
 | |
| 						"url" => $this->unshiturl($video["content"])
 | |
| 					];
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get news
 | |
| 		//
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.duckbar\.load\(\s*\'news\'\s*,\s*/',
 | |
| 				$js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) > 1){
 | |
| 			
 | |
| 			$json =
 | |
| 				json_decode(
 | |
| 					$this->fuckhtml
 | |
| 					->extract_json(
 | |
| 						$js_tmp[1]
 | |
| 					),
 | |
| 					true
 | |
| 				);
 | |
| 			
 | |
| 			if($json !== null){
 | |
| 				
 | |
| 				foreach($json["results"] as $news){
 | |
| 					
 | |
| 					if(isset($news["image"])){
 | |
| 						
 | |
| 						$thumb = [
 | |
| 							"ratio" => "16:9",
 | |
| 							"url" => $news["image"]
 | |
| 						];
 | |
| 					}else{
 | |
| 						
 | |
| 						$thumb = [
 | |
| 							"ratio" => null,
 | |
| 							"url" => null
 | |
| 						];
 | |
| 					}
 | |
| 					
 | |
| 					$out["news"][] = [
 | |
| 						"title" => $news["title"],
 | |
| 						"description" =>
 | |
| 							$this->fuckhtml
 | |
| 							->getTextContent(
 | |
| 								$news["excerpt"]
 | |
| 							),
 | |
| 						"date" => (int)$news["date"],
 | |
| 						"thumb" => $thumb,
 | |
| 						"url" => $news["url"]
 | |
| 					];
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get related searches
 | |
| 		//
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.duckbar\.loadModule\(\s*\'related_searches\'\s*,\s*/',
 | |
| 				$js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) > 1){
 | |
| 			
 | |
| 			$json =
 | |
| 				json_decode(
 | |
| 					$this->fuckhtml
 | |
| 					->extract_json(
 | |
| 						$js_tmp[1]
 | |
| 					),
 | |
| 					true
 | |
| 				);
 | |
| 			
 | |
| 			if($json !== null){
 | |
| 				
 | |
| 				foreach($json["results"] as $related){
 | |
| 					
 | |
| 					$out["related"][] = $related["text"];
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get instant answers
 | |
| 		//
 | |
| 		$js_tmp =
 | |
| 			preg_split(
 | |
| 				'/DDG\.duckbar\.add\(\s*/',
 | |
| 				$html . $js,
 | |
| 				2
 | |
| 			);
 | |
| 		
 | |
| 		if(count($js_tmp) > 1){
 | |
| 			
 | |
| 			$json =
 | |
| 				json_decode(
 | |
| 					$this->fuckhtml
 | |
| 					->extract_json(
 | |
| 						$js_tmp[1]
 | |
| 					),
 | |
| 					true
 | |
| 				);
 | |
| 			
 | |
| 			if($json !== null){
 | |
| 				
 | |
| 				$json = $json["data"];
 | |
| 				$table = [];
 | |
| 				$sublinks = [];
 | |
| 				$description = [];
 | |
| 				
 | |
| 				// get official website
 | |
| 				if(
 | |
| 					isset($json["OfficialWebsite"]) &&
 | |
| 					$json["OfficialWebsite"] !== null
 | |
| 				){
 | |
| 					
 | |
| 					$sublinks["Website"] = $json["OfficialWebsite"];
 | |
| 				}
 | |
| 				
 | |
| 				// get sublinks & table elements
 | |
| 				if(isset($json["Infobox"]["content"])){
 | |
| 					foreach($json["Infobox"]["content"] as $info){
 | |
| 						
 | |
| 						if($info["data_type"] == "string"){
 | |
| 							
 | |
| 							// add table element
 | |
| 							$table[$info["label"]] = $info["value"];
 | |
| 							continue;
 | |
| 						}
 | |
| 						
 | |
| 						if($info["data_type"] == "wd_description"){
 | |
| 							
 | |
| 							$description[] = [
 | |
| 								"type" => "quote",
 | |
| 								"value" => $info["value"]
 | |
| 							];
 | |
| 							continue;
 | |
| 						}
 | |
| 						
 | |
| 						// add sublink
 | |
| 						switch($info["data_type"]){
 | |
| 							
 | |
| 							case "official_site":
 | |
| 							case "official_website":
 | |
| 								$type = "Website";
 | |
| 								break;
 | |
| 							
 | |
| 							case "wikipedia": $type = "Wikipedia"; break;
 | |
| 							case "itunes": $type = "iTunes"; break;
 | |
| 							case "amazon": $type = "Amazon"; break;
 | |
| 							
 | |
| 							case "imdb_title_id":
 | |
| 							case "imdb_id":
 | |
| 							case "imdb_name_id":
 | |
| 								$type = "IMDb";
 | |
| 								$delim = substr($info["value"], 0, 2);
 | |
| 								
 | |
| 								if($delim == "nm"){
 | |
| 									
 | |
| 									$prefix = "https://www.imdb.com/name/";
 | |
| 								}elseif($delim == "tt"){
 | |
| 									
 | |
| 									$prefix = "https://www.imdb.com/title/";
 | |
| 								}elseif($delim == "co"){
 | |
| 									
 | |
| 									$prefix = "https://www.imdb.com/search/title/?companies=";
 | |
| 								}else{
 | |
| 									
 | |
| 									$prefix = "https://www.imdb.com/title/";
 | |
| 								}
 | |
| 								break;
 | |
| 
 | |
| 							case "imdb_name_id": $prefix = "https://www.imdb.com/name/"; $type = "IMDb"; break;
 | |
| 							case "twitter_profile": $prefix = "https://twitter.com/"; $type = "Twitter"; break;
 | |
| 							case "instagram_profile": $prefix = "https://instagram.com/"; $type = "Instagram"; break;
 | |
| 							case "facebook_profile": $prefix = "https://facebook.com/"; $type = "Facebook"; break;
 | |
| 							case "spotify_artist_id": $prefix = "https://open.spotify.com/artist/"; $type = "Spotify"; break;
 | |
| 							case "itunes_artist_id": $prefix = "https://music.apple.com/us/artist/"; $type = "iTunes"; break;
 | |
| 							case "rotten_tomatoes": $prefix = "https://rottentomatoes.com/"; $type = "Rotten Tomatoes"; break;
 | |
| 							case "youtube_channel": $prefix = "https://youtube.com/channel/"; $type = "YouTube"; break;
 | |
| 							case "soundcloud_id": $prefix = "https://soundcloud.com/"; $type = "SoundCloud"; break;
 | |
| 							
 | |
| 							default:							
 | |
| 								$prefix = null;
 | |
| 								$type = false;
 | |
| 						}
 | |
| 						
 | |
| 						if($type !== false){
 | |
| 							
 | |
| 							if($prefix === null){
 | |
| 								
 | |
| 								$sublinks[$type] = $info["value"];
 | |
| 							}else{
 | |
| 								
 | |
| 								$sublinks[$type] = $prefix . $info["value"];
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 				if(isset($json["Abstract"])){
 | |
| 					
 | |
| 					$description[] =
 | |
| 						[
 | |
| 							"type" => "text",
 | |
| 							"value" => $json["Abstract"]
 | |
| 						];
 | |
| 				}
 | |
| 				
 | |
| 				$out["answer"][] = [
 | |
| 					"title" => $json["Heading"],
 | |
| 					"description" => $description,
 | |
| 					"url" => $json["AbstractURL"],
 | |
| 					"thumb" =>
 | |
| 						(isset($json["Image"]) && $json["Image"]) !== null ?
 | |
| 						"https://duckduckgo.com" . $json["Image"] : null,
 | |
| 					"table" => $table,
 | |
| 					"sublink" => $sublinks
 | |
| 				];
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		if($get["extendedsearch"] == "no"){
 | |
| 			
 | |
| 			return $out;
 | |
| 		}
 | |
| 		
 | |
| 		//
 | |
| 		// Get wordnik definition
 | |
| 		//
 | |
| 		//nrj('/js/spice/dictionary/definition/create', null, null, null, null, 'dictionary_definition');
 | |
| 		
 | |
| 		preg_match(
 | |
| 			'/nrj\(\s*\'([^\']+)\'/',
 | |
| 			$js,
 | |
| 			$nrj
 | |
| 		);
 | |
| 		
 | |
| 		if(isset($nrj[1])){
 | |
| 			
 | |
| 			$nrj = $nrj[1];
 | |
| 			
 | |
| 			preg_match(
 | |
| 				'/\/js\/spice\/dictionary\/definition\/([^\/]+)/',
 | |
| 				$nrj,
 | |
| 				$word
 | |
| 			);
 | |
| 			
 | |
| 			if(isset($word[1])){
 | |
| 				
 | |
| 				$word = $word[1];
 | |
| 				
 | |
| 				// found wordnik definition & word
 | |
| 				try{
 | |
| 					$nik =
 | |
| 						$this->get(
 | |
| 							$proxy,
 | |
| 							"https://duckduckgo.com/js/spice/dictionary/definition/" . $word,
 | |
| 							[],
 | |
| 							ddg::req_xhr
 | |
| 						);
 | |
| 					
 | |
| 				}catch(Exception $e){
 | |
| 					
 | |
| 					// fail gracefully
 | |
| 					return $out;
 | |
| 				}
 | |
| 				
 | |
| 				// remove javascript
 | |
| 				$js_tmp =
 | |
| 					preg_split(
 | |
| 						'/ddg_spice_dictionary_definition\(\s*/',
 | |
| 						$nik,
 | |
| 						2
 | |
| 					);
 | |
| 				
 | |
| 				if(count($js_tmp) > 1){
 | |
| 					
 | |
| 					$nik =
 | |
| 						json_decode(
 | |
| 							$this->fuckhtml
 | |
| 							->extract_json(
 | |
| 								$js_tmp[1]
 | |
| 							),
 | |
| 							true
 | |
| 						);
 | |
| 				}
 | |
| 				
 | |
| 				if($nik === null){
 | |
| 					
 | |
| 					return $out;
 | |
| 				}
 | |
| 				
 | |
| 				$answer_cat = [];
 | |
| 				$answer = [];
 | |
| 				
 | |
| 				foreach($nik as $snippet){
 | |
| 					
 | |
| 					if(!isset($snippet["partOfSpeech"])){ continue; }
 | |
| 					
 | |
| 					$push = [];
 | |
| 					
 | |
| 					// add text snippet
 | |
| 					if(isset($snippet["text"])){
 | |
| 						
 | |
| 						$push[] = [
 | |
| 							"type" => "text",
 | |
| 							"value" =>
 | |
| 								$this->fuckhtml
 | |
| 								->getTextContent(
 | |
| 									$snippet["text"]
 | |
| 								)
 | |
| 						];
 | |
| 					}
 | |
| 					
 | |
| 					// add example uses
 | |
| 					if(isset($snippet["exampleUses"])){
 | |
| 						
 | |
| 						foreach($snippet["exampleUses"] as $example){
 | |
| 							
 | |
| 							$push[] = [
 | |
| 								"type" => "quote",
 | |
| 								"value" => "\"" .
 | |
| 									$this->fuckhtml
 | |
| 									->getTextContent(
 | |
| 										$example["text"]
 | |
| 									) . "\""
 | |
| 							];
 | |
| 						}
 | |
| 					}
 | |
| 					
 | |
| 					// add citations
 | |
| 					if(isset($snippet["citations"])){
 | |
| 						
 | |
| 						foreach($snippet["citations"] as $citation){
 | |
| 							
 | |
| 							if(!isset($citation["cite"])){ continue; }
 | |
| 							
 | |
| 							$text =
 | |
| 								$this->fuckhtml
 | |
| 								->getTextContent(
 | |
| 									$citation["cite"]
 | |
| 								);
 | |
| 							
 | |
| 							if(isset($citation["source"])){
 | |
| 								
 | |
| 								$text .=
 | |
| 									" - " .
 | |
| 									$this->fuckhtml
 | |
| 									->getTextContent(
 | |
| 										$citation["source"]
 | |
| 									);
 | |
| 							}
 | |
| 							
 | |
| 							$push[] = [
 | |
| 								"type" => "quote",
 | |
| 								"value" => $text
 | |
| 							];
 | |
| 						}
 | |
| 					}
 | |
| 					
 | |
| 					// add related words
 | |
| 					if(isset($snippet["relatedWords"])){
 | |
| 						
 | |
| 						$relations = [];
 | |
| 						
 | |
| 						foreach($snippet["relatedWords"] as $related){
 | |
| 							
 | |
| 							$words = [];
 | |
| 							foreach($related["words"] as $wrd){
 | |
| 								
 | |
| 								$words[] =
 | |
| 									$this->fuckhtml
 | |
| 									->getTextContent(
 | |
| 										$wrd
 | |
| 									);
 | |
| 							}
 | |
| 							
 | |
| 							if(count($words) !== 0){
 | |
| 								
 | |
| 								$relations[ucfirst($related["relationshipType"]) . "s"] =
 | |
| 									implode(", ", $words);
 | |
| 							}
 | |
| 						}
 | |
| 						
 | |
| 						foreach($relations as $relation_title => $relation_content){
 | |
| 							
 | |
| 							$push[] = [
 | |
| 								"type" => "quote",
 | |
| 								"value" => $relation_title . ": " . $relation_content
 | |
| 							];
 | |
| 						}
 | |
| 					}
 | |
| 					
 | |
| 					
 | |
| 					if(count($push) !== 0){
 | |
| 						
 | |
| 						// push data to answer_cat
 | |
| 						if(!isset($answer_cat[$snippet["partOfSpeech"]])){
 | |
| 							
 | |
| 							$answer_cat[$snippet["partOfSpeech"]] = [];
 | |
| 						}
 | |
| 						
 | |
| 						$answer_cat[$snippet["partOfSpeech"]] =
 | |
| 							array_merge(
 | |
| 								$answer_cat[$snippet["partOfSpeech"]],
 | |
| 								$push
 | |
| 							);
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 				foreach($answer_cat as $answer_title => $answer_content){
 | |
| 					
 | |
| 					$i = 0;
 | |
| 					$answer[] = [
 | |
| 						"type" => "title",
 | |
| 						"value" => $answer_title
 | |
| 					];
 | |
| 					
 | |
| 					$old_type = $answer[count($answer) - 1]["type"];
 | |
| 					
 | |
| 					foreach($answer_content as $ans){
 | |
| 						
 | |
| 						if(
 | |
| 							$ans["type"] == "text" &&
 | |
| 							$old_type == "text"
 | |
| 						){
 | |
| 							
 | |
| 							$i++;
 | |
| 							$c = count($answer) - 1;
 | |
| 							
 | |
| 							// append text to existing textfield
 | |
| 							$answer[$c] = [
 | |
| 								"type" => "text",
 | |
| 								"value" => $answer[$c]["value"] . "\n" . $i . ". " . $ans["value"]
 | |
| 							];
 | |
| 							
 | |
| 						}elseif($ans["type"] == "text"){
 | |
| 							
 | |
| 							$i++;
 | |
| 							$answer[] = [
 | |
| 								"type" => "text",
 | |
| 								"value" => $i . ". " . $ans["value"]
 | |
| 							];
 | |
| 						}else{
 | |
| 							
 | |
| 							// append normally
 | |
| 							$answer[] = $ans;
 | |
| 						}
 | |
| 						
 | |
| 						$old_type = $ans["type"];
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 				// yeah.. sometimes duckduckgo doesnt give us a definition back
 | |
| 				if(count($answer) !== 0){
 | |
| 					
 | |
| 					$out["answer"][] = [
 | |
| 						"title" => ucfirst($word),
 | |
| 						"description" => $answer,
 | |
| 						"url" => "https://www.wordnik.com/words/" . $word,
 | |
| 						"thumb" => null,
 | |
| 						"table" => [],
 | |
| 						"sublink" => []
 | |
| 					];
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		return $out;
 | |
| 	}
 | |
| 	
 | |
| 	public function image($get){
 | |
| 		
 | |
| 		if($get["npt"]){
 | |
| 			
 | |
| 			[$js_link, $proxy] = $this->backend->get($get["npt"], "images");
 | |
| 			
 | |
| 		}else{
 | |
| 			if(strlen($get["s"]) === 0){
 | |
| 				
 | |
| 				throw new Exception("Search term is empty!");
 | |
| 			}
 | |
| 			
 | |
| 			$proxy = $this->backend->get_ip();
 | |
| 			
 | |
| 			$filters = [];
 | |
| 			
 | |
| 			if($get["date"] != "any"){ $filters[] = "time:{$get["date"]}"; }
 | |
| 			if($get["size"] != "any"){ $filters[] = "size:{$get["size"]}"; }
 | |
| 			if($get["color"] != "any"){ $filters[] = "color:{$get["color"]}"; }
 | |
| 			if($get["type"] != "any"){ $filters[] = "type:{$get["type"]}"; }
 | |
| 			if($get["layout"] != "any"){ $filters[] = "layout:{$get["layout"]}"; }
 | |
| 			if($get["license"] != "any"){ $filters[] = "license:{$get["license"]}"; }
 | |
| 			
 | |
| 			$filters = implode(",", $filters);
 | |
| 			
 | |
| 			$get_filters = [
 | |
| 				"q" => $get["s"],
 | |
| 				"iax" => "images",
 | |
| 				"ia" => "images"
 | |
| 			];
 | |
| 			
 | |
| 			if($filters != ""){
 | |
| 				
 | |
| 				$get_filters["iaf"] = $filters;
 | |
| 			}
 | |
| 			
 | |
| 			$nsfw = $get["nsfw"] == "yes" ? "-2" : "-1";
 | |
| 			$get_filters["kp"] = $nsfw;
 | |
| 			
 | |
| 			try{
 | |
| 				
 | |
| 				$html = $this->get(
 | |
| 					$proxy,
 | |
| 					"https://duckduckgo.com",
 | |
| 					$get_filters,
 | |
| 					ddg::req_web
 | |
| 				);
 | |
| 			}catch(Exception $err){
 | |
| 				
 | |
| 				throw new Exception("Failed to fetch search page");
 | |
| 			}
 | |
| 			
 | |
| 			preg_match(
 | |
| 				'/vqd="([0-9-]+)"/',
 | |
| 				$html,
 | |
| 				$vqd
 | |
| 			);
 | |
| 			
 | |
| 			if(!isset($vqd[1])){
 | |
| 				
 | |
| 				throw new Exception("Failed to grep VQD token");
 | |
| 			}
 | |
| 			
 | |
| 			$js_link =
 | |
| 				"i.js?" .
 | |
| 				http_build_query([
 | |
| 					"l" => $get["country"],
 | |
| 					"o" => "json",
 | |
| 					"q" => $get["s"],
 | |
| 					"vqd" => $vqd[1],
 | |
| 					"f" => $filters,
 | |
| 					"p" => $nsfw
 | |
| 				]);
 | |
| 		}
 | |
| 		
 | |
| 		try{
 | |
| 			
 | |
| 			$json =
 | |
| 				$this->get(
 | |
| 					$proxy,
 | |
| 					"https://duckduckgo.com/" . $js_link,
 | |
| 					[],
 | |
| 					ddg::req_xhr
 | |
| 				);
 | |
| 		}catch(Exception $error){
 | |
| 			
 | |
| 			throw new Exception("Failed to get i.js");
 | |
| 		}
 | |
| 		
 | |
| 		$json = json_decode($json, true);
 | |
| 		
 | |
| 		if($json === null){
 | |
| 			
 | |
| 			throw new Exception("Failed to decode JSON");
 | |
| 		}
 | |
| 		
 | |
| 		$out = [
 | |
| 			"status" => "ok",
 | |
| 			"npt" => null,
 | |
| 			"image" => []
 | |
| 		];
 | |
| 		
 | |
| 		if(!isset($json["results"])){
 | |
| 			
 | |
| 			return $out;
 | |
| 		}
 | |
| 		
 | |
| 		// get npt
 | |
| 		if(
 | |
| 			isset($json["next"]) &&
 | |
| 			$json["next"] !== null
 | |
| 		){
 | |
| 			
 | |
| 			$vqd = null;
 | |
| 			
 | |
| 			if(isset($vqd[1])){
 | |
| 				
 | |
| 				$vqd = $vqd[1];
 | |
| 			}else{
 | |
| 				
 | |
| 				$vqd = array_values($json["vqd"]);
 | |
| 				
 | |
| 				if(count($vqd) > 0){
 | |
| 					
 | |
| 					$vqd = $vqd[0];
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			if($vqd !== null){
 | |
| 				
 | |
| 				$out["npt"] =
 | |
| 					$this->backend->store(
 | |
| 						$json["next"] . "&vqd=" . $vqd,
 | |
| 						"images",
 | |
| 						$proxy
 | |
| 					);
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// get images
 | |
| 		foreach($json["results"] as $image){
 | |
| 			
 | |
| 			$ratio =
 | |
| 				$this->bingratio(
 | |
| 					(int)$image["width"],
 | |
| 					(int)$image["height"]
 | |
| 				);
 | |
| 			
 | |
| 			$out["image"][] = [
 | |
| 				"title" => $this->titledots($image["title"]),
 | |
| 				"source" => [
 | |
| 					[
 | |
| 						"url" => $image["image"],
 | |
| 						"width" => (int)$image["width"],
 | |
| 						"height" => (int)$image["height"]
 | |
| 					],
 | |
| 					[
 | |
| 						"url" => $this->bingimg($image["thumbnail"]),
 | |
| 						"width" => $ratio[0],
 | |
| 						"height" => $ratio[1]
 | |
| 					]
 | |
| 				],
 | |
| 				"url" => $this->unshiturl($image["url"])
 | |
| 			];
 | |
| 		}
 | |
| 		
 | |
| 		return $out;
 | |
| 	}
 | |
| 	
 | |
| 	public function video($get){
 | |
| 		
 | |
| 		if($get["npt"]){
 | |
| 			
 | |
| 			[$js_link, $proxy] = $this->backend->get($get["npt"], "videos");
 | |
| 			
 | |
| 		}else{
 | |
| 			if(strlen($get["s"]) === 0){
 | |
| 				
 | |
| 				throw new Exception("Search term is empty!");
 | |
| 			}
 | |
| 			
 | |
| 			$proxy = $this->backend->get_ip();
 | |
| 			
 | |
| 			$get_filters = [
 | |
| 				"q" => $get["s"],
 | |
| 				"iax" => "videos",
 | |
| 				"ia" => "videos"
 | |
| 			];
 | |
| 			
 | |
| 			$nsfw = $get["nsfw"] == "yes" ? "-2" : "-1";
 | |
| 			$get_filters["kp"] = $nsfw;
 | |
| 			
 | |
| 			$filters = [];
 | |
| 			
 | |
| 			if($get["date"] != "any"){ $filters[] = "publishedAfter:{$date}"; }
 | |
| 			if($get["resolution"] != "any"){ $filters[] = "videoDefinition:{$resolution}"; }
 | |
| 			if($get["duration"] != "any"){ $filters[] = "videoDuration:{$duration}"; }
 | |
| 			if($get["license"] != "any"){ $filters[] = "videoLicense:{$license}"; }
 | |
| 			
 | |
| 			$filters = implode(",", $filters);
 | |
| 			
 | |
| 			if($filters != ""){
 | |
| 				
 | |
| 				$get_filters["iaf"] = $filters;
 | |
| 			}
 | |
| 			
 | |
| 			try{
 | |
| 				
 | |
| 				$html =
 | |
| 					$this->get(
 | |
| 						$proxy,
 | |
| 						"https://duckduckgo.com/",
 | |
| 						$get_filters,
 | |
| 						ddg::req_web
 | |
| 					);
 | |
| 			}catch(Exception $error){
 | |
| 				
 | |
| 				throw new Exception("Failed to fetch search page");
 | |
| 			}
 | |
| 			
 | |
| 			preg_match(
 | |
| 				'/vqd="([0-9-]+)"/',
 | |
| 				$html,
 | |
| 				$vqd
 | |
| 			);
 | |
| 			
 | |
| 			if(!isset($vqd[1])){
 | |
| 				
 | |
| 				throw new Exception("Failed to grep VQD token");
 | |
| 			}
 | |
| 			
 | |
| 			$js_link =
 | |
| 				"v.js?" .
 | |
| 				http_build_query([
 | |
| 					"l" => $get["country"],
 | |
| 					"o" => "json",
 | |
| 					"sr" => "1",
 | |
| 					"q" => $get["s"],
 | |
| 					"vqd" => $vqd[1],
 | |
| 					"f" => $filters,
 | |
| 					"p" => $nsfw
 | |
| 				]);
 | |
| 		}
 | |
| 		
 | |
| 		try{
 | |
| 			
 | |
| 			$json =
 | |
| 				$this->get(
 | |
| 					$proxy,
 | |
| 					"https://duckduckgo.com/" . $js_link,
 | |
| 					[],
 | |
| 					ddg::req_xhr
 | |
| 				);
 | |
| 		}catch(Exception $error){
 | |
| 			
 | |
| 			throw new Exception("Failed to fetch JSON");
 | |
| 		}
 | |
| 		
 | |
| 		$json = json_decode($json, true);
 | |
| 		
 | |
| 		if($json === null){
 | |
| 			
 | |
| 			throw new Exception("Failed to decode JSON");
 | |
| 		}
 | |
| 		
 | |
| 		$out = [
 | |
| 			"status" => "ok",
 | |
| 			"npt" => null,
 | |
| 			"video" => [],
 | |
| 			"author" => [],
 | |
| 			"livestream" => [],
 | |
| 			"playlist" => [],
 | |
| 			"reel" => []
 | |
| 		];
 | |
| 		
 | |
| 		if(!isset($json["results"])){
 | |
| 			
 | |
| 			return $out;
 | |
| 		}
 | |
| 		
 | |
| 		// get NPT
 | |
| 		if(
 | |
| 			isset($json["next"]) &&
 | |
| 			$json["next"] !== null
 | |
| 		){
 | |
| 			
 | |
| 			$out["npt"] =
 | |
| 				$this->backend->store(
 | |
| 					$json["next"],
 | |
| 					"videos",
 | |
| 					$proxy
 | |
| 				);
 | |
| 		}
 | |
| 		
 | |
| 		foreach($json["results"] as $video){
 | |
| 			
 | |
| 			$thumb = [
 | |
| 				"ratio" => null,
 | |
| 				"url" => null
 | |
| 			];
 | |
| 			
 | |
| 			foreach(["large", "medium", "small"] as $contender){
 | |
| 				
 | |
| 				if(isset($video["images"][$contender])){
 | |
| 					
 | |
| 					$thumb = [
 | |
| 						"ratio" => "16:9",
 | |
| 						"url" => $this->bingimg($video["images"][$contender])
 | |
| 					];
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			$out["video"][] = [
 | |
| 				"title" => $this->titledots($video["title"]),
 | |
| 				"description" => $this->titledots($video["description"]),
 | |
| 				"author" => [
 | |
| 					"name" =>
 | |
| 						(
 | |
| 							isset($video["uploader"]) &&
 | |
| 							$video["uploader"] != ""
 | |
| 						) ?
 | |
| 						$video["uploader"] : null,
 | |
| 					"url" => null,
 | |
| 					"avatar" => null
 | |
| 				],
 | |
| 				"date" =>
 | |
| 					(
 | |
| 						isset($video["published"]) &&
 | |
| 						$video["published"] != ""
 | |
| 					) ?
 | |
| 					strtotime($video["published"]) : null,
 | |
| 				"duration" =>
 | |
| 					(
 | |
| 						isset($video["duration"]) &&
 | |
| 						$video["duration"] != ""
 | |
| 					) ?
 | |
| 					$this->hms2int($video["duration"]) : null,
 | |
| 				"views" =>
 | |
| 					isset($video["statistics"]["viewCount"]) ?
 | |
| 					(int)$video["statistics"]["viewCount"] : null,
 | |
| 				"thumb" => $thumb,
 | |
| 				"url" => $this->unshiturl($video["content"])
 | |
| 			];
 | |
| 		}
 | |
| 		
 | |
| 		return $out;
 | |
| 	}
 | |
| 	
 | |
| 	public function news($get){
 | |
| 		
 | |
| 		if($get["npt"]){
 | |
| 			
 | |
| 			[$js_link, $proxy] = $this->backend->get($get["npt"], "news");
 | |
| 			
 | |
| 		}else{
 | |
| 			if(strlen($get["s"]) === 0){
 | |
| 				
 | |
| 				throw new Exception("Search term is empty!");
 | |
| 			}
 | |
| 			
 | |
| 			$proxy = $this->backend->get_ip();
 | |
| 			
 | |
| 			$get_filters = [
 | |
| 				"q" => $get["s"],
 | |
| 				"iar" => "news",
 | |
| 				"ia" => "news"
 | |
| 			];
 | |
| 			
 | |
| 			if($get["date"] != "any"){
 | |
| 				
 | |
| 				$date = $get["date"];
 | |
| 				$get_filters["df"] = $date;
 | |
| 			}else{
 | |
| 				
 | |
| 				$date = "";
 | |
| 			}
 | |
| 			
 | |
| 			switch($get["nsfw"]){
 | |
| 				
 | |
| 				case "yes": $get_filters["kp"] = "-2"; break;
 | |
| 				case "maybe": $get_filters["kp"] = "-1"; break;
 | |
| 				case "no": $get_filters["kp"] = "1"; break;
 | |
| 			}
 | |
| 			
 | |
| 			try{
 | |
| 				
 | |
| 				$html =
 | |
| 					$this->get(
 | |
| 						$proxy,
 | |
| 						"https://duckduckgo.com/",
 | |
| 						$get_filters,
 | |
| 						ddg::req_web
 | |
| 					);
 | |
| 			}catch(Exception $error){
 | |
| 				
 | |
| 				throw new Exception("Failed to fetch search page");
 | |
| 			}
 | |
| 			
 | |
| 			preg_match(
 | |
| 				'/vqd="([0-9-]+)"/',
 | |
| 				$html,
 | |
| 				$vqd
 | |
| 			);
 | |
| 			
 | |
| 			if(!isset($vqd[1])){
 | |
| 				
 | |
| 				throw new Exception("Failed to grep VQD token");
 | |
| 			}
 | |
| 			
 | |
| 			$js_link =
 | |
| 				"news.js?" .
 | |
| 				http_build_query([
 | |
| 					"l" => $get["country"],
 | |
| 					"o" => "json",
 | |
| 					"noamp" => "1",
 | |
| 					"m" => "30",
 | |
| 					"q" => $get["s"],
 | |
| 					"vqd" => $vqd[1],
 | |
| 					"p" => $get_filters["kp"],
 | |
| 					"df" => $date,
 | |
| 					"u" => "bing"
 | |
| 				]);
 | |
| 		}
 | |
| 		
 | |
| 		try{
 | |
| 			
 | |
| 			$json =
 | |
| 				$this->get(
 | |
| 					$proxy,
 | |
| 					"https://duckduckgo.com/" . $js_link,
 | |
| 					[],
 | |
| 					ddg::req_xhr
 | |
| 				);
 | |
| 		}catch(Exception $error){
 | |
| 			
 | |
| 			throw new Exception("Failed to fetch JSON");
 | |
| 		}
 | |
| 		
 | |
| 		$json = json_decode($json, true);
 | |
| 		
 | |
| 		if($json === null){
 | |
| 			
 | |
| 			throw new Exception("Failed to decode JSON");
 | |
| 		}
 | |
| 		
 | |
| 		$out = [
 | |
| 			"status" => "ok",
 | |
| 			"npt" => null,
 | |
| 			"news" => []
 | |
| 		];
 | |
| 		
 | |
| 		if(!isset($json["results"])){
 | |
| 			
 | |
| 			return $out;
 | |
| 		}
 | |
| 		
 | |
| 		// get NPT
 | |
| 		if(
 | |
| 			isset($json["next"]) &&
 | |
| 			$json["next"] !== null
 | |
| 		){
 | |
| 			
 | |
| 			$out["npt"] =
 | |
| 				$this->backend->store(
 | |
| 					$json["next"],
 | |
| 					"news",
 | |
| 					$proxy
 | |
| 				);
 | |
| 		}
 | |
| 		
 | |
| 		foreach($json["results"] as $news){
 | |
| 			
 | |
| 			if(
 | |
| 				isset($news["image"]) &&
 | |
| 				$news["image"] != ""
 | |
| 			){
 | |
| 				
 | |
| 				$thumb = [
 | |
| 					"ratio" => "16:9",
 | |
| 					"url" => $news["image"]
 | |
| 				];
 | |
| 			}else{
 | |
| 				
 | |
| 				$thumb = [
 | |
| 					"ratio" => null,
 | |
| 					"url" => null
 | |
| 				];
 | |
| 			}
 | |
| 			
 | |
| 			$out["news"][] = [
 | |
| 				"title" => $news["title"],
 | |
| 				"author" =>
 | |
| 					(
 | |
| 						isset($news["source"]) &&
 | |
| 						$news["source"] != ""
 | |
| 					) ?
 | |
| 					$news["source"] : null,
 | |
| 				"description" =>
 | |
| 					(
 | |
| 						isset($news["excerpt"]) &&
 | |
| 						$news["excerpt"] != ""
 | |
| 					) ?
 | |
| 					$this->fuckhtml
 | |
| 					->getTextContent(
 | |
| 						$news["excerpt"]
 | |
| 					) : null,
 | |
| 				"date" =>
 | |
| 					isset($news["date"]) ?
 | |
| 					(int)$news["date"] : null,
 | |
| 				"thumb" => $thumb,
 | |
| 				"url" => $this->unshiturl($news["url"])
 | |
| 			];
 | |
| 		}
 | |
| 		
 | |
| 		return $out;
 | |
| 	}
 | |
| 	
 | |
| 	private function titledots($title){
 | |
| 		
 | |
| 		$substr = substr($title, -3);
 | |
| 		
 | |
| 		if(
 | |
| 			$substr == "..." ||
 | |
| 			$substr == "…"
 | |
| 		){
 | |
| 						
 | |
| 			return trim(substr($title, 0, -3));
 | |
| 		}
 | |
| 		
 | |
| 		return trim($title);
 | |
| 	}
 | |
| 	
 | |
| 	private function hms2int($time){
 | |
| 		
 | |
| 		$parts = explode(":", $time, 3);
 | |
| 		$time = 0;
 | |
| 		
 | |
| 		if(count($parts) === 3){
 | |
| 			
 | |
| 			// hours
 | |
| 			$time = $time + ((int)$parts[0] * 3600);
 | |
| 			array_shift($parts);
 | |
| 		}
 | |
| 		
 | |
| 		if(count($parts) === 2){
 | |
| 			
 | |
| 			// minutes
 | |
| 			$time = $time + ((int)$parts[0] * 60);
 | |
| 			array_shift($parts);
 | |
| 		}
 | |
| 		
 | |
| 		// seconds
 | |
| 		$time = $time + (int)$parts[0];
 | |
| 		
 | |
| 		return $time;
 | |
| 	}
 | |
| 
 | |
| 	
 | |
| 	private function unshiturl($url){
 | |
| 		
 | |
| 		// check for domains w/out first short subdomain (ex: www.)
 | |
| 		
 | |
| 		$domain = parse_url($url, PHP_URL_HOST);
 | |
| 		
 | |
| 		$subdomain = preg_replace(
 | |
| 			'/^[A-z0-9]{1,3}\./',
 | |
| 			"",
 | |
| 			$domain
 | |
| 		);
 | |
| 		
 | |
| 		switch($subdomain){
 | |
| 			case "ebay.com.au":
 | |
| 			case "ebay.at":
 | |
| 			case "ebay.ca":
 | |
| 			case "ebay.fr":
 | |
| 			case "ebay.de":
 | |
| 			case "ebay.com.hk":
 | |
| 			case "ebay.ie":
 | |
| 			case "ebay.it":
 | |
| 			case "ebay.com.my":
 | |
| 			case "ebay.nl":
 | |
| 			case "ebay.ph":
 | |
| 			case "ebay.pl":
 | |
| 			case "ebay.com.sg":
 | |
| 			case "ebay.es":
 | |
| 			case "ebay.ch":
 | |
| 			case "ebay.co.uk":
 | |
| 			case "cafr.ebay.ca":
 | |
| 			case "ebay.com":
 | |
| 			case "community.ebay.com":
 | |
| 			case "pages.ebay.com":
 | |
| 				
 | |
| 				// remove ebay tracking elements
 | |
| 				$old_params = parse_url($url, PHP_URL_QUERY);
 | |
| 				parse_str($old_params, $params);
 | |
| 				
 | |
| 				if(isset($params["mkevt"])){ unset($params["mkevt"]); }
 | |
| 				if(isset($params["mkcid"])){ unset($params["mkcid"]); }
 | |
| 				if(isset($params["mkrid"])){ unset($params["mkrid"]); }
 | |
| 				if(isset($params["campid"])){ unset($params["campid"]); }
 | |
| 				if(isset($params["customid"])){ unset($params["customid"]); }
 | |
| 				if(isset($params["toolid"])){ unset($params["toolid"]); }
 | |
| 				if(isset($params["_sop"])){ unset($params["_sop"]); }
 | |
| 				if(isset($params["_dcat"])){ unset($params["_dcat"]); }
 | |
| 				if(isset($params["epid"])){ unset($params["epid"]); }
 | |
| 				if(isset($params["epid"])){ unset($params["oid"]); }
 | |
| 				
 | |
| 				$params = http_build_query($params);
 | |
| 				
 | |
| 				if(strlen($params) === 0){
 | |
| 					$replace = "\?";
 | |
| 				}else{
 | |
| 					$replace = "";
 | |
| 				}
 | |
| 				
 | |
| 				$url = preg_replace(
 | |
| 					"/" . $replace . preg_quote($old_params, "/") . "$/",
 | |
| 					$params,
 | |
| 					$url
 | |
| 				);
 | |
| 				break;
 | |
| 		}
 | |
| 		
 | |
| 		return $url;
 | |
| 	}
 | |
| 	
 | |
| 	private function bingimg($url){
 | |
| 		
 | |
| 		$parse = parse_url($url);
 | |
| 		parse_str($parse["query"], $parts);
 | |
| 		
 | |
| 		return "https://" . $parse["host"] . "/th?id=" . urlencode($parts["id"]);
 | |
| 	}
 | |
| 	
 | |
| 	private function bingratio($width, $height){
 | |
| 		
 | |
| 		$ratio = [
 | |
| 			474 / $width,
 | |
| 			474 / $height
 | |
| 		];
 | |
| 		
 | |
| 		if($ratio[0] < $ratio[1]){
 | |
| 			
 | |
| 			$ratio = $ratio[0];
 | |
| 		}else{
 | |
| 			
 | |
| 			$ratio = $ratio[1];
 | |
| 		}
 | |
| 		
 | |
| 		return [
 | |
| 			floor($width * $ratio),
 | |
| 			floor($height * $ratio)
 | |
| 		];
 | |
| 	}
 | |
| }
 |