startpage.php 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273
  1. <?php
  2. class startpage{
  3. public function __construct(){
  4. include "lib/backend.php";
  5. $this->backend = new backend("startpage");
  6. include "lib/fuckhtml.php";
  7. $this->fuckhtml = new fuckhtml();
  8. }
  9. public function getfilters($page){
  10. switch($page){
  11. case "web":
  12. return [
  13. "country" => [
  14. "display" => "Country",
  15. "option" => [
  16. "any" => "All Regions",
  17. "es_AR" => "Argentina",
  18. "en_AU" => "Australia",
  19. "de_AT" => "Austria",
  20. "ru_BY" => "Belarus",
  21. "fr_BE" => "Belgium (FR)",
  22. "nl_BE" => "Belgium (NL)",
  23. "bg_BG" => "Bulgaria",
  24. "en_CA" => "Canada (EN)",
  25. "fr_CA" => "Canada (FR)",
  26. "es_CL" => "Chile",
  27. "es_CO" => "Colombia",
  28. "cs_CZ" => "Czech Republic",
  29. "da_DK" => "Denmark",
  30. "ar_EG" => "Egypt",
  31. "et_EE" => "Estonia",
  32. "fi_FI" => "Finland",
  33. "fr_FR" => "France",
  34. "de_DE" => "Germany",
  35. "el_GR" => "Greece",
  36. "hu_HU" => "Hungary",
  37. "hi_IN" => "India (HI)",
  38. "en_IN" => "India (EN)",
  39. "id_ID" => "Indonesia (ID)",
  40. "en_ID" => "Indonesia (EN)",
  41. "en_IE" => "Ireland",
  42. "it_IT" => "Italy",
  43. "ja_JP" => "Japan",
  44. "ko_KR" => "Korea",
  45. "ms_MY" => "Malaysia (MS)",
  46. "en_MY" => "Malaysia (EN)",
  47. "es_MX" => "Mexico",
  48. "nl_NL" => "Netherlands",
  49. "en_NZ" => "New Zealand",
  50. "no_NO" => "Norway",
  51. "es_PE" => "Peru",
  52. "fil_PH" => "Philippines (FIL)",
  53. "en_PH" => "Philippines (EN)",
  54. "pl_PL" => "Poland",
  55. "pt_PT" => "Portugal",
  56. "ro_RO" => "Romania",
  57. "ru_RU" => "Russia",
  58. "ms_SG" => "Singapore (MS)",
  59. "en_SG" => "Singapore (EN)",
  60. "es_ES" => "Spain (ES)",
  61. "ca_ES" => "Spain (CA)",
  62. "sv_SE" => "Sweden",
  63. "de_CH" => "Switzerland (DE)",
  64. "fr_CH" => "Switzerland (FR)",
  65. "it_CH" => "Switzerland (IT)",
  66. "tr_TR" => "Turkey",
  67. "uk_UA" => "Ukraine",
  68. "en_US" => "US (EN)",
  69. "es_US" => "US (ES)",
  70. "es_UY" => "Uruguay",
  71. "es_VE" => "Venezuela",
  72. "vi_VN" => "Vietnam (VI)",
  73. "en_VN" => "Vietnam (EN)",
  74. "en_ZA" => "South Africa"
  75. ]
  76. ],
  77. "nsfw" => [ // qadf
  78. "display" => "NSFW",
  79. "option" => [
  80. "yes" => "Yes", // qadf=none
  81. "no" => "No" // qadf=heavy
  82. ]
  83. ],
  84. "time" => [ // with_date
  85. "display" => "Time posted",
  86. "option" => [
  87. "any" => "Any time",
  88. "d" => "Past 24 hours",
  89. "w" => "Past week",
  90. "m" => "Past month",
  91. "y" => "Past year",
  92. ]
  93. ],
  94. "extendedsearch" => [
  95. // undefined display, so it wont show in frontend
  96. "option" => [
  97. "yes" => "Yes",
  98. "no" => "No"
  99. ]
  100. ]
  101. ];
  102. break;
  103. case "images":
  104. return [
  105. "nsfw" => [ // qadf
  106. "display" => "NSFW",
  107. "option" => [
  108. "yes" => "Yes", // qadf=none
  109. "no" => "No" // qadf=heavy
  110. ]
  111. ],
  112. "size" => [ // flimgsize
  113. "display" => "Size",
  114. "option" => [
  115. "any" => "Any size",
  116. "Small" => "Small",
  117. "Medium" => "Medium",
  118. "Large" => "Large",
  119. "Wallpaper" => "Wallpaper",
  120. // from here, image-size-select, var prefix = isz:lt,islt:
  121. "qsvgs" => "Larger than 400x300",
  122. "vga" => "Larger than 640x480",
  123. "svga" => "Larger than 800x600",
  124. "xga" => "Larger than 1024x768",
  125. "qsvgs" => "Larger than 400x300",
  126. "2mp" => "Larger than 2 MP (1600x1200)",
  127. "4mp" => "Larger than 4 MP (2272x1704)",
  128. "6mp" => "Larger than 6 MP (2816x2112)",
  129. "8mp" => "Larger than 8 MP (3264x2448)",
  130. "10mp" => "Larger than 10 MP (3648x2736)",
  131. "12mp" => "Larger than 12 MP (4096x3072)",
  132. "15mp" => "Larger than 15 MP (4480x3360)",
  133. "20mp" => "Larger than 20 MP (5120x3840)",
  134. "40mp" => "Larger than 40 MP (7216x5412)",
  135. "70mp" => "Larger than 70 MP (9600x7200)"
  136. ]
  137. ],
  138. "color" => [ // flimgcolor
  139. "display" => "Color",
  140. "option" => [
  141. "any" => "Any color",
  142. // from here, var prefix = ic:
  143. "color" => "Color only",
  144. "bnw" => "Black & white", // set to "gray"
  145. // from here, var prefix = ic:specific,isc:
  146. "red" => "Red",
  147. "orange" => "Orange",
  148. "yellow" => "Yellow",
  149. "green" => "Green",
  150. "teal" => "Teal",
  151. "blue" => "Blue",
  152. "purple" => "Purple",
  153. "pink" => "Pink",
  154. "white" => "White",
  155. "gray" => "Gray",
  156. "black" => "Black",
  157. "brown" => "Brown"
  158. ]
  159. ],
  160. "type" => [ // flimgtype
  161. "display" => "Type",
  162. "option" => [
  163. "any" => "Any type",
  164. "AnimatedGif" => "Animated GIF",
  165. "Clipart" => "Clip Art",
  166. "Line" => "Line Drawing",
  167. "Photo" => "Photograph",
  168. "Transparent" => "Transparent Background"
  169. ]
  170. ],
  171. "license" => [ // flimglicense
  172. "display" => "License",
  173. "option" => [
  174. "any" => "Any license",
  175. "p" => "Public domain",
  176. "s" => "Free to share",
  177. "sc" => "Free to share commercially",
  178. "m" => "Free to modify",
  179. "mc" => "Free to modify commercially"
  180. ]
  181. ]
  182. ];
  183. break;
  184. case "videos":
  185. return [
  186. "nsfw" => [ // qadf
  187. "display" => "NSFW",
  188. "option" => [
  189. "yes" => "Yes", // qadf=none
  190. "no" => "No" // qadf=heavy
  191. ]
  192. ],
  193. "sort" => [
  194. "display" => "Sort by",
  195. "option" => [
  196. "relevance" => "Most relevant",
  197. "popular" => "Most popular",
  198. "recent" => "Most recent"
  199. ]
  200. ],
  201. "duration" => [ // with_duration
  202. "display" => "Duration",
  203. "option" => [
  204. "any" => "Any duration",
  205. "short" => "Short",
  206. "medium" => "Medium",
  207. "long" => "Long"
  208. ]
  209. ]
  210. ];
  211. break;
  212. case "news":
  213. return [
  214. "nsfw" => [ // qadf
  215. "display" => "NSFW",
  216. "option" => [
  217. "yes" => "Yes", // qadf=none
  218. "no" => "No" // qadf=heavy
  219. ]
  220. ],
  221. "time" => [ // with_date
  222. "display" => "Time posted",
  223. "option" => [
  224. "any" => "Any time",
  225. "d" => "Past 24 hours",
  226. "w" => "Past week",
  227. "m" => "Past month"
  228. ]
  229. ]
  230. ];
  231. break;
  232. //preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEazerbaijaniN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius; Domain=startpage.com; Expires=Mon, 28 Oct 2024 20:21:58 GMT; Secure; Path=/
  233. //preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius; Domain=startpage.com; Expires=Mon, 28 Oct 2024 20:22:52 GMT; Secure; Path=/
  234. }
  235. }
  236. private function get($proxy, $url, $get = [], $post = false, $is_xhr = false){
  237. $curlproc = curl_init();
  238. if($post === true){
  239. curl_setopt($curlproc, CURLOPT_POST, true);
  240. curl_setopt($curlproc, CURLOPT_POSTFIELDS, $get);
  241. }elseif($get !== []){
  242. $get = http_build_query($get);
  243. $url .= "?" . $get;
  244. }
  245. curl_setopt($curlproc, CURLOPT_URL, $url);
  246. // http2 bypass
  247. curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  248. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  249. if($is_xhr === true){
  250. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  251. ["User-Agent: " . config::USER_AGENT,
  252. "Accept: application/json",
  253. "Accept-Language: en-US,en;q=0.5",
  254. "Accept-Encoding: gzip",
  255. "Referer: https://www.startpage.com/",
  256. "Content-Type: application/json",
  257. "Content-Length: " . strlen($get),
  258. "Origin: https://www.startpage.com/",
  259. "DNT: 1",
  260. "Connection: keep-alive",
  261. "Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius",
  262. "Sec-Fetch-Dest: empty",
  263. "Sec-Fetch-Mode: cors",
  264. "Sec-Fetch-Site: same-origin",
  265. "TE: trailers"]
  266. );
  267. }elseif($post === true){
  268. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  269. ["User-Agent: " . config::USER_AGENT,
  270. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  271. "Accept-Language: en-US,en;q=0.5",
  272. "Accept-Encoding: gzip",
  273. "Referer: https://www.startpage.com/",
  274. "Content-Type: application/x-www-form-urlencoded",
  275. "Content-Length: " . strlen($get),
  276. "DNT: 1",
  277. "Connection: keep-alive",
  278. "Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius",
  279. "Upgrade-Insecure-Requests: 1",
  280. "Sec-Fetch-Dest: document",
  281. "Sec-Fetch-Mode: navigate",
  282. "Sec-Fetch-Site: none",
  283. "Sec-Fetch-User: ?1",
  284. "Priority: u=0, i",
  285. "TE: trailers"]
  286. );
  287. }else{
  288. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  289. ["User-Agent: " . config::USER_AGENT,
  290. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  291. "Accept-Language: en-US,en;q=0.5",
  292. "Accept-Encoding: gzip",
  293. "DNT: 1",
  294. "Connection: keep-alive",
  295. "Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius",
  296. "Sec-Fetch-Dest: document",
  297. "Sec-Fetch-Mode: navigate",
  298. "Sec-Fetch-Site: none",
  299. "Sec-Fetch-User: ?1",
  300. "Priority: u=0, i",
  301. "TE: trailers"]
  302. );
  303. }
  304. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  305. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  306. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  307. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  308. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  309. $this->backend->assign_proxy($curlproc, $proxy);
  310. $data = curl_exec($curlproc);
  311. if(curl_errno($curlproc)){
  312. throw new Exception(curl_error($curlproc));
  313. }
  314. curl_close($curlproc);
  315. return $data;
  316. }
  317. public function web($get){
  318. if($get["npt"]){
  319. [$post, $proxy] = $this->backend->get($get["npt"], "web");
  320. try{
  321. $html = $this->get(
  322. $proxy,
  323. "https://www.startpage.com/sp/search",
  324. $post,
  325. true
  326. );
  327. }catch(Exception $error){
  328. throw new Exception("Failed to fetch search page");
  329. }
  330. $get_instant_answer = false;
  331. }else{
  332. $proxy = $this->backend->get_ip();
  333. $params = [
  334. "query" => $get["s"],
  335. "cat" => "web",
  336. "pl" => "opensearch"
  337. ];
  338. if($get["nsfw"] == "no"){
  339. $params["qadf"] = "heavy";
  340. $get_instant_answer = false;
  341. }else{
  342. $get_instant_answer = true;
  343. }
  344. if($get["country"] !== "any"){
  345. $params["qsr"] = $get["country"];
  346. }
  347. if($get["time"] !== "any"){
  348. $params["with_date"] = $get["time"];
  349. }
  350. try{
  351. $html = $this->get(
  352. $proxy,
  353. "https://www.startpage.com/sp/search",
  354. $params
  355. );
  356. }catch(Exception $error){
  357. throw new Exception("Failed to fetch search page");
  358. }
  359. //$html = file_get_contents("scraper/startpage.html");
  360. }
  361. //echo htmlspecialchars($html);
  362. $this->detect_captcha($html);
  363. if(
  364. preg_match(
  365. '/React\.createElement\(UIStartpage\.AppSerpWeb, ?(.+)\),?$/m',
  366. $html,
  367. $matches
  368. ) === 0
  369. ){
  370. throw new Exception("Failed to grep JSON object");
  371. }
  372. $json = json_decode($matches[1], true);
  373. if($json === null){
  374. throw new Exception("Failed to decode JSON");
  375. }
  376. //print_r($json);
  377. $out = [
  378. "status" => "ok",
  379. "spelling" => [
  380. "type" => "no_correction",
  381. "using" => null,
  382. "correction" => null
  383. ],
  384. "npt" => null,
  385. "answer" => [],
  386. "web" => [],
  387. "image" => [],
  388. "video" => [],
  389. "news" => [],
  390. "related" => []
  391. ];
  392. // get npt
  393. $out["npt"] = $this->parse_npt($json, "web", $proxy);
  394. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  395. if(!isset($category["display_type"])){
  396. continue;
  397. }
  398. switch($category["display_type"]){
  399. case "web-google":
  400. foreach($category["results"] as $result){
  401. $sublinks = [];
  402. foreach($result["siteLinks"] as $sublink){
  403. $sublinks[] = [
  404. "title" => $sublink["title"],
  405. "description" => null,
  406. "url" => $sublink["clickUrl"]
  407. ];
  408. }
  409. $description =
  410. explode(
  411. "...",
  412. $this->titledots(
  413. html_entity_decode(
  414. $this->fuckhtml
  415. ->getTextContent(
  416. $result["description"]
  417. )
  418. )
  419. ),
  420. 2
  421. );
  422. $date = strtotime(trim($description[0]));
  423. if(
  424. $date === false ||
  425. count($description) !== 2 ||
  426. strlen($description[0]) > 14
  427. ){
  428. // no date found
  429. $description =
  430. implode(
  431. " ... ",
  432. $description
  433. );
  434. $date = null;
  435. }else{
  436. // date found
  437. $description = ltrim($description[1]);
  438. }
  439. $out["web"][] = [
  440. "title" =>
  441. $this->titledots(
  442. html_entity_decode(
  443. $this->fuckhtml
  444. ->getTextContent(
  445. $result["title"]
  446. )
  447. )
  448. ),
  449. "description" => $description,
  450. "url" => $result["clickUrl"],
  451. "date" => $date,
  452. "type" => "web",
  453. "thumb" => [
  454. "url" => null,
  455. "ratio" => null
  456. ],
  457. "sublink" => $sublinks,
  458. "table" => []
  459. ];
  460. }
  461. break;
  462. case "images-qi-top":
  463. foreach($category["results"] as $result){
  464. $out["image"][] = [
  465. "title" =>
  466. $this->titledots(
  467. html_entity_decode(
  468. $this->fuckhtml
  469. ->getTextContent(
  470. $result["title"]
  471. )
  472. )
  473. ),
  474. "source" => [
  475. [
  476. "url" => $result["rawImageUrl"],
  477. "width" => (int)$result["width"],
  478. "height" => (int)$result["height"]
  479. ],
  480. [
  481. "url" => $this->unshitimage($result["mdThumbnailUrl"]),
  482. "width" => (int)$result["mdThumbnailWidth"],
  483. "height" => (int)$result["mdThumbnailHeight"]
  484. ]
  485. ],
  486. "url" =>
  487. $result["altClickUrl"]
  488. ];
  489. }
  490. break;
  491. case "spellsuggest-google":
  492. if(isset($category["results"][0]["query"])){
  493. $out["spelling"] =
  494. [
  495. "type" => "including",
  496. "using" => rawurldecode($json["render"]["query"]),
  497. "correction" => html_entity_decode($category["results"][0]["query"])
  498. ];
  499. }
  500. break;
  501. case "dictionary-qi":
  502. foreach($category["results"] as $result){
  503. $answer = [
  504. "title" => $result["word"],
  505. "description" => [],
  506. "url" => null,
  507. "thumb" => null,
  508. "table" => [],
  509. "sublink" => []
  510. ];
  511. foreach($result["lexical_categories"] as $lexic_type => $definitions){
  512. $answer["description"][] = [
  513. "type" => "title",
  514. "value" => $lexic_type
  515. ];
  516. $i = 0;
  517. foreach($definitions as $definition){
  518. $text_definition = trim($definition["definition"]);
  519. $text_example = trim($definition["example"]);
  520. $text_synonyms = implode(", ", $definition["synonyms"]);
  521. if($text_definition != ""){
  522. $i++;
  523. $c = count($answer["description"]) - 1;
  524. if(
  525. $c !== 0 &&
  526. $answer["description"][$c]["type"] == "text"
  527. ){
  528. $answer["description"][$c]["value"] .=
  529. "\n\n" . $i . ". " . $text_definition;
  530. }else{
  531. $answer["description"][] = [
  532. "type" => "text",
  533. "value" => $i . ". " . $text_definition
  534. ];
  535. }
  536. }
  537. if($text_example != ""){
  538. $answer["description"][] = [
  539. "type" => "quote",
  540. "value" => $text_example
  541. ];
  542. }
  543. if($text_synonyms != ""){
  544. $answer["description"][] = [
  545. "type" => "text",
  546. "value" => "Synonyms: " . $text_synonyms
  547. ];
  548. }
  549. }
  550. }
  551. $out["answer"][] = $answer;
  552. }
  553. break;
  554. }
  555. }
  556. return $out;
  557. }
  558. public function image($get){
  559. if($get["npt"]){
  560. [$post, $proxy] = $this->backend->get($get["npt"], "images");
  561. try{
  562. $html = $this->get(
  563. $proxy,
  564. "https://www.startpage.com/sp/search",
  565. $post,
  566. true
  567. );
  568. }catch(Exception $error){
  569. throw new Exception("Failed to fetch search page");
  570. }
  571. }else{
  572. $search = $get["s"];
  573. if(strlen($search) === 0){
  574. throw new Exception("Search term is empty!");
  575. }
  576. try{
  577. $proxy = $this->backend->get_ip();
  578. $params = [
  579. "query" => $get["s"],
  580. "cat" => "images",
  581. "pl" => "opensearch"
  582. ];
  583. if($get["nsfw"] == "no"){
  584. $params["qadf"] = "heavy";
  585. }
  586. if($get["size"] != "any"){
  587. if(
  588. $get["size"] == "Small" ||
  589. $get["size"] == "Medium" ||
  590. $get["size"] == "Large" ||
  591. $get["size"] == "Wallpaper"
  592. ){
  593. $params["flimgsize"] = $get["size"];
  594. }else{
  595. $params["image-size-select"] = "isz:lt,islt:" . $get["size"];
  596. }
  597. }
  598. if($get["color"] != "any"){
  599. if($get["color"] == "color"){
  600. $params["flimgcolor"] = "ic:color";
  601. }elseif($get["color"] == "bnw"){
  602. $params["flimgcolor"] = "ic:gray";
  603. }else{
  604. $params["flimgcolor"] = "ic:specific,isc:" . $get["color"];
  605. }
  606. }
  607. if($get["type"] != "any"){
  608. $params["flimgtype"] = $get["type"];
  609. }
  610. if($get["license"] != "any"){
  611. $params["flimglicense"] = $get["license"];
  612. }
  613. try{
  614. $html = $this->get(
  615. $proxy,
  616. "https://www.startpage.com/sp/search",
  617. $params
  618. );
  619. }catch(Exception $error){
  620. throw new Exception("Failed to fetch search page");
  621. }
  622. //$html = file_get_contents("scraper/startpage.html");
  623. }catch(Exception $error){
  624. throw new Exception("Failed to fetch search page");
  625. }
  626. }
  627. $this->detect_captcha($html);
  628. $out = [
  629. "status" => "ok",
  630. "npt" => null,
  631. "image" => []
  632. ];
  633. if(
  634. preg_match(
  635. '/React\.createElement\(UIStartpage\.AppSerpImages, ?(.+)\),?$/m',
  636. $html,
  637. $matches
  638. ) === 0
  639. ){
  640. throw new Exception("Failed to grep JSON object");
  641. }
  642. $json = json_decode($matches[1], true);
  643. if($json === null){
  644. throw new Exception("Failed to decode JSON object");
  645. }
  646. // get npt
  647. $out["npt"] = $this->parse_npt($json, "images", $proxy);
  648. // get images
  649. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  650. if($category["display_type"] != "images-bing"){
  651. // ignore ads and !! suggestions !! @todo
  652. continue;
  653. }
  654. foreach($category["results"] as $image){
  655. $out["image"][] = [
  656. "title" => $this->titledots($image["title"]),
  657. "source" => [
  658. [
  659. "url" => $this->unshitimage($image["clickUrl"]),
  660. "width" => (int)$image["width"],
  661. "height" => (int)$image["height"]
  662. ],
  663. [
  664. "url" => $this->unshitimage($image["thumbnailUrl"]),
  665. "width" => (int)$image["thumbnailWidth"],
  666. "height" => (int)$image["thumbnailHeight"]
  667. ]
  668. ],
  669. "url" => $image["altClickUrl"]
  670. ];
  671. }
  672. }
  673. return $out;
  674. }
  675. public function video($get){
  676. if($get["npt"]){
  677. [$post, $proxy] = $this->backend->get($get["npt"], "videos");
  678. try{
  679. $html = $this->get(
  680. $proxy,
  681. "https://www.startpage.com/sp/search",
  682. $post,
  683. true
  684. );
  685. }catch(Exception $error){
  686. throw new Exception("Failed to fetch search page");
  687. }
  688. }else{
  689. $search = $get["s"];
  690. if(strlen($search) === 0){
  691. throw new Exception("Search term is empty!");
  692. }
  693. try{
  694. $proxy = $this->backend->get_ip();
  695. $params = [
  696. "query" => $get["s"],
  697. "cat" => "video",
  698. "pl" => "opensearch"
  699. ];
  700. if($get["nsfw"] == "no"){
  701. $params["qadf"] = "heavy";
  702. }
  703. if($get["sort"] != "relevance"){
  704. $params["sort_by"] = $get["sort"];
  705. }
  706. if($get["duration"] != "any"){
  707. $params["with_duration"] = $get["duration"];
  708. }
  709. try{
  710. $html = $this->get(
  711. $proxy,
  712. "https://www.startpage.com/sp/search",
  713. $params
  714. );
  715. }catch(Exception $error){
  716. throw new Exception("Failed to fetch search page");
  717. }
  718. //$html = file_get_contents("scraper/startpage.html");
  719. }catch(Exception $error){
  720. throw new Exception("Failed to fetch search page");
  721. }
  722. }
  723. $this->detect_captcha($html);
  724. if(
  725. preg_match(
  726. '/React\.createElement\(UIStartpage\.AppSerpVideos, ?(.+)\),?$/m',
  727. $html,
  728. $matches
  729. ) === 0
  730. ){
  731. throw new Exception("Failed to get JSON object");
  732. }
  733. $json = json_decode($matches[1], true);
  734. if($json === null){
  735. throw new Exception("Failed to decode JSON object");
  736. }
  737. $out = [
  738. "status" => "ok",
  739. "npt" => null,
  740. "video" => [],
  741. "author" => [],
  742. "livestream" => [],
  743. "playlist" => [],
  744. "reel" => []
  745. ];
  746. // get npt
  747. $out["npt"] = $this->parse_npt($json, "video", $proxy);
  748. // get results
  749. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  750. if(
  751. preg_match(
  752. '/^video-/i',
  753. $category["display_type"]
  754. )
  755. ){
  756. foreach($category["results"] as $video){
  757. if(
  758. isset($video["thumbnailUrl"]) &&
  759. $video["thumbnailUrl"] !== null
  760. ){
  761. $thumb = [
  762. "ratio" => "16:9",
  763. "url" => $this->unshitimage($video["thumbnailUrl"])
  764. ];
  765. }else{
  766. $thumb = [
  767. "ratio" => null,
  768. "url" => null
  769. ];
  770. }
  771. $out["video"][] = [
  772. "title" => str_replace(["", ""], "", $video["title"]),
  773. "description" => $this->limitstrlen($video["description"]),
  774. "author" => [
  775. "name" => $video["channelTitle"],
  776. "url" => null,
  777. "avatar" => null
  778. ],
  779. "date" => strtotime($video["publishDate"]),
  780. "duration" => $this->hms2int($category["display_type"] == "video-youtube" ? $video["duration"] : $video["duration"] / 1000),
  781. "views" => (int)$video["viewCount"],
  782. "thumb" => $thumb,
  783. "url" => $video["clickUrl"]
  784. ];
  785. }
  786. }
  787. }
  788. return $out;
  789. }
  790. public function news($get){
  791. if($get["npt"]){
  792. [$post, $proxy] = $this->backend->get($get["npt"], "news");
  793. try{
  794. $html = $this->get(
  795. $proxy,
  796. "https://www.startpage.com/sp/search",
  797. $post,
  798. true
  799. );
  800. }catch(Exception $error){
  801. throw new Exception("Failed to fetch search page");
  802. }
  803. }else{
  804. $search = $get["s"];
  805. if(strlen($search) === 0){
  806. throw new Exception("Search term is empty!");
  807. }
  808. try{
  809. $proxy = $this->backend->get_ip();
  810. $params = [
  811. "query" => $get["s"],
  812. "cat" => "news",
  813. "pl" => "opensearch"
  814. ];
  815. if($get["nsfw"] == "no"){
  816. $params["qadf"] = "heavy";
  817. }
  818. if($get["time"] != "any"){
  819. $params["with_date"] = $get["time"];
  820. }
  821. try{
  822. $html = $this->get(
  823. $proxy,
  824. "https://www.startpage.com/sp/search",
  825. $params
  826. );
  827. }catch(Exception $error){
  828. throw new Exception("Failed to fetch search page");
  829. }
  830. //$html = file_get_contents("scraper/startpage.html");
  831. }catch(Exception $error){
  832. throw new Exception("Failed to fetch search page");
  833. }
  834. }
  835. $this->detect_captcha($html);
  836. if(
  837. preg_match(
  838. '/React\.createElement\(UIStartpage\.AppSerpNews, ?(.+)\),?$/m',
  839. $html,
  840. $matches
  841. ) === 0
  842. ){
  843. throw new Exception("Failed to get JSON object");
  844. }
  845. $json = json_decode($matches[1], true);
  846. if($json === null){
  847. throw new Exception("Failed to decode JSON object");
  848. }
  849. $out = [
  850. "status" => "ok",
  851. "npt" => null,
  852. "news" => []
  853. ];
  854. // get npt
  855. $out["npt"] = $this->parse_npt($json, "news", $proxy);
  856. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  857. if($category["display_type"] != "news-bing"){
  858. // unsupported category
  859. continue;
  860. }
  861. foreach($category["results"] as $news){
  862. if(
  863. isset($news["thumbnailUrl"]) &&
  864. $news["thumbnailUrl"] !== null
  865. ){
  866. $thumb = [
  867. "ratio" => "16:9",
  868. "url" => $this->unshitimage($news["thumbnailUrl"])
  869. ];
  870. }else{
  871. $thumb = [
  872. "ratio" => null,
  873. "url" => null
  874. ];
  875. }
  876. $out["news"][] = [
  877. "title" => $this->titledots($this->remove_penguins($news["title"])),
  878. "author" => $news["source"],
  879. "description" => $this->titledots($this->remove_penguins($news["description"])),
  880. "date" => (int)substr((string)$news["date"], 0, -3),
  881. "thumb" => $thumb,
  882. "url" => $news["clickUrl"]
  883. ];
  884. }
  885. }
  886. return $out;
  887. }
  888. private function parse_npt($json, $pagetype, $proxy){
  889. foreach($json["render"]["presenter"]["pagination"]["pages"] as $page){
  890. if($page["name"] == "Next"){
  891. return
  892. $this->backend->store(
  893. http_build_query(
  894. [
  895. "lui" => "english",
  896. "language" => "english",
  897. "sc" => $json["render"]["search_sc"],
  898. "t" => "device",
  899. "cat" => $pagetype,
  900. "segment" => "organic",
  901. "abd" => "0",
  902. "abe" => "0",
  903. "query" => rawurldecode($json["render"]["query"]),
  904. "page" => $page["number"],
  905. "qsr" => "all",
  906. "qadf" => "none" // @ todo fix (??)
  907. ]
  908. ),
  909. $pagetype,
  910. $proxy
  911. );
  912. break;
  913. }
  914. }
  915. return null;
  916. }
  917. private function unshitimage($url){
  918. $query = parse_url($url, PHP_URL_QUERY);
  919. parse_str($query, $query);
  920. if(isset($query["piurl"])){
  921. if(strpos($query["piurl"], "gstatic.com/")){
  922. return
  923. explode(
  924. "&",
  925. $query["piurl"],
  926. 2
  927. )[0];
  928. }
  929. if(
  930. strpos($query["piurl"], "bing.net/") ||
  931. strpos($query["piurl"], "bing.com/")
  932. ){
  933. return
  934. explode(
  935. "&",
  936. $query["piurl"],
  937. 2
  938. )[0];
  939. }
  940. return $query["piurl"];
  941. }
  942. return $url;
  943. }
  944. private function limitstrlen($text){
  945. return
  946. explode(
  947. "\n",
  948. wordwrap(
  949. str_replace(
  950. ["\n\r", "\r\n", "\n", "\r"],
  951. " ",
  952. $text
  953. ),
  954. 300,
  955. "\n"
  956. ),
  957. 2
  958. )[0];
  959. }
  960. private function titledots($title){
  961. return trim($title, " .\t\n\r\0\x0B…");
  962. }
  963. private function hms2int($time){
  964. $parts = explode(":", $time, 3);
  965. $time = 0;
  966. if(count($parts) === 3){
  967. // hours
  968. $time = $time + ((int)$parts[0] * 3600);
  969. array_shift($parts);
  970. }
  971. if(count($parts) === 2){
  972. // minutes
  973. $time = $time + ((int)$parts[0] * 60);
  974. array_shift($parts);
  975. }
  976. // seconds
  977. $time = $time + (int)$parts[0];
  978. return $time;
  979. }
  980. private function remove_penguins($text){
  981. return str_replace(
  982. ["", ""],
  983. "",
  984. $text
  985. );
  986. }
  987. private function detect_captcha($html){
  988. $this->fuckhtml->load($html);
  989. $title =
  990. $this->fuckhtml
  991. ->getElementsByTagName(
  992. "title"
  993. );
  994. if(
  995. count($title) !== 0 &&
  996. $title[0]["innerHTML"] == "Redirecting..."
  997. ){
  998. // check if it's a captcha
  999. $as =
  1000. $this->fuckhtml
  1001. ->getElementsByTagName(
  1002. "a"
  1003. );
  1004. foreach($as as $a){
  1005. if(
  1006. strpos(
  1007. $this->fuckhtml
  1008. ->getTextContent(
  1009. $a["innerHTML"]
  1010. ),
  1011. "https://www.startpage.com/sp/captcha"
  1012. ) !== false
  1013. ){
  1014. throw new Exception("Startpage returned a captcha");
  1015. }
  1016. }
  1017. throw new Exception("Startpage redirected the scraper to an unhandled page");
  1018. }
  1019. }
  1020. }