0
Fork 0
mirror of https://git.lolcat.ca/lolcat/4get.git synced 2024-12-17 23:56:16 -05:00
4get/scraper/ddg.php
2024-12-17 21:01:36 -05:00

1945 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)
];
}
}