qwant.php 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. <?php
  2. class qwant{
  3. public function __construct(){
  4. include "lib/backend.php";
  5. $this->backend = new backend("qwant");
  6. }
  7. public function getfilters($page){
  8. $base = [
  9. "nsfw" => [
  10. "display" => "NSFW",
  11. "option" => [
  12. "yes" => "Yes",
  13. "maybe" => "Maybe",
  14. "no" => "No"
  15. ]
  16. ],
  17. "country" => [
  18. "display" => "Country",
  19. "option" => [
  20. "en_US" => "United States",
  21. "fr_FR" => "France",
  22. "en_GB" => "Great Britain",
  23. "de_DE" => "Germany",
  24. "it_IT" => "Italy",
  25. "es_AR" => "Argentina",
  26. "en_AU" => "Australia",
  27. "es_ES" => "Spain (es)",
  28. "ca_ES" => "Spain (ca)",
  29. "cs_CZ" => "Czech Republic",
  30. "ro_RO" => "Romania",
  31. "el_GR" => "Greece",
  32. "zh_CN" => "China",
  33. "zh_HK" => "Hong Kong",
  34. "en_NZ" => "New Zealand",
  35. "fr_FR" => "France",
  36. "th_TH" => "Thailand",
  37. "ko_KR" => "South Korea",
  38. "sv_SE" => "Sweden",
  39. "nb_NO" => "Norway",
  40. "da_DK" => "Denmark",
  41. "hu_HU" => "Hungary",
  42. "et_EE" => "Estonia",
  43. "es_MX" => "Mexico",
  44. "es_CL" => "Chile",
  45. "en_CA" => "Canada (en)",
  46. "fr_CA" => "Canada (fr)",
  47. "en_MY" => "Malaysia",
  48. "bg_BG" => "Bulgaria",
  49. "fi_FI" => "Finland",
  50. "pl_PL" => "Poland",
  51. "nl_NL" => "Netherlands",
  52. "pt_PT" => "Portugal",
  53. "de_CH" => "Switzerland (de)",
  54. "fr_CH" => "Switzerland (fr)",
  55. "it_CH" => "Switzerland (it)",
  56. "de_AT" => "Austria",
  57. "fr_BE" => "Belgium (fr)",
  58. "nl_BE" => "Belgium (nl)",
  59. "en_IE" => "Ireland",
  60. "he_IL" => "Israel"
  61. ]
  62. ]
  63. ];
  64. switch($page){
  65. case "web":
  66. $base = array_merge(
  67. $base,
  68. [
  69. "time" => [
  70. "display" => "Time posted",
  71. "option" => [
  72. "any" => "Any time",
  73. "day" => "Past 24 hours",
  74. "week" => "Past week",
  75. "month" => "Past month"
  76. ]
  77. ],
  78. "extendedsearch" => [
  79. // no display, wont show in interface
  80. "option" => [
  81. "yes" => "Yes",
  82. "no" => "No"
  83. ]
  84. ]
  85. ]
  86. );
  87. break;
  88. case "images":
  89. $base = array_merge(
  90. $base,
  91. [
  92. "time" => [
  93. "display" => "Time posted",
  94. "option" => [
  95. "any" => "Any time",
  96. "day" => "Past 24 hours",
  97. "week" => "Past week",
  98. "month" => "Past month"
  99. ]
  100. ],
  101. "size" => [
  102. "display" => "Size",
  103. "option" => [
  104. "any" => "Any size",
  105. "large" => "Large",
  106. "medium" => "Medium",
  107. "small" => "Small"
  108. ]
  109. ],
  110. "color" => [
  111. "display" => "Color",
  112. "option" => [
  113. "any" => "Any color",
  114. "coloronly" => "Color only",
  115. "monochrome" => "Monochrome",
  116. "black" => "Black",
  117. "brown" => "Brown",
  118. "gray" => "Gray",
  119. "white" => "White",
  120. "yellow" => "Yellow",
  121. "orange" => "Orange",
  122. "red" => "Red",
  123. "pink" => "Pink",
  124. "purple" => "Purple",
  125. "blue" => "Blue",
  126. "teal" => "Teal",
  127. "green" => "Green"
  128. ]
  129. ],
  130. "imagetype" => [
  131. "display" => "Type",
  132. "option" => [
  133. "any" => "Any type",
  134. "animatedgif" => "Animated GIF",
  135. "photo" => "Photograph",
  136. "transparent" => "Transparent"
  137. ]
  138. ],
  139. "license" => [
  140. "display" => "License",
  141. "option" => [
  142. "any" => "Any license",
  143. "share" => "Non-commercial reproduction and sharing",
  144. "sharecommercially" => "Reproduction and sharing",
  145. "modify" => "Non-commercial reproduction, sharing and modification",
  146. "modifycommercially" => "Reproduction, sharing and modification",
  147. "public" => "Public domain"
  148. ]
  149. ]
  150. ]
  151. );
  152. break;
  153. case "videos":
  154. $base = array_merge(
  155. $base,
  156. [
  157. "order" => [
  158. "display" => "Order by",
  159. "option" => [
  160. "relevance" => "Relevance",
  161. "views" => "Views",
  162. "date" => "Most recent",
  163. ]
  164. ],
  165. "source" => [
  166. "display" => "Source",
  167. "option" => [
  168. "any" => "Any source",
  169. "youtube" => "YouTube",
  170. "dailymotion" => "Dailymotion",
  171. ]
  172. ]
  173. ]
  174. );
  175. break;
  176. case "news":
  177. $base = array_merge(
  178. $base,
  179. [
  180. "time" => [
  181. "display" => "Time posted",
  182. "option" => [
  183. "any" => "Any time",
  184. "hour" => "Less than 1 hour ago",
  185. "day" => "Past 24 hours",
  186. "week" => "Past week",
  187. "month" => "Past month"
  188. ]
  189. ],
  190. "order" => [
  191. "display" => "Order by",
  192. "option" => [
  193. "relevance" => "Relevance",
  194. "date" => "Most recent"
  195. ]
  196. ]
  197. ]
  198. );
  199. break;
  200. }
  201. return $base;
  202. }
  203. private function get($proxy, $url, $get = [], $is_post = false, $cookie = null){
  204. $curlproc = curl_init();
  205. if($is_post){
  206. $headers = [
  207. "User-Agent: " . config::USER_AGENT,
  208. "Accept: */*",
  209. "Accept-Language: en-US,en;q=0.9",
  210. "Accept-Encoding: gzip, deflate, br, zstd",
  211. "Referer: https://www.qwant.com/",
  212. "Content-type: application/x-www-form-urlencoded",
  213. "Origin: https://www.qwant.com",
  214. "DNT: 1",
  215. "Sec-GPC: 1",
  216. "Connection: keep-alive",
  217. "Sec-Fetch-Dest: empty",
  218. "Sec-Fetch-Mode: cors",
  219. "Sec-Fetch-Site: same-site",
  220. "Pragma: no-cache",
  221. "Cache-Control: no-cache"
  222. ];
  223. curl_setopt($curlproc, CURLOPT_POST, true);
  224. curl_setopt($curlproc, CURLOPT_POSTFIELDS, http_build_query($get));
  225. }else{
  226. $headers = [
  227. "User-Agent: " . config::USER_AGENT,
  228. "Accept: application/json, text/plain, */*",
  229. "Accept-Language: en-US,en;q=0.9",
  230. "Accept-Encoding: gzip, deflate, br, zstd",
  231. "Referer: https://www.qwant.com/",
  232. "Origin: https://www.qwant.com",
  233. "DNT: 1",
  234. ($cookie !== null ? "Cookie: {$cookie}" : ""),
  235. "Sec-GPC: 1",
  236. "Connection: keep-alive",
  237. "Sec-Fetch-Dest: empty",
  238. "Sec-Fetch-Mode: cors",
  239. "Sec-Fetch-Site: same-site",
  240. "Priority: u=4",
  241. "Pragma: no-cache",
  242. "Cache-Control: no-cache",
  243. "TE: trailers"
  244. ];
  245. if($get !== []){
  246. $get = http_build_query($get);
  247. $url .= "?" . $get;
  248. }
  249. }
  250. curl_setopt($curlproc, CURLOPT_URL, $url);
  251. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  252. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  253. // Bypass HTTP/2 check
  254. curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  255. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  256. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  257. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  258. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  259. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  260. $this->backend->assign_proxy($curlproc, $proxy);
  261. $data = curl_exec($curlproc);
  262. if(curl_errno($curlproc)){
  263. throw new Exception(curl_error($curlproc));
  264. }
  265. curl_close($curlproc);
  266. return $data;
  267. }
  268. public function web($get){
  269. if($get["npt"]){
  270. // get next page data
  271. [$params, $proxy] = $this->backend->get($get["npt"], "web");
  272. $params = json_decode($params, true);
  273. $cookie = $params["cookie"];
  274. unset($params["cookie"]);
  275. }else{
  276. // get _GET data instead
  277. $search = $get["s"];
  278. if(strlen($search) === 0){
  279. throw new Exception("Search term is empty!");
  280. }
  281. if(strlen($search) > 2048){
  282. throw new Exception("Search term is too long!");
  283. }
  284. $proxy = $this->backend->get_ip();
  285. $params = [
  286. "q" => $search,
  287. "freshness" => $get["time"],
  288. "count" => 10,
  289. "locale" => $get["country"],
  290. "offset" => 0,
  291. "device" => "desktop",
  292. "tgp" => 3,
  293. "safesearch" => 0,
  294. "displayed" => "true",
  295. "llm" => "false"
  296. ];
  297. switch($get["nsfw"]){
  298. case "yes": $params["safesearch"] = 0; break;
  299. case "maybe": $params["safesearch"] = 1; break;
  300. case "no": $params["safesearch"] = 2; break;
  301. }
  302. $cookie = null;
  303. }
  304. /*
  305. $handle = fopen("scraper/qwant_web.json", "r");
  306. $json = fread($handle, filesize("scraper/qwant_web.json"));
  307. fclose($handle);*/
  308. $cookie = $this->fuck_datadome($proxy, $search);
  309. try{
  310. $json =
  311. $this->get(
  312. $proxy,
  313. "https://api.qwant.com/v3/search/web",
  314. $params,
  315. false,
  316. $cookie
  317. );
  318. }catch(Exception $error){
  319. throw new Exception("Could not fetch JSON");
  320. }
  321. $json = json_decode($json, true);
  322. if($json === NULL){
  323. throw new Exception("Failed to decode JSON");
  324. }
  325. if(isset($json["data"]["message"][0])){
  326. throw new Exception("Server returned an error:\n" . $json["data"]["message"][0]);
  327. }
  328. $out = [
  329. "status" => "ok",
  330. "spelling" => [
  331. "type" => "no_correction",
  332. "using" => null,
  333. "correction" => null
  334. ],
  335. "npt" => null,
  336. "answer" => [],
  337. "web" => [],
  338. "image" => [],
  339. "video" => [],
  340. "news" => [],
  341. "related" => []
  342. ];
  343. $this->detect_errors($json);
  344. if(
  345. $json["status"] != "success" &&
  346. $json["data"]["error_code"] === 5
  347. ){
  348. // no results
  349. return $out;
  350. }
  351. if(!isset($json["data"]["result"]["items"]["mainline"])){
  352. throw new Exception("Server did not return a result object");
  353. }
  354. // data is OK, parse
  355. // get instant answer
  356. if(
  357. $get["extendedsearch"] == "yes" &&
  358. isset($json["data"]["result"]["items"]["sidebar"][0]["endpoint"])
  359. ){
  360. try{
  361. $answer =
  362. $this->get(
  363. $proxy,
  364. "https://api.qwant.com/v3" .
  365. $json["data"]["result"]["items"]["sidebar"][0]["endpoint"],
  366. []
  367. );
  368. $answer = json_decode($answer, true);
  369. if(
  370. $answer === null ||
  371. $answer["status"] != "success" ||
  372. $answer["data"]["result"] === null
  373. ){
  374. throw new Exception();
  375. }
  376. // parse answer
  377. $out["answer"][] = [
  378. "title" => $answer["data"]["result"]["title"],
  379. "description" => [
  380. [
  381. "type" => "text",
  382. "value" => $this->trimdots($answer["data"]["result"]["description"])
  383. ]
  384. ],
  385. "url" => $answer["data"]["result"]["url"],
  386. "thumb" =>
  387. $answer["data"]["result"]["thumbnail"]["landscape"] == null ?
  388. null :
  389. $this->unshitimage($answer["data"]["result"]["thumbnail"]["landscape"]),
  390. "table" => [],
  391. "sublink" => []
  392. ];
  393. }catch(Exception $error){
  394. // do nothing in case of failure
  395. }
  396. }
  397. // get word correction
  398. if(isset($json["data"]["query"]["queryContext"]["alteredQuery"])){
  399. $out["spelling"] = [
  400. "type" => "including",
  401. "using" => $json["data"]["query"]["queryContext"]["alteredQuery"],
  402. "correction" => $json["data"]["query"]["queryContext"]["alterationOverrideQuery"]
  403. ];
  404. }
  405. // check for next page
  406. if($json["data"]["result"]["lastPage"] === false){
  407. $params["offset"] = $params["offset"] + 10;
  408. $params["cookie"] = $cookie;
  409. $out["npt"] =
  410. $this->backend->store(
  411. json_encode($params),
  412. "web",
  413. $proxy
  414. );
  415. }
  416. // parse results
  417. foreach($json["data"]["result"]["items"]["mainline"] as $item){
  418. switch($item["type"]){ // ignores ads
  419. case "web":
  420. $first_iteration = true;
  421. foreach($item["items"] as $result){
  422. if(isset($result["thumbnailUrl"])){
  423. $thumb = [
  424. "url" => $this->unshitimage($result["thumbnailUrl"]),
  425. "ratio" => "16:9"
  426. ];
  427. }else{
  428. $thumb = [
  429. "url" => null,
  430. "ratio" => null
  431. ];
  432. }
  433. $sublinks = [];
  434. if(isset($result["links"])){
  435. foreach($result["links"] as $link){
  436. $sublinks[] = [
  437. "title" => $this->trimdots($link["title"]),
  438. "date" => null,
  439. "description" => isset($link["desc"]) ? $this->trimdots($link["desc"]) : null,
  440. "url" => $link["url"]
  441. ];
  442. }
  443. }
  444. // detect gibberish results
  445. if(
  446. $first_iteration &&
  447. !isset($result["urlPingSuffix"])
  448. ){
  449. throw new Exception("Qwant returned gibberish results");
  450. }
  451. $out["web"][] = [
  452. "title" => $this->trimdots($result["title"]),
  453. "description" => $this->trimdots($result["desc"]),
  454. "url" => $result["url"],
  455. "date" => null,
  456. "type" => "web",
  457. "thumb" => $thumb,
  458. "sublink" => $sublinks,
  459. "table" => []
  460. ];
  461. $first_iteration = false;
  462. }
  463. break;
  464. case "images":
  465. foreach($item["items"] as $image){
  466. $out["image"][] = [
  467. "title" => $image["title"],
  468. "source" => [
  469. [
  470. "url" => $image["media"],
  471. "width" => (int)$image["width"],
  472. "height" => (int)$image["height"]
  473. ],
  474. [
  475. "url" => $this->unshitimage($image["thumbnail"]),
  476. "width" => $image["thumb_width"],
  477. "height" => $image["thumb_height"]
  478. ]
  479. ],
  480. "url" => $image["url"]
  481. ];
  482. }
  483. break;
  484. case "videos":
  485. foreach($item["items"] as $video){
  486. $out["video"][] = [
  487. "title" => $video["title"],
  488. "description" => null,
  489. "date" => (int)$video["date"],
  490. "duration" => $video["duration"] === null ? null : $video["duration"] / 1000,
  491. "views" => null,
  492. "thumb" =>
  493. $video["thumbnail"] === null ?
  494. [
  495. "url" => null,
  496. "ratio" => null,
  497. ] :
  498. [
  499. "url" => $this->unshitimage($video["thumbnail"]),
  500. "ratio" => "16:9",
  501. ],
  502. "url" => $video["url"]
  503. ];
  504. }
  505. break;
  506. case "related_searches":
  507. foreach($item["items"] as $related){
  508. $out["related"][] = $related["text"];
  509. }
  510. break;
  511. }
  512. }
  513. return $out;
  514. }
  515. public function image($get){
  516. if($get["npt"]){
  517. [$params, $proxy] =
  518. $this->backend->get(
  519. $get["npt"],
  520. "images"
  521. );
  522. $params = json_decode($params, true);
  523. $cookie = $params["cookie"];
  524. unset($params["cookie"]);
  525. }else{
  526. $search = $get["s"];
  527. if(strlen($search) === 0){
  528. throw new Exception("Search term is empty!");
  529. }
  530. $proxy = $this->backend->get_ip();
  531. $params = [
  532. "t" => "images",
  533. "q" => $search,
  534. "count" => 125,
  535. "locale" => $get["country"],
  536. "offset" => 0, // increment by 125
  537. "device" => "desktop",
  538. "tgp" => 3
  539. ];
  540. if($get["time"] != "any"){
  541. $params["freshness"] = $get["time"];
  542. }
  543. foreach(["size", "color", "imagetype", "license"] as $p){
  544. if($get[$p] != "any"){
  545. $params[$p] = $get[$p];
  546. }
  547. }
  548. switch($get["nsfw"]){
  549. case "yes": $params["safesearch"] = 0; break;
  550. case "maybe": $params["safesearch"] = 1; break;
  551. case "no": $params["safesearch"] = 2; break;
  552. }
  553. $cookie = null;
  554. }
  555. $cookie = $this->fuck_datadome($proxy, $search);
  556. try{
  557. $json = $this->get(
  558. $proxy,
  559. "https://api.qwant.com/v3/search/images",
  560. $params,
  561. false,
  562. $cookie
  563. );
  564. }catch(Exception $err){
  565. throw new Exception("Failed to get JSON");
  566. }
  567. /*
  568. $handle = fopen("scraper/yandex.json", "r");
  569. $json = fread($handle, filesize("scraper/yandex.json"));
  570. fclose($handle);*/
  571. $json = json_decode($json, true);
  572. if($json === null){
  573. throw new Exception("Failed to decode JSON");
  574. }
  575. $this->detect_errors($json);
  576. if(isset($json["data"]["result"]["items"]["mainline"])){
  577. throw new Exception("Qwant returned gibberish results");
  578. }
  579. $out = [
  580. "status" => "ok",
  581. "npt" => null,
  582. "image" => []
  583. ];
  584. if($json["data"]["result"]["lastPage"] === false){
  585. $params["offset"] = $params["offset"] + 125;
  586. $out["npt"] = $this->backend->store(
  587. json_encode($params),
  588. "images",
  589. $proxy
  590. );
  591. }
  592. foreach($json["data"]["result"]["items"] as $image){
  593. $out["image"][] = [
  594. "title" => $this->trimdots($image["title"]),
  595. "source" => [
  596. [
  597. "url" => $image["media"],
  598. "width" => $image["width"],
  599. "height" => $image["height"]
  600. ],
  601. [
  602. "url" => $this->unshitimage($image["thumbnail"]),
  603. "width" => $image["thumb_width"],
  604. "height" => $image["thumb_height"]
  605. ]
  606. ],
  607. "url" => $image["url"]
  608. ];
  609. }
  610. return $out;
  611. }
  612. public function video($get){
  613. $search = $get["s"];
  614. if(strlen($search) === 0){
  615. throw new Exception("Search term is empty!");
  616. }
  617. $params = [
  618. "t" => "videos",
  619. "q" => $search,
  620. "count" => 50,
  621. "locale" => $get["country"],
  622. "offset" => 0, // dont implement pagination
  623. "device" => "desktop",
  624. "tgp" => 3
  625. ];
  626. switch($get["nsfw"]){
  627. case "yes": $params["safesearch"] = 0; break;
  628. case "maybe": $params["safesearch"] = 1; break;
  629. case "no": $params["safesearch"] = 2; break;
  630. }
  631. try{
  632. $json =
  633. $this->get(
  634. $this->backend->get_ip(),
  635. "https://api.qwant.com/v3/search/videos",
  636. $params
  637. );
  638. }catch(Exception $error){
  639. throw new Exception("Could not fetch JSON");
  640. }
  641. /*
  642. $handle = fopen("scraper/yandex-video.json", "r");
  643. $json = fread($handle, filesize("scraper/yandex-video.json"));
  644. fclose($handle);
  645. */
  646. $json = json_decode($json, true);
  647. if($json === null){
  648. throw new Exception("Could not parse JSON");
  649. }
  650. $this->detect_errors($json);
  651. if(isset($json["data"]["result"]["items"]["mainline"])){
  652. throw new Exception("Qwant returned gibberish results");
  653. }
  654. $out = [
  655. "status" => "ok",
  656. "npt" => null,
  657. "video" => [],
  658. "author" => [],
  659. "livestream" => [],
  660. "playlist" => [],
  661. "reel" => []
  662. ];
  663. foreach($json["data"]["result"]["items"] as $video){
  664. if(empty($video["thumbnail"])){
  665. $thumb = [
  666. "url" => null,
  667. "ratio" => null
  668. ];
  669. }else{
  670. $thumb = [
  671. "url" => $this->unshitimage($video["thumbnail"]),
  672. "ratio" => "16:9"
  673. ];
  674. }
  675. $duration = (int)$video["duration"];
  676. $out["video"][] = [
  677. "title" => $video["title"],
  678. "description" => $this->limitstrlen($video["desc"]),
  679. "author" => [
  680. "name" => $video["channel"],
  681. "url" => null,
  682. "avatar" => null
  683. ],
  684. "date" => (int)$video["date"],
  685. "duration" => $duration === 0 ? null : $duration,
  686. "views" => null,
  687. "thumb" => $thumb,
  688. "url" => preg_replace("/\?syndication=.+/", "", $video["url"])
  689. ];
  690. }
  691. return $out;
  692. }
  693. public function news($get){
  694. $search = $get["s"];
  695. if(strlen($search) === 0){
  696. throw new Exception("Search term is empty!");
  697. }
  698. $params = [
  699. "t" => "news",
  700. "q" => $search,
  701. "count" => 50,
  702. "locale" => $get["country"],
  703. "offset" => 0, // dont implement pagination
  704. "device" => "desktop",
  705. "tgp" => 3
  706. ];
  707. switch($get["nsfw"]){
  708. case "yes": $params["safesearch"] = 0; break;
  709. case "maybe": $params["safesearch"] = 1; break;
  710. case "no": $params["safesearch"] = 2; break;
  711. }
  712. try{
  713. $json =
  714. $this->get(
  715. $this->backend->get_ip(),
  716. "https://api.qwant.com/v3/search/news",
  717. $params
  718. );
  719. }catch(Exception $error){
  720. throw new Exception("Could not fetch JSON");
  721. }
  722. /*
  723. $handle = fopen("scraper/yandex-video.json", "r");
  724. $json = fread($handle, filesize("scraper/yandex-video.json"));
  725. fclose($handle);
  726. */
  727. $json = json_decode($json, true);
  728. if($json === null){
  729. throw new Exception("Could not parse JSON");
  730. }
  731. $this->detect_errors($json);
  732. if(isset($json["data"]["result"]["items"]["mainline"])){
  733. throw new Exception("Qwant returned gibberish results");
  734. }
  735. $out = [
  736. "status" => "ok",
  737. "npt" => null,
  738. "news" => []
  739. ];
  740. foreach($json["data"]["result"]["items"] as $news){
  741. if(empty($news["media"][0]["pict_big"]["url"])){
  742. $thumb = [
  743. "url" => null,
  744. "ratio" => null
  745. ];
  746. }else{
  747. $thumb = [
  748. "url" => $this->unshitimage($news["media"][0]["pict_big"]["url"]),
  749. "ratio" => "16:9"
  750. ];
  751. }
  752. $out["news"][] = [
  753. "title" => $news["title"],
  754. "author" => $news["press_name"],
  755. "description" => $this->trimdots($news["desc"]),
  756. "date" => (int)$news["date"],
  757. "thumb" => $thumb,
  758. "url" => $news["url"]
  759. ];
  760. }
  761. return $out;
  762. }
  763. private function detect_errors($json){
  764. if(
  765. isset($json["status"]) &&
  766. $json["status"] == "error"
  767. ){
  768. if(isset($json["data"]["error_data"]["captchaUrl"])){
  769. throw new Exception("Qwant returned a captcha");
  770. }elseif(isset($json["data"]["error_data"]["error_code"])){
  771. throw new Exception(
  772. "Qwant returned an API error: " .
  773. $json["data"]["error_data"]["error_code"]
  774. );
  775. }
  776. throw new Exception("Qwant returned an API error");
  777. }
  778. if(
  779. isset($json["url"]) &&
  780. preg_match(
  781. '/captcha/i',
  782. $json["url"]
  783. )
  784. ){
  785. throw new Exception("Qwant returned a captcha redirect");
  786. }
  787. }
  788. private function limitstrlen($text){
  789. return explode("\n", wordwrap($text, 300, "\n"))[0];
  790. }
  791. private function trimdots($text){
  792. return trim($text, ". ");
  793. }
  794. private function unshitimage($url){
  795. // https://s1.qwant.com/thumbr/0x0/8/d/f6de4deb2c2b12f55d8bdcaae576f9f62fd58a05ec0feeac117b354d1bf5c2/th.jpg?u=https%3A%2F%2Fwww.bing.com%2Fth%3Fid%3DOIP.vvDWsagzxjoKKP_rOqhwrQAAAA%26w%3D160%26h%3D160%26c%3D7%26pid%3D5.1&q=0&b=1&p=0&a=0
  796. // https://s2.qwant.com/thumbr/474x289/7/f/412d13b3fe3a03eb2b89633c8e88b609b7d0b93cdd9a5e52db3c663e41e65e/th.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%3Fid%3DOIP.9Tm_Eo6m7V7ltN19mxduDgHaEh%26pid%3DApi&q=0&b=1&p=0&a=0
  797. $image = parse_url($url);
  798. if(
  799. !isset($image["host"]) ||
  800. !isset($image["query"])
  801. ){
  802. // cant do anything
  803. return $url;
  804. }
  805. $id = null;
  806. if(
  807. preg_match(
  808. '/s[0-9]+\.qwant\.com$/',
  809. $image["host"]
  810. )
  811. ){
  812. parse_str($image["query"], $str);
  813. // we're being served a proxy URL
  814. if(isset($str["u"])){
  815. $bing_url = $str["u"];
  816. }else{
  817. // give up
  818. return $url;
  819. }
  820. }
  821. // parse bing URL
  822. $id = null;
  823. $image = parse_url($bing_url);
  824. if(isset($image["query"])){
  825. parse_str($image["query"], $str);
  826. if(isset($str["id"])){
  827. $id = $str["id"];
  828. }
  829. }
  830. if($id === null){
  831. $id = explode("/th/id/", $image["path"], 2);
  832. if(count($id) !== 2){
  833. // malformed
  834. return $url;
  835. }
  836. $id = $id[1];
  837. }
  838. if(is_array($id)){
  839. // fuck off, let proxy.php deal with it
  840. return $url;
  841. }
  842. return "https://" . $image["host"] . "/th?id=" . rawurlencode($id);
  843. }
  844. private function fuck_datadome($proxy, $search){
  845. if($cookie === null){
  846. // get datadome bullshit
  847. // yeah go ahead and patch this you code monkey
  848. // ... you know, instead of fixing this shit, can you make the second page of results return more than 1 result?
  849. // i search shit like "higurashi" and i only get 1 result and then just a bunch of related searches ad finitum
  850. // also, stop pretending you guys have your own index, this is clearly all bing results
  851. try{
  852. $datadome =
  853. $this->get(
  854. $proxy,
  855. "https://dd.qwant.com/js/",
  856. [
  857. "jspl" => "-oirHDeP7G3G9LXY3rhxGDYf6t_ZlYPzaiL2dRhQ34uNUc4dO2Td0SmmKu_VJp7-jrAIoOMrTqKgCtV4cPzntMyNuOYN7QQ1q0Fi2c-679vYpJr9ub5dRFXMhCMQmjZsOWXUa8yafIXf85UA5Goct-n4hVT5dFfBGM-dE6gwZEFkHQ1j1195sLybZFTNA1IfX26MBMxlVHoyT-aHtkd6YjGXbvOPMMsObVHDa_Cf4BmApRGo-eehsAttsCqDRwkl0M2E1b0OXCIwMVnCK-qWZ81MGaJGjqxdj_LaxhAjE9JI_4oNedAg4Yj4m_vkQBKdmWJT_bTUsllAZXqX5IxYQHJ8WvlknOTr6sR3wgoSOa_JNVHkUQxqXBbQcoDdy6VciL5rIqwWl4Y2caVYzeXa8YNXxwsfrPDegH3dQ6vOiIiCDT3bexkcS_y7GAioPPv9qDu7M9y4sI8t0VkPgMbWY6MIu5u9WAv6lEiKIYrtlyU-iiXnNdkd9EnpC1TD8_s12PtCuLg5bGgLVmI1dw8VXVZxmVyFvfBtuqXkzM-kZOyv-YIRcf-wPY8DUtnHox1d6t64oYhGv-pYV5dmZ0rGs99LC6ThiifeF_oHz5ZK46Ms68WsiAjc1HLAbFPFxWw8X6lyLlpT5aHgFdDBeCTExKXyItlc9PBxkgK8UnXHQFiWX3HTen8rziwkI31AbkMU7xGP6cvuwuLG7MA7sH3yLF1uimQlhr6EuTrImWQNV7X81DqyZ0OE7I-uT9bp_xISa5it-6AJpFtGcwz04_OuO2zvZazBeiKJI5Fw0X2r3RHTHdA29SXqTIe4y2LRAAp0MSAZUIJAMIbbrz9BG-3D5BHfQUA1Bbuklxv-tNWiuanWbbTmAiuxTAo9P-D2ZMrWHwdN8sCb3nINLur38z1xviSs2qEEjKo9kzQIWoHgwm_amV-jjDtNBXwpCQnoaqt3FfL8e59ecYWVg61Gnl3YCwq6rubFdkIv6UStcWSlj4pRFlFZSjQxzznvGTAc5Nz1JJWVeBHJItei4wqDUYEw2BNTS_fU9keV3LFyKDH_x248AxHLiXt583DhZZwPAKlpmMo0E7odzfOgVQGM5b9l64NL3RQRq8cDxI8KgmOFnPHdI7Myq1A3nW1uoBB8m9_GR_9orboKl5tAQrT0VTMRd6JyNwOOQ71ZoQpMwsD5Xd9YDZd618TNE9dA38h9OyBco_-hwKCYayyEqib1atLLBO_gnO4InR471AklOokDOKx1oY_xgNhlFrZskE4nQQOqeCQ_47oysob9s3kblA8UqDrL2vM3i2KPLWwlQzMAPrQAIv7Amp6rO-wYwBQdbP7XghYympxx9-hNxc1Duq-nS4R3pjPtZg3LoCvG1N_0JLf9jfPBqw_rdPM6DnjEOvpCdvrfv4wBlEpgqbaRQCZBPEz-O5ZlY24OyPvaNX0krPeXkbi-KYuwZSAbfW0zsksXb8cbRmz6tvh25IRYP1XNDPVd8NFrYOdDH9GzhV5MYZziYMurQlVwgumGLz85Y6qTHZU2D5qkMH7o5C5bzptReO96889oxkReS_LDhzdxH0tAQ5a9GgETJTHAXJ9Lt7X037i0qQlatbPLizOl9fo4vUBGJ7w-uSUmIzg1yIwOo8xhLu39WghXfoJ_EX5ptNi7arpAo11UoJIGEgWgq7bS40q2dtnG5HYpp9ef37iFgztM7PV5dlJf_W-n9uXJyCk2nhxZeuGiIGhMTzjP_8oeyiMFelmoyLlqK8wOs6hvWqgDD4NwtZc-XBkEAKsxuPrirb_sszp0z6BWsh3D6fBxAW_lTrNVkxZ1EBtoEN72HvO1aeX8PVuJP3CKwkkUcAhJ5Zvajo8t4Ai8ave2MTZul4QhDIe7ZALJMo71u-hNSj8YG9JLPqF1jcnbkl8ykt_XMMxX8wSZ_AfTsfmAuOWBfEzxv8Fl2Ne-oxcEtfuYnYNJIjQDHXXdtYbWKBezE41wonQu7FWh3wSSF3DDm9EocWMsUZ610nrIviOFdtTqjrY7KnOZp0hS9Hm2V04z-06Db-To71L5O5poYKuOlDAP_ULfW0hgMgYaPnt4Nive9gtue6ZQ5yNFylKM11zCWcF8vmuEPGx4NIZjvNFB-QSkdxZtBTQOgO6r4BqHJNZIDqiD9A6PyMemVtQEo-JhooX6bwHFHx6C84r-kKq1lg0RaDelbu1h6347kocxOczdgHWlagKPFrUvHpPHvCZeFmzLLmPWbDEpUhhfkbEZlNe47svO5WN1TYZMCAlE0g01pe6b_NB9-twQ4FJB1SkUQRA4o5DxiIHSgXBBfw1X98XPEKcJgCU2zRbYuU69mvyPcsxoPsLo8Cl2OAXj2U1tcLUxVhR_vu3I0SG_Ee8hxBp5b6QbpIu2sE0bgpqfi88XqUXf3qvxGbkoZDZ8dPvEvCVmpDZOOGJY5Db4MnpKBByW79lUnpbLCbsVOoxYu8SVmVQ63eGY9yBlACuWxlXA_PhCpnKYwMppjxKvKqalNJGMMg5WQ8cvhsIftE5wgaj21n0A15GwRO1WgUcsO_dLe1MqO88hTHWHV0Q3gNut3sdsmVr_n-0SniLtVAaw4IEtDZWNUfhpaoC7iyKxoJ86t8r697EYPlWUu4nTQhb3TOXAjHF22rFsJJMldKHVxjxjd086ky3OY3L_7cbjVof60-y4gTJdumw5hch-mhaT93nVnaTuPLGCAC48dZSgOitW8Dw62mPS05fTPAfKUY1HpyIWr9ACk7U2KVAijpzFB-bXvqJeCyBeUHr4cB61K2XxTmkNA8Oo9RC32TeYDrThFzoUyYZ_D6zPe1lLtQaFsPRvDBSsYCcr5fcABlST4DyUgnkgqtx-0-D1QEwtWiH3xiHLQGa82ldjqNIXL9LqwxYoCRBKuljHyqEylZD26A9Jp6SL9rk_Ol6TJlYa3zx44PHyMoA3Coy-HIZW5NMwVKdV7egUDDOchvuFnc1Zv2QpSFKc0JY8lYFtSm1HZFwjq7hf2hBqkU0Xneh8AT4Ty9QVb4mJ8rpzc8m_NBZNrpZVE-pIzmv2yGjnRwKSrwsjat7b2iY34UPFqGBlDk5FX6EfRlN9bq-nztWdextLc47ndr1gxsjC-vr6tbdXl5FxMVhbsnLH80wOC6hHubxCysr5C7Ft59s3MLhVQCc3IIOPMBhLOjjjVsu6K8kAJvvamIpj-iYUQj9T8JztAqfobytzbiwPsQ3mywUGkJpZxTjXjqkKdT6yfFdW5ksR8p3VlKGAAKft9TKKcc5IgErmIRtiZF4hGhB45nwIO5XHA59V94QuV3b0oJrleuDCBj0iDFqWYpsUVHyKl3RjrO86Yq1IbNRwXIIMrlsVYos9CS1I75k9kW4yQC6ni3a2ZitDkiEB-SoQuTlBsvfTs892Wt5q3CWH6_7srZO6l2QEAlWqyQp_P4i8j8uiEbZksGsgghVIgOkriblDP9ml7PdkpRX7S3OpY7jjQgTNR8XHaZ-SEw0-zPiqIIt5VioFK8JljPn-nMIDx-ew_vTDKqmKEOF39ezQcc5oQNTQNUvTAe8qCbK4ghS3fagItezoHtB5zm3qwhx9Iu6JMJy0UhywUZBaInY6NokiVZGDgtoqlVjOk7K4299MVZgsOr2P9QHRDo41lYnCeWWFUf50d-ouEOUO_ngteDpgG1DfV64eycrUOWAnpfI8XOUZ7mxS89SqJriw3T3tcljowHiOFw9QjYsjJi0vuHL5jfoBZVdYAUH6KiLfIScO-KgqitdUTpFwks8Qk7xfd2HcEfArlzSiAt3Hmb2Ty-5mhvtQj2RbPehgoI7gqa60b6Qffth-5tbIjXoKlZCQsaKVMAggF9C3w0Rv5uJ3ExCTGsfKO9IVOf--AfxaA8cNoO0W3NTaMH2iWftY0O1-1x3VJEcerp2v9BcZ0iIL3VFJ9eyOPYNHNwTYrI3S6OrVZxuQXFbu2E_Us32wxYqXvnMx-DEOk7bUUFIanL69Cbn_mR_9Wm1sFixBQ15diJoe1Oyl3XWHWL6_YF4XnHzZlMWySt3Awdeynwu99Ter8WMGXl-6K30novKRWC9jDVOfAtxPvXBkkRtpm8nnJT8fwkmIQoWOEPT2MCg3VYzzHzVEH-AueuRiCZj_o7L-fRqbtFrWPyVnGuetUYS5u0D3n7Rr6kCVqWhLfwjqAwV_calzIQs8-J0-AJe6GGaaONPYfXwXjYan0Sl11CS64lXvx7i3MgeOZuH210T6g6hj54dSLw0upO1L8k0FGli3I5OhLMekeGMLT_N2akyHktjbz8Odpk2ms0pJP_PdqvUegjXgH81F5lr40HAXZs7jfC9ytgcWaEjwlmnoFHSCTdlCIgJqEBzItXiz6ilk5zP35VwxdQTNoms7Meqg-ZrgRuLRaqXNtpQT9qWBmiFP6zoYLwymqytZ_P1SV9DCM-KIZQ2Cq-ew8PsYSrwl8GmfuegoKEMBiON1I4mC9ChDgzJmbo5P9te60T0URS5g9ExiQuSo-JXs0MKC57LLiNo3MPZvqR1wRJOTg0_FqS4JFxzPwN5OqhBzfs25f6kK-axf0wWp-LetUx3U1CTNaxfbCTK5tb8KnLOIs3lDwJw5H5wIIRgXnfNLBJT8A2srfXkdpA",
  858. "eventCounters" => [],
  859. "jsType" => "ch",
  860. "cid" => "3i4vPGHCl74uIBumWaxF2a0PjFkyCJxcSvFnn~heX7keA6sGHgRbnzSYcMr_RzJFlfeeBdg9HeufzFPxnvSHbyWowSvjm6mNCSPhkx6MwDqptANoj93ZgGVbrzxGtcYe",
  861. "ddk" => "78B13B7513D180B7AB6D6FF9EB0A51",
  862. "Referer" => "https://www.qwant.com/?q={$search}&t=web",
  863. "request" => "/?q={$search}&t=web",
  864. "responsePage" => "origin",
  865. "ddv" => "5.8.0"
  866. ],
  867. true
  868. );
  869. }catch(Exception $error){
  870. throw new Exception("Failed to fetch JS challenge token");
  871. }
  872. $datadome = json_decode($datadome, true);
  873. if($datadome === null){
  874. throw new Exception("Failed to decode JS challenge JSON");
  875. }
  876. if(!isset($datadome["cookie"])){
  877. throw new Exception("Failed to get cookie from JS challenge endpoint");
  878. }
  879. $cookie = explode(";", $datadome["cookie"])[0];
  880. }
  881. return $cookie;
  882. }
  883. }