backend = new backend("coccoc"); include "lib/fuckhtml.php"; $this->fuckhtml = new fuckhtml(); $this->proc = curl_multi_init(); $this->handles = []; } private function get($proxy, $url, $cookie = false, $get = []){ $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); if($cookie === false){ curl_setopt($curlproc, CURLOPT_HTTPHEADER, [ "User-Agent: " . config::USER_AGENT, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language: en-CA,en-US;q=0.9,en;q=0.8", "Accept-Encoding: gzip, deflate, br, zstd", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: none", "Sec-Fetch-User: ?1", "Priority: u=0, i" ]); }else{ curl_setopt($curlproc, CURLOPT_HTTPHEADER, [ "User-Agent: " . config::USER_AGENT, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language: en-CA,en-US;q=0.9,en;q=0.8", "Accept-Encoding: gzip, deflate, br, zstd", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Referer: https://coccoc.com/search", "Cookie: {$cookie}", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: same-origin", "Priority: u=0, i", "TE: trailers" ]); } $this->backend->assign_proxy($curlproc, $proxy); curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding 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){ return [ "nsfw" => [ "display" => "NSFW", "option" => [ "yes" => "Yes", // nsfw by default???? "no" => "No" // &safe=1 ] ], "time" => [ "display" => "Time posted", "option" => [ "any" => "Any time", "1w" => "1 week ago", "2w" => "2 weeks ago", "1m" => "1 month ago", "3m" => "3 months ago", "6m" => "6 months ago", "1Y" => "1 year ago" ] ], "filter" => [ "display" => "Remove duplicates", "option" => [ "no" => "No", "yes" => "Yes" // &filter=0 ] ] ]; } private function handle_redir($proxy, $html){ $this->fuckhtml->load($html); $title = $this->fuckhtml ->getElementsByTagName("title"); if( count($title) !== 0 && $this->fuckhtml ->getTextContent( $title[0] ) == "Redirecting..." ){ // get cookie if( preg_match( '/document\.cookie ?= ?"([^;"]+).*"/', $html, $cookie ) === 0 ){ throw new Exception("Failed to grep cookie"); } // get redirect URL if( preg_match( '/window\.location\.replace\("([^"]+)"\)/', $html, $redir ) === 0 ){ throw new Exception("Failed to grep redirection URL"); } try{ $html = $this->get( $proxy, $redir[1], $cookie[1], [] ); }catch(Exception $error){ throw new Exception("Failed to get redirection page"); } } return $html; } private function redirect_add_url($url){ // https://coccoc.com/composer/goto?url=0AWtXcuK-mJVvFl8Qhny9LZdMOyEd08nFmTtpg82bmuqtp8p8G_OnTebNhPtNyIXMGQ1TZZ-ADMz0QEiFJhsTLIFkkPDXnJjHo3kqsobmyntiOhFag-2aqTs if( preg_match( '/\/goto\?url=([^&]+)/', $url, $slug ) === 0 ){ // not a coccoc redirect return $url; } $curlproc = curl_init(); curl_setopt($curlproc, CURLOPT_URL, "https://coccoc.com/composer/goto?url=" . urlencode($slug[1])); curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding curl_setopt($curlproc, CURLOPT_HTTPHEADER, ["User-Agent: " . config::USER_AGENT, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language: en-US,en;q=0.5", "Accept-Encoding: gzip, deflate, br, zstd", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: none", "Sec-Fetch-User: ?1", "Priority: u=0, i"] ); 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); // funny curl_setopt($curlproc, CURLOPT_HEADER, true); //$this->backend->assign_proxy($curlproc, $proxy); curl_multi_add_handle($this->proc, $curlproc); $this->handles[] = [ "replacement" => $url, "handle" => $curlproc ]; return $url; } private function resolve_urls(&$scraped_data){ do{ do{ $status = curl_multi_exec($this->proc, $active); }while($status === CURLM_CALL_MULTI_PERFORM); if($active){ curl_multi_select($this->proc); } }while($active && $status === CURLM_OK); // // if we reach this, we're done downloading garbage // // parse redirect values & create data shit $keys = []; $replacements = []; for($i=0; $ihandles); $i++){ $headers = curl_multi_getcontent( $this->handles[$i]["handle"] ); if( !preg_match( '/^location: ?([^ ]+)/im', $headers, $link ) ){ // failed to extract redirect location continue; } // theyre too retarded to encode shit properly apparently. // this is the best i can do, it will still yield errors when searching for "higurashi" // example: https://coccoc.com/composer/goto?url=0AWtXcuIKWPOkxvarGvy3GJ36XipRirzzqAvltMM5QUfB1RPu49LrNB9qmuAQyvvI0eCTwf-FpjRjxUN8XbruhA0rvCHNNlhVNEozMUqgRcjzsdhyUMCEgwLCXVTQkXnLXqrUw8celcIZdfmZ1geOvioUcg // redirects to https://vi.wikipedia.org/wiki/Higurashi_no_Naku_Koro_ni#C%E1ch_ch?i // notice the encoding errors after # // retards $link = mb_convert_encoding($link[1], "UTF-8", "ISO-8859-1"); if( preg_match( '/^(https?:|)\/\//', $link ) ){ $keys[] = $this->handles[$i]["replacement"]; $replacements[] = $link; } } $this->recursive_replace($keys, $replacements, $scraped_data); curl_multi_close($this->proc); } private function recursive_replace(&$keys, &$replacements, &$pointer){ foreach($pointer as $key => &$value){ if($key == "url"){ $search = array_search( $value, $keys ); if($search !== false){ // replace url $value = $replacements[$search]; } }elseif(is_array($value)){ $this->recursive_replace($keys, $replacements, $value); } } } public function web($get){ if($get["npt"]){ [$query, $proxy] = $this->backend->get( $get["npt"], "web" ); $query = json_decode($query, true); }else{ $proxy = $this->backend->get_ip(); $query = [ "query" => $get["s"] ]; // add filters if($get["nsfw"] == "no"){ $query["safe"] = 1; } if($get["time"] != "any"){ $query["tbs"] = $get["time"]; } if($get["filter"] == "yes"){ $query["filter"] = 0; } } try{ $html = $this->get( $proxy, "https://coccoc.com/search", false, $query ); }catch(Exception $error){ throw new Exception("Failed to get search page"); } //$html = file_get_contents("scraper/coccoc.html"); $html = $this->handle_redir($proxy, $html); $html = explode("window.composerResponse", $html, 2); if(count($html) !== 2){ throw new Exception("Failed to grep window.composerResponse"); } $html = json_decode( $this->fuckhtml ->extract_json( ltrim($html[1], " =") ), true ); if($html === null){ throw new Exception("Failed to decode JSON"); } if( isset($html["captcha"]) && (int)$html["captcha"] === 1 ){ throw new Exception("Coc Coc returned a Captcha"); } if(!isset($html["search"]["search_results"])){ throw new Exception("Coc Coc did not return a search_results object"); } $out = [ "status" => "ok", "spelling" => [ "type" => "no_correction", "using" => null, "correction" => null ], "npt" => null, "answer" => [], "web" => [], "image" => [], "video" => [], "news" => [], "related" => [] ]; // word correction foreach($html["top"] as $element){ if(isset($element["spellChecker"][0]["query"])){ $out["spelling"] = [ "type" => "not_many", "using" => $html["search"]["query"], "correction" => $element["spellChecker"][0]["query"] ]; } } foreach($html["search"]["search_results"] as $result){ if(isset($result["type"])){ switch($result["type"]){ // // Related searches // case "related_queries": $out["related"] = $result["queries"]; continue 2; // // Videos // case "video_hits": foreach($result["results"] as $video){ if( isset($video["image_url"]) && !empty($video["image_url"]) ){ $thumb = [ "ratio" => "16:9", "url" => $video["image_url"] ]; }else{ $thumb = [ "ratio" => null, "url" => null ]; } $out["video"][] = [ "title" => $this->titledots( $this->fuckhtml ->getTextContent( $video["title"] ) ), "description" => null, "author" => [ "name" => $video["uploader"], "url" => null, "avatar" => null ], "date" => (int)$video["date"], "duration" => (int)$video["duration"], "views" => null, "thumb" => $thumb, "url" => $video["url"] ]; } continue 2; } } if( !isset($result["title"]) || !isset($result["url"]) ){ // should not happen continue; } if(isset($result["rich"]["data"]["image_url"])){ $thumb = [ "url" => $result["rich"]["data"]["image_url"], "ratio" => "16:9" ]; }else{ $thumb = [ "url" => null, "ratio" => null ]; } $sublinks = []; if(isset($result["rich"]["data"]["linked_docs"])){ foreach($result["rich"]["data"]["linked_docs"] as $sub){ $sublinks[] = [ "title" => $this->titledots( $this->fuckhtml ->getTextContent( $sub["title"] ) ), "description" => $this->titledots( $this->fuckhtml ->getTextContent( $sub["content"] ) ), "date" => null, "url" => $this->redirect_add_url($sub["url"]) ]; } } // get date if(isset($result["date"])){ $date = (int)$result["date"]; }else{ $date = null; } // probe for metadata $table = []; if(isset($result["rich"]["data"]["rating"])){ $table["Rating"] = $result["rich"]["data"]["rating"]; if(isset($result["rich"]["data"]["num_rating"])){ $table["Rating"] .= " (" . number_format($result["rich"]["data"]["num_rating"]) . " ratings)"; } } if(isset($result["rich"]["data"]["views"])){ $table["Views"] = number_format($result["rich"]["data"]["views"]); } if(isset($result["rich"]["data"]["duration"])){ $table["Duration"] = $this->int2hms($result["rich"]["data"]["duration"]); } if(isset($result["rich"]["data"]["channel_name"])){ $table["Author"] = $result["rich"]["data"]["channel_name"]; } if(isset($result["rich"]["data"]["video_quality"])){ $table["Quality"] = $result["rich"]["data"]["video_quality"]; } if(isset($result["rich"]["data"]["category"])){ $table["Category"] = $result["rich"]["data"]["category"]; } $out["web"][] = [ "title" => $this->titledots( $this->fuckhtml ->getTextContent( $result["title"] ) ), "description" => $this->titledots( $this->fuckhtml ->getTextContent( $result["content"] ) ), "url" => $this->redirect_add_url($result["url"]), "date" => $date, "type" => "web", "thumb" => $thumb, "sublink" => $sublinks, "table" => $table ]; } // // Get wikipedia head // if(isset($html["right"])){ foreach($html["right"] as $wiki){ $description = []; if(isset($wiki["short_intro"])){ $description[] = [ "type" => "quote", "value" => $wiki["short_intro"], ]; } if(isset($wiki["intro"])){ $description[] = [ "type" => "text", "value" => $wiki["intro"], ]; } // get table elements $table = []; if(isset($wiki["fields"])){ foreach($wiki["fields"] as $element){ $table[$element["title"]] = implode(", ", $element["value"]); } } // get sublinks $sublinks = []; if(isset($wiki["website"])){ if( preg_match( '/^http/', $wiki["website"] ) === 0 ){ $sublinks["Website"] = "https://" . $wiki["website"]; }else{ $sublinks["Website"] = $wiki["website"]; } } if(isset($wiki["profiles"])){ foreach($wiki["profiles"] as $sitename => $url){ $sitename = explode("_", $sitename); $sitename = ucfirst($sitename[count($sitename) - 1]); $sublinks[$sitename] = $url; } } $out["answer"][] = [ "title" => isset($wiki["title"]) ? $this->titledots( $wiki["title"] ) : null, "description" => $description, "url" => null, "thumb" => isset($wiki["image"]["contentUrl"]) ? $wiki["image"]["contentUrl"] : null, "table" => $table, "sublink" => $sublinks ]; } } // get next page if((int)$html["search"]["page"] < (int)$html["search"]["max_page"]){ // https://coccoc.com/composer?_=1754021153532&p=0&q=zbabduiqwhduwqhdnwq&reqid=bwcAs00q&s=direct&apiV=1 // ^json endpoint, but we can just do &page=2 lol if(!isset($query["page"])){ $query["page"] = 2; }else{ $query["page"]++; } $out["npt"] = $this->backend ->store( json_encode($query), "web", $proxy ); } $this->resolve_urls($out); return $out; } public function video($get){ //$html = file_get_contents("scraper/coccoc.html"); if($get["npt"]){ [$query, $proxy] = $this->backend->get( $get["npt"], "videos" ); $query = json_decode($query, true); }else{ $proxy = $this->backend->get_ip(); $query = [ "query" => $get["s"], "tbm" => "vid" ]; // add filters if($get["nsfw"] == "no"){ $query["safe"] = 1; } if($get["time"] != "any"){ $query["tbs"] = $get["time"]; } if($get["filter"] == "yes"){ $query["filter"] = 0; } } try{ $html = $this->get( $proxy, "https://coccoc.com/search", false, $query ); }catch(Exception $error){ throw new Exception("Failed to get search page"); } $html = $this->handle_redir($proxy, $html); $html = explode("window.composerResponse", $html, 2); if(count($html) !== 2){ throw new Exception("Failed to grep window.composerResponse"); } $html = json_decode( $this->fuckhtml ->extract_json( ltrim($html[1], " =") ), true ); if($html === null){ throw new Exception("Failed to decode JSON"); } $out = [ "status" => "ok", "npt" => null, "video" => [], "author" => [], "livestream" => [], "playlist" => [], "reel" => [] ]; if(!isset($html["search_video"]["search_results"])){ if(isset($html["search_video"]["error"]["title"])){ if($html["search_video"]["error"]["title"] == "Không tìm thấy kết quả nào"){ return $out; } throw new Exception("Coc Coc returned an error: " . $html["search_video"]["error"]["title"]); } throw new Exception("Coc Coc did not supply a search_results object"); } foreach($html["search_video"]["search_results"] as $video){ if(isset($video["rich"]["data"]["image_url"])){ $thumb = [ "ratio" => "16:9", "url" => $video["rich"]["data"]["image_url"] ]; }else{ $thumb = [ "ratio" => null, "url" => null ]; } $out["video"][] = [ "title" => $this->titledots( $this->fuckhtml ->getTextContent( $video["title"] ) ), "description" => $this->titledots( $this->fuckhtml ->getTextContent( $video["content"] ) ), "author" => [ "name" => isset($video["rich"]["data"]["channel_name"]) ? $video["rich"]["data"]["channel_name"] : null, "url" => null, "avatar" => null ], "date" => isset($video["date"]) ? $video["date"] : null, "duration" => isset($video["rich"]["data"]["duration"]) ? (int)$video["rich"]["data"]["duration"] : null, "views" => null, "thumb" => $thumb, "url" => $this->redirect_add_url($video["url"]) ]; } // get next page if((int)$html["search_video"]["page"] < (int)$html["search_video"]["max_page"]){ if(!isset($query["page"])){ $query["page"] = 2; }else{ $query["page"]++; } $out["npt"] = $this->backend ->store( json_encode($query), "videos", $proxy ); } $this->resolve_urls($out); return $out; } private function titledots($title){ return trim($title, " .\t\n\r\0\x0B…"); } private function int2hms($seconds){ $hours = floor($seconds / 3600); $minutes = floor(($seconds % 3600) / 60); $seconds = $seconds % 60; return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds); } }