brave.php 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537
  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(
  344. isset($result["thumbnail"]) &&
  345. is_array($result["thumbnail"])
  346. ){
  347. $thumb = [
  348. "ratio" => $result["thumbnail"]["logo"] == "false" ? "16:9" : "1:1",
  349. "url" => $result["thumbnail"]["original"]
  350. ];
  351. }else{
  352. $thumb = [
  353. "ratio" => null,
  354. "url" => null
  355. ];
  356. }
  357. $sublink = [];
  358. if(
  359. isset($result["cluster"]) &&
  360. is_array($result["cluster"])
  361. ){
  362. foreach($result["cluster"] as $cluster){
  363. $sublink[] = [
  364. "title" => $this->titledots($cluster["title"]),
  365. "description" =>
  366. $this->titledots(
  367. $this->fuckhtml
  368. ->getTextContent(
  369. $cluster["description"]
  370. )
  371. ),
  372. "url" => $cluster["url"],
  373. "date" => null
  374. ];
  375. }
  376. }
  377. // parse table elements
  378. $table = [];
  379. // product
  380. $ref = null;
  381. if(isset($result["product"])){
  382. $ref = &$result["product"];
  383. }elseif(isset($result["creative_work"])){
  384. $ref = &$result["creative_work"];
  385. }
  386. if($ref !== null){
  387. if(isset($ref["offers"])){
  388. foreach($ref["offers"] as $offer){
  389. $price = null;
  390. if(isset($offer["price"])){
  391. if((float)$offer["price"] == 0){
  392. $price = "Free";
  393. }else{
  394. $price = $offer["price"];
  395. }
  396. }
  397. if($price !== "Free"){
  398. if(isset($offer["priceCurrency"])){
  399. $price .= " " . $offer["priceCurrency"];
  400. }
  401. }
  402. if($price !== null){
  403. $table["Price"] = trim($price);
  404. }
  405. }
  406. }
  407. if(isset($ref["rating"])){
  408. $rating = null;
  409. if(isset($ref["rating"]["ratingValue"])){
  410. $rating = $ref["rating"]["ratingValue"];
  411. if(isset($ref["rating"]["bestRating"])){
  412. $rating .= "/" . $ref["rating"]["bestRating"];
  413. }
  414. }
  415. if(isset($ref["rating"]["reviewCount"])){
  416. $isnull = $rating === null ? false : true;
  417. if($isnull){
  418. $rating .= " (";
  419. }
  420. $rating .= number_format($ref["rating"]["reviewCount"]) . " hits";
  421. if($isnull){
  422. $rating .= ")";
  423. }
  424. }
  425. if($rating !== null){
  426. $table["Rating"] = $rating;
  427. }
  428. }
  429. }
  430. // video
  431. if(
  432. isset($result["video"]) &&
  433. is_array($result["video"])
  434. ){
  435. foreach($result["video"] as $key => $value){
  436. if(is_string($result["video"][$key]) === false){
  437. continue;
  438. }
  439. $table[ucfirst($key)] = $value;
  440. }
  441. }
  442. /*
  443. Get StackOverflow answers
  444. */
  445. // commented out since it also returns alot of garbage
  446. /*
  447. if(
  448. $has_so_answer === false &&
  449. isset($result["qa"])
  450. ){
  451. $has_so_answer = true;
  452. $answer = $this->stackoverflow_parse($result["qa"]["answer"]["text"]);
  453. if(isset($result["qa"]["answer"]["author"])){
  454. $answer[] = [
  455. "type" => "quote",
  456. "value" => "Answer from " . $result["qa"]["answer"]["author"]
  457. ];
  458. }
  459. $out["answer"][] = [
  460. "title" =>
  461. $this->fuckhtml
  462. ->getTextContent(
  463. $result["qa"]["question"]
  464. ),
  465. "description" => $answer,
  466. "url" => $result["url"],
  467. "thumb" => null,
  468. "table" => [],
  469. "sublink" => []
  470. ];
  471. }*/
  472. $out["web"][] = [
  473. "title" =>
  474. $this->titledots(
  475. $result["title"]
  476. ),
  477. "description" =>
  478. $this->titledots(
  479. $this->fuckhtml
  480. ->getTextContent(
  481. $result["description"]
  482. )
  483. ),
  484. "url" => $result["url"],
  485. "date" => (isset($result["age"]) && $result["age"] != "void 0") ? strtotime($result["age"]) : null,
  486. "type" => "web",
  487. "thumb" => $thumb,
  488. "sublink" => $sublink,
  489. "table" => $table
  490. ];
  491. }
  492. /*
  493. Get spelling autocorrect
  494. */
  495. if(
  496. isset($data["query"]["bo_altered_diff"][0][0]) &&
  497. $data["query"]["bo_altered_diff"][0][0] == "true"
  498. ){
  499. $out["spelling"] = [
  500. "type" => "including",
  501. "using" => $data["query"]["bo_altered_diff"][0][1],
  502. "correction" => $get["s"]
  503. ];
  504. }
  505. /*
  506. Get wikipedia heads
  507. */
  508. if(isset($data["infobox"]["results"][0])){
  509. foreach($data["infobox"]["results"] as $info){
  510. if($info["subtype"] == "code"){
  511. $description =
  512. $this->stackoverflow_parse($info["data"]["answer"]["text"]);
  513. if(isset($info["data"]["answer"]["author"])){
  514. $description[] = [
  515. "type" => "quote",
  516. "value" => "Answer from " . $info["data"]["answer"]["author"]
  517. ];
  518. }
  519. }else{
  520. $description = [];
  521. if(
  522. isset($info["description"]) &&
  523. $info["description"] != ""
  524. ){
  525. $description[] = [
  526. "type" => "quote",
  527. "value" => $info["description"]
  528. ];
  529. }
  530. if(
  531. isset($info["long_desc"]) &&
  532. $info["long_desc"] != ""
  533. ){
  534. $description[] = [
  535. "type" => "text",
  536. "value" => $this->titledots($info["long_desc"])
  537. ];
  538. }
  539. }
  540. $table = [];
  541. if(isset($info["attributes"])){
  542. foreach($info["attributes"] as $row){
  543. if(
  544. $row[1] == "null" &&
  545. count($table) !== 0
  546. ){
  547. break;
  548. }
  549. if($row[1] == "null"){
  550. continue;
  551. }
  552. $table[
  553. $this->fuckhtml->getTextContent($row[0])
  554. ] =
  555. $this->fuckhtml->getTextContent($row[1]);
  556. }
  557. }
  558. $sublink = [];
  559. if(isset($info["profiles"])){
  560. foreach($info["profiles"] as $row){
  561. $name = $this->fuckhtml->getTextContent($row["name"]);
  562. if(strtolower($name) == "steampowered"){
  563. $name = "Steam";
  564. }
  565. $sublink[
  566. $this->fuckhtml->getTextContent($name)
  567. ] =
  568. $this->fuckhtml->getTextContent($row["url"]);
  569. }
  570. }
  571. $out["answer"][] = [
  572. "title" => $this->fuckhtml->getTextContent($info["title"]),
  573. "description" => $description,
  574. "url" => $info["url"],
  575. "thumb" => isset($info["images"][0]["original"]) ? $info["images"][0]["original"] : null,
  576. "table" => $table,
  577. "sublink" => $sublink
  578. ];
  579. break; // only iterate once, we get garbage most of the time
  580. }
  581. }
  582. /*
  583. Get videos
  584. */
  585. if(isset($data["videos"]["results"])){
  586. foreach($data["videos"]["results"] as $video){
  587. $out["video"][] = [
  588. "title" => $this->titledots($video["title"]),
  589. "description" => $this->titledots($video["description"]),
  590. "date" => isset($video["age"]) ? strtotime($video["age"]) : null,
  591. "duration" => isset($video["video"]["duration"]) ? $this->hms2int($video["video"]["duration"]) : null,
  592. "views" => null,
  593. "thumb" =>
  594. isset($video["thumbnail"]["src"]) ?
  595. [
  596. "ratio" => "16:9",
  597. "url" => $this->unshiturl($video["thumbnail"]["src"])
  598. ] :
  599. [
  600. "ratio" => null,
  601. "url" => null
  602. ],
  603. "url" => $video["url"]
  604. ];
  605. }
  606. }
  607. /*
  608. Get news
  609. */
  610. if(isset($data["news"]["results"])){
  611. foreach($data["news"]["results"] as $news){
  612. $out["news"][] = [
  613. "title" => $this->titledots($news["title"]),
  614. "description" => $this->titledots($news["description"]),
  615. "date" => isset($news["age"]) ? strtotime($news["age"]) : null,
  616. "thumb" =>
  617. isset($video["thumbnail"]["src"]) ?
  618. [
  619. "ratio" => "16:9",
  620. "url" => $this->unshiturl($video["thumbnail"]["src"])
  621. ] :
  622. [
  623. "ratio" => null,
  624. "url" => null
  625. ],
  626. "url" => $news["url"]
  627. ];
  628. }
  629. }
  630. /*
  631. Get discussions
  632. */
  633. $disc_out = [];
  634. if(isset($data["discussions"]["results"])){
  635. foreach($data["discussions"]["results"] as $disc){
  636. $table = [];
  637. if(isset($disc["data"]["num_votes"])){
  638. $table["Votes"] = number_format($disc["data"]["num_votes"]);
  639. }
  640. if(isset($disc["data"]["num_answers"])){
  641. $table["Comments"] = number_format($disc["data"]["num_answers"]);
  642. }
  643. $disc_out[] = [
  644. "title" =>
  645. $this->titledots(
  646. $disc["title"]
  647. ),
  648. "description" =>
  649. $this->limitstrlen(
  650. $this->titledots(
  651. $this->fuckhtml
  652. ->getTextContent(
  653. $disc["description"]
  654. )
  655. )
  656. ),
  657. "url" => $disc["url"],
  658. "date" => isset($disc["age"]) ? strtotime($disc["age"]) : null,
  659. "type" => "web",
  660. "thumb" => [
  661. "ratio" => null,
  662. "url" => null
  663. ],
  664. "sublink" => [],
  665. "table" => $table
  666. ];
  667. }
  668. }
  669. // append discussions at position 2
  670. array_splice($out["web"], 1, 0, $disc_out);
  671. return $out;
  672. }
  673. public function news($get){
  674. $search = $get["s"];
  675. if(strlen($search) === 0){
  676. throw new Exception("Search term is empty!");
  677. }
  678. $nsfw = $get["nsfw"];
  679. $country = $get["country"];
  680. if(strlen($search) > 2048){
  681. throw new Exception("Search query is too long!");
  682. }
  683. /*
  684. $handle = fopen("scraper/brave-news.html", "r");
  685. $html = fread($handle, filesize("scraper/brave-news.html"));
  686. fclose($handle);*/
  687. try{
  688. $html =
  689. $this->get(
  690. "https://search.brave.com/news",
  691. [
  692. "q" => $search
  693. ],
  694. $nsfw,
  695. $country
  696. );
  697. }catch(Exception $error){
  698. throw new Exception("Could not fetch search page");
  699. }
  700. $out = [
  701. "status" => "ok",
  702. "npt" => null,
  703. "news" => []
  704. ];
  705. // load html
  706. $this->fuckhtml->load($html);
  707. $news =
  708. $this->fuckhtml
  709. ->getElementsByClassName(
  710. "snippet inline gap-standard",
  711. "div"
  712. );
  713. foreach($news as $article){
  714. $data = [
  715. "title" => null,
  716. "author" => null,
  717. "description" => null,
  718. "date" => null,
  719. "thumb" =>
  720. [
  721. "url" => null,
  722. "ratio" => null
  723. ],
  724. "url" => null
  725. ];
  726. $this->fuckhtml->load($article);
  727. $elems =
  728. $this->fuckhtml
  729. ->getElementsByTagName("*");
  730. // get title
  731. $data["title"] =
  732. $this->fuckhtml
  733. ->getTextContent(
  734. $this->fuckhtml
  735. ->getElementsByClassName(
  736. "snippet-title",
  737. $elems
  738. )
  739. [0]
  740. ["innerHTML"]
  741. );
  742. // get description
  743. $data["description"] =
  744. $this->titledots(
  745. $this->fuckhtml
  746. ->getTextContent(
  747. $this->fuckhtml
  748. ->getElementsByClassName(
  749. "snippet-description",
  750. $elems
  751. )
  752. [0]
  753. ["innerHTML"]
  754. )
  755. );
  756. // get date
  757. $date =
  758. explode(
  759. "•",
  760. $this->fuckhtml
  761. ->getTextContent(
  762. $this->fuckhtml
  763. ->getElementsByClassName(
  764. "snippet-url",
  765. $elems
  766. )[0]
  767. )
  768. );
  769. if(
  770. count($date) !== 1 &&
  771. trim($date[1]) != ""
  772. ){
  773. $data["date"] =
  774. strtotime(
  775. $date[1]
  776. );
  777. }
  778. // get URL
  779. $data["url"] =
  780. $this->fuckhtml->getTextContent(
  781. $this->unshiturl(
  782. $this->fuckhtml
  783. ->getElementsByClassName(
  784. "result-header",
  785. $elems
  786. )
  787. [0]
  788. ["attributes"]
  789. ["href"]
  790. )
  791. );
  792. // get thumbnail
  793. $thumb =
  794. $this->fuckhtml
  795. ->getElementsByTagName(
  796. "img"
  797. );
  798. if(
  799. count($thumb) === 2 &&
  800. trim(
  801. $thumb[1]
  802. ["attributes"]
  803. ["src"]
  804. ) != ""
  805. ){
  806. $data["thumb"] = [
  807. "url" =>
  808. $this->fuckhtml->getTextContent(
  809. $this->unshiturl(
  810. $thumb[1]
  811. ["attributes"]
  812. ["src"]
  813. )
  814. ),
  815. "ratio" => "16:9"
  816. ];
  817. }
  818. $out["news"][] = $data;
  819. }
  820. return $out;
  821. }
  822. public function image($get){
  823. $search = $get["s"];
  824. $country = $get["country"];
  825. $nsfw = $get["nsfw"];
  826. $out = [
  827. "status" => "ok",
  828. "npt" => null,
  829. "image" => []
  830. ];
  831. try{
  832. $html =
  833. $this->get(
  834. "https://search.brave.com/images",
  835. [
  836. "q" => $search
  837. ],
  838. $nsfw,
  839. $country
  840. );
  841. }catch(Exception $error){
  842. throw new Exception("Could not fetch search page");
  843. }
  844. /*
  845. $handle = fopen("scraper/brave-image.html", "r");
  846. $html = fread($handle, filesize("scraper/brave-image.html"));
  847. fclose($handle);*/
  848. preg_match(
  849. '/const data = (\[{.*}\]);/',
  850. $html,
  851. $json
  852. );
  853. if(!isset($json[1])){
  854. throw new Exception("Failed to get data object");
  855. }
  856. $json =
  857. $this->fuckhtml
  858. ->parseJsObject(
  859. $json[1]
  860. );
  861. foreach(
  862. $json[1]
  863. ["data"]
  864. ["body"]
  865. ["response"]
  866. ["results"]
  867. as $result
  868. ){
  869. $out["image"][] = [
  870. "title" => $result["title"],
  871. "source" => [
  872. [
  873. "url" => $result["properties"]["url"],
  874. "width" => null,
  875. "height" => null
  876. ],
  877. [
  878. "url" => $result["thumbnail"]["src"],
  879. "width" => null,
  880. "height" => null
  881. ]
  882. ],
  883. "url" => $result["url"]
  884. ];
  885. }
  886. return $out;
  887. }
  888. public function video($get){
  889. $search = $get["s"];
  890. $country = $get["country"];
  891. $nsfw = $get["nsfw"];
  892. $out = [
  893. "status" => "ok",
  894. "npt" => null,
  895. "video" => [],
  896. "author" => [],
  897. "livestream" => [],
  898. "playlist" => [],
  899. "reel" => []
  900. ];
  901. try{
  902. $html =
  903. $this->get(
  904. "https://search.brave.com/videos",
  905. [
  906. "q" => $search
  907. ],
  908. $nsfw,
  909. $country
  910. );
  911. }catch(Exception $error){
  912. throw new Exception("Could not fetch search page");
  913. }
  914. /*
  915. $handle = fopen("scraper/brave-video.html", "r");
  916. $html = fread($handle, filesize("scraper/brave-video.html"));
  917. fclose($handle);*/
  918. preg_match(
  919. '/const data = (\[{.*}\]);/',
  920. $html,
  921. $json
  922. );
  923. if(!isset($json[1])){
  924. throw new Exception("Failed to get data object");
  925. }
  926. $json =
  927. $this->fuckhtml
  928. ->parseJsObject(
  929. $json[1]
  930. );
  931. foreach(
  932. $json
  933. [1]
  934. ["data"]
  935. ["body"]
  936. ["response"]
  937. ["results"]
  938. as $result
  939. ){
  940. if($result["video"]["author"] != "null"){
  941. $author = [
  942. "name" => $result["video"]["author"]["name"] == "null" ? null : $result["video"]["author"]["name"],
  943. "url" => $result["video"]["author"]["url"] == "null" ? null : $result["video"]["author"]["url"],
  944. "avatar" => $result["video"]["author"]["img"] == "null" ? null : $result["video"]["author"]["img"]
  945. ];
  946. }else{
  947. $author = [
  948. "name" => null,
  949. "url" => null,
  950. "avatar" => null
  951. ];
  952. }
  953. if($result["thumbnail"] != "null"){
  954. $thumb = [
  955. "url" => $result["thumbnail"]["original"],
  956. "ratio" => "16:9"
  957. ];
  958. }else{
  959. $thumb = [
  960. "url" => null,
  961. "ratio" => null
  962. ];
  963. }
  964. $out["video"][] = [
  965. "title" => $result["title"],
  966. "description" => $result["description"] == "null" ? null : $this->titledots($result["description"]),
  967. "author" => $author,
  968. "date" => $result["age"] == "null" ? null : strtotime($result["age"]),
  969. "duration" => $result["video"]["duration"] == "null" ? null : $this->hms2int($result["video"]["duration"]),
  970. "views" => $result["video"]["views"] == "null" ? null : (int)$result["video"]["views"],
  971. "thumb" => $thumb,
  972. "url" => $result["url"]
  973. ];
  974. }
  975. return $out;
  976. }
  977. private function stackoverflow_parse($html){
  978. $i = 0;
  979. $answer = [];
  980. $this->fuckhtml->load($html);
  981. foreach(
  982. $this->fuckhtml->getElementsByTagName("*")
  983. as $snippet
  984. ){
  985. switch($snippet["tagName"]){
  986. case "p":
  987. $this->fuckhtml->load($snippet["innerHTML"]);
  988. $codetags =
  989. $this->fuckhtml
  990. ->getElementsByTagName("*");
  991. $tmphtml = $snippet["innerHTML"];
  992. foreach($codetags as $tag){
  993. if(!isset($tag["outerHTML"])){
  994. continue;
  995. }
  996. $tmphtml =
  997. explode(
  998. $tag["outerHTML"],
  999. $tmphtml,
  1000. 2
  1001. );
  1002. $value = $this->fuckhtml->getTextContent($tmphtml[0], false, false);
  1003. $this->appendtext($value, $answer, $i);
  1004. $type = null;
  1005. switch($tag["tagName"]){
  1006. case "code": $type = "inline_code"; break;
  1007. case "em": $type = "italic"; break;
  1008. case "blockquote": $type = "quote"; break;
  1009. default: $type = "text";
  1010. }
  1011. if($type !== null){
  1012. $value = $this->fuckhtml->getTextContent($tag, false, true);
  1013. if(trim($value) != ""){
  1014. if(
  1015. $i !== 0 &&
  1016. $type == "title"
  1017. ){
  1018. $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]);
  1019. }
  1020. $answer[] = [
  1021. "type" => $type,
  1022. "value" => $value
  1023. ];
  1024. $i++;
  1025. }
  1026. }
  1027. if(count($tmphtml) === 2){
  1028. $tmphtml = $tmphtml[1];
  1029. }else{
  1030. break;
  1031. }
  1032. }
  1033. if(is_array($tmphtml)){
  1034. $tmphtml = $tmphtml[0];
  1035. }
  1036. if(strlen($tmphtml) !== 0){
  1037. $value = $this->fuckhtml->getTextContent($tmphtml, false, false);
  1038. $this->appendtext($value, $answer, $i);
  1039. }
  1040. break;
  1041. case "img":
  1042. $answer[] = [
  1043. "type" => "image",
  1044. "url" =>
  1045. $this->fuckhtml
  1046. ->getTextContent(
  1047. $tag["attributes"]["src"]
  1048. )
  1049. ];
  1050. $i++;
  1051. break;
  1052. case "pre":
  1053. switch($answer[$i - 1]["type"]){
  1054. case "text":
  1055. case "italic":
  1056. $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]);
  1057. break;
  1058. }
  1059. $answer[] =
  1060. [
  1061. "type" => "code",
  1062. "value" =>
  1063. rtrim(
  1064. $this->fuckhtml
  1065. ->getTextContent(
  1066. $snippet,
  1067. true,
  1068. false
  1069. )
  1070. )
  1071. ];
  1072. $i++;
  1073. break;
  1074. case "ol":
  1075. $o = 0;
  1076. $this->fuckhtml->load($snippet);
  1077. $li =
  1078. $this->fuckhtml
  1079. ->getElementsByTagName("li");
  1080. foreach($li as $elem){
  1081. $o++;
  1082. $this->appendtext(
  1083. $o . ". " .
  1084. $this->fuckhtml
  1085. ->getTextContent(
  1086. $elem
  1087. ),
  1088. $answer,
  1089. $i
  1090. );
  1091. }
  1092. break;
  1093. }
  1094. }
  1095. if(
  1096. $i !== 0 &&
  1097. $answer[$i - 1]["type"] == "text"
  1098. ){
  1099. $answer[$i - 1]["value"] = rtrim($answer[$i - 1]["value"]);
  1100. }
  1101. return $answer;
  1102. }
  1103. private function hms2int($time){
  1104. $parts = explode(":", $time, 3);
  1105. $time = 0;
  1106. if(count($parts) === 3){
  1107. // hours
  1108. $time = $time + ((int)$parts[0] * 3600);
  1109. array_shift($parts);
  1110. }
  1111. if(count($parts) === 2){
  1112. // minutes
  1113. $time = $time + ((int)$parts[0] * 60);
  1114. array_shift($parts);
  1115. }
  1116. // seconds
  1117. $time = $time + (int)$parts[0];
  1118. return $time;
  1119. }
  1120. private function appendtext($payload, &$text, &$index){
  1121. if(trim($payload) == ""){
  1122. return;
  1123. }
  1124. if(
  1125. $index !== 0 &&
  1126. $text[$index - 1]["type"] == "text"
  1127. ){
  1128. $text[$index - 1]["value"] .= "\n\n" . preg_replace('/ $/', " ", $payload);
  1129. }else{
  1130. $text[] = [
  1131. "type" => "text",
  1132. "value" => preg_replace('/ $/', " ", $payload)
  1133. ];
  1134. $index++;
  1135. }
  1136. }
  1137. private function tablesublink($html_collection, &$data){
  1138. foreach($html_collection as $html){
  1139. $html["innerHTML"] = preg_replace(
  1140. '/<style>[\S\s]*<\/style>/i',
  1141. "",
  1142. $html["innerHTML"]
  1143. );
  1144. $html =
  1145. explode(
  1146. ":",
  1147. $this->fuckhtml->getTextContent($html),
  1148. 2
  1149. );
  1150. if(count($html) === 1){
  1151. $html = ["Rating", $html[0]];
  1152. }
  1153. $data["table"][trim($html[0])] = trim($html[1]);
  1154. }
  1155. }
  1156. private function getimagelinkfromstyle($thumb){
  1157. $thumb =
  1158. $this->fuckhtml
  1159. ->getElementsByClassName(
  1160. $thumb,
  1161. "div"
  1162. );
  1163. if(count($thumb) === 0){
  1164. return [
  1165. "url" => null,
  1166. "ratio" => null
  1167. ];
  1168. }
  1169. $thumb = $thumb[0]["attributes"]["style"];
  1170. preg_match(
  1171. '/background-image: ?url\((\'[^\']+\'|"[^"]+"|[^\)]+)\)/',
  1172. $thumb,
  1173. $thumb
  1174. );
  1175. $url = $this->fuckhtml->getTextContent($this->unshiturl(trim($thumb[1], '"\' ')));
  1176. if(parse_url($url, PHP_URL_HOST) == "cdn.search.brave.com"){
  1177. return [
  1178. "url" => null,
  1179. "ratio" => null
  1180. ];
  1181. }
  1182. return [
  1183. "url" => $url,
  1184. "ratio" => "16:9"
  1185. ];
  1186. }
  1187. private function limitstrlen($text){
  1188. return explode("\n", wordwrap($text, 300, "\n"))[0];
  1189. }
  1190. private function limitwhitespace($text){
  1191. return
  1192. preg_replace(
  1193. '/[\s]+/',
  1194. " ",
  1195. $text
  1196. );
  1197. }
  1198. private function titledots($title){
  1199. $substr = substr($title, -3);
  1200. if(
  1201. $substr == "..." ||
  1202. $substr == "…"
  1203. ){
  1204. return trim(substr($title, 0, -3));
  1205. }
  1206. return trim($title);
  1207. }
  1208. private function unshiturl($url){
  1209. // https://imgs.search.brave.com/XFnbR8Sl7ge82MBDEH7ju0UHImRovMVmQ2qnDvgNTuA/rs:fit:844:225:1/g:ce/aHR0cHM6Ly90c2U0/Lm1tLmJpbmcubmV0/L3RoP2lkPU9JUC54/UWotQXU5N2ozVndT/RDJnNG9BNVhnSGFF/SyZwaWQ9QXBp.jpeg
  1210. $tmp = explode("aHR0", $url);
  1211. if(count($tmp) !== 2){
  1212. // nothing to do
  1213. return $url;
  1214. }
  1215. return
  1216. base64_decode(
  1217. "aHR0" .
  1218. str_replace(["/", "_"], ["", "/"],
  1219. explode(
  1220. ".",
  1221. $tmp[1]
  1222. )[0]
  1223. )
  1224. );
  1225. }
  1226. }