startpage.php 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271
  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. $this->detect_captcha($html);
  362. if(
  363. preg_match(
  364. '/React\.createElement\(UIStartpage\.AppSerpWeb, ?(.+)\),?$/m',
  365. $html,
  366. $matches
  367. ) === 0
  368. ){
  369. throw new Exception("Failed to grep JSON object");
  370. }
  371. $json = json_decode($matches[1], true);
  372. if($json === null){
  373. throw new Exception("Failed to decode JSON");
  374. }
  375. //print_r($json);
  376. $out = [
  377. "status" => "ok",
  378. "spelling" => [
  379. "type" => "no_correction",
  380. "using" => null,
  381. "correction" => null
  382. ],
  383. "npt" => null,
  384. "answer" => [],
  385. "web" => [],
  386. "image" => [],
  387. "video" => [],
  388. "news" => [],
  389. "related" => []
  390. ];
  391. // get npt
  392. $out["npt"] = $this->parse_npt($json, "web", $proxy);
  393. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  394. if(!isset($category["display_type"])){
  395. continue;
  396. }
  397. switch($category["display_type"]){
  398. case "web-google":
  399. foreach($category["results"] as $result){
  400. $sublinks = [];
  401. foreach($result["siteLinks"] as $sublink){
  402. $sublinks[] = [
  403. "title" => $sublink["title"],
  404. "description" => null,
  405. "url" => $sublink["clickUrl"]
  406. ];
  407. }
  408. $description =
  409. explode(
  410. "...",
  411. $this->titledots(
  412. html_entity_decode(
  413. $this->fuckhtml
  414. ->getTextContent(
  415. $result["description"]
  416. )
  417. )
  418. ),
  419. 2
  420. );
  421. $date = strtotime(trim($description[0]));
  422. if(
  423. $date === false ||
  424. count($description) !== 2 ||
  425. strlen($description[0]) > 14
  426. ){
  427. // no date found
  428. $description =
  429. implode(
  430. " ... ",
  431. $description
  432. );
  433. $date = null;
  434. }else{
  435. // date found
  436. $description = ltrim($description[1]);
  437. }
  438. $out["web"][] = [
  439. "title" =>
  440. $this->titledots(
  441. html_entity_decode(
  442. $this->fuckhtml
  443. ->getTextContent(
  444. $result["title"]
  445. )
  446. )
  447. ),
  448. "description" => $description,
  449. "url" => $result["clickUrl"],
  450. "date" => $date,
  451. "type" => "web",
  452. "thumb" => [
  453. "url" => null,
  454. "ratio" => null
  455. ],
  456. "sublink" => $sublinks,
  457. "table" => []
  458. ];
  459. }
  460. break;
  461. case "images-qi-top":
  462. foreach($category["results"] as $result){
  463. $out["image"][] = [
  464. "title" =>
  465. $this->titledots(
  466. html_entity_decode(
  467. $this->fuckhtml
  468. ->getTextContent(
  469. $result["title"]
  470. )
  471. )
  472. ),
  473. "source" => [
  474. [
  475. "url" => $result["rawImageUrl"],
  476. "width" => (int)$result["width"],
  477. "height" => (int)$result["height"]
  478. ],
  479. [
  480. "url" => $this->unshitimage($result["mdThumbnailUrl"]),
  481. "width" => (int)$result["mdThumbnailWidth"],
  482. "height" => (int)$result["mdThumbnailHeight"]
  483. ]
  484. ],
  485. "url" =>
  486. $result["altClickUrl"]
  487. ];
  488. }
  489. break;
  490. case "spellsuggest-google":
  491. if(isset($category["results"][0]["query"])){
  492. $out["spelling"] =
  493. [
  494. "type" => "including",
  495. "using" => rawurldecode($json["render"]["query"]),
  496. "correction" => html_entity_decode($category["results"][0]["query"])
  497. ];
  498. }
  499. break;
  500. case "dictionary-qi":
  501. foreach($category["results"] as $result){
  502. $answer = [
  503. "title" => $result["word"],
  504. "description" => [],
  505. "url" => null,
  506. "thumb" => null,
  507. "table" => [],
  508. "sublink" => []
  509. ];
  510. foreach($result["lexical_categories"] as $lexic_type => $definitions){
  511. $answer["description"][] = [
  512. "type" => "title",
  513. "value" => $lexic_type
  514. ];
  515. $i = 0;
  516. foreach($definitions as $definition){
  517. $text_definition = trim($definition["definition"]);
  518. $text_example = trim($definition["example"]);
  519. $text_synonyms = implode(", ", $definition["synonyms"]);
  520. if($text_definition != ""){
  521. $i++;
  522. $c = count($answer["description"]) - 1;
  523. if(
  524. $c !== 0 &&
  525. $answer["description"][$c]["type"] == "text"
  526. ){
  527. $answer["description"][$c]["value"] .=
  528. "\n\n" . $i . ". " . $text_definition;
  529. }else{
  530. $answer["description"][] = [
  531. "type" => "text",
  532. "value" => $i . ". " . $text_definition
  533. ];
  534. }
  535. }
  536. if($text_example != ""){
  537. $answer["description"][] = [
  538. "type" => "quote",
  539. "value" => $text_example
  540. ];
  541. }
  542. if($text_synonyms != ""){
  543. $answer["description"][] = [
  544. "type" => "text",
  545. "value" => "Synonyms: " . $text_synonyms
  546. ];
  547. }
  548. }
  549. }
  550. $out["answer"][] = $answer;
  551. }
  552. break;
  553. }
  554. }
  555. return $out;
  556. }
  557. public function image($get){
  558. if($get["npt"]){
  559. [$post, $proxy] = $this->backend->get($get["npt"], "images");
  560. try{
  561. $html = $this->get(
  562. $proxy,
  563. "https://www.startpage.com/sp/search",
  564. $post,
  565. true
  566. );
  567. }catch(Exception $error){
  568. throw new Exception("Failed to fetch search page");
  569. }
  570. }else{
  571. $search = $get["s"];
  572. if(strlen($search) === 0){
  573. throw new Exception("Search term is empty!");
  574. }
  575. try{
  576. $proxy = $this->backend->get_ip();
  577. $params = [
  578. "query" => $get["s"],
  579. "cat" => "images",
  580. "pl" => "opensearch"
  581. ];
  582. if($get["nsfw"] == "no"){
  583. $params["qadf"] = "heavy";
  584. }
  585. if($get["size"] != "any"){
  586. if(
  587. $get["size"] == "Small" ||
  588. $get["size"] == "Medium" ||
  589. $get["size"] == "Large" ||
  590. $get["size"] == "Wallpaper"
  591. ){
  592. $params["flimgsize"] = $get["size"];
  593. }else{
  594. $params["image-size-select"] = "isz:lt,islt:" . $get["size"];
  595. }
  596. }
  597. if($get["color"] != "any"){
  598. if($get["color"] == "color"){
  599. $params["flimgcolor"] = "ic:color";
  600. }elseif($get["color"] == "bnw"){
  601. $params["flimgcolor"] = "ic:gray";
  602. }else{
  603. $params["flimgcolor"] = "ic:specific,isc:" . $get["color"];
  604. }
  605. }
  606. if($get["type"] != "any"){
  607. $params["flimgtype"] = $get["type"];
  608. }
  609. if($get["license"] != "any"){
  610. $params["flimglicense"] = $get["license"];
  611. }
  612. try{
  613. $html = $this->get(
  614. $proxy,
  615. "https://www.startpage.com/sp/search",
  616. $params
  617. );
  618. }catch(Exception $error){
  619. throw new Exception("Failed to fetch search page");
  620. }
  621. //$html = file_get_contents("scraper/startpage.html");
  622. }catch(Exception $error){
  623. throw new Exception("Failed to fetch search page");
  624. }
  625. }
  626. $this->detect_captcha($html);
  627. $out = [
  628. "status" => "ok",
  629. "npt" => null,
  630. "image" => []
  631. ];
  632. if(
  633. preg_match(
  634. '/React\.createElement\(UIStartpage\.AppSerpImages, ?(.+)\),?$/m',
  635. $html,
  636. $matches
  637. ) === 0
  638. ){
  639. throw new Exception("Failed to grep JSON object");
  640. }
  641. $json = json_decode($matches[1], true);
  642. if($json === null){
  643. throw new Exception("Failed to decode JSON object");
  644. }
  645. // get npt
  646. $out["npt"] = $this->parse_npt($json, "images", $proxy);
  647. // get images
  648. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  649. if($category["display_type"] != "images-bing"){
  650. // ignore ads and !! suggestions !! @todo
  651. continue;
  652. }
  653. foreach($category["results"] as $image){
  654. $out["image"][] = [
  655. "title" => $this->titledots($image["title"]),
  656. "source" => [
  657. [
  658. "url" => $this->unshitimage($image["clickUrl"]),
  659. "width" => (int)$image["width"],
  660. "height" => (int)$image["height"]
  661. ],
  662. [
  663. "url" => $this->unshitimage($image["thumbnailUrl"]),
  664. "width" => (int)$image["thumbnailWidth"],
  665. "height" => (int)$image["thumbnailHeight"]
  666. ]
  667. ],
  668. "url" => $image["altClickUrl"]
  669. ];
  670. }
  671. }
  672. return $out;
  673. }
  674. public function video($get){
  675. if($get["npt"]){
  676. [$post, $proxy] = $this->backend->get($get["npt"], "videos");
  677. try{
  678. $html = $this->get(
  679. $proxy,
  680. "https://www.startpage.com/sp/search",
  681. $post,
  682. true
  683. );
  684. }catch(Exception $error){
  685. throw new Exception("Failed to fetch search page");
  686. }
  687. }else{
  688. $search = $get["s"];
  689. if(strlen($search) === 0){
  690. throw new Exception("Search term is empty!");
  691. }
  692. try{
  693. $proxy = $this->backend->get_ip();
  694. $params = [
  695. "query" => $get["s"],
  696. "cat" => "video",
  697. "pl" => "opensearch"
  698. ];
  699. if($get["nsfw"] == "no"){
  700. $params["qadf"] = "heavy";
  701. }
  702. if($get["sort"] != "relevance"){
  703. $params["sort_by"] = $get["sort"];
  704. }
  705. if($get["duration"] != "any"){
  706. $params["with_duration"] = $get["duration"];
  707. }
  708. try{
  709. $html = $this->get(
  710. $proxy,
  711. "https://www.startpage.com/sp/search",
  712. $params
  713. );
  714. }catch(Exception $error){
  715. throw new Exception("Failed to fetch search page");
  716. }
  717. //$html = file_get_contents("scraper/startpage.html");
  718. }catch(Exception $error){
  719. throw new Exception("Failed to fetch search page");
  720. }
  721. }
  722. $this->detect_captcha($html);
  723. if(
  724. preg_match(
  725. '/React\.createElement\(UIStartpage\.AppSerpVideos, ?(.+)\),?$/m',
  726. $html,
  727. $matches
  728. ) === 0
  729. ){
  730. throw new Exception("Failed to get JSON object");
  731. }
  732. $json = json_decode($matches[1], true);
  733. if($json === null){
  734. throw new Exception("Failed to decode JSON object");
  735. }
  736. $out = [
  737. "status" => "ok",
  738. "npt" => null,
  739. "video" => [],
  740. "author" => [],
  741. "livestream" => [],
  742. "playlist" => [],
  743. "reel" => []
  744. ];
  745. // get npt
  746. $out["npt"] = $this->parse_npt($json, "video", $proxy);
  747. // get results
  748. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  749. if(
  750. preg_match(
  751. '/^video-/i',
  752. $category["display_type"]
  753. )
  754. ){
  755. foreach($category["results"] as $video){
  756. if(
  757. isset($video["thumbnailUrl"]) &&
  758. $video["thumbnailUrl"] !== null
  759. ){
  760. $thumb = [
  761. "ratio" => "16:9",
  762. "url" => $this->unshitimage($video["thumbnailUrl"])
  763. ];
  764. }else{
  765. $thumb = [
  766. "ratio" => null,
  767. "url" => null
  768. ];
  769. }
  770. $out["video"][] = [
  771. "title" => str_replace(["", ""], "", $video["title"]),
  772. "description" => $this->limitstrlen($video["description"]),
  773. "author" => [
  774. "name" => $video["channelTitle"],
  775. "url" => null,
  776. "avatar" => null
  777. ],
  778. "date" => strtotime($video["publishDate"]),
  779. "duration" => $this->hms2int($category["display_type"] == "video-youtube" ? $video["duration"] : $video["duration"] / 1000),
  780. "views" => (int)$video["viewCount"],
  781. "thumb" => $thumb,
  782. "url" => $video["clickUrl"]
  783. ];
  784. }
  785. }
  786. }
  787. return $out;
  788. }
  789. public function news($get){
  790. if($get["npt"]){
  791. [$post, $proxy] = $this->backend->get($get["npt"], "news");
  792. try{
  793. $html = $this->get(
  794. $proxy,
  795. "https://www.startpage.com/sp/search",
  796. $post,
  797. true
  798. );
  799. }catch(Exception $error){
  800. throw new Exception("Failed to fetch search page");
  801. }
  802. }else{
  803. $search = $get["s"];
  804. if(strlen($search) === 0){
  805. throw new Exception("Search term is empty!");
  806. }
  807. try{
  808. $proxy = $this->backend->get_ip();
  809. $params = [
  810. "query" => $get["s"],
  811. "cat" => "news",
  812. "pl" => "opensearch"
  813. ];
  814. if($get["nsfw"] == "no"){
  815. $params["qadf"] = "heavy";
  816. }
  817. if($get["time"] != "any"){
  818. $params["with_date"] = $get["time"];
  819. }
  820. try{
  821. $html = $this->get(
  822. $proxy,
  823. "https://www.startpage.com/sp/search",
  824. $params
  825. );
  826. }catch(Exception $error){
  827. throw new Exception("Failed to fetch search page");
  828. }
  829. //$html = file_get_contents("scraper/startpage.html");
  830. }catch(Exception $error){
  831. throw new Exception("Failed to fetch search page");
  832. }
  833. }
  834. $this->detect_captcha($html);
  835. if(
  836. preg_match(
  837. '/React\.createElement\(UIStartpage\.AppSerpNews, ?(.+)\),?$/m',
  838. $html,
  839. $matches
  840. ) === 0
  841. ){
  842. throw new Exception("Failed to get JSON object");
  843. }
  844. $json = json_decode($matches[1], true);
  845. if($json === null){
  846. throw new Exception("Failed to decode JSON object");
  847. }
  848. $out = [
  849. "status" => "ok",
  850. "npt" => null,
  851. "news" => []
  852. ];
  853. // get npt
  854. $out["npt"] = $this->parse_npt($json, "news", $proxy);
  855. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  856. if($category["display_type"] != "news-bing"){
  857. // unsupported category
  858. continue;
  859. }
  860. foreach($category["results"] as $news){
  861. if(
  862. isset($news["thumbnailUrl"]) &&
  863. $news["thumbnailUrl"] !== null
  864. ){
  865. $thumb = [
  866. "ratio" => "16:9",
  867. "url" => $this->unshitimage($news["thumbnailUrl"])
  868. ];
  869. }else{
  870. $thumb = [
  871. "ratio" => null,
  872. "url" => null
  873. ];
  874. }
  875. $out["news"][] = [
  876. "title" => $this->titledots($this->remove_penguins($news["title"])),
  877. "author" => $news["source"],
  878. "description" => $this->titledots($this->remove_penguins($news["description"])),
  879. "date" => (int)substr((string)$news["date"], 0, -3),
  880. "thumb" => $thumb,
  881. "url" => $news["clickUrl"]
  882. ];
  883. }
  884. }
  885. return $out;
  886. }
  887. private function parse_npt($json, $pagetype, $proxy){
  888. foreach($json["render"]["presenter"]["pagination"]["pages"] as $page){
  889. if($page["name"] == "Next"){
  890. return
  891. $this->backend->store(
  892. http_build_query(
  893. [
  894. "lui" => "english",
  895. "language" => "english",
  896. "sc" => $json["render"]["search_sc"],
  897. "t" => "device",
  898. "cat" => $pagetype,
  899. "segment" => "organic",
  900. "abd" => "0",
  901. "abe" => "0",
  902. "query" => $json["render"]["query"],
  903. "page" => $page["number"],
  904. "qsr" => "all",
  905. "qadf" => "none" // @ todo fix (??)
  906. ]
  907. ),
  908. $pagetype,
  909. $proxy
  910. );
  911. break;
  912. }
  913. }
  914. return null;
  915. }
  916. private function unshitimage($url){
  917. $query = parse_url($url, PHP_URL_QUERY);
  918. parse_str($query, $query);
  919. if(isset($query["piurl"])){
  920. if(strpos($query["piurl"], "gstatic.com/")){
  921. return
  922. explode(
  923. "&",
  924. $query["piurl"],
  925. 2
  926. )[0];
  927. }
  928. if(
  929. strpos($query["piurl"], "bing.net/") ||
  930. strpos($query["piurl"], "bing.com/")
  931. ){
  932. return
  933. explode(
  934. "&",
  935. $query["piurl"],
  936. 2
  937. )[0];
  938. }
  939. return $query["piurl"];
  940. }
  941. return $url;
  942. }
  943. private function limitstrlen($text){
  944. return
  945. explode(
  946. "\n",
  947. wordwrap(
  948. str_replace(
  949. ["\n\r", "\r\n", "\n", "\r"],
  950. " ",
  951. $text
  952. ),
  953. 300,
  954. "\n"
  955. ),
  956. 2
  957. )[0];
  958. }
  959. private function titledots($title){
  960. return trim($title, " .\t\n\r\0\x0B…");
  961. }
  962. private function hms2int($time){
  963. $parts = explode(":", $time, 3);
  964. $time = 0;
  965. if(count($parts) === 3){
  966. // hours
  967. $time = $time + ((int)$parts[0] * 3600);
  968. array_shift($parts);
  969. }
  970. if(count($parts) === 2){
  971. // minutes
  972. $time = $time + ((int)$parts[0] * 60);
  973. array_shift($parts);
  974. }
  975. // seconds
  976. $time = $time + (int)$parts[0];
  977. return $time;
  978. }
  979. private function remove_penguins($text){
  980. return str_replace(
  981. ["", ""],
  982. "",
  983. $text
  984. );
  985. }
  986. private function detect_captcha($html){
  987. $this->fuckhtml->load($html);
  988. $title =
  989. $this->fuckhtml
  990. ->getElementsByTagName(
  991. "title"
  992. );
  993. if(
  994. count($title) !== 0 &&
  995. $title[0]["innerHTML"] == "Redirecting..."
  996. ){
  997. // check if it's a captcha
  998. $as =
  999. $this->fuckhtml
  1000. ->getElementsByTagName(
  1001. "a"
  1002. );
  1003. foreach($as as $a){
  1004. if(
  1005. strpos(
  1006. $this->fuckhtml
  1007. ->getTextContent(
  1008. $a["innerHTML"]
  1009. ),
  1010. "https://www.startpage.com/sp/captcha"
  1011. ) !== false
  1012. ){
  1013. throw new Exception("Startpage returned a captcha");
  1014. }
  1015. }
  1016. throw new Exception("Startpage redirected the scraper to an unhandled page");
  1017. }
  1018. }
  1019. }