diff --git a/api.txt b/api.txt index a64873e..4d2626b 100644 --- a/api.txt +++ b/api.txt @@ -1,10 +1,17 @@ - __ __ __ - / // / ____ ____ / /_ - / // /_/ __ `/ _ \/ __/ - /__ __/ /_/ / __/ /_ - /_/ \__, /\___/\__/ - /____/ - + 44 + 4444444 44 + 44444444 44444 444 + 44444444 444444 444444444 + 44444 44444444 444444444 + 444444444 4444444 + 4444444444 444444 + 4444444444444 + 444444444444444444 + 444444444444444 + 44444444 + 4444 + 44 + + Welcome to the 4get API documentation + + Terms of use diff --git a/data/config.php b/data/config.php index efae706..bcda644 100644 --- a/data/config.php +++ b/data/config.php @@ -119,7 +119,7 @@ class config{ // Default user agent to use for scraper requests. Sometimes ignored to get specific webpages // Changing this might break things. - const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0"; + const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0"; // Proxy pool assignments for each scraper // false = Use server's raw IP diff --git a/lib/backend.php b/lib/backend.php index 7c450da..66e78a1 100644 --- a/lib/backend.php +++ b/lib/backend.php @@ -75,6 +75,7 @@ class backend{ break; case "socks5_hostname": + case "socks5h": case "socks5a": curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port); diff --git a/lib/fuckhtml.php b/lib/fuckhtml.php index ee1353c..5b45578 100644 --- a/lib/fuckhtml.php +++ b/lib/fuckhtml.php @@ -526,4 +526,85 @@ class fuckhtml{ $string ); } + + public function extract_json($json){ + + $len = strlen($json); + $array_level = 0; + $object_level = 0; + $in_quote = null; + $start = null; + + for($i=0; $i<$len; $i++){ + + switch($json[$i]){ + + case "[": + if($in_quote === null){ + + $array_level++; + if($start === null){ + + $start = $i; + } + } + break; + + case "]": + if($in_quote === null){ + + $array_level--; + } + break; + + case "{": + if($in_quote === null){ + + $object_level++; + if($start === null){ + + $start = $i; + } + } + break; + + case "}": + if($in_quote === null){ + + $object_level--; + } + break; + + case "\"": + case "'": + if( + $i !== 0 && + $json[$i - 1] !== "\\" + ){ + // found a non-escaped quote + + if($in_quote === null){ + + // open quote + $in_quote = $json[$i]; + }elseif($in_quote === $json[$i]){ + + // close quote + $in_quote = null; + } + } + break; + } + + if( + $start !== null && + $array_level === 0 && + $object_level === 0 + ){ + + return substr($json, $start, $i - $start + 1); + break; + } + } + } } diff --git a/scraper/ddg.php b/scraper/ddg.php index 90bd65f..8b1e714 100644 --- a/scraper/ddg.php +++ b/scraper/ddg.php @@ -36,8 +36,8 @@ class ddg{ $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-Encoding: gzip", "Accept-Language: en-US,en;q=0.5", + "Accept-Encoding: gzip", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", @@ -53,19 +53,17 @@ class ddg{ case self::req_xhr: $headers = ["User-Agent: " . config::USER_AGENT, - "Accept: application/json, text/javascript, */*; q=0.01", - "Accept-Encoding: gzip", + "Accept: */*", "Accept-Language: en-US,en;q=0.5", - "Connection: keep-alive", + "Accept-Encoding: gzip", "Referer: https://duckduckgo.com/", - "X-Requested-With: XMLHttpRequest", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", - "Sec-Fetch-Dest: empty", - "Sec-Fetch-Mode: cors", - "Sec-Fetch-Site: same-origin", - "TE: trailers"]; + "Sec-Fetch-Dest: script", + "Sec-Fetch-Mode: no-cors", + "Sec-Fetch-Site: same-site", + "Priority: u=1"]; break; } @@ -92,582 +90,270 @@ class ddg{ 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": - return - [ - "country" => [ - "display" => "Country", - "option" => [ - "any" => "All Regions", - "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-en" => "US (English)", - "us-es" => "US (Spanish)", - "ua-uk" => "Ukraine", - "uk-en" => "United Kingdom", - "vn-en" => "Vietnam (en)" - ] - ], - "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, so it wont show in frontend - "option" => [ - "yes" => "Yes", - "no" => "No" + $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 - [ - "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)" - ] - ], - "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", // blame ddg for this - "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" + 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 - [ - "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-en" => "US (English)", - "us-es" => "US (Spanish)", - "ua-uk" => "Ukraine", - "uk-en" => "United Kingdom", - "vn-en" => "Vietnam (en)" - ] - ], - "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" + 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 - [ - "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-en" => "US (English)", - "us-es" => "US (Spanish)", - "ua-uk" => "Ukraine", - "uk-en" => "United Kingdom", - "vn-en" => "Vietnam (en)" - ] - ], - "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" + 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; - - default: - return []; + ); break; } } public function web($get){ - if($get["npt"]){ - - [$jsgrep, $proxy] = $this->backend->get($get["npt"], "web"); - - $extendedsearch = false; - $inithtml = ""; - - }else{ - $search = $get["s"]; - if(strlen($search) === 0){ - - throw new Exception("Search term is empty!"); - } - - $proxy = $this->backend->get_ip(); - $country = $get["country"]; - $nsfw = $get["nsfw"]; - $older = $get["older"]; - $newer = $get["newer"]; - $extendedsearch = $get["extendedsearch"] == "yes" ? true : false; - - // generate filters - $get_filters = [ - "q" => $search, - "kz" => "1" // force instant answers - ]; - - if($country == "any"){ - - $get_filters["kl"] = "wt-wt"; - }else{ - - $get_filters["kl"] = $country; - } - - switch($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($newer === false){ - - if($older !== false){ - - $start = 36000; - $end = $older; - }else{ - - $df = false; - } - }else{ - - $start = $newer; - - if($older !== false){ - - $end = $older; - }else{ - - $end = time(); - } - } - - if($df === true){ - $get_filters["df"] = date("Y-m-d", $start) . ".." . date("Y-m-d", $end); - } - - /* - Get html - */ - try{ - $inithtml = $this->get( - $proxy, - "https://duckduckgo.com/", - $get_filters - ); - }catch(Exception $e){ - - throw new Exception("Failed to get html"); - } - - preg_match( - '/DDG\.deep\.initialize\(\'(.*)\',/U', - $inithtml, - $jsgrep - ); - - if(!isset($jsgrep[1])){ - - throw new Exception("Failed to get d.js URL"); - } - - $jsgrep = $jsgrep[1]; - } - - // get javascript - try{ - - $js = $this->get( - $proxy, - "https://links.duckduckgo.com" . $jsgrep, - [], - ddg::req_xhr - ); - }catch(Exception $e){ - - throw new Exception("Failed to fetch d.js"); - } - - // initialize api response array $out = [ "status" => "ok", "spelling" => [ @@ -684,1207 +370,903 @@ class ddg{ "related" => [] ]; - /* - Additional requests - */ - - if($extendedsearch){ + if($get["npt"]){ - /* - Check for worknik results - */ - preg_match( - '/nrj\(\'\/js\/spice\/dictionary\/definition\/([^\'\)]+)/', - $js, - $wordnik - ); + [$js_link, $proxy] = $this->backend->get($get["npt"], "web"); + $js_link = "https://links.duckduckgo.com" . $js_link; - if(isset($wordnik[1])){ + $html = ""; + $get["extendedsearch"] = "no"; + + }else{ + if(strlen($get["s"]) === 0){ - try{ - - $wordnik = $wordnik[1]; - - // get definition - $wordnikjs = $this->get( - $proxy, - "https://duckduckgo.com/js/spice/dictionary/definition/" . $wordnik, - [], - ddg::req_xhr - ); - - preg_match( - '/ddg_spice_dictionary_definition\(\n?(\[{[\S\s]*}])/', - $wordnikjs, - $wordnikjson - ); - - if(isset($wordnikjson[1])){ - - $wordnikjson = json_decode($wordnikjson[1], true); - - $out["answer"][0] = [ - "title" => urldecode($wordnik), - "description" => [], - "url" => "https://www.wordnik.com/words/" . $wordnik, - "thumb" => null, - "table" => [], - "sublink" => [] - ]; - - $partofspeech = false; - $wastext = false; - $textindent = 1; - - // get audio - - $wordnikaudio_json = - json_decode( - $this->get( - $proxy, - "https://duckduckgo.com/js/spice/dictionary/audio/" . $wordnik, - [], - ddg::req_xhr - ), - true - ); - - if(isset($wordnikaudio_json[0]["id"])){ - - usort($wordnikaudio_json, function($a, $b){ - - return $a["id"] < $b["id"]; - }); - - $out["answer"][0]["description"][] = [ - "type" => "audio", - "url" => $wordnikaudio_json[0]["fileUrl"] - ]; - } - - $collection = []; - $e[] = []; - - foreach($wordnikjson as $data){ - - if(!isset($data["partOfSpeech"])){ - - continue; - } - - if(isset($data["text"])){ - - if(!isset($collection[$data["partOfSpeech"]])){ - - $collection[$data["partOfSpeech"]] = []; - $c = 0; - }else{ - $c = count($collection[$data["partOfSpeech"]]); - } - - if(!isset($e[$data["partOfSpeech"]])){ - - $e[$data["partOfSpeech"]] = 0; - } - - $e[$data["partOfSpeech"]]++; - $text = $e[$data["partOfSpeech"]] . ". " . $this->unescapehtml(strip_tags($data["text"])); - - $syn = false; - if( - isset($data["relatedWords"]) && - count($data["relatedWords"]) !== 0 - ){ - - $syn = " ("; - - $u = 0; - foreach($data["relatedWords"] as $related){ - - $syn .= ucfirst($related["relationshipType"]) . ": "; - - $c = count($related["words"]); - $b = 0; - foreach($related["words"] as $word){ - - $syn .= trim($this->unescapehtml(strip_tags($word))); - - $b++; - if($b !== $c){ - - $syn .= ", "; - } - } - - $u++; - if($u !== count($data["relatedWords"])){ - - $syn .= ". "; - } - } - - $syn .= ")"; - } - - if( - $c !== 0 && - $collection[$data["partOfSpeech"]][$c - 1]["type"] == "text" - ){ - $collection[$data["partOfSpeech"]][$c - 1]["value"] .= - "\n" . $text; - - }else{ - - if( - $c !== 0 && - ( - $collection[$data["partOfSpeech"]][$c - 1]["type"] == "text" || - $collection[$data["partOfSpeech"]][$c - 1]["type"] == "italic" - ) - ){ - - $text = "\n" . $text; - } - - $collection[$data["partOfSpeech"]][] = - [ - "type" => "text", - "value" => $text - ]; - } - - if($syn){ - - $collection[$data["partOfSpeech"]][] = [ - "type" => "italic", - "value" => $syn - ]; - } - - if(isset($data["exampleUses"])){ - - foreach($data["exampleUses"] as $use){ - - $collection[$data["partOfSpeech"]][] = [ - "type" => "quote", - "value" => $this->unescapehtml(strip_tags($use["text"])) - ]; - } - } - - if(isset($data["citations"])){ - - foreach($data["citations"] as $citation){ - - if(!isset($citation["cite"])){ - - continue; - } - - $value = $this->unescapehtml(strip_tags($citation["cite"])); - - if( - isset($citation["source"]) && - trim($citation["source"]) != "" - ){ - $value .= " - " . $this->unescapehtml(strip_tags($citation["source"])); - } - - $collection[$data["partOfSpeech"]][] = [ - "type" => "quote", - "value" => $value - ]; - } - } - } - } - - foreach($collection as $key => $items){ - - $out["answer"][0]["description"][] = - [ - "type" => "title", - "value" => $key - ]; - - $out["answer"][0]["description"] = - array_merge($out["answer"][0]["description"], $items); - } - } - - }catch(Exception $e){ - - // do nothing - } + throw new Exception("Search term is empty!"); } - unset($wordnik); + $proxy = $this->backend->get_ip(); - /* - Check for stackoverflow answers - */ + // generate filters + $get_filters = [ + "q" => $get["s"] + ]; - // /a.js?p=1&src_id=stack_overflow&from=nlp_qa&id=3390396,2559318&q=how%20can%20i%20check%20for%20undefined%20in%20javascript&s=stackoverflow.com&tl=How%20can%20I%20check%20for%20%22undefined%22%20in%20JavaScript%3F%20%2D%20Stack%20Overflow - // /a.js?p=1&src_id=arqade&from=nlp_qa&id=370293,375682&q=what%20is%20the%20difference%20between%20at%20and%20positioned%20in%20execute&s=gaming.stackexchange.com&tl=minecraft%20java%20edition%20minecraft%20commands%20%2D%20What%20is%20the%20difference - // /a.js?p=1&src_id=unix&from=nlp_qa&id=312754&q=how%20to%20strip%20metadata%20from%20image%20files&s=unix.stackexchange.com&tl=How%20to%20strip%20metadata%20from%20image%20files%20%2D%20Unix%20%26%20Linux%20Stack%20Exchange - preg_match( - '/nrj\(\'(\/a\.js\?.*from=nlp_qa.*)\'\)/U', - $js, - $stack - ); - - if(isset($stack[1])){ + if($get["country"] == "any"){ - $stack = $stack[1]; + $get_filters["kl"] = "wt-wt"; + }else{ - try{ - $stackjs = $this->get( - $proxy, - "https://duckduckgo.com" . $stack, - [], - ddg::req_xhr - ); - - if( - !preg_match( - '/^DDG\.duckbar\.failed/', - $stackjs - ) - ){ - - preg_match( - '/DDG\.duckbar\.add_array\((\[\{[\S\s]*}])\)/U', - $stackjs, - $stackjson - ); - - $stackjson = json_decode($stackjson[1], true)[0]["data"][0]; - - $out["answer"][] = [ - "title" => $stackjson["Heading"], - "description" => $this->stackoverflow_parse($stackjson["Abstract"]), - "url" => str_replace(["http://", "ddg"], ["https://", ""], $stackjson["AbstractURL"]), - "thumb" => null, - "table" => [], - "sublink" => [] - ]; - } - - }catch(Exception $e){ - - // do nothing - } + $get_filters["kl"] = $get["country"]; } - /* - Check for musicmatch (lyrics) - */ - preg_match( - '/nrj\(\'(\/a\.js\?.*&s=lyrics.*)\'\)/U', - $js, - $lyrics - ); - - if(isset($lyrics[1])){ + switch($get["nsfw"]){ - $lyrics = $lyrics[1]; - - try{ - $lyricsjs = $this->get( - $proxy, - "https://duckduckgo.com" . $lyrics, - [], - ddg::req_xhr - ); - - if( - !preg_match( - '/^DDG\.duckbar\.failed/', - $lyricsjs - ) - ){ - - preg_match( - '/DDG\.duckbar\.add_array\((\[\{[\S\s]*}])\)/U', - $lyricsjs, - $lyricsjson - ); - - $lyricsjson = json_decode($lyricsjson[1], true)[0]["data"][0]; - - $title = null; - - if(isset($lyricsjson["Heading"])){ - - $title = $lyricsjson["Heading"]; - }elseif(isset($lyricsjson["data"][1]["urlTitle"])){ - - $title = $lyricsjson["data"][1]["urlTitle"]; - }else{ - - $title = $lyricsjson["data"][0]["song_title"]; - } - - $description = [ - [ - "type" => "text", - "value" => null - ] - ]; - $parts = - explode( - "
", - str_ireplace( - ["
", "
", "
"], - "
", - $lyricsjson["Abstract"] - ), - ); - - for($i=0; $i "quote", - "value" => - "Written by " . implode(", ", $lyricsjson["data"][0]["writers"]) . - "\nFrom the album " . $lyricsjson["data"][0]["albums"][0]["title"] . - "\nReleased on the " . date("jS \of F Y", strtotime($lyricsjson["data"][0]["albums"][0]["release_date"])) - ]; - - $out["answer"][] = [ - "title" => $title, - "description" => $description, - "url" => $lyricsjson["AbstractURL"], - "thumb" => null, - "table" => [], - "sublink" => [] - ]; - } - - }catch(Exception $e){ - - // do nothing - } + case "yes": $get_filters["kp"] = "-2"; break; + case "maybe": $get_filters["kp"] = "-1"; break; + case "no": $get_filters["kp"] = "1"; break; } - } - - /* - Get related searches - */ - preg_match( - '/DDG\.duckbar\.loadModule\(\'related_searches\', ?{[\s\S]*"results":(\[{[\s\S]*}]),"vqd"/U', - $js, - $related - ); - - if(isset($related[1])){ - try{ - $related = json_decode($related[1], true); - - for($i=0; $isanitizeurl($answers[$i]["data"]["AbstractURL"])) - ){ - - continue 2; - } - } - - // get more related queries - if( - isset($answers[$i]["data"]["RelatedTopics"]) && - $answers[$i]["data"]["RelatedTopics"] != 0 - ){ - - for($k=0; $k(.*)<\//', - $answers[$i]["data"]["RelatedTopics"][$k]["Result"], - $label - ); - - array_push($out["related"], htmlspecialchars_decode(strip_tags($label[1]))); - } - } - } - - $image = null; - - // get image - if( - isset($answers[$i]["data"]["Image"]) && - !empty($answers[$i]["data"]["Image"]) && - $answers[$i]["data"]["Image"] != "https://duckduckgo.com/i/" - ){ - if(strpos($answers[$i]["data"]["Image"], "https://duckduckgo.com/i/") === true){ - - $image = $answers[$i]["data"]["Image"]; - }else{ - - if( - strlen($answers[$i]["data"]["Image"]) > 0 && - $answers[$i]["data"]["Image"][0] == "/" - ){ - - $answers[$i]["data"]["Image"] = substr($answers[$i]["data"]["Image"], 1); - } - - $image = "https://duckduckgo.com/" . $answers[$i]["data"]["Image"]; - } - } - - $count = count($out["answer"]); - - if(isset($answers[$i]["data"]["AbstractText"]) && !empty($answers[$i]["data"]["AbstractText"])){ - - $description = $this->stackoverflow_parse($answers[$i]["data"]["AbstractText"]); - }elseif(isset($answers[$i]["data"]["Abstract"]) && !empty($answers[$i]["data"]["Abstract"])){ - - $description = $this->stackoverflow_parse($answers[$i]["data"]["Abstract"]); - }elseif(isset($answers[$i]["data"]["Answer"]) && !empty($answers[$i]["data"]["Answer"])){ - - $description = $this->stackoverflow_parse($answers[$i]["data"]["Answer"]); - }else{ - - $description = []; - } - - if(isset($answers[$i]["data"]["Heading"]) && !empty($answers[$i]["data"]["Heading"])){ - - $title = $this->unescapehtml($answers[$i]["data"]["Heading"]); - }else{ - - // no title, ignore bs - continue; - //$title = null; - } - - if(isset($answers[$i]["data"]["AbstractURL"]) && !empty($answers[$i]["data"]["AbstractURL"])){ - - $url = $answers[$i]["data"]["AbstractURL"]; - }else{ - - $url = null; - } - - $out["answer"][$count] = [ - "title" => $title, - "description" => $description, - "url" => $this->sanitizeurl($url), - "thumb" => $image, - "table" => [], - "sublink" => [] - ]; - - if(isset($answers[$i]["data"]["Infobox"]["content"])){ - - for($k=0; $kget( - $proxy, - "https://duckduckgo.com/js/spice/cryptocurrency/{$shitcoins[1]}/{$shitcoins[2]}/1", - [], - ddg::req_xhr - ); - - preg_match( - '/ddg_spice_cryptocurrency\(\s*({[\S\s]*})\s*\);/', - $shitcoinjs, - $shitcoinjson - ); - - $shitcoinjson = json_decode($shitcoinjson[1], true); - - if( - !isset($shitcoinjson["error"]) && - $shitcoinjson["status"]["error_code"] == 0 - ){ - - $shitcoinjson = $shitcoinjson["data"]; - $array_values = array_values($shitcoinjson["quote"])[0]; - - if($amount != 1){ - - // show conversion - $description[] = [ - "type" => "title", - "value" => "Conversion" - ]; - - $description[] = [ - "type" => "text", - "value" => - "{$amount} {$shitcoinjson["name"]} ({$shitcoinjson["symbol"]}) = " . $this->number_format($array_values["price"] * $amount) . " " . strtoupper($shitcoins[2]) . "\n" . - "{$amount} " . strtoupper($shitcoins[2]) . " = " . $this->number_format((1 / $array_values["price"]) * $amount) . " {$shitcoinjson["symbol"]}" - ]; - } - - $description[] = [ - "type" => "title", - "value" => "Current rates" - ]; - - // rates - $description[] = [ - "type" => "text", - "value" => - "1 {$shitcoinjson["name"]} ({$shitcoinjson["symbol"]}) = " . $this->number_format($array_values["price"]) . " " . strtoupper($shitcoins[2]) . "\n" . - "1 " . strtoupper($shitcoins[2]) . " = " . $this->number_format(1 / $array_values["price"]) . " {$shitcoinjson["symbol"]}" - ]; - - $description[] = [ - "type" => "quote", - "value" => "Last fetched: " . date("jS \of F Y @ g:ia", strtotime($shitcoinjson["last_updated"])) - ]; - - $out["answer"][] = [ - "title" => $shitcoinjson["name"] . " (" . strtoupper($shitcoins[1]) . ") & " . strtoupper($shitcoins[2]) . " market", - "description" => $description, - "url" => "https://coinmarketcap.com/converter/" . strtoupper($shitcoins[1]) . "/" . strtoupper($shitcoins[2]) . "/?amt={$amount}", - "thumb" => null, - "table" => [], - "sublink" => [] - ]; - } - - }catch(Exception $e){ - - // do nothing + $df = false; } }else{ - /* - Get currency conversion - */ - if( - preg_match( - '/"https:\/\/www\.xe\.com\/currencyconverter\/convert\/\?From=([A-Z0-9]+)&To=([A-Z0-9]+)"/', - $js, - $currencies - ) - ){ - - preg_match( - '/(?:[\s,.]*[0-9]+)+/', - $search, - $amount - ); - - if(count($amount) === 1){ - - $amount = (float)str_replace([" ", ","], ["", "."], $amount[0]); - }else{ - - $amount = 1; - } - - try{ - $currencyjs = $this->get( - $proxy, - "https://duckduckgo.com/js/spice/currency/{$amount}/" . strtolower($currencies[1]) . "/" . strtolower($currencies[2]), - [], - ddg::req_xhr - ); - - preg_match( - '/ddg_spice_currency\(\s*({[\S\s]*})\s*\);/', - $currencyjs, - $currencyjson - ); - - $currencyjson = json_decode($currencyjson[1], true); - - if(empty($currencyjson["headers"]["description"])){ - - $currencyjson = $currencyjson["conversion"]; - $description = []; - - if($amount != 1){ - - $description[] = - [ - "type" => "title", - "value" => "Conversion" - ]; - - $description[] = - [ - "type" => "text", - "value" => - $this->number_format($currencyjson["from-amount"]) . " {$currencyjson["from-currency-symbol"]} = " . - $this->number_format($currencyjson["converted-amount"]) . " {$currencyjson["to-currency-symbol"]}" - ]; - } - - $description[] = - [ - "type" => "title", - "value" => "Current rates" - ]; - - $description[] = - [ - "type" => "text", - "value" => - "{$currencyjson["conversion-rate"]}\n" . - "{$currencyjson["conversion-inverse"]}" - ]; - - $description[] = - [ - "type" => "quote", - "value" => "Last fetched: " . date("jS \of F Y @ g:ia", strtotime($currencyjson["rate-utc-timestamp"])) - ]; - - $out["answer"][] = [ - "title" => - "{$currencyjson["from-currency-name"]} ({$currencyjson["from-currency-symbol"]}) to " . - "{$currencyjson["to-currency-name"]} ({$currencyjson["to-currency-symbol"]})", - "description" => $description, - "url" => "https://www.xe.com/currencyconverter/convert/?Amount={$amount}&From={$currencies[1]}&To={$currencies[2]}", - "thumb" => null, - "table" => [], - "sublink" => [] - ]; - } - - }catch(Exception $e){ - - // do nothing - } - } - } - } - - /* - Get small answer - */ - preg_match( - '/DDG\.ready\(function ?\(\) ?{DDH\.add\(({[\S\s]+}),"index"\)}\)/U', - $inithtml, - $smallanswer - ); - - if(isset($smallanswer[1])){ - - $smallanswer = json_decode($smallanswer[1], true); - - if( - !isset($smallanswer["require"]) && - isset($smallanswer["data"]["title"]) - ){ + $start = $get["newer"]; - if(isset($smallanswer["data"]["url"])){ + if($get["older"] !== false){ - $url = $this->unescapehtml($smallanswer["data"]["url"]); - }elseif(isset($smallanswer["meta"]["sourceUrl"])){ - - $url = $this->unescapehtml($smallanswer["meta"]["sourceUrl"]); + $end = $get["older"]; }else{ - $url = null; + $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){ - $out["answer"] = [ - [ - "title" => $this->unescapehtml($smallanswer["data"]["title"]), - "description" => [], - "url" => $this->sanitizeurl($url), - "thumb" => null, - "table" => [], - "sublink" => [] - ], - ...$out["answer"] - ]; + 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"]) + ){ - if(isset($smallanswer["data"]["subtitle"])){ + 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"])){ - $out["answer"][0]["description"][] = + $thumb = [ - "type" => "text", - "value" => isset($smallanswer["data"]["subtitle"]) ? $this->unescapehtml($smallanswer["data"]["subtitle"]) : null + "ratio" => "16:9", + "url" => $this->bingimg($item["video"]["thumbnail_url_template"]) + ]; + }else{ + + $thumb = + [ + "ratio" => null, + "url" => null ]; } - } - } - - unset($inithtml); - unset($answers); - unset($answer_count); - - /* - Get spelling autocorrect - */ - - preg_match( - '/DDG\.page\.showMessage\(\'spelling\',({[\S\s]+})\)/U', - $js, - $spelling - ); - - if(isset($spelling[1])){ - - $spelling = json_decode($spelling[1], true); - - switch((int)$spelling["qc"]){ - case 1: - case 3: - case 5: - $type = "including"; - break; - - default: - $type = "not_many"; - break; - } - - $out["spelling"] = [ - "type" => $type, - "using" => $this->unescapehtml(strip_tags($spelling["suggestion"])), - "correction" => $this->unescapehtml(strip_tags($spelling["recourseText"])) - ]; - } - - unset($spelling); - - /* - Get web results - */ - preg_match( - '/DDG\.pageLayout\.load\(\'d\', ?(\[{"[\S\s]*"}])\)/U', - $js, - $web - ); - - if(isset($web[1])){ - - try{ - $web = json_decode($web[1], true); - - for($i=0; $ibackend->store($web[$i]["n"] . "&biaexp=b&eslexp=a&litexp=c&msvrtexp=b&wrap=1", "web", $proxy); - continue; - } - - // ignore malformed data - if(!isset($web[$i]["t"])){ - - continue; - } - - $sublinks = []; - - if(isset($web[$i]["l"])){ - - for($k=0; $k $this->titledots($this->unescapehtml($web[$i]["l"][$k]["text"])), - "date" => null, - "description" => isset($web[$i]["l"][$k]["snippet"]) ? $this->titledots($this->unescapehtml($web[$i]["l"][$k]["snippet"])) : null, - "url" => $this->sanitizeurl($web[$i]["l"][$k]["targetUrl"]) - ] - ); + $table["Rating"] = $text; } } - - if( - preg_match( - '/^PDF<\/span>/', - $web[$i]["t"] - ) - ){ - - $type = "pdf"; - $web[$i]["t"] = - str_replace( - 'PDF', - "", - $web[$i]["t"] - ); - }else{ - - $type = "web"; - } - - if(isset($web[$i]["e"])){ - - $date = strtotime($web[$i]["e"]); - }else{ - - $date = null; - } - - array_push( - $out["web"], - [ - "title" => $this->titledots($this->unescapehtml(strip_tags($web[$i]["t"]))), - "description" => $this->titledots($this->unescapehtml(strip_tags($web[$i]["a"]))), - "url" => isset($web[$i]["u"]) ? $this->sanitizeurl($web[$i]["u"]) : $this->sanitizeurl($web[$i]["c"]), - "date" => $date, - "type" => $type, - "thumb" => - [ - "url" => null, - "ratio" => null - ], - "sublink" => $sublinks, - "table" => [] - ] - ); } - }catch(Exception $e){ + // get sublinks + $sublinks = []; - // do nothing + 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; } - } - - unset($web); - - /* - Get images - */ - preg_match( - '/DDG\.duckbar\.load\(\'images\', ?{[\s\S]*"results":(\[{"[\s\S]*}]),"vqd"/U', - $js, - $images - ); - - if(isset($images[1])){ - try{ - $images = json_decode($images[1], true); + if(isset($item["n"])){ - for($i=0; $ibingratio( - (int)$images[$i]["width"], - (int)$images[$i]["height"] - ); - - array_push( - $out["image"], - [ - "title" => $this->titledots($this->unescapehtml($images[$i]["title"])), - "source" => [ - [ - "url" => $images[$i]["image"], - "width" => (int)$images[$i]["width"], - "height" => (int)$images[$i]["height"] - ], - [ - "url" => $this->bingimg($images[$i]["thumbnail"]), - "width" => $ratio[0], - "height" => $ratio[1] - ] - ], - "url" => $this->sanitizeurl($images[$i]["url"]) - ] + // get NPT + $out["npt"] = + $this->backend->store( + $item["n"], + "web", + $proxy ); - } - - }catch(Exception $e){ - - // do nothing + continue; } } - unset($images); + // + // Get spelling + // + $js_tmp = + preg_split( + '/DDG\.page\.showMessage\(\s*\'spelling\'\s*,\s*/', + $js, + 2 + ); - /* - Get videos - */ - preg_match( - '/DDG\.duckbar\.load\(\'videos\', ?{[\s\S]*"results":(\[{"[\s\S]*}]),"vqd"/U', - $js, - $videos - ); - - if(isset($videos[1])){ - try{ - $videos = json_decode($videos[1], true); + if(count($js_tmp) > 1){ + + $json = + json_decode( + $this->fuckhtml + ->extract_json( + $js_tmp[1] + ), + true + ); + + if($json !== null){ - for($i=0; $i $type, + "using" => $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($videos[$i]["images"][$key])){ + if(isset($video["images"][$contender])){ - $cachekey = $key; + $thumb = [ + "ratio" => "16:9", + "url" => $this->bingimg($video["images"][$contender]) + ]; break; } } - if( - !isset($videos[$i]["title"]) || - !isset($videos[$i]["description"]) || - $cachekey === false || - !isset($videos[$i]["content"]) - ){ - - continue; - } - - array_push( - $out["video"], - [ - "title" => $this->titledots($this->unescapehtml($videos[$i]["title"])), - "description" => $videos[$i]["description"] == "" ? null : $this->titledots($this->unescapehtml($videos[$i]["description"])), - "date" => $videos[$i]["published"] == "" ? null : strtotime($videos[$i]["published"]), - "duration" => $videos[$i]["duration"] == 0 ? null : $this->hmstoseconds($videos[$i]["duration"]), - "views" => $videos[$i]["statistics"]["viewCount"] == 0 ? null : $videos[$i]["statistics"]["viewCount"], - "thumb" => - [ - "url" => $this->bingimg($videos[$i]["images"][$cachekey]), - "ratio" => "16:9" - ], - "url" => $this->sanitizeurl($videos[$i]["content"]) - ] - ); + $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"]) + ]; } - - }catch(Exception $e){ - - // do nothing } } - unset($videos); + // + // Get news + // + $js_tmp = + preg_split( + '/DDG\.duckbar\.load\(\s*\'news\'\s*,\s*/', + $js, + 2 + ); - /* - Get news - */ - preg_match( - '/DDG\.duckbar\.load\(\'news\', ?{[\s\S]*"results":(\[{"[\s\S]*}]),"vqd"/U', - $js, - $news - ); - - if(isset($news[1])){ - try{ - $news = json_decode($news[1], true); + if(count($js_tmp) > 1){ + + $json = + json_decode( + $this->fuckhtml + ->extract_json( + $js_tmp[1] + ), + true + ); + + if($json !== null){ - for($i=0; $i "16:9", + "url" => $news["image"] + ]; + }else{ + + $thumb = [ + "ratio" => null, + "url" => null + ]; } - array_push( - $out["news"], - [ - "title" => $this->titledots($this->unescapehtml($news[$i]["title"])), - "description" => $this->titledots($this->unescapehtml(strip_tags($news[$i]["excerpt"]))), - "date" => isset($news[$i]["date"]) ? (int)$news[$i]["date"] : null, - "thumb" => - [ - "url" => isset($news[$i]["image"]) ? $news[$i]["image"] : null, - "ratio" => "16:9" - ], - "url" => $this->sanitizeurl($news[$i]["url"]) - ] - ); + $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"]; } - }catch(Exception $e){ + // 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"]; + } + } + } + } - // do nothing + 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){ + + $push[] = [ + "type" => "quote", + "value" => + $this->fuckhtml + ->getTextContent( + $citation["cite"] + ) . " - " . + $this->fuckhtml + ->getTextContent( + $citation["source"] + ) + ]; + } + } + + // 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" => [] + ]; + } } } @@ -1895,65 +1277,40 @@ class ddg{ if($get["npt"]){ - [$npt, $proxy] = $this->backend->get($get["npt"], "images"); - - try{ - $json = $this->get( - $proxy, - "https://duckduckgo.com/i.js?" . $npt, - [], - ddg::req_xhr - ); - - }catch(Exception $err){ - - throw new Exception("Failed to get i.js"); - } + [$js_link, $proxy] = $this->backend->get($get["npt"], "images"); }else{ - $search = $get["s"]; - if(strlen($search) === 0){ + if(strlen($get["s"]) === 0){ throw new Exception("Search term is empty!"); } $proxy = $this->backend->get_ip(); - $country = $get["country"]; - $nsfw = $get["nsfw"]; - $date = $get["date"]; - $size = $get["size"]; - $color = $get["color"]; - $type = $get["type"]; - $layout = $get["layout"]; - $license = $get["license"]; - $filter = []; + $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 = [ - "hps" => "1", - "q" => $search, + "q" => $get["s"], "iax" => "images", "ia" => "images" ]; - if($date != "any"){ $filter[] = "time:$date"; } - if($size != "any"){ $filter[] = "size:$size"; } - if($color != "any"){ $filter[] = "color:$color"; } - if($type != "any"){ $filter[] = "type:$type"; } - if($layout != "any"){ $filter[] = "layout:$layout"; } - if($license != "any"){ $filter[] = "license:$license"; } - - $filter = implode(",", $filter); - - if($filter != ""){ + if($filters != ""){ - $get_filters["iaf"] = $filter; + $get_filters["iaf"] = $filters; } - switch($nsfw){ - - case "yes": $get_filters["kp"] = "-2"; break; - case "no": $get_filters["kp"] = "-1"; break; - } + $nsfw = $get["nsfw"] == "yes" ? "-2" : "-1"; + $get_filters["kp"] = $nsfw; try{ @@ -1965,56 +1322,44 @@ class ddg{ ); }catch(Exception $err){ - throw new Exception("Failed to get html"); + throw new Exception("Failed to fetch search page"); } preg_match( - '/vqd=([0-9-]+)/', + '/vqd="([0-9-]+)"/', $html, $vqd ); if(!isset($vqd[1])){ - throw new Exception("Failed to get vqd token"); + throw new Exception("Failed to grep VQD token"); } - $vqd = $vqd[1]; + $js_link = + "i.js?" . + http_build_query([ + "l" => $get["country"], + "o" => "json", + "q" => $get["s"], + "vqd" => $vqd[1], + "f" => $filters, + "p" => $nsfw + ]); + } + + try{ - // @TODO: s param = image offset - $js_params = [ - "l" => $country, - "o" => "json", - "q" => $search, - "vqd" => $vqd - ]; - - switch($nsfw){ - - case "yes": $js_params["p"] = "-1"; break; - case "no": $js_params["p"] = "1"; break; - } - - if(empty($filter)){ - - $js_params["f"] = "1"; - }else{ - - $js_params["f"] = $filter; - } - - try{ - $json = $this->get( + $json = + $this->get( $proxy, - "https://duckduckgo.com/i.js", - $js_params, + "https://duckduckgo.com/" . $js_link, + [], ddg::req_xhr ); - - }catch(Exception $err){ - - throw new Exception("Failed to get i.js"); - } + }catch(Exception $error){ + + throw new Exception("Failed to get i.js"); } $json = json_decode($json, true); @@ -2030,46 +1375,67 @@ class ddg{ "image" => [] ]; - if(isset($json["next"])){ + if(!isset($json["results"])){ - if(!isset($vqd)){ - - $vqd = array_values($json["vqd"])[0]; - } - - $out["npt"] = - $this->backend->store( - explode("?", $json["next"])[1] . "&vqd=" . - $vqd, - "images", - $proxy - ); + return $out; } - for($i=0; $i 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){ - $bingimg = $this->bingimg($json["results"][$i]["thumbnail"]); $ratio = $this->bingratio( - (int)$json["results"][$i]["width"], - (int)$json["results"][$i]["height"] + (int)$image["width"], + (int)$image["height"] ); $out["image"][] = [ - "title" => $this->titledots($this->unescapehtml($json["results"][$i]["title"])), + "title" => $this->titledots($image["title"]), "source" => [ [ - "url" => $json["results"][$i]["image"], - "width" => (int)$json["results"][$i]["width"], - "height" => (int)$json["results"][$i]["height"] + "url" => $image["image"], + "width" => (int)$image["width"], + "height" => (int)$image["height"] ], [ - "url" => $bingimg, + "url" => $this->bingimg($image["thumbnail"]), "width" => $ratio[0], - "height" => $ratio[1], + "height" => $ratio[1] ] ], - "url" => $this->sanitizeurl($json["results"][$i]["url"]) + "url" => $this->unshiturl($image["url"]) ]; } @@ -2080,104 +1446,96 @@ class ddg{ if($get["npt"]){ - [$npt, $proxy] = $this->backend->get($get["npt"], "videos"); + [$js_link, $proxy] = $this->backend->get($get["npt"], "videos"); - try{ - $json = json_decode($this->get( - $proxy, - "https://duckduckgo.com/v.js?" . - $npt, - [], - ddg::req_xhr - ), true); - - }catch(Exception $err){ - - throw new Exception("Failed to get v.js"); - } }else{ - - $search = $get["s"]; - if(strlen($search) === 0){ + if(strlen($get["s"]) === 0){ throw new Exception("Search term is empty!"); } $proxy = $this->backend->get_ip(); - $country = $get["country"]; - $nsfw = $get["nsfw"]; - $date = $get["date"]; - $resolution = $get["resolution"]; - $duration = $get["duration"]; - $license = $get["license"]; - - $filter = []; $get_filters = [ - "q" => $search, + "q" => $get["s"], "iax" => "videos", "ia" => "videos" ]; - switch($nsfw){ + $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 != ""){ - case "yes": $get_filters["kp"] = "-2"; break; - case "no": $get_filters["kp"] = "-1"; break; + $get_filters["iaf"] = $filters; } - if($date != "any"){ $filter[] = "publishedAfter:{$date}"; } - if($resolution != "any"){ $filter[] = "videoDefinition:{$resolution}"; } - if($duration != "any"){ $filter[] = "videoDuration:{$duration}"; } - if($license != "any"){ $filter[] = "videoLicense:{$license}"; } - - $filter = implode(",", $filter); - try{ - $html = $this->get( - $proxy, - "https://duckduckgo.com", - $get_filters, - ddg::req_web - ); - }catch(Exception $err){ + $html = + $this->get( + $proxy, + "https://duckduckgo.com/", + $get_filters, + ddg::req_web + ); + }catch(Exception $error){ - throw new Exception("Failed to get html"); + throw new Exception("Failed to fetch search page"); } preg_match( - '/vqd=([0-9-]+)/', + '/vqd="([0-9-]+)"/', $html, $vqd ); if(!isset($vqd[1])){ - throw new Exception("Failed to get vqd token"); + throw new Exception("Failed to grep VQD token"); } - $vqd = $vqd[1]; + $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{ - try{ - $json = json_decode($this->get( + $json = + $this->get( $proxy, - "https://duckduckgo.com/v.js", - [ - "l" => "us-en", - "o" => "json", - "sr" => 1, - "q" => $search, - "vqd" => $vqd, - "f" => $filter, - "p" => $get_filters["kp"] - ], + "https://duckduckgo.com/" . $js_link, + [], ddg::req_xhr - ), true); - - }catch(Exception $err){ - - throw new Exception("Failed to get v.js"); - } + ); + }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 = [ @@ -2190,59 +1548,75 @@ class ddg{ "reel" => [] ]; - if(isset($json["next"])){ + if(!isset($json["results"])){ + + return $out; + } + + // get NPT + if( + isset($json["next"]) && + $json["next"] !== null + ){ $out["npt"] = $this->backend->store( - explode("?", $json["next"])[1], + $json["next"], "videos", $proxy ); } - for($i=0; $i null, + "url" => null + ]; - foreach(["large", "medium", "small"] as &$key){ + foreach(["large", "medium", "small"] as $contender){ - if(isset($json["results"][$i]["images"][$key])){ + if(isset($video["images"][$contender])){ - $cachekey = $key; + $thumb = [ + "ratio" => "16:9", + "url" => $this->bingimg($video["images"][$contender]) + ]; break; } } - if( - !isset($json["results"][$i]["title"]) || - !isset($json["results"][$i]["description"]) || - $cachekey === false || - !isset($json["results"][$i]["content"]) - ){ - - continue; - } - - array_push( - $out["video"], - [ - "title" => $this->titledots($this->unescapehtml($json["results"][$i]["title"])), - "description" => $json["results"][$i]["description"] == "" ? null : $this->titledots($this->unescapehtml($json["results"][$i]["description"])), - "author" => [ - "name" => empty($json["results"][$i]["uploader"]) ? null : $this->unescapehtml($json["results"][$i]["uploader"]), - "url" => null, - "avatar" => null - ], - "date" => $json["results"][$i]["published"] == "" ? null : strtotime($json["results"][$i]["published"]), - "duration" => $json["results"][$i]["duration"] == 0 ? null : $this->hmstoseconds($json["results"][$i]["duration"]), - "views" => $json["results"][$i]["statistics"]["viewCount"] == 0 ? null : $json["results"][$i]["statistics"]["viewCount"], - "thumb" => [ - "url" => $this->bingimg($json["results"][$i]["images"][$cachekey]), - "ratio" => "16:9" - ], - "url" => $this->sanitizeurl($json["results"][$i]["content"]) - ] - ); + $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; @@ -2252,109 +1626,97 @@ class ddg{ if($get["npt"]){ - [$req, $proxy] = $this->backend->get($get["npt"], "news"); + [$js_link, $proxy] = $this->backend->get($get["npt"], "news"); - try{ - - $json = json_decode($this->get( - $proxy, - "https://duckduckgo.com/news.js?" . - $req, - [], - ddg::req_xhr - ), true); - - }catch(Exception $err){ - - throw new Exception("Failed to get news.js"); - } }else{ - - $search = $get["s"]; - if(strlen($search) === 0){ + if(strlen($get["s"]) === 0){ throw new Exception("Search term is empty!"); } $proxy = $this->backend->get_ip(); - $country = $get["country"]; - $nsfw = $get["nsfw"]; - $date = $get["date"]; - $get_params = [ - "q" => $search, + $get_filters = [ + "q" => $get["s"], "iar" => "news", "ia" => "news" ]; - switch($nsfw){ + 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; } - if($date != "any"){ - - $get_params["df"] = $date; - } - try{ - $html = $this->get( - $proxy, - "https://duckduckgo.com", - $get_params, - ddg::req_web - ); - }catch(Exception $err){ + $html = + $this->get( + $proxy, + "https://duckduckgo.com/", + $get_filters, + ddg::req_web + ); + }catch(Exception $error){ - throw new Exception("Failed to get html"); + throw new Exception("Failed to fetch search page"); } preg_match( - '/vqd=([0-9-]+)/', + '/vqd="([0-9-]+)"/', $html, $vqd ); if(!isset($vqd[1])){ - throw new Exception("Failed to get vqd token"); + throw new Exception("Failed to grep VQD token"); } - $vqd = $vqd[1]; - - try{ - - $js_params = [ - "l" => $country, + $js_link = + "news.js?" . + http_build_query([ + "l" => $get["country"], "o" => "json", "noamp" => "1", - "q" => $search, - "vqd" => $vqd, - "p" => $get_filters["kp"] - ]; - - if($date != "any"){ - - $js_params["df"] = $date; - }else{ - - $js_params["df"] = ""; - } - - $json = json_decode($this->get( + "m" => "30", + "q" => $get["s"], + "vqd" => $vqd[1], + "p" => $get_filters["kp"], + "df" => $date, + "u" => "bing" + ]); + } + + try{ + + $json = + $this->get( $proxy, - "https://duckduckgo.com/news.js", - $js_params, + "https://duckduckgo.com/" . $js_link, + [], ddg::req_xhr - ), true); - - }catch(Exception $err){ - - throw new Exception("Failed to get news.js"); - } + ); + }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 = [ @@ -2363,36 +1725,88 @@ class ddg{ "news" => [] ]; - if(isset($json["next"])){ + if(!isset($json["results"])){ + + return $out; + } + + // get NPT + if( + isset($json["next"]) && + $json["next"] !== null + ){ $out["npt"] = $this->backend->store( - explode("?", $json["next"])[1], + $json["next"], "news", $proxy ); } - for($i=0; $i "16:9", + "url" => $news["image"] + ]; + }else{ + + $thumb = [ + "ratio" => null, + "url" => null + ]; + } $out["news"][] = [ - "title" => $this->titledots($this->unescapehtml($json["results"][$i]["title"])), - "author" => $this->unescapehtml($json["results"][$i]["source"]), - "description" => $this->titledots($this->unescapehtml(strip_tags($json["results"][$i]["excerpt"]))), - "date" => $json["results"][$i]["date"], - "thumb" => - [ - "url" => isset($json["results"][$i]["image"]) ? $json["results"][$i]["image"] : null, - "ratio" => "16:9" - ], - "url" => $this->sanitizeurl($json["results"][$i]["url"]) + "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 hmstoseconds($time){ + 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; @@ -2416,255 +1830,9 @@ class ddg{ return $time; } + - private function titledots($title){ - - $substr = substr($title, -3); - - if( - $substr == "..." || - $substr == "…" - ){ - - return trim(substr($title, 0, -3)); - } - - return trim($title); - } - - private function unescapehtml($str){ - - return html_entity_decode( - str_replace( - [ - "
", - "
", - "
", - "
", - "
", - "
", - ], - "\n", - $str - ), - ENT_QUOTES | ENT_XML1, 'UTF-8' - ); - } - - private function bingimg($url){ - - $parse = parse_url($url); - parse_str($parse["query"], $parts); - - return "https://" . $parse["host"] . "/th?id=" . urlencode($parts["id"]); - } - - private function appendtext($payload, &$text, &$index){ - - if(trim($payload) == ""){ - - return; - } - - if( - $index !== 0 && - $text[$index - 1]["type"] == "text" - ){ - - $text[$index - 1]["value"] .= preg_replace('/ $/', " ", $payload); - }else{ - - $text[] = [ - "type" => "text", - "value" => preg_replace('/ $/', " ", $payload) - ]; - $index++; - } - } - - private function stackoverflow_parse($html){ - - $i = 0; - $answer = []; - - $this->fuckhtml->load($html); - - $tags = $this->fuckhtml->getElementsByTagName("*"); - - if(count($tags) === 0){ - - return [ - [ - "type" => "text", - "value" => htmlspecialchars_decode($html) - ] - ]; - } - - foreach($tags as $snippet){ - - switch($snippet["tagName"]){ - - case "p": - $this->fuckhtml->load($snippet["innerHTML"]); - - $codetags = - $this->fuckhtml - ->getElementsByTagName("*"); - - $tmphtml = $snippet["innerHTML"]; - - foreach($codetags as $tag){ - - if(!isset($tag["outerHTML"])){ - - continue; - } - - $tmphtml = - explode( - $tag["outerHTML"], - $tmphtml, - 2 - ); - - $value = $this->fuckhtml->getTextContent($tmphtml[0], false, false); - $this->appendtext($value, $answer, $i); - - $type = null; - switch($tag["tagName"]){ - - case "code": $type = "inline_code"; break; - case "em": $type = "italic"; break; - case "blockquote": $type = "quote"; break; - default: $type = "text"; - } - - if($type !== null){ - $value = $this->fuckhtml->getTextContent($tag, false, false); - - if(trim($value) != ""){ - - $answer[] = [ - "type" => $type, - "value" => rtrim($value) - ]; - $i++; - } - } - - if(count($tmphtml) === 2){ - - $tmphtml = $tmphtml[1] . "\n"; - }else{ - - break; - } - } - - if(is_array($tmphtml)){ - - $tmphtml = $tmphtml[0]; - } - - if(strlen($tmphtml) !== 0){ - - $value = $this->fuckhtml->getTextContent($tmphtml, true, false); - $this->appendtext($value, $answer, $i); - } - break; - - case "img": - $answer[] = [ - "type" => "image", - "url" => - $this->fuckhtml - ->getTextContent( - $tag["attributes"]["src"] - ) - ]; - $i++; - break; - - case "pre": - switch($answer[$i - 1]["type"]){ - - case "text": - case "italic": - $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]); - break; - } - - $answer[] = - [ - "type" => "code", - "value" => - rtrim( - $this->fuckhtml - ->getTextContent( - $snippet, - true, - false - ) - ) - ]; - $i++; - - break; - - case "ol": - $o = 0; - - $this->fuckhtml->load($snippet); - $li = - $this->fuckhtml - ->getElementsByTagName("li"); - - foreach($li as $elem){ - $o++; - - $this->appendtext( - $o . ". " . - $this->fuckhtml - ->getTextContent( - $elem - ), - $answer, - $i - ); - } - break; - } - } - - if( - $i !== 0 && - $answer[$i - 1]["type"] == "text" - ){ - - $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]); - } - - return $answer; - } - - private function bstoutf8($bs){ - - return iconv("UTF-8", "ISO-8859-1//TRANSLIT", $bs); - } - - private function limitnewlines($text){ - - preg_replace( - '/(?:[\n\r] *){2,}/m', - "\n\n", - $text - ); - - return $text; - } - - private function sanitizeurl($url){ + private function unshiturl($url){ // check for domains w/out first short subdomain (ex: www.) @@ -2732,16 +1900,12 @@ class ddg{ return $url; } - private function number_format($number){ + private function bingimg($url){ - $number = explode(".", sprintf('%f', $number)); + $parse = parse_url($url); + parse_str($parse["query"], $parts); - if(count($number) === 1){ - - return number_format((float)$number[0], 0, ",", "."); - } - - return number_format((float)$number[0], 0, ",", "") . "." . (string)$number[1]; + return "https://" . $parse["host"] . "/th?id=" . urlencode($parts["id"]); } private function bingratio($width, $height){ diff --git a/scraper/out.txt b/scraper/out.txt new file mode 100644 index 0000000..3d3cc16 --- /dev/null +++ b/scraper/out.txt @@ -0,0 +1 @@ +window.execDeep = function() {DDG.deep.adUnitIndex = '9';if (DDG.deep && DDG.deep.setUpstream) DDG.deep.setUpstream("bingv7aa");DDG.deep.bn={'ivc':1};if (DDG.pageLayout) DDG.pageLayout.load('a',[], {"page_load_url":"https://duckduckgo.com/y.js?iurl=%7B2%7DIG%3DB6F0220C3D7149908C9F88B8A339DBF5%26CID%3D0A9064B4E76965BC095471FEE6D96447%26Type%3DEvent.CPT%26DATA%3D0"});DDG.duckbar.future_signal_tab({signal:'low',from:'lyrics'});nrj('/a.js?q=4get&s=lyrics&ta=the%20living%20tombstone%20%26%20cg5&tl=4get&from=lyrics&l=ca-en',null,null,null,null,'lyrics');DDG.deep.signalSummary = "lyrics:l";DDG.inject('DDG.Data.languages.resultLanguages', {"en":["https://4get.ca/","https://www.youtube.com/watch?v=rdiQ6h9p2Qo","https://www.youtube.com/watch?v=13Azi9Jnsw4","https://genius.com/The-living-tombstone-and-cg5-4get-lyrics","https://soundcloud.com/cg5-beats/4get","https://music.apple.com/us/album/4get-feat-or3o-single/1659731644","https://music.apple.com/ca/album/4get-feat-or3o-single/1659731644","https://www.shazam.com/song/1659731645/4get-feat-or3o"]});DDG.deep.pageLayoutSummary = "w6RelatedQnA1w4r1";DDG.inject('DDG.Data.languages.adLanguages', {});if (DDG.pageLayout) DDG.pageLayout.load('d',[{"a":"4get: 4get is a proxy search engine that doesn't suck. Settings \u2022 Instances \u2022 News \u2022 API \u2022 About \u2022 Source Donate to the project. Clearnet \u2022 Tor \u2022 Report a problem Running on v8!! 4get: 4get is a proxy search engine that doesn't suck.","ae":null,"c":"https://4get.ca/","d":"4get.ca","da":"","h":0,"i":"4get.ca","k":null,"l":[{"snippet":"For example, a search query like \"hello my full legal name is jonathan gallindo and i want pictures of cloacas\" would definitively blow your cover. 4get doesn't contain ads or any third party javascript applets or trackers. I don't profile you, and quite frankly, I don't give a shit about what you search on there.","targetUrl":"https://4get.ca/about","text":"About"},{"snippet":"4get: Settings. Preference link Following this link will re-apply all cookies configured here and will redirect you to the front page. Useful if your browser clears out cookies after a browsing session. Bookmark me! Settings. By clicking . Update settings!, a plaintext . key=value. cookie will be stored on your browser. When selecting a default setting, the parameter is removed from your ...","targetUrl":"https://4get.ca/settings","text":"Settings"},{"snippet":"Learn how to setup your own instance here! https://git.lolcat.ca/lolcat/4get","targetUrl":"https://4get.ca/instances","text":"Instance Browser"}],"m":0,"o":0,"p":0,"s":"bingv7aa","t":"4get","u":"https://4get.ca/"},{"a":"Watch the official music video of 4GET, a song by CG5 and The Living Tombstone featuring OR3O, a popular YouTube rapper. The song is about forgetting about a breakup and dealing with the consequences of moving on.","ae":null,"b":"yt\tYouTube\twww.youtube.com","c":"https://www.youtube.com/watch?v=rdiQ6h9p2Qo","d":"www.youtube.com/watch?v=rdiQ6h9p2Qo","da":"mlb_games,nba_games,ncaafb_games,ncaamb_games,nfl_games,nhl_games,soccer_games,videos,wheretowatch","h":0,"i":"www.youtube.com","k":null,"m":0,"o":0,"p":0,"rf":[{"hint":{"text":"MEDIA:DURATION"},"items":[{"text":"3 min"}],"label":{"text":"Video Duration"}},{"hint":{"text":"BASE:AUTHOR"},"items":[{"text":"CG5"}],"label":{"text":"Author"}},{"hint":{"text":"BASE:VIEWSCOUNT"},"items":[{"text":"6524837"}],"label":{"text":"Views"}}],"s":"bingv7aa","sn":"YouTube","t":"CG5 \u00d7 The Living Tombstone - 4GET (feat. OR3O) [Official Music Video]","u":"https://www.youtube.com/watch?v=rdiQ6h9p2Qo","video":{"duration":"2:47","height":72,"statistics":{"view_count":"6524837"},"thumbnail_url_template":"https://www.bing.com/th?id=OVP.mJd6d0rVmbZXI21G1owfewHgFo&w={width}&h={height}&c=4&rs=1&pid=6.1","width":128}},{"a":"Music Production/Composition by @CG5 & @TheLivingTombstoneLyrics by @CG5 & @OR3O_xdExecutive Producer/Writer - CG5Director - Maudolith, Luximus, CG5Project M...","ae":null,"b":"yt\tYouTube\twww.youtube.com","c":"https://www.youtube.com/watch?v=13Azi9Jnsw4","d":"www.youtube.com/watch?v=13Azi9Jnsw4","da":"mlb_games,nba_games,ncaafb_games,ncaamb_games,nfl_games,nhl_games,soccer_games,videos,wheretowatch","h":0,"i":"www.youtube.com","k":null,"m":0,"o":0,"p":0,"rf":[{"hint":{"text":"MEDIA:DURATION"},"items":[{"text":"3 min"}],"label":{"text":"Video Duration"}},{"hint":{"text":"BASE:AUTHOR"},"items":[{"text":"OlayerGames"}],"label":{"text":"Author"}},{"hint":{"text":"BASE:VIEWSCOUNT"},"items":[{"text":"28"}],"label":{"text":"Views"}}],"s":"bingv7aa","sn":"YouTube","t":"CG5 \u00d7 The Living Tombstone - 4GET (feat. OR3O) [Official ... - YouTube","u":"https://www.youtube.com/watch?v=13Azi9Jnsw4","video":{"duration":"2:40","height":72,"statistics":{"view_count":"28"},"thumbnail_url_template":"https://www.bing.com/th?id=OVP.yUNwcfcNtyQhFC_y0FpsxgEsDh&w={width}&h={height}&c=4&rs=1&pid=6.1","width":128}},{"a":"4GET is a song written about the crippling anxiety that I have always fought through in my life, either during or moving on from a relationship. I have a pattern of overthinking, and I feel like ...","ae":null,"c":"https://genius.com/The-living-tombstone-and-cg5-4get-lyrics","d":"genius.com/The-living-tombstone-and-cg5-4get-lyrics","da":"lyrics","e":"2023-02-10T00:00:00.0000000","h":0,"i":"genius.com","k":null,"m":0,"o":0,"p":0,"s":"bingv7aa","sn":"Genius","t":"The Living Tombstone & CG5 - 4GET Lyrics - Genius","u":"https://genius.com/The-living-tombstone-and-cg5-4get-lyrics"},{"a":"CG5 \u00d7 The Living Tombstone - 4GET (feat. OR3O) [Official Music Video]Original Video: https://www.youtube.com/watch?v=rdiQ6h9p2QoSTREAM NOW! Spotify https:/...","ae":null,"b":"yt\tYouTube\twww.youtube.com","c":"https://www.youtube.com/watch?v=dhubsTy2lZo","d":"www.youtube.com/watch?v=dhubsTy2lZo","da":"mlb_games,nba_games,ncaafb_games,ncaamb_games,nfl_games,nhl_games,soccer_games,videos,wheretowatch","h":0,"i":"www.youtube.com","k":null,"m":0,"o":0,"p":0,"rf":[{"hint":{"text":"MEDIA:DURATION"},"items":[{"text":"60 min"}],"label":{"text":"Video Duration"}},{"hint":{"text":"BASE:AUTHOR"},"items":[{"text":"Hour Loop of Everything"}],"label":{"text":"Author"}},{"hint":{"text":"BASE:VIEWSCOUNT"},"items":[{"text":"25307"}],"label":{"text":"Views"}}],"s":"bingv7aa","sn":"YouTube","t":"[1 HOUR] CG5 \u00d7 The Living Tombstone - 4GET (feat. OR3O)","u":"https://www.youtube.com/watch?v=dhubsTy2lZo","video":{"duration":"1:00:00","height":72,"statistics":{"view_count":"25307"},"thumbnail_url_template":"https://www.bing.com/th?id=OVP.laPobSHOcCtZxEHjVVr4lwHgFo&w={width}&h={height}&c=4&rs=1&pid=6.1","width":128}},{"a":"4GET ABOUT IT. 2024-08-04T01:37:20Z Comment by Harrison Henderson. moving on moving on. 2024-08-04T01:36:53Z Comment by Squish. Still haven't 4gotten about it guys. 2024-07-31T23:22:27Z Comment by ( \u0361\u00b0 \u035c\u0296 \u0361\u00b0) @ryker-babcock-104111728 shut up you goober. 2024-07-26T19:23:49Z Buy 4GET (with The Living Tombstone) Users who like 4GET (with ...","ae":null,"b":"sc\tSoundCloud\tsoundcloud.com","c":"https://soundcloud.com/cg5-beats/4get","d":"soundcloud.com/cg5-beats/4get","da":"music_domains","e":"2023-02-11T00:00:00.0000000","h":0,"i":"soundcloud.com","k":null,"m":0,"o":0,"p":0,"s":"bingv7aa","sn":"SoundCloud ","t":"Stream 4GET (with The Living Tombstone) by CG5 - SoundCloud","u":"https://soundcloud.com/cg5-beats/4get"},{"a":"CG5, The Living Tombstone, OR3O \u00b7 Song \u00b7 2023","ae":null,"c":"https://open.spotify.com/track/0r8O0dqCAAUeHNMwuG5cGE","d":"open.spotify.com/track/0r8O0dqCAAUeHNMwuG5cGE","da":"translations","h":0,"i":"open.spotify.com","k":null,"m":0,"o":0,"p":0,"s":"bingv7aa","sn":"Spotify","t":"4GET - song by CG5, The Living Tombstone, OR3O | Spotify","u":"https://open.spotify.com/track/0r8O0dqCAAUeHNMwuG5cGE"},{"a":"Listen to 4Get (feat. Or3o) - Single by CG5 & The Living Tombstone on Apple Music. 2023. 1 Song. Duration: 2 minutes.","ae":null,"b":"apple\tApple\twww.apple.com","c":"https://music.apple.com/us/album/4get-feat-or3o-single/1659731644","d":"music.apple.com/us/album/4get-feat-or3o-single/1659731644","da":"","e":"2023-02-10T00:00:00.0000000","h":0,"i":"music.apple.com","k":null,"m":0,"o":0,"p":0,"s":"bingv7aa","sn":"Apple Music","t":"\u200e4Get (feat. Or3o) - Single - Album by CG5 & The Living Tombstone ...","u":"https://music.apple.com/us/album/4get-feat-or3o-single/1659731644"},{"a":"Listen to 4Get (feat. Or3o) - Single by CG5 & The Living Tombstone on Apple Music. Stream songs including 4Get (feat. Or3o). Album \u00b7 2023 \u00b7 1 Song. Listen Now; Browse; Radio; Search; Open in Music. 4Get (feat. Or3o) - Single. CG5, The Living Tombstone. POP \u00b7 2023","ae":null,"b":"apple\tApple\twww.apple.com","c":"https://music.apple.com/ca/album/4get-feat-or3o-single/1659731644","d":"music.apple.com/ca/album/4get-feat-or3o-single/1659731644","da":"","e":"2023-02-10T00:00:00.0000000","h":0,"i":"music.apple.com","k":null,"m":0,"o":0,"p":0,"s":"bingv7aa","sn":"Apple Music","t":"4Get (feat. Or3o) - Single by CG5 & The Living Tombstone","u":"https://music.apple.com/ca/album/4get-feat-or3o-single/1659731644"},{"a":"A pop song by CG5 and The Living Tombstone featuring Or3o, about struggling to forget about a breakup. Watch the music video, read the lyrics, and find concert dates and tickets.","ae":null,"c":"https://www.shazam.com/song/1659731645/4get-feat-or3o","d":"www.shazam.com/song/1659731645/4get-feat-or3o","da":"","e":"2024-10-04T00:00:00.0000000","h":0,"i":"www.shazam.com","k":null,"m":0,"o":0,"p":0,"s":"bingv7aa","sn":"Shazam","t":"4Get (feat. Or3o) - CG5 & The Living Tombstone: Song Lyrics ... - Shazam","u":"https://www.shazam.com/song/1659731645/4get-feat-or3o"},{"n":"/d.js?q=4get&kl=wt-wt&l=ca-en&p=&s=10&ex=-2&ct=CA&sp=0&vqd=4-103887988336486918322486492329158063110&host_region=use&dp=9j6VpiVid_rh-1fHIgwMUcLQX8qtCd-psGqF4kf4uwebRPE2jdYEY3NIlp3Ft3-32s4ZYFpnpdg8GIirIBiW8M4QxN-J0GNEVDw1o_brpfU.JHX7IDoYnBvxb0g3Ks-jyA"}]);DDG.duckbar.load('images');DDG.duckbar.load('news');DDG.duckbar.load('videos');DDG.duckbar.loadModule('related_searches', {"ads":[],"query":"4get","queryEncoded":"4get","response_type":"places","results":[{"display_text":"4get meaning","text":"4get meaning","web_search_url":"?q=4get%20meaning"},{"display_text":"what is 4get about cg5","text":"what is 4get about cg5","web_search_url":"?q=what%20is%204get%20about%20cg5"},{"display_text":"living tombstone 4get","text":"living tombstone 4get","web_search_url":"?q=living%20tombstone%204get"},{"display_text":"4get github","text":"4get github","web_search_url":"?q=4get%20github"},{"display_text":"4get 1 hour","text":"4get 1 hour","web_search_url":"?q=4get%201%20hour"},{"display_text":"the story behind 4get","text":"the story behind 4get","web_search_url":"?q=the%20story%20behind%204get"},{"display_text":"4get.ca","text":"4get.ca","web_search_url":"?q=4get.ca"},{"display_text":"4get lucki","text":"4get lucki","web_search_url":"?q=4get%20lucki"}],"vqd":{"4get":"4-103887988336486918322486492329158063110"}});DDG.duckbar.load('products');if (DDG.pageLayout) DDG.pageLayout.initialize({"mainline":{"items":[["organic"],["organic"],["organic"],["organic"],["organic"],["organic"],["organic"],["organic"],["organic"],["organic"],["related_searches"]]}}, { start: 0 });DDG.deep.emit("load:completed");};