brave.php 32 KB

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