qwant.php 21 KB

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