qwant.php 21 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  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 = []){
  204. $headers = [
  205. "User-Agent: " . config::USER_AGENT,
  206. "Accept: application/json, text/plain, */*",
  207. "Accept-Language: en-US,en;q=0.9",
  208. "Accept-Encoding: gzip, deflate, br, zstd",
  209. "Referer: https://www.qwant.com/",
  210. "Origin: https://www.qwant.com",
  211. "DNT: 1",
  212. "Sec-GPC: 1",
  213. "Connection: keep-alive",
  214. "Sec-Fetch-Dest: empty",
  215. "Sec-Fetch-Mode: cors",
  216. "Sec-Fetch-Site: same-site",
  217. "Priority: u=4",
  218. "Pragma: no-cache",
  219. "Cache-Control: no-cache",
  220. "TE: trailers"
  221. ];
  222. $curlproc = curl_init();
  223. if($get !== []){
  224. $get = http_build_query($get);
  225. $url .= "?" . $get;
  226. }
  227. curl_setopt($curlproc, CURLOPT_URL, $url);
  228. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  229. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  230. // Bypass HTTP/2 check
  231. curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  232. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  233. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  234. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  235. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  236. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  237. $this->backend->assign_proxy($curlproc, $proxy);
  238. $data = curl_exec($curlproc);
  239. if(curl_errno($curlproc)){
  240. throw new Exception(curl_error($curlproc));
  241. }
  242. curl_close($curlproc);
  243. return $data;
  244. }
  245. public function web($get){
  246. if($get["npt"]){
  247. // get next page data
  248. [$params, $proxy] = $this->backend->get($get["npt"], "web");
  249. $params = json_decode($params, true);
  250. }else{
  251. // get _GET data instead
  252. $search = $get["s"];
  253. if(strlen($search) === 0){
  254. throw new Exception("Search term is empty!");
  255. }
  256. if(strlen($search) > 2048){
  257. throw new Exception("Search term is too long!");
  258. }
  259. $proxy = $this->backend->get_ip();
  260. $params = [
  261. "q" => $search,
  262. "freshness" => $get["time"],
  263. "count" => 10,
  264. "locale" => $get["country"],
  265. "offset" => 0,
  266. "device" => "desktop",
  267. "tgp" => 3,
  268. "safesearch" => 0,
  269. "displayed" => "true",
  270. "llm" => "false"
  271. ];
  272. switch($get["nsfw"]){
  273. case "yes": $params["safesearch"] = 0; break;
  274. case "maybe": $params["safesearch"] = 1; break;
  275. case "no": $params["safesearch"] = 2; break;
  276. }
  277. }
  278. /*
  279. $handle = fopen("scraper/qwant_web.json", "r");
  280. $json = fread($handle, filesize("scraper/qwant_web.json"));
  281. fclose($handle);*/
  282. try{
  283. $json =
  284. $this->get(
  285. $proxy,
  286. "https://api.qwant.com/v3/search/web",
  287. $params
  288. );
  289. }catch(Exception $error){
  290. throw new Exception("Could not fetch JSON");
  291. }
  292. $json = json_decode($json, true);
  293. if($json === NULL){
  294. throw new Exception("Failed to decode JSON");
  295. }
  296. if(isset($json["data"]["message"][0])){
  297. throw new Exception("Server returned an error:\n" . $json["data"]["message"][0]);
  298. }
  299. $out = [
  300. "status" => "ok",
  301. "spelling" => [
  302. "type" => "no_correction",
  303. "using" => null,
  304. "correction" => null
  305. ],
  306. "npt" => null,
  307. "answer" => [],
  308. "web" => [],
  309. "image" => [],
  310. "video" => [],
  311. "news" => [],
  312. "related" => []
  313. ];
  314. $this->detect_errors($json);
  315. if(
  316. $json["status"] != "success" &&
  317. $json["data"]["error_code"] === 5
  318. ){
  319. // no results
  320. return $out;
  321. }
  322. if(!isset($json["data"]["result"]["items"]["mainline"])){
  323. throw new Exception("Server did not return a result object");
  324. }
  325. // data is OK, parse
  326. // get instant answer
  327. if(
  328. $get["extendedsearch"] == "yes" &&
  329. isset($json["data"]["result"]["items"]["sidebar"][0]["endpoint"])
  330. ){
  331. try{
  332. $answer =
  333. $this->get(
  334. $proxy,
  335. "https://api.qwant.com/v3" .
  336. $json["data"]["result"]["items"]["sidebar"][0]["endpoint"],
  337. []
  338. );
  339. $answer = json_decode($answer, true);
  340. if(
  341. $answer === null ||
  342. $answer["status"] != "success" ||
  343. $answer["data"]["result"] === null
  344. ){
  345. throw new Exception();
  346. }
  347. // parse answer
  348. $out["answer"][] = [
  349. "title" => $answer["data"]["result"]["title"],
  350. "description" => [
  351. [
  352. "type" => "text",
  353. "value" => $this->trimdots($answer["data"]["result"]["description"])
  354. ]
  355. ],
  356. "url" => $answer["data"]["result"]["url"],
  357. "thumb" =>
  358. $answer["data"]["result"]["thumbnail"]["landscape"] == null ?
  359. null :
  360. $this->unshitimage($answer["data"]["result"]["thumbnail"]["landscape"]),
  361. "table" => [],
  362. "sublink" => []
  363. ];
  364. }catch(Exception $error){
  365. // do nothing in case of failure
  366. }
  367. }
  368. // get word correction
  369. if(isset($json["data"]["query"]["queryContext"]["alteredQuery"])){
  370. $out["spelling"] = [
  371. "type" => "including",
  372. "using" => $json["data"]["query"]["queryContext"]["alteredQuery"],
  373. "correction" => $json["data"]["query"]["queryContext"]["alterationOverrideQuery"]
  374. ];
  375. }
  376. // check for next page
  377. if($json["data"]["result"]["lastPage"] === false){
  378. $params["offset"] = $params["offset"] + 10;
  379. $out["npt"] =
  380. $this->backend->store(
  381. json_encode($params),
  382. "web",
  383. $proxy
  384. );
  385. }
  386. // parse results
  387. foreach($json["data"]["result"]["items"]["mainline"] as $item){
  388. switch($item["type"]){ // ignores ads
  389. case "web":
  390. $first_iteration = true;
  391. foreach($item["items"] as $result){
  392. if(isset($result["thumbnailUrl"])){
  393. $thumb = [
  394. "url" => $this->unshitimage($result["thumbnailUrl"]),
  395. "ratio" => "16:9"
  396. ];
  397. }else{
  398. $thumb = [
  399. "url" => null,
  400. "ratio" => null
  401. ];
  402. }
  403. $sublinks = [];
  404. if(isset($result["links"])){
  405. foreach($result["links"] as $link){
  406. $sublinks[] = [
  407. "title" => $this->trimdots($link["title"]),
  408. "date" => null,
  409. "description" => isset($link["desc"]) ? $this->trimdots($link["desc"]) : null,
  410. "url" => $link["url"]
  411. ];
  412. }
  413. }
  414. // detect gibberish results
  415. if(
  416. $first_iteration &&
  417. !isset($result["urlPingSuffix"])
  418. ){
  419. throw new Exception("Qwant returned gibberish results");
  420. }
  421. $out["web"][] = [
  422. "title" => $this->trimdots($result["title"]),
  423. "description" => $this->trimdots($result["desc"]),
  424. "url" => $result["url"],
  425. "date" => null,
  426. "type" => "web",
  427. "thumb" => $thumb,
  428. "sublink" => $sublinks,
  429. "table" => []
  430. ];
  431. $first_iteration = false;
  432. }
  433. break;
  434. case "images":
  435. foreach($item["items"] as $image){
  436. $out["image"][] = [
  437. "title" => $image["title"],
  438. "source" => [
  439. [
  440. "url" => $image["media"],
  441. "width" => (int)$image["width"],
  442. "height" => (int)$image["height"]
  443. ],
  444. [
  445. "url" => $this->unshitimage($image["thumbnail"]),
  446. "width" => $image["thumb_width"],
  447. "height" => $image["thumb_height"]
  448. ]
  449. ],
  450. "url" => $image["url"]
  451. ];
  452. }
  453. break;
  454. case "videos":
  455. foreach($item["items"] as $video){
  456. $out["video"][] = [
  457. "title" => $video["title"],
  458. "description" => null,
  459. "date" => (int)$video["date"],
  460. "duration" => $video["duration"] === null ? null : $video["duration"] / 1000,
  461. "views" => null,
  462. "thumb" =>
  463. $video["thumbnail"] === null ?
  464. [
  465. "url" => null,
  466. "ratio" => null,
  467. ] :
  468. [
  469. "url" => $this->unshitimage($video["thumbnail"]),
  470. "ratio" => "16:9",
  471. ],
  472. "url" => $video["url"]
  473. ];
  474. }
  475. break;
  476. case "related_searches":
  477. foreach($item["items"] as $related){
  478. $out["related"][] = $related["text"];
  479. }
  480. break;
  481. }
  482. }
  483. return $out;
  484. }
  485. public function image($get){
  486. if($get["npt"]){
  487. [$params, $proxy] =
  488. $this->backend->get(
  489. $get["npt"],
  490. "images"
  491. );
  492. $params = json_decode($params, true);
  493. }else{
  494. $search = $get["s"];
  495. if(strlen($search) === 0){
  496. throw new Exception("Search term is empty!");
  497. }
  498. $proxy = $this->backend->get_ip();
  499. $params = [
  500. "t" => "images",
  501. "q" => $search,
  502. "count" => 125,
  503. "locale" => $get["country"],
  504. "offset" => 0, // increment by 125
  505. "device" => "desktop",
  506. "tgp" => 3
  507. ];
  508. if($get["time"] != "any"){
  509. $params["freshness"] = $get["time"];
  510. }
  511. foreach(["size", "color", "imagetype", "license"] as $p){
  512. if($get[$p] != "any"){
  513. $params[$p] = $get[$p];
  514. }
  515. }
  516. switch($get["nsfw"]){
  517. case "yes": $params["safesearch"] = 0; break;
  518. case "maybe": $params["safesearch"] = 1; break;
  519. case "no": $params["safesearch"] = 2; break;
  520. }
  521. }
  522. try{
  523. $json = $this->get(
  524. $proxy,
  525. "https://api.qwant.com/v3/search/images",
  526. $params,
  527. );
  528. }catch(Exception $err){
  529. throw new Exception("Failed to get JSON");
  530. }
  531. /*
  532. $handle = fopen("scraper/yandex.json", "r");
  533. $json = fread($handle, filesize("scraper/yandex.json"));
  534. fclose($handle);*/
  535. $json = json_decode($json, true);
  536. if($json === null){
  537. throw new Exception("Failed to decode JSON");
  538. }
  539. $this->detect_errors($json);
  540. if(isset($json["data"]["result"]["items"]["mainline"])){
  541. throw new Exception("Qwant returned gibberish results");
  542. }
  543. $out = [
  544. "status" => "ok",
  545. "npt" => null,
  546. "image" => []
  547. ];
  548. if($json["data"]["result"]["lastPage"] === false){
  549. $params["offset"] = $params["offset"] + 125;
  550. $out["npt"] = $this->backend->store(
  551. json_encode($params),
  552. "images",
  553. $proxy
  554. );
  555. }
  556. foreach($json["data"]["result"]["items"] as $image){
  557. $out["image"][] = [
  558. "title" => $this->trimdots($image["title"]),
  559. "source" => [
  560. [
  561. "url" => $image["media"],
  562. "width" => $image["width"],
  563. "height" => $image["height"]
  564. ],
  565. [
  566. "url" => $this->unshitimage($image["thumbnail"]),
  567. "width" => $image["thumb_width"],
  568. "height" => $image["thumb_height"]
  569. ]
  570. ],
  571. "url" => $image["url"]
  572. ];
  573. }
  574. return $out;
  575. }
  576. public function video($get){
  577. $search = $get["s"];
  578. if(strlen($search) === 0){
  579. throw new Exception("Search term is empty!");
  580. }
  581. $params = [
  582. "t" => "videos",
  583. "q" => $search,
  584. "count" => 50,
  585. "locale" => $get["country"],
  586. "offset" => 0, // dont implement pagination
  587. "device" => "desktop",
  588. "tgp" => 3
  589. ];
  590. switch($get["nsfw"]){
  591. case "yes": $params["safesearch"] = 0; break;
  592. case "maybe": $params["safesearch"] = 1; break;
  593. case "no": $params["safesearch"] = 2; break;
  594. }
  595. try{
  596. $json =
  597. $this->get(
  598. $this->backend->get_ip(),
  599. "https://api.qwant.com/v3/search/videos",
  600. $params
  601. );
  602. }catch(Exception $error){
  603. throw new Exception("Could not fetch JSON");
  604. }
  605. /*
  606. $handle = fopen("scraper/yandex-video.json", "r");
  607. $json = fread($handle, filesize("scraper/yandex-video.json"));
  608. fclose($handle);
  609. */
  610. $json = json_decode($json, true);
  611. if($json === null){
  612. throw new Exception("Could not parse JSON");
  613. }
  614. $this->detect_errors($json);
  615. if(isset($json["data"]["result"]["items"]["mainline"])){
  616. throw new Exception("Qwant returned gibberish results");
  617. }
  618. $out = [
  619. "status" => "ok",
  620. "npt" => null,
  621. "video" => [],
  622. "author" => [],
  623. "livestream" => [],
  624. "playlist" => [],
  625. "reel" => []
  626. ];
  627. foreach($json["data"]["result"]["items"] as $video){
  628. if(empty($video["thumbnail"])){
  629. $thumb = [
  630. "url" => null,
  631. "ratio" => null
  632. ];
  633. }else{
  634. $thumb = [
  635. "url" => $this->unshitimage($video["thumbnail"]),
  636. "ratio" => "16:9"
  637. ];
  638. }
  639. $duration = (int)$video["duration"];
  640. $out["video"][] = [
  641. "title" => $video["title"],
  642. "description" => $this->limitstrlen($video["desc"]),
  643. "author" => [
  644. "name" => $video["channel"],
  645. "url" => null,
  646. "avatar" => null
  647. ],
  648. "date" => (int)$video["date"],
  649. "duration" => $duration === 0 ? null : $duration,
  650. "views" => null,
  651. "thumb" => $thumb,
  652. "url" => preg_replace("/\?syndication=.+/", "", $video["url"])
  653. ];
  654. }
  655. return $out;
  656. }
  657. public function news($get){
  658. $search = $get["s"];
  659. if(strlen($search) === 0){
  660. throw new Exception("Search term is empty!");
  661. }
  662. $params = [
  663. "t" => "news",
  664. "q" => $search,
  665. "count" => 50,
  666. "locale" => $get["country"],
  667. "offset" => 0, // dont implement pagination
  668. "device" => "desktop",
  669. "tgp" => 3
  670. ];
  671. switch($get["nsfw"]){
  672. case "yes": $params["safesearch"] = 0; break;
  673. case "maybe": $params["safesearch"] = 1; break;
  674. case "no": $params["safesearch"] = 2; break;
  675. }
  676. try{
  677. $json =
  678. $this->get(
  679. $this->backend->get_ip(),
  680. "https://api.qwant.com/v3/search/news",
  681. $params
  682. );
  683. }catch(Exception $error){
  684. throw new Exception("Could not fetch JSON");
  685. }
  686. /*
  687. $handle = fopen("scraper/yandex-video.json", "r");
  688. $json = fread($handle, filesize("scraper/yandex-video.json"));
  689. fclose($handle);
  690. */
  691. $json = json_decode($json, true);
  692. if($json === null){
  693. throw new Exception("Could not parse JSON");
  694. }
  695. $this->detect_errors($json);
  696. if(isset($json["data"]["result"]["items"]["mainline"])){
  697. throw new Exception("Qwant returned gibberish results");
  698. }
  699. $out = [
  700. "status" => "ok",
  701. "npt" => null,
  702. "news" => []
  703. ];
  704. foreach($json["data"]["result"]["items"] as $news){
  705. if(empty($news["media"][0]["pict_big"]["url"])){
  706. $thumb = [
  707. "url" => null,
  708. "ratio" => null
  709. ];
  710. }else{
  711. $thumb = [
  712. "url" => $this->unshitimage($news["media"][0]["pict_big"]["url"]),
  713. "ratio" => "16:9"
  714. ];
  715. }
  716. $out["news"][] = [
  717. "title" => $news["title"],
  718. "author" => $news["press_name"],
  719. "description" => $this->trimdots($news["desc"]),
  720. "date" => (int)$news["date"],
  721. "thumb" => $thumb,
  722. "url" => $news["url"]
  723. ];
  724. }
  725. return $out;
  726. }
  727. private function detect_errors($json){
  728. if(
  729. isset($json["status"]) &&
  730. $json["status"] == "error"
  731. ){
  732. if(isset($json["data"]["error_data"]["captchaUrl"])){
  733. throw new Exception("Qwant returned a captcha");
  734. }elseif(isset($json["data"]["error_data"]["error_code"])){
  735. throw new Exception(
  736. "Qwant returned an API error: " .
  737. $json["data"]["error_data"]["error_code"]
  738. );
  739. }
  740. throw new Exception("Qwant returned an API error");
  741. }
  742. if(
  743. isset($json["url"]) &&
  744. preg_match(
  745. '/captcha/i',
  746. $json["url"]
  747. )
  748. ){
  749. throw new Exception("Qwant returned a captcha redirect");
  750. }
  751. }
  752. private function limitstrlen($text){
  753. return explode("\n", wordwrap($text, 300, "\n"))[0];
  754. }
  755. private function trimdots($text){
  756. return trim($text, ". ");
  757. }
  758. private function unshitimage($url){
  759. // 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
  760. // 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
  761. $image = parse_url($url);
  762. if(
  763. !isset($image["host"]) ||
  764. !isset($image["query"])
  765. ){
  766. // cant do anything
  767. return $url;
  768. }
  769. $id = null;
  770. if(
  771. preg_match(
  772. '/s[0-9]+\.qwant\.com$/',
  773. $image["host"]
  774. )
  775. ){
  776. parse_str($image["query"], $str);
  777. // we're being served a proxy URL
  778. if(isset($str["u"])){
  779. $bing_url = $str["u"];
  780. }else{
  781. // give up
  782. return $url;
  783. }
  784. }
  785. // parse bing URL
  786. $id = null;
  787. $image = parse_url($bing_url);
  788. if(isset($image["query"])){
  789. parse_str($image["query"], $str);
  790. if(isset($str["id"])){
  791. $id = $str["id"];
  792. }
  793. }
  794. if($id === null){
  795. $id = explode("/th/id/", $image["path"], 2);
  796. if(count($id) !== 2){
  797. // malformed
  798. return $url;
  799. }
  800. $id = $id[1];
  801. }
  802. if(is_array($id)){
  803. // fuck off, let proxy.php deal with it
  804. return $url;
  805. }
  806. return "https://" . $image["host"] . "/th?id=" . rawurlencode($id);
  807. }
  808. }