brave.php 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528
  1. <?php
  2. class brave{
  3. public function __construct(){
  4. include "lib/fuckhtml.php";
  5. $this->fuckhtml = new fuckhtml();
  6. include "lib/nextpage.php";
  7. $this->nextpage = new nextpage("brave");
  8. }
  9. public function getfilters($page){
  10. switch($page){
  11. case "web":
  12. return [
  13. "country" => [
  14. "display" => "Country",
  15. "option" => [
  16. "all" => "All Regions",
  17. "ar" => "Argentina",
  18. "au" => "Australia",
  19. "at" => "Austria",
  20. "be" => "Belgium",
  21. "br" => "Brazil",
  22. "ca" => "Canada",
  23. "cl" => "Chile",
  24. "cn" => "China",
  25. "dk" => "Denmark",
  26. "fi" => "Finland",
  27. "fr" => "France",
  28. "de" => "Germany",
  29. "hk" => "Hong Kong",
  30. "in" => "India",
  31. "id" => "Indonesia",
  32. "it" => "Italy",
  33. "jp" => "Japan",
  34. "kr" => "Korea",
  35. "my" => "Malaysia",
  36. "mx" => "Mexico",
  37. "nl" => "Netherlands",
  38. "nz" => "New Zealand",
  39. "no" => "Norway",
  40. "pl" => "Poland",
  41. "pt" => "Portugal",
  42. "ph" => "Philippines",
  43. "ru" => "Russia",
  44. "sa" => "Saudi Arabia",
  45. "za" => "South Africa",
  46. "es" => "Spain",
  47. "se" => "Sweden",
  48. "ch" => "Switzerland",
  49. "tw" => "Taiwan",
  50. "tr" => "Turkey",
  51. "gb" => "United Kingdom",
  52. "us" => "United States"
  53. ]
  54. ],
  55. "nsfw" => [
  56. "display" => "NSFW",
  57. "option" => [
  58. "yes" => "Yes",
  59. "maybe" => "Maybe",
  60. "no" => "No"
  61. ]
  62. ],
  63. "newer" => [
  64. "display" => "Newer than",
  65. "option" => "_DATE"
  66. ],
  67. "older" => [
  68. "display" => "Older than",
  69. "option" => "_DATE"
  70. ],
  71. "spellcheck" => [
  72. "display" => "Spellcheck",
  73. "option" => [
  74. "yes" => "Yes",
  75. "no" => "No"
  76. ]
  77. ]
  78. ];
  79. break;
  80. case "images":
  81. case "videos":
  82. case "news":
  83. return [
  84. "country" => [
  85. "display" => "Country",
  86. "option" => [
  87. "all" => "All regions",
  88. "ar" => "Argentina",
  89. "au" => "Australia",
  90. "at" => "Austria",
  91. "be" => "Belgium",
  92. "br" => "Brazil",
  93. "ca" => "Canada",
  94. "cl" => "Chile",
  95. "cn" => "China",
  96. "dk" => "Denmark",
  97. "fi" => "Finland",
  98. "fr" => "France",
  99. "de" => "Germany",
  100. "hk" => "Hong Kong",
  101. "in" => "India",
  102. "id" => "Indonesia",
  103. "it" => "Italy",
  104. "jp" => "Japan",
  105. "kr" => "Korea",
  106. "my" => "Malaysia",
  107. "mx" => "Mexico",
  108. "nl" => "Netherlands",
  109. "nz" => "New Zealand",
  110. "no" => "Norway",
  111. "pl" => "Poland",
  112. "pt" => "Portugal",
  113. "ph" => "Philippines",
  114. "ru" => "Russia",
  115. "sa" => "Saudi Arabia",
  116. "za" => "South Africa",
  117. "es" => "Spain",
  118. "se" => "Sweden",
  119. "ch" => "Switzerland",
  120. "tw" => "Taiwan",
  121. "tr" => "Turkey",
  122. "gb" => "United Kingdom",
  123. "us" => "United States"
  124. ]
  125. ],
  126. "nsfw" => [
  127. "display" => "NSFW",
  128. "option" => [
  129. "yes" => "Yes",
  130. "maybe" => "Maybe",
  131. "no" => "No"
  132. ]
  133. ]
  134. ];
  135. break;
  136. }
  137. }
  138. private function get($url, $get = [], $nsfw, $country){
  139. switch($nsfw){
  140. case "yes": $nsfw = "off"; break;
  141. case "maybe": $nsfw = "moderate"; break;
  142. case "no": $nsfw = "strict"; break;
  143. }
  144. if($country == "any"){
  145. $country = "all";
  146. }
  147. $headers = [
  148. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0",
  149. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  150. "Accept-Language: en-US,en;q=0.5",
  151. "Accept-Encoding: gzip",
  152. "Cookie: safesearch={$nsfw}; country={$country}; useLocation=0; summarizer=0",
  153. "DNT: 1",
  154. "Connection: keep-alive",
  155. "Upgrade-Insecure-Requests: 1",
  156. "Sec-Fetch-Dest: document",
  157. "Sec-Fetch-Mode: navigate",
  158. "Sec-Fetch-Site: none",
  159. "Sec-Fetch-User: ?1"
  160. ];
  161. $curlproc = curl_init();
  162. if($get !== []){
  163. $get = http_build_query($get);
  164. $url .= "?" . $get;
  165. }
  166. curl_setopt($curlproc, CURLOPT_URL, $url);
  167. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  168. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  169. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  170. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  171. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  172. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  173. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  174. $data = curl_exec($curlproc);
  175. if(curl_errno($curlproc)){
  176. throw new Exception(curl_error($curlproc));
  177. }
  178. curl_close($curlproc);
  179. return $data;
  180. }
  181. public function web($get){
  182. if($get["npt"]){
  183. // get next page data
  184. $q = json_decode($this->nextpage->get($get["npt"], "web"), true);
  185. $search = $q["q"];
  186. $q["spellcheck"] = "0";
  187. $nsfw = $q["nsfw"];
  188. unset($q["nsfw"]);
  189. $country = $q["country"];
  190. unset($q["country"]);
  191. }else{
  192. // get _GET data instead
  193. $search = $get["s"];
  194. if(strlen($search) === 0){
  195. throw new Exception("Search term is empty!");
  196. }
  197. if(strlen($search) > 2048){
  198. throw new Exception("Search query is too long!");
  199. }
  200. $nsfw = $get["nsfw"];
  201. $country = $get["country"];
  202. $older = $get["older"];
  203. $newer = $get["newer"];
  204. $spellcheck = $get["spellcheck"];
  205. $q = [
  206. "q" => $search
  207. ];
  208. /*
  209. Pass older/newer filters to brave
  210. */
  211. if($newer !== false){
  212. $newer = date("Y-m-d", $newer);
  213. if($older === false){
  214. $older = date("Y-m-d", time());
  215. }
  216. }
  217. if(
  218. is_string($older) === false &&
  219. $older !== false
  220. ){
  221. $older = date("Y-m-d", $older);
  222. if($newer === false){
  223. $newer = "1970-01-02";
  224. }
  225. }
  226. if($older !== false){
  227. $q["tf"] = "{$newer}to{$older}";
  228. }
  229. // spellcheck
  230. if($spellcheck == "no"){
  231. $q["spellcheck"] = "0";
  232. }
  233. }
  234. /*
  235. $handle = fopen("scraper/brave.html", "r");
  236. $html = fread($handle, filesize("scraper/brave.html"));
  237. fclose($handle);
  238. */
  239. try{
  240. $html =
  241. $this->get(
  242. "https://search.brave.com/search",
  243. $q,
  244. $nsfw,
  245. $country
  246. );
  247. }catch(Exception $error){
  248. throw new Exception("Could not fetch search page");
  249. }
  250. $out = [
  251. "status" => "ok",
  252. "spelling" => [
  253. "type" => "no_correction",
  254. "using" => null,
  255. "correction" => null
  256. ],
  257. "npt" => null,
  258. "answer" => [],
  259. "web" => [],
  260. "image" => [],
  261. "video" => [],
  262. "news" => [],
  263. "related" => []
  264. ];
  265. // load html
  266. $this->fuckhtml->load($html);
  267. /*
  268. Get next page "token"
  269. */
  270. $nextpage =
  271. $this->fuckhtml
  272. ->getElementById(
  273. "pagination",
  274. "div"
  275. );
  276. if($nextpage){
  277. $this->fuckhtml->load($nextpage);
  278. $nextpage =
  279. $this->fuckhtml
  280. ->getElementsByClassName("btn", "a");
  281. if(count($nextpage) !== 0){
  282. $nextpage =
  283. $nextpage[count($nextpage) - 1];
  284. if(
  285. strtolower(
  286. $this->fuckhtml
  287. ->getTextContent(
  288. $nextpage
  289. )
  290. ) == "next"
  291. ){
  292. preg_match(
  293. '/offset=([0-9]+)/',
  294. $this->fuckhtml->getTextContent($nextpage["attributes"]["href"]),
  295. $nextpage
  296. );
  297. $q["offset"] = (int)$nextpage[1];
  298. $q["nsfw"] = $nsfw;
  299. $q["country"] = $country;
  300. $out["npt"] =
  301. $this->nextpage->store(
  302. json_encode($q),
  303. "web"
  304. );
  305. }
  306. }
  307. }
  308. $this->fuckhtml->load($html);
  309. $script_disc =
  310. $this->fuckhtml
  311. ->getElementsByTagName(
  312. "script"
  313. );
  314. $grep = [];
  315. foreach($script_disc as $discs){
  316. preg_match(
  317. '/const data ?= ?(\[{.*}]);/',
  318. $discs["innerHTML"],
  319. $grep
  320. );
  321. if(isset($grep[1])){
  322. break;
  323. }
  324. }
  325. if(!isset($grep[1])){
  326. throw new Exception("Could not get data JS");
  327. }
  328. $data =
  329. $this->fuckhtml
  330. ->parseJsObject(
  331. $grep[1]
  332. );
  333. unset($grep);
  334. $data = $data[1]["data"]["body"]["response"];
  335. /*
  336. Get web results
  337. */
  338. if(!isset($data["web"]["results"])){
  339. return $out;
  340. }
  341. //$has_so_answer = false;
  342. foreach($data["web"]["results"] as $result){
  343. if(isset($result["thumbnail"])){
  344. $thumb = [
  345. "ratio" => $result["thumbnail"]["logo"] == "false" ? "16:9" : "1:1",
  346. "url" => $result["thumbnail"]["original"]
  347. ];
  348. }else{
  349. $thumb = [
  350. "ratio" => null,
  351. "url" => null
  352. ];
  353. }
  354. $sublink = [];
  355. if(isset($result["cluster"])){
  356. foreach($result["cluster"] as $cluster){
  357. $sublink[] = [
  358. "title" => $this->titledots($cluster["title"]),
  359. "description" =>
  360. $this->titledots(
  361. $this->fuckhtml
  362. ->getTextContent(
  363. $cluster["description"]
  364. )
  365. ),
  366. "url" => $cluster["url"],
  367. "date" => null
  368. ];
  369. }
  370. }
  371. // parse table elements
  372. $table = [];
  373. // product
  374. $ref = null;
  375. if(isset($result["product"])){
  376. $ref = &$result["product"];
  377. }elseif(isset($result["creative_work"])){
  378. $ref = &$result["creative_work"];
  379. }
  380. if($ref !== null){
  381. if(isset($ref["offers"])){
  382. foreach($ref["offers"] as $offer){
  383. $price = null;
  384. if(isset($offer["price"])){
  385. if((float)$offer["price"] == 0){
  386. $price = "Free";
  387. }else{
  388. $price = $offer["price"];
  389. }
  390. }
  391. if($price !== "Free"){
  392. if(isset($offer["priceCurrency"])){
  393. $price .= " " . $offer["priceCurrency"];
  394. }
  395. }
  396. if($price !== null){
  397. $table["Price"] = trim($price);
  398. }
  399. }
  400. }
  401. if(isset($ref["rating"])){
  402. $rating = null;
  403. if(isset($ref["rating"]["ratingValue"])){
  404. $rating = $ref["rating"]["ratingValue"];
  405. if(isset($ref["rating"]["bestRating"])){
  406. $rating .= "/" . $ref["rating"]["bestRating"];
  407. }
  408. }
  409. if(isset($ref["rating"]["reviewCount"])){
  410. $isnull = $rating === null ? false : true;
  411. if($isnull){
  412. $rating .= " (";
  413. }
  414. $rating .= number_format($ref["rating"]["reviewCount"]) . " hits";
  415. if($isnull){
  416. $rating .= ")";
  417. }
  418. }
  419. if($rating !== null){
  420. $table["Rating"] = $rating;
  421. }
  422. }
  423. }
  424. // video
  425. if(isset($result["video"])){
  426. foreach($result["video"] as $key => $value){
  427. if(is_string($result["video"][$key]) === false){
  428. continue;
  429. }
  430. $table[ucfirst($key)] = $value;
  431. }
  432. }
  433. /*
  434. Get StackOverflow answers
  435. */
  436. // commented out since it also returns alot of garbage
  437. /*
  438. if(
  439. $has_so_answer === false &&
  440. isset($result["qa"])
  441. ){
  442. $has_so_answer = true;
  443. $answer = $this->stackoverflow_parse($result["qa"]["answer"]["text"]);
  444. if(isset($result["qa"]["answer"]["author"])){
  445. $answer[] = [
  446. "type" => "quote",
  447. "value" => "Answer from " . $result["qa"]["answer"]["author"]
  448. ];
  449. }
  450. $out["answer"][] = [
  451. "title" =>
  452. $this->fuckhtml
  453. ->getTextContent(
  454. $result["qa"]["question"]
  455. ),
  456. "description" => $answer,
  457. "url" => $result["url"],
  458. "thumb" => null,
  459. "table" => [],
  460. "sublink" => []
  461. ];
  462. }*/
  463. $out["web"][] = [
  464. "title" =>
  465. $this->titledots(
  466. $result["title"]
  467. ),
  468. "description" =>
  469. $this->titledots(
  470. $this->fuckhtml
  471. ->getTextContent(
  472. $result["description"]
  473. )
  474. ),
  475. "url" => $result["url"],
  476. "date" => isset($result["age"]) ? strtotime($result["age"]) : null,
  477. "type" => "web",
  478. "thumb" => $thumb,
  479. "sublink" => $sublink,
  480. "table" => $table
  481. ];
  482. }
  483. /*
  484. Get spelling autocorrect
  485. */
  486. if(
  487. isset($data["query"]["bo_altered_diff"][0][0]) &&
  488. $data["query"]["bo_altered_diff"][0][0] == "true"
  489. ){
  490. $out["spelling"] = [
  491. "type" => "including",
  492. "using" => $data["query"]["bo_altered_diff"][0][1],
  493. "correction" => $get["s"]
  494. ];
  495. }
  496. /*
  497. Get wikipedia heads
  498. */
  499. if(isset($data["infobox"]["results"][0])){
  500. foreach($data["infobox"]["results"] as $info){
  501. if($info["subtype"] == "code"){
  502. $description =
  503. $this->stackoverflow_parse($info["data"]["answer"]["text"]);
  504. if(isset($info["data"]["answer"]["author"])){
  505. $description[] = [
  506. "type" => "quote",
  507. "value" => "Answer from " . $info["data"]["answer"]["author"]
  508. ];
  509. }
  510. }else{
  511. $description = [];
  512. if(
  513. isset($info["description"]) &&
  514. $info["description"] != ""
  515. ){
  516. $description[] = [
  517. "type" => "quote",
  518. "value" => $info["description"]
  519. ];
  520. }
  521. if(
  522. isset($info["long_desc"]) &&
  523. $info["long_desc"] != ""
  524. ){
  525. $description[] = [
  526. "type" => "text",
  527. "value" => $this->titledots($info["long_desc"])
  528. ];
  529. }
  530. }
  531. $table = [];
  532. if(isset($info["attributes"])){
  533. foreach($info["attributes"] as $row){
  534. if(
  535. $row[1] == "null" &&
  536. count($table) !== 0
  537. ){
  538. break;
  539. }
  540. if($row[1] == "null"){
  541. continue;
  542. }
  543. $table[
  544. $this->fuckhtml->getTextContent($row[0])
  545. ] =
  546. $this->fuckhtml->getTextContent($row[1]);
  547. }
  548. }
  549. $sublink = [];
  550. if(isset($info["profiles"])){
  551. foreach($info["profiles"] as $row){
  552. $name = $this->fuckhtml->getTextContent($row["name"]);
  553. if(strtolower($name) == "steampowered"){
  554. $name = "Steam";
  555. }
  556. $sublink[
  557. $this->fuckhtml->getTextContent($name)
  558. ] =
  559. $this->fuckhtml->getTextContent($row["url"]);
  560. }
  561. }
  562. $out["answer"][] = [
  563. "title" => $this->fuckhtml->getTextContent($info["title"]),
  564. "description" => $description,
  565. "url" => $info["url"],
  566. "thumb" => isset($info["images"][0]["original"]) ? $info["images"][0]["original"] : null,
  567. "table" => $table,
  568. "sublink" => $sublink
  569. ];
  570. break; // only iterate once, we get garbage most of the time
  571. }
  572. }
  573. /*
  574. Get videos
  575. */
  576. if(isset($data["videos"]["results"])){
  577. foreach($data["videos"]["results"] as $video){
  578. $out["video"][] = [
  579. "title" => $this->titledots($video["title"]),
  580. "description" => $this->titledots($video["description"]),
  581. "date" => isset($video["age"]) ? strtotime($video["age"]) : null,
  582. "duration" => isset($video["video"]["duration"]) ? $this->hms2int($video["video"]["duration"]) : null,
  583. "views" => null,
  584. "thumb" =>
  585. isset($video["thumbnail"]["src"]) ?
  586. [
  587. "ratio" => "16:9",
  588. "url" => $this->unshiturl($video["thumbnail"]["src"])
  589. ] :
  590. [
  591. "ratio" => null,
  592. "url" => null
  593. ],
  594. "url" => $video["url"]
  595. ];
  596. }
  597. }
  598. /*
  599. Get news
  600. */
  601. if(isset($data["news"]["results"])){
  602. foreach($data["news"]["results"] as $news){
  603. $out["news"][] = [
  604. "title" => $this->titledots($news["title"]),
  605. "description" => $this->titledots($news["description"]),
  606. "date" => isset($news["age"]) ? strtotime($news["age"]) : null,
  607. "thumb" =>
  608. isset($video["thumbnail"]["src"]) ?
  609. [
  610. "ratio" => "16:9",
  611. "url" => $this->unshiturl($video["thumbnail"]["src"])
  612. ] :
  613. [
  614. "ratio" => null,
  615. "url" => null
  616. ],
  617. "url" => $news["url"]
  618. ];
  619. }
  620. }
  621. /*
  622. Get discussions
  623. */
  624. $disc_out = [];
  625. if(isset($data["discussions"]["results"])){
  626. foreach($data["discussions"]["results"] as $disc){
  627. $table = [];
  628. if(isset($disc["data"]["num_votes"])){
  629. $table["Votes"] = (int)$disc["data"]["num_votes"];
  630. }
  631. if(isset($disc["data"]["num_answers"])){
  632. $table["Comments"] = (int)$disc["data"]["num_answers"];
  633. }
  634. $disc_out[] = [
  635. "title" =>
  636. $this->titledots(
  637. $disc["title"]
  638. ),
  639. "description" =>
  640. $this->limitstrlen(
  641. $this->titledots(
  642. $this->fuckhtml
  643. ->getTextContent(
  644. $disc["description"]
  645. )
  646. )
  647. ),
  648. "url" => $disc["url"],
  649. "date" => isset($disc["age"]) ? strtotime($disc["age"]) : null,
  650. "type" => "web",
  651. "thumb" => [
  652. "ratio" => null,
  653. "url" => null
  654. ],
  655. "sublink" => [],
  656. "table" => $table
  657. ];
  658. }
  659. }
  660. // append discussions at position 2
  661. array_splice($out["web"], 1, 0, $disc_out);
  662. return $out;
  663. }
  664. public function news($get){
  665. $search = $get["s"];
  666. if(strlen($search) === 0){
  667. throw new Exception("Search term is empty!");
  668. }
  669. $nsfw = $get["nsfw"];
  670. $country = $get["country"];
  671. if(strlen($search) > 2048){
  672. throw new Exception("Search query is too long!");
  673. }
  674. /*
  675. $handle = fopen("scraper/brave-news.html", "r");
  676. $html = fread($handle, filesize("scraper/brave-news.html"));
  677. fclose($handle);*/
  678. try{
  679. $html =
  680. $this->get(
  681. "https://search.brave.com/news",
  682. [
  683. "q" => $search
  684. ],
  685. $nsfw,
  686. $country
  687. );
  688. }catch(Exception $error){
  689. throw new Exception("Could not fetch search page");
  690. }
  691. $out = [
  692. "status" => "ok",
  693. "npt" => null,
  694. "news" => []
  695. ];
  696. // load html
  697. $this->fuckhtml->load($html);
  698. $news =
  699. $this->fuckhtml
  700. ->getElementsByClassName(
  701. "snippet inline gap-standard",
  702. "div"
  703. );
  704. foreach($news as $article){
  705. $data = [
  706. "title" => null,
  707. "author" => null,
  708. "description" => null,
  709. "date" => null,
  710. "thumb" =>
  711. [
  712. "url" => null,
  713. "ratio" => null
  714. ],
  715. "url" => null
  716. ];
  717. $this->fuckhtml->load($article);
  718. $elems =
  719. $this->fuckhtml
  720. ->getElementsByTagName("*");
  721. // get title
  722. $data["title"] =
  723. $this->fuckhtml
  724. ->getTextContent(
  725. $this->fuckhtml
  726. ->getElementsByClassName(
  727. "snippet-title",
  728. $elems
  729. )
  730. [0]
  731. ["innerHTML"]
  732. );
  733. // get description
  734. $data["description"] =
  735. $this->titledots(
  736. $this->fuckhtml
  737. ->getTextContent(
  738. $this->fuckhtml
  739. ->getElementsByClassName(
  740. "snippet-description",
  741. $elems
  742. )
  743. [0]
  744. ["innerHTML"]
  745. )
  746. );
  747. // get date
  748. $date =
  749. explode(
  750. "•",
  751. $this->fuckhtml
  752. ->getTextContent(
  753. $this->fuckhtml
  754. ->getElementsByClassName(
  755. "snippet-url",
  756. $elems
  757. )[0]
  758. )
  759. );
  760. if(
  761. count($date) !== 1 &&
  762. trim($date[1]) != ""
  763. ){
  764. $data["date"] =
  765. strtotime(
  766. $date[1]
  767. );
  768. }
  769. // get URL
  770. $data["url"] =
  771. $this->fuckhtml->getTextContent(
  772. $this->unshiturl(
  773. $this->fuckhtml
  774. ->getElementsByClassName(
  775. "result-header",
  776. $elems
  777. )
  778. [0]
  779. ["attributes"]
  780. ["href"]
  781. )
  782. );
  783. // get thumbnail
  784. $thumb =
  785. $this->fuckhtml
  786. ->getElementsByTagName(
  787. "img"
  788. );
  789. if(
  790. count($thumb) === 2 &&
  791. trim(
  792. $thumb[1]
  793. ["attributes"]
  794. ["src"]
  795. ) != ""
  796. ){
  797. $data["thumb"] = [
  798. "url" =>
  799. $this->fuckhtml->getTextContent(
  800. $this->unshiturl(
  801. $thumb[1]
  802. ["attributes"]
  803. ["src"]
  804. )
  805. ),
  806. "ratio" => "16:9"
  807. ];
  808. }
  809. $out["news"][] = $data;
  810. }
  811. return $out;
  812. }
  813. public function image($get){
  814. $search = $get["s"];
  815. $country = $get["country"];
  816. $nsfw = $get["nsfw"];
  817. $out = [
  818. "status" => "ok",
  819. "npt" => null,
  820. "image" => []
  821. ];
  822. try{
  823. $html =
  824. $this->get(
  825. "https://search.brave.com/images",
  826. [
  827. "q" => $search
  828. ],
  829. $nsfw,
  830. $country
  831. );
  832. }catch(Exception $error){
  833. throw new Exception("Could not fetch search page");
  834. }
  835. /*
  836. $handle = fopen("scraper/brave-image.html", "r");
  837. $html = fread($handle, filesize("scraper/brave-image.html"));
  838. fclose($handle);*/
  839. preg_match(
  840. '/const data = (\[{.*}\]);/',
  841. $html,
  842. $json
  843. );
  844. if(!isset($json[1])){
  845. throw new Exception("Failed to get data object");
  846. }
  847. $json =
  848. $this->fuckhtml
  849. ->parseJsObject(
  850. $json[1]
  851. );
  852. foreach(
  853. $json[1]
  854. ["data"]
  855. ["body"]
  856. ["response"]
  857. ["results"]
  858. as $result
  859. ){
  860. $out["image"][] = [
  861. "title" => $result["title"],
  862. "source" => [
  863. [
  864. "url" => $result["properties"]["url"],
  865. "width" => null,
  866. "height" => null
  867. ],
  868. [
  869. "url" => $result["thumbnail"]["src"],
  870. "width" => null,
  871. "height" => null
  872. ]
  873. ],
  874. "url" => $result["url"]
  875. ];
  876. }
  877. return $out;
  878. }
  879. public function video($get){
  880. $search = $get["s"];
  881. $country = $get["country"];
  882. $nsfw = $get["nsfw"];
  883. $out = [
  884. "status" => "ok",
  885. "npt" => null,
  886. "video" => [],
  887. "author" => [],
  888. "livestream" => [],
  889. "playlist" => [],
  890. "reel" => []
  891. ];
  892. try{
  893. $html =
  894. $this->get(
  895. "https://search.brave.com/videos",
  896. [
  897. "q" => $search
  898. ],
  899. $nsfw,
  900. $country
  901. );
  902. }catch(Exception $error){
  903. throw new Exception("Could not fetch search page");
  904. }
  905. /*
  906. $handle = fopen("scraper/brave-video.html", "r");
  907. $html = fread($handle, filesize("scraper/brave-video.html"));
  908. fclose($handle);*/
  909. preg_match(
  910. '/const data = (\[{.*}\]);/',
  911. $html,
  912. $json
  913. );
  914. if(!isset($json[1])){
  915. throw new Exception("Failed to get data object");
  916. }
  917. $json =
  918. $this->fuckhtml
  919. ->parseJsObject(
  920. $json[1]
  921. );
  922. foreach(
  923. $json
  924. [1]
  925. ["data"]
  926. ["body"]
  927. ["response"]
  928. ["results"]
  929. as $result
  930. ){
  931. if($result["video"]["author"] != "null"){
  932. $author = [
  933. "name" => $result["video"]["author"]["name"] == "null" ? null : $result["video"]["author"]["name"],
  934. "url" => $result["video"]["author"]["url"] == "null" ? null : $result["video"]["author"]["url"],
  935. "avatar" => $result["video"]["author"]["img"] == "null" ? null : $result["video"]["author"]["img"]
  936. ];
  937. }else{
  938. $author = [
  939. "name" => null,
  940. "url" => null,
  941. "avatar" => null
  942. ];
  943. }
  944. if($result["thumbnail"] != "null"){
  945. $thumb = [
  946. "url" => $result["thumbnail"]["original"],
  947. "ratio" => "16:9"
  948. ];
  949. }else{
  950. $thumb = [
  951. "url" => null,
  952. "ratio" => null
  953. ];
  954. }
  955. $out["video"][] = [
  956. "title" => $result["title"],
  957. "description" => $result["description"] == "null" ? null : $this->titledots($result["description"]),
  958. "author" => $author,
  959. "date" => $result["age"] == "null" ? null : strtotime($result["age"]),
  960. "duration" => $result["video"]["duration"] == "null" ? null : $this->hms2int($result["video"]["duration"]),
  961. "views" => $result["video"]["views"] == "null" ? null : (int)$result["video"]["views"],
  962. "thumb" => $thumb,
  963. "url" => $result["url"]
  964. ];
  965. }
  966. return $out;
  967. }
  968. private function stackoverflow_parse($html){
  969. $i = 0;
  970. $answer = [];
  971. $this->fuckhtml->load($html);
  972. foreach(
  973. $this->fuckhtml->getElementsByTagName("*")
  974. as $snippet
  975. ){
  976. switch($snippet["tagName"]){
  977. case "p":
  978. $this->fuckhtml->load($snippet["innerHTML"]);
  979. $codetags =
  980. $this->fuckhtml
  981. ->getElementsByTagName("*");
  982. $tmphtml = $snippet["innerHTML"];
  983. foreach($codetags as $tag){
  984. if(!isset($tag["outerHTML"])){
  985. continue;
  986. }
  987. $tmphtml =
  988. explode(
  989. $tag["outerHTML"],
  990. $tmphtml,
  991. 2
  992. );
  993. $value = $this->fuckhtml->getTextContent($tmphtml[0], false, false);
  994. $this->appendtext($value, $answer, $i);
  995. $type = null;
  996. switch($tag["tagName"]){
  997. case "code": $type = "inline_code"; break;
  998. case "em": $type = "italic"; break;
  999. case "blockquote": $type = "quote"; break;
  1000. default: $type = "text";
  1001. }
  1002. if($type !== null){
  1003. $value = $this->fuckhtml->getTextContent($tag, false, true);
  1004. if(trim($value) != ""){
  1005. if(
  1006. $i !== 0 &&
  1007. $type == "title"
  1008. ){
  1009. $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]);
  1010. }
  1011. $answer[] = [
  1012. "type" => $type,
  1013. "value" => $value
  1014. ];
  1015. $i++;
  1016. }
  1017. }
  1018. if(count($tmphtml) === 2){
  1019. $tmphtml = $tmphtml[1];
  1020. }else{
  1021. break;
  1022. }
  1023. }
  1024. if(is_array($tmphtml)){
  1025. $tmphtml = $tmphtml[0];
  1026. }
  1027. if(strlen($tmphtml) !== 0){
  1028. $value = $this->fuckhtml->getTextContent($tmphtml, false, false);
  1029. $this->appendtext($value, $answer, $i);
  1030. }
  1031. break;
  1032. case "img":
  1033. $answer[] = [
  1034. "type" => "image",
  1035. "url" =>
  1036. $this->fuckhtml
  1037. ->getTextContent(
  1038. $tag["attributes"]["src"]
  1039. )
  1040. ];
  1041. $i++;
  1042. break;
  1043. case "pre":
  1044. switch($answer[$i - 1]["type"]){
  1045. case "text":
  1046. case "italic":
  1047. $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]);
  1048. break;
  1049. }
  1050. $answer[] =
  1051. [
  1052. "type" => "code",
  1053. "value" =>
  1054. rtrim(
  1055. $this->fuckhtml
  1056. ->getTextContent(
  1057. $snippet,
  1058. true,
  1059. false
  1060. )
  1061. )
  1062. ];
  1063. $i++;
  1064. break;
  1065. case "ol":
  1066. $o = 0;
  1067. $this->fuckhtml->load($snippet);
  1068. $li =
  1069. $this->fuckhtml
  1070. ->getElementsByTagName("li");
  1071. foreach($li as $elem){
  1072. $o++;
  1073. $this->appendtext(
  1074. $o . ". " .
  1075. $this->fuckhtml
  1076. ->getTextContent(
  1077. $elem
  1078. ),
  1079. $answer,
  1080. $i
  1081. );
  1082. }
  1083. break;
  1084. }
  1085. }
  1086. if(
  1087. $i !== 0 &&
  1088. $answer[$i - 1]["type"] == "text"
  1089. ){
  1090. $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]);
  1091. }
  1092. return $answer;
  1093. }
  1094. private function hms2int($time){
  1095. $parts = explode(":", $time, 3);
  1096. $time = 0;
  1097. if(count($parts) === 3){
  1098. // hours
  1099. $time = $time + ((int)$parts[0] * 3600);
  1100. array_shift($parts);
  1101. }
  1102. if(count($parts) === 2){
  1103. // minutes
  1104. $time = $time + ((int)$parts[0] * 60);
  1105. array_shift($parts);
  1106. }
  1107. // seconds
  1108. $time = $time + (int)$parts[0];
  1109. return $time;
  1110. }
  1111. private function appendtext($payload, &$text, &$index){
  1112. if(trim($payload) == ""){
  1113. return;
  1114. }
  1115. if(
  1116. $index !== 0 &&
  1117. $text[$index - 1]["type"] == "text"
  1118. ){
  1119. $text[$index - 1]["value"] .= "\n\n" . preg_replace('/ $/', " ", $payload);
  1120. }else{
  1121. $text[] = [
  1122. "type" => "text",
  1123. "value" => preg_replace('/ $/', " ", $payload)
  1124. ];
  1125. $index++;
  1126. }
  1127. }
  1128. private function tablesublink($html_collection, &$data){
  1129. foreach($html_collection as $html){
  1130. $html["innerHTML"] = preg_replace(
  1131. '/<style>[\S\s]*<\/style>/i',
  1132. "",
  1133. $html["innerHTML"]
  1134. );
  1135. $html =
  1136. explode(
  1137. ":",
  1138. $this->fuckhtml->getTextContent($html),
  1139. 2
  1140. );
  1141. if(count($html) === 1){
  1142. $html = ["Rating", $html[0]];
  1143. }
  1144. $data["table"][trim($html[0])] = trim($html[1]);
  1145. }
  1146. }
  1147. private function getimagelinkfromstyle($thumb){
  1148. $thumb =
  1149. $this->fuckhtml
  1150. ->getElementsByClassName(
  1151. $thumb,
  1152. "div"
  1153. );
  1154. if(count($thumb) === 0){
  1155. return [
  1156. "url" => null,
  1157. "ratio" => null
  1158. ];
  1159. }
  1160. $thumb = $thumb[0]["attributes"]["style"];
  1161. preg_match(
  1162. '/background-image: ?url\((\'[^\']+\'|"[^"]+"|[^\)]+)\)/',
  1163. $thumb,
  1164. $thumb
  1165. );
  1166. $url = $this->fuckhtml->getTextContent($this->unshiturl(trim($thumb[1], '"\' ')));
  1167. if(parse_url($url, PHP_URL_HOST) == "cdn.search.brave.com"){
  1168. return [
  1169. "url" => null,
  1170. "ratio" => null
  1171. ];
  1172. }
  1173. return [
  1174. "url" => $url,
  1175. "ratio" => "16:9"
  1176. ];
  1177. }
  1178. private function limitstrlen($text){
  1179. return explode("\n", wordwrap($text, 300, "\n"))[0];
  1180. }
  1181. private function limitwhitespace($text){
  1182. return
  1183. preg_replace(
  1184. '/[\s]+/',
  1185. " ",
  1186. $text
  1187. );
  1188. }
  1189. private function titledots($title){
  1190. $substr = substr($title, -3);
  1191. if(
  1192. $substr == "..." ||
  1193. $substr == "…"
  1194. ){
  1195. return trim(substr($title, 0, -3));
  1196. }
  1197. return trim($title);
  1198. }
  1199. private function unshiturl($url){
  1200. // https://imgs.search.brave.com/XFnbR8Sl7ge82MBDEH7ju0UHImRovMVmQ2qnDvgNTuA/rs:fit:844:225:1/g:ce/aHR0cHM6Ly90c2U0/Lm1tLmJpbmcubmV0/L3RoP2lkPU9JUC54/UWotQXU5N2ozVndT/RDJnNG9BNVhnSGFF/SyZwaWQ9QXBp.jpeg
  1201. $tmp = explode("aHR0", $url);
  1202. if(count($tmp) !== 2){
  1203. // nothing to do
  1204. return $url;
  1205. }
  1206. return
  1207. base64_decode(
  1208. "aHR0" .
  1209. str_replace(["/", "_"], ["", "/"],
  1210. explode(
  1211. ".",
  1212. $tmp[1]
  1213. )[0]
  1214. )
  1215. );
  1216. }
  1217. }