startpage.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  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 fetched",
  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. }
  104. }
  105. private function get($proxy, $url, $get = [], $post = false, $is_xhr = false){
  106. $curlproc = curl_init();
  107. if($post === true){
  108. curl_setopt($curlproc, CURLOPT_POST, true);
  109. curl_setopt($curlproc, CURLOPT_POSTFIELDS, $get);
  110. }elseif($get !== []){
  111. $get = http_build_query($get);
  112. $url .= "?" . $get;
  113. }
  114. curl_setopt($curlproc, CURLOPT_URL, $url);
  115. // http2 bypass
  116. curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  117. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  118. if($is_xhr === true){
  119. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  120. ["User-Agent: " . config::USER_AGENT,
  121. "Accept: application/json",
  122. "Accept-Language: en-US,en;q=0.5",
  123. "Accept-Encoding: gzip",
  124. "Referer: https://www.startpage.com/",
  125. "Content-Type: application/json",
  126. "Content-Length: " . strlen($get),
  127. "Origin: https://www.startpage.com/",
  128. "DNT: 1",
  129. "Connection: keep-alive",
  130. "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",
  131. "Sec-Fetch-Dest: empty",
  132. "Sec-Fetch-Mode: cors",
  133. "Sec-Fetch-Site: same-origin",
  134. "TE: trailers"]
  135. );
  136. }elseif($post === true){
  137. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  138. ["User-Agent: " . config::USER_AGENT,
  139. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  140. "Accept-Language: en-US,en;q=0.5",
  141. "Accept-Encoding: gzip",
  142. "Referer: https://www.startpage.com/",
  143. "Content-Type: application/x-www-form-urlencoded",
  144. "Content-Length: " . strlen($get),
  145. "DNT: 1",
  146. "Connection: keep-alive",
  147. "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",
  148. "Upgrade-Insecure-Requests: 1",
  149. "Sec-Fetch-Dest: document",
  150. "Sec-Fetch-Mode: navigate",
  151. "Sec-Fetch-Site: none",
  152. "Sec-Fetch-User: ?1",
  153. "Priority: u=0, i",
  154. "TE: trailers"]
  155. );
  156. }else{
  157. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  158. ["User-Agent: " . config::USER_AGENT,
  159. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  160. "Accept-Language: en-US,en;q=0.5",
  161. "Accept-Encoding: gzip",
  162. "DNT: 1",
  163. "Connection: keep-alive",
  164. "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",
  165. "Sec-Fetch-Dest: document",
  166. "Sec-Fetch-Mode: navigate",
  167. "Sec-Fetch-Site: none",
  168. "Sec-Fetch-User: ?1",
  169. "Priority: u=0, i",
  170. "TE: trailers"]
  171. );
  172. }
  173. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  174. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  175. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  176. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  177. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  178. $this->backend->assign_proxy($curlproc, $proxy);
  179. $data = curl_exec($curlproc);
  180. if(curl_errno($curlproc)){
  181. throw new Exception(curl_error($curlproc));
  182. }
  183. curl_close($curlproc);
  184. return $data;
  185. }
  186. public function web($get){
  187. if($get["npt"]){
  188. [$post, $proxy] = $this->backend->get($get["npt"], "web");
  189. try{
  190. $html = $this->get(
  191. $proxy,
  192. "https://www.startpage.com/sp/search",
  193. $post,
  194. true
  195. );
  196. }catch(Exception $error){
  197. throw new Exception("Failed to fetch search page");
  198. }
  199. $is_first_page = false;
  200. }else{
  201. $proxy = $this->backend->get_ip();
  202. $params = [
  203. "query" => $get["s"],
  204. "cat" => "web",
  205. "pl" => "opensearch",
  206. "qadf" => $get["nsfw"] == "yes" ? "none" : "heavy"
  207. ];
  208. if($get["country"] !== "any"){
  209. $params["qsr"] = $get["country"];
  210. }
  211. if($get["time"] !== "any"){
  212. $params["with_date"] = $get["time"];
  213. }
  214. try{
  215. $html = $this->get(
  216. $proxy,
  217. "https://www.startpage.com/sp/search",
  218. $params
  219. );
  220. }catch(Exception $error){
  221. throw new Exception("Failed to fetch search page");
  222. }
  223. //$html = file_get_contents("scraper/startpage.html");
  224. $is_first_page = true;
  225. }
  226. if(
  227. preg_match(
  228. '/React\.createElement\(UIStartpage\.AppSerpWeb, ?(.+)\),$/m',
  229. $html,
  230. $matches
  231. ) === 0
  232. ){
  233. throw new Exception("Failed to grep JSON object");
  234. }
  235. $json = json_decode($matches[1], true);
  236. if($json === null){
  237. throw new Exception("Failed to decode JSON");
  238. }
  239. $out = [
  240. "status" => "ok",
  241. "spelling" => [
  242. "type" => "no_correction",
  243. "using" => null,
  244. "correction" => null
  245. ],
  246. "npt" => null,
  247. "answer" => [],
  248. "web" => [],
  249. "image" => [],
  250. "video" => [],
  251. "news" => [],
  252. "related" => []
  253. ];
  254. // get npt
  255. foreach($json["render"]["presenter"]["pagination"]["pages"] as $page){
  256. if($page["name"] == "Next"){
  257. parse_str(
  258. explode(
  259. "?",
  260. $page["url"],
  261. 2
  262. )[1],
  263. $str
  264. );
  265. $out["npt"] =
  266. $this->backend->store(
  267. http_build_query(
  268. [
  269. "lui" => "english",
  270. "language" => "english",
  271. "query" => $str["q"],
  272. "cat" => "web",
  273. "sc" => $str["sc"],
  274. "t" => "device",
  275. "segment" => "startpage.udog",
  276. "page" => $str["page"]
  277. ]
  278. ),
  279. "web",
  280. $proxy
  281. );
  282. break;
  283. }
  284. }
  285. foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
  286. if(!isset($category["display_type"])){
  287. continue;
  288. }
  289. switch($category["display_type"]){
  290. case "web-google":
  291. foreach($category["results"] as $result){
  292. $sublinks = [];
  293. foreach($result["siteLinks"] as $sublink){
  294. $sublinks[] = [
  295. "title" => $sublink["title"],
  296. "description" => null,
  297. "url" => $sublink["clickUrl"]
  298. ];
  299. }
  300. $description =
  301. explode(
  302. "...",
  303. $this->titledots(
  304. html_entity_decode(
  305. $this->fuckhtml
  306. ->getTextContent(
  307. $result["description"]
  308. )
  309. )
  310. ),
  311. 2
  312. );
  313. $date = strtotime(trim($description[0]));
  314. if(
  315. $date === false ||
  316. count($description) !== 2 ||
  317. strlen($description[0]) > 14
  318. ){
  319. // no date found
  320. $description =
  321. implode(
  322. " ... ",
  323. $description
  324. );
  325. $date = null;
  326. }else{
  327. // date found
  328. $description = ltrim($description[1]);
  329. }
  330. $out["web"][] = [
  331. "title" =>
  332. $this->titledots(
  333. html_entity_decode(
  334. $this->fuckhtml
  335. ->getTextContent(
  336. $result["title"]
  337. )
  338. )
  339. ),
  340. "description" => $description,
  341. "url" => $result["clickUrl"],
  342. "date" => $date,
  343. "type" => "web",
  344. "thumb" => [
  345. "url" => null,
  346. "ratio" => null
  347. ],
  348. "sublink" => $sublinks,
  349. "table" => []
  350. ];
  351. }
  352. break;
  353. case "images-qi-top":
  354. foreach($category["results"] as $result){
  355. $out["image"][] = [
  356. "title" =>
  357. $this->titledots(
  358. html_entity_decode(
  359. $this->fuckhtml
  360. ->getTextContent(
  361. $result["title"]
  362. )
  363. )
  364. ),
  365. "source" => [
  366. [
  367. "url" => $result["rawImageUrl"],
  368. "width" => (int)$result["width"],
  369. "height" => (int)$result["height"]
  370. ],
  371. [
  372. "url" => $this->unshitimage($result["mdThumbnailUrl"]),
  373. "width" => (int)$result["mdThumbnailWidth"],
  374. "height" => (int)$result["mdThumbnailHeight"]
  375. ]
  376. ],
  377. "url" =>
  378. $result["altClickUrl"]
  379. ];
  380. }
  381. break;
  382. }
  383. }
  384. // parse instant answers
  385. if(
  386. $get["extendedsearch"] == "yes" &&
  387. $is_first_page === true
  388. ){
  389. // https://www.startpage.com/sp/qi?qimsn=ex&sxap=%2Fv1%2Fquery&sc=BqZ3inqrAgF701&sr=1
  390. try{
  391. $post = [
  392. "se" => "n0vze2y9dqwy",
  393. "q" => $json["render"]["query"],
  394. "results" => [], // populate
  395. "enableKnowledgePanel" => true,
  396. "enableMediaThumbBar" => false,
  397. "enableSearchSuggestions" => false,
  398. "enableTripadvisorProperties" => [],
  399. "enableTripadvisorPlaces" => [],
  400. "enableTripadvisorPlacesForLocations" => [],
  401. "enableWebProducts" => false,
  402. "tripadvisorPartnerId" => null,
  403. "tripadvisorMapColorMode" => "light",
  404. "tripadvisorDisablesKnowledgePanel" => false,
  405. "instantAnswers" => [
  406. "smartAnswers",
  407. "youtube",
  408. "tripadvisor"
  409. ],
  410. "iaType" => null,
  411. "forceEnhancedKnowledgePanel" => false,
  412. "shoppingOnly" => false,
  413. "allowAdultProducts" => true,
  414. "lang" => "en",
  415. "browserLang" => "en-US",
  416. "browserTimezone" => "America/New_York",
  417. "market" => null,
  418. "userLocation" => null,
  419. "userDate" => date("Y-m-d"),
  420. "userAgentType" => "unknown"
  421. ];
  422. foreach($out["web"] as $result){
  423. $post["results"][] = [
  424. "url" => $result["url"],
  425. "title" => $result["title"]
  426. ];
  427. }
  428. $post = json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_IGNORE);
  429. $additional_data =
  430. $this->get(
  431. $proxy,
  432. "https://www.startpage.com/sp/qi?qimsn=ex&sxap=%2Fv1%2Fquery&sc=" . $json["render"]["callback_sc"] . "&sr=1",
  433. $post,
  434. true,
  435. true
  436. );
  437. $additional_data = json_decode($additional_data, true);
  438. if($additional_data === null){
  439. throw new Exception("Failed to decode JSON"); // just break out, dont fail completely
  440. }
  441. if(!isset($additional_data["knowledgePanel"])){
  442. throw new Exception("Response has missing data (knowledgePanel)");
  443. }
  444. $additional_data = $additional_data["knowledgePanel"];
  445. $answer = [
  446. "title" => $additional_data["meta"]["title"],
  447. "description" => [
  448. [
  449. "type" => "quote",
  450. "value" => $additional_data["meta"]["description"]
  451. ]
  452. ],
  453. "url" => $additional_data["meta"]["origWikiUrl"],
  454. "thumb" => $additional_data["meta"]["image"],
  455. "table" => [],
  456. "sublink" => []
  457. ];
  458. // parse html for instant answer
  459. $this->fuckhtml->load($additional_data["html"]);
  460. $div =
  461. $this->fuckhtml
  462. ->getElementsByTagName(
  463. "div"
  464. );
  465. // get description
  466. $description =
  467. $this->fuckhtml
  468. ->getElementsByClassName(
  469. "sx-kp-short-extract sx-kp-short-extract-complete",
  470. $div
  471. );
  472. if(count($description) !== 0){
  473. $answer["description"][] = [
  474. "type" => "text",
  475. "value" =>
  476. $this->fuckhtml
  477. ->getTextContent(
  478. $description[0]
  479. )
  480. ];
  481. }
  482. // get socials
  483. $socials =
  484. $this->fuckhtml
  485. ->getElementsByClassName(
  486. "sx-wiki-social-link",
  487. "a"
  488. );
  489. foreach($socials as $social){
  490. $title =
  491. $this->fuckhtml
  492. ->getTextContent(
  493. $social["attributes"]["title"]
  494. );
  495. $url =
  496. $this->fuckhtml
  497. ->getTextContent(
  498. $social["attributes"]["href"]
  499. );
  500. switch($title){
  501. case "Official Website":
  502. $title = "Website";
  503. break;
  504. }
  505. $answer["sublink"][$title] = $url;
  506. }
  507. // get videos
  508. $videos =
  509. $this->fuckhtml
  510. ->getElementsByClassName(
  511. "sx-kp-video-grid-item",
  512. $div
  513. );
  514. foreach($videos as $video){
  515. $this->fuckhtml->load($video);
  516. $as =
  517. $this->fuckhtml
  518. ->getElementsByTagName(
  519. "a"
  520. );
  521. if(count($as) === 0){
  522. // ?? invalid
  523. continue;
  524. }
  525. $image =
  526. $this->fuckhtml
  527. ->getElementsByAttributeName(
  528. "data-sx-src",
  529. "img"
  530. );
  531. if(count($image) !== 0){
  532. $thumb = [
  533. "ratio" => "16:9",
  534. "url" =>
  535. $this->fuckhtml
  536. ->getTextContent(
  537. $image[0]["attributes"]["data-sx-src"]
  538. )
  539. ];
  540. }else{
  541. $thumb = [
  542. "ratio" => null,
  543. "url" => null
  544. ];
  545. }
  546. $out["video"][] = [
  547. "title" =>
  548. $this->fuckhtml
  549. ->getTextContent(
  550. $as[0]["attributes"]["title"]
  551. ),
  552. "description" => null,
  553. "date" => null,
  554. "duration" => null,
  555. "views" => null,
  556. "thumb" => $thumb,
  557. "url" =>
  558. $this->fuckhtml
  559. ->getTextContent(
  560. $as[0]["attributes"]["href"]
  561. )
  562. ];
  563. }
  564. // reset
  565. $this->fuckhtml->load($additional_data["html"]);
  566. // get table elements
  567. $table =
  568. $this->fuckhtml
  569. ->getElementsByClassName(
  570. "sx-infobox",
  571. "table"
  572. );
  573. if(count($table) !== 0){
  574. $trs =
  575. $this->fuckhtml
  576. ->getElementsByTagName(
  577. "tr"
  578. );
  579. foreach($trs as $tr){
  580. $this->fuckhtml->load($tr);
  581. // ok so startpage devs cant fucking code a table
  582. // td = content
  583. // th (AAAHH) = title
  584. $tds =
  585. $this->fuckhtml
  586. ->getElementsByTagName(
  587. "td"
  588. );
  589. $ths =
  590. $this->fuckhtml
  591. ->getElementsByTagName(
  592. "th"
  593. );
  594. if(
  595. count($ths) === 1 &&
  596. count($tds) === 1
  597. ){
  598. $title =
  599. $this->fuckhtml
  600. ->getTextContent(
  601. $ths[0]
  602. );
  603. $description = [];
  604. $this->fuckhtml->load($tds[0]);
  605. $lis =
  606. $this->fuckhtml
  607. ->getElementsByTagName(
  608. "li"
  609. );
  610. if(count($lis) !== 0){
  611. foreach($lis as $li){
  612. $description[] =
  613. $this->fuckhtml
  614. ->getTextContent(
  615. $li
  616. );
  617. }
  618. $description = implode(", ", $description);
  619. }else{
  620. $description =
  621. $this->fuckhtml
  622. ->getTextContent(
  623. $tds[0]
  624. );
  625. }
  626. $answer["table"][$title] = $description;
  627. }
  628. }
  629. }
  630. $out["answer"][] = $answer;
  631. }catch(Exception $error){
  632. // do nothing
  633. //echo "error!";
  634. }
  635. }
  636. return $out;
  637. }
  638. private function unshitimage($url){
  639. $query = parse_url($url, PHP_URL_QUERY);
  640. parse_str($query, $query);
  641. if(isset($query["piurl"])){
  642. if(strpos($query["piurl"], "gstatic.com/")){
  643. return
  644. explode(
  645. "&",
  646. $query["piurl"],
  647. 2
  648. )[0];
  649. }
  650. return $query["piurl"];
  651. }
  652. return $url;
  653. }
  654. private function titledots($title){
  655. return trim($title, " .\t\n\r\0\x0B…");
  656. }
  657. }