marginalia.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <?php
  2. class marginalia{
  3. public function __construct(){
  4. include "lib/anubis.php";
  5. $this->anubis = new anubis();
  6. include_once "lib/fuckhtml.php";
  7. $this->fuckhtml = new fuckhtml();
  8. include "lib/backend.php";
  9. $this->backend = new backend("marginalia");
  10. }
  11. public function getfilters($page){
  12. if(config::MARGINALIA_API_KEY === null){
  13. $base = [
  14. "adtech" => [
  15. "display" => "Reduce adtech",
  16. "option" => [
  17. "no" => "No",
  18. "yes" => "Yes"
  19. ]
  20. ],
  21. "recent" => [
  22. "display" => "Recent results",
  23. "option" => [
  24. "no" => "No",
  25. "yes" => "Yes"
  26. ]
  27. ],
  28. "intitle" => [
  29. "display" => "Search in title",
  30. "option" => [
  31. "no" => "No",
  32. "yes" => "Yes"
  33. ]
  34. ]
  35. ];
  36. }else{
  37. $base = [];
  38. }
  39. return array_merge(
  40. $base,
  41. [
  42. "format" => [
  43. "display" => "Format",
  44. "option" => [
  45. "any" => "Any format",
  46. "html5" => "html5",
  47. "xhtml" => "xhtml",
  48. "html123" => "html123"
  49. ]
  50. ],
  51. "file" => [
  52. "display" => "Filetype",
  53. "option" => [
  54. "any" => "Any filetype",
  55. "nomedia" => "Deny media",
  56. "media" => "Contains media",
  57. "audio" => "Contains audio",
  58. "video" => "Contains video",
  59. "archive" => "Contains archive",
  60. "document" => "Contains document"
  61. ]
  62. ],
  63. "javascript" => [
  64. "display" => "Javascript",
  65. "option" => [
  66. "any" => "Allow JS",
  67. "deny" => "Deny JS",
  68. "require" => "Require JS"
  69. ]
  70. ],
  71. "trackers" => [
  72. "display" => "Trackers",
  73. "option" => [
  74. "any" => "Allow trackers",
  75. "deny" => "Deny trackers",
  76. "require" => "Require trackers"
  77. ]
  78. ],
  79. "cookies" => [
  80. "display" => "Cookies",
  81. "option" => [
  82. "any" => "Allow cookies",
  83. "deny" => "Deny cookies",
  84. "require" => "Require cookies"
  85. ]
  86. ],
  87. "affiliate" => [
  88. "display" => "Affiliate links in body",
  89. "option" => [
  90. "any" => "Allow affiliate links",
  91. "deny" => "Deny affiliate links",
  92. "require" => "Require affiliate links"
  93. ]
  94. ]
  95. ]
  96. );
  97. }
  98. private function get($proxy, $url, $get = [], $get_cookies = 1){
  99. $curlproc = curl_init();
  100. switch($get_cookies){
  101. case 0:
  102. $cookies = "";
  103. $cookies_tmp = [];
  104. curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){
  105. $length = strlen($header);
  106. $header = explode(":", $header, 2);
  107. if(trim(strtolower($header[0])) == "set-cookie"){
  108. $cookie_tmp = explode("=", trim($header[1]), 2);
  109. $cookies_tmp[trim($cookie_tmp[0])] =
  110. explode(";", $cookie_tmp[1], 2)[0];
  111. }
  112. return $length;
  113. });
  114. break;
  115. case 1:
  116. $cookies = "";
  117. break;
  118. default:
  119. $cookies = "Cookie: " . $get_cookies;
  120. }
  121. $headers = [
  122. "User-Agent: " . config::USER_AGENT,
  123. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  124. "Accept-Language: en-US,en;q=0.5",
  125. "Accept-Encoding: gzip",
  126. "DNT: 1",
  127. $cookies,
  128. "Connection: keep-alive",
  129. "Upgrade-Insecure-Requests: 1",
  130. "Sec-Fetch-Dest: document",
  131. "Sec-Fetch-Mode: navigate",
  132. "Sec-Fetch-Site: none",
  133. "Sec-Fetch-User: ?1"
  134. ];
  135. if($get !== []){
  136. $get = http_build_query($get);
  137. $url .= "?" . $get;
  138. }
  139. curl_setopt($curlproc, CURLOPT_URL, $url);
  140. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  141. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  142. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  143. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  144. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  145. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  146. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  147. $this->backend->assign_proxy($curlproc, $proxy);
  148. $data = curl_exec($curlproc);
  149. if(curl_errno($curlproc)){
  150. throw new Exception(curl_error($curlproc));
  151. }
  152. if($get_cookies === 0){
  153. $cookie = [];
  154. foreach($cookies_tmp as $key => $value){
  155. $cookie[] = $key . "=" . $value;
  156. }
  157. curl_close($curlproc);
  158. return implode(";", $cookie);
  159. }
  160. return $data;
  161. }
  162. public function web($get){
  163. $search = [$get["s"]];
  164. if(strlen($get["s"]) === 0){
  165. throw new Exception("Search term is empty!");
  166. }
  167. $format = $get["format"];
  168. $file = $get["file"];
  169. foreach(
  170. [
  171. "javascript" => $get["javascript"],
  172. "trackers" => $get["trackers"],
  173. "cookies" => $get["cookies"],
  174. "affiliate" => $get["affiliate"]
  175. ]
  176. as $key => $value
  177. ){
  178. if($value == "any"){ continue; }
  179. switch($key){
  180. case "javascript": $str = "js:true"; break;
  181. case "trackers": $str = "special:tracking"; break;
  182. case "cookies": $str = "special:cookies"; break;
  183. case "affiliate": $str = "special:affiliate"; break;
  184. }
  185. if($value == "deny"){
  186. $str = "-" . $str;
  187. }
  188. $search[] = $str;
  189. }
  190. if($format != "any"){
  191. $search[] = "format:$format";
  192. }
  193. switch($file){
  194. case "any": break;
  195. case "nomedia": $search[] = "-special:media"; break;
  196. case "media": $search[] = "special:media"; break;
  197. default:
  198. $search[] = "file:$file";
  199. }
  200. $search = implode(" ", $search);
  201. $out = [
  202. "status" => "ok",
  203. "spelling" => [
  204. "type" => "no_correction",
  205. "using" => null,
  206. "correction" => null
  207. ],
  208. "npt" => null,
  209. "answer" => [],
  210. "web" => [],
  211. "image" => [],
  212. "video" => [],
  213. "news" => [],
  214. "related" => []
  215. ];
  216. // API scraper
  217. if(config::MARGINALIA_API_KEY !== null){
  218. try{
  219. $json =
  220. $this->get(
  221. $this->backend->get_ip(), // no nextpage
  222. "https://api.marginalia-search.com/" . config::MARGINALIA_API_KEY . "/search/" . urlencode($search),
  223. [
  224. "count" => 20
  225. ]
  226. );
  227. }catch(Exception $error){
  228. throw new Exception("Failed to get JSON");
  229. }
  230. if($json == "Slow down"){
  231. throw new Exception("The API key used is rate limited. Please try again in a few minutes.");
  232. }
  233. $json = json_decode($json, true);
  234. foreach($json["results"] as $result){
  235. $out["web"][] = [
  236. "title" => $result["title"],
  237. "description" => str_replace("\n", " ", $result["description"]),
  238. "url" => $result["url"],
  239. "date" => null,
  240. "type" => "web",
  241. "thumb" => [
  242. "url" => null,
  243. "ratio" => null
  244. ],
  245. "sublink" => [],
  246. "table" => []
  247. ];
  248. }
  249. return $out;
  250. }
  251. // HTML parser
  252. $proxy = $this->backend->get_ip();
  253. //
  254. // Bypass anubis check
  255. //
  256. /*
  257. if(($anubis_key = apcu_fetch("marginalia_cookie")) === false){
  258. try{
  259. $html =
  260. $this->get(
  261. $proxy,
  262. "https://old-search.marginalia.nu/search",
  263. [
  264. "query" => $search
  265. ]
  266. );
  267. }catch(Exception $error){
  268. throw new Exception("Failed to get anubis challenge");
  269. }
  270. try{
  271. $anubis_data = $this->anubis->scrape($html);
  272. }catch(Exception $error){
  273. throw new Exception($error);
  274. }
  275. // send anubis response & get cookies
  276. // https://old-search.marginalia.nu/.within.website/x/cmd/anubis/api/pass-challenge?response=0000018966b086834f738bacba6031028adb5aa875974ead197a8b75778baf3a&nonce=39947&redir=https%3A%2F%2Fold-search.marginalia.nu%2F&elapsedTime=1164
  277. try{
  278. $anubis_key =
  279. $this->get(
  280. $proxy,
  281. "https://old-search.marginalia.nu/.within.website/x/cmd/anubis/api/pass-challenge",
  282. [
  283. "response" => $anubis_data["response"],
  284. "nonce" => $anubis_data["nonce"],
  285. "redir" => "https://old-search.marginalia.nu/",
  286. "elapsedTime" => random_int(1000, 2000)
  287. ],
  288. 0
  289. );
  290. }catch(Exception $error){
  291. throw new Exception("Failed to submit anubis challenge");
  292. }
  293. apcu_store("marginalia_cookie", $anubis_key);
  294. }*/
  295. if($get["npt"]){
  296. [$params, $proxy] =
  297. $this->backend->get(
  298. $get["npt"],
  299. "web"
  300. );
  301. try{
  302. $html =
  303. $this->get(
  304. $proxy,
  305. "https://old-search.marginalia.nu/search?" . $params,
  306. [],
  307. //$anubis_key
  308. );
  309. }catch(Exception $error){
  310. throw new Exception("Failed to get HTML");
  311. }
  312. }else{
  313. $params = [
  314. "query" => $search
  315. ];
  316. foreach(["adtech", "recent", "intitle"] as $v){
  317. if($get[$v] == "yes"){
  318. switch($v){
  319. case "adtech": $params["adtech"] = "reduce"; break;
  320. case "recent": $params["recent"] = "recent"; break;
  321. case "adtech": $params["searchTitle"] = "title"; break;
  322. }
  323. }
  324. }
  325. try{
  326. $html =
  327. $this->get(
  328. $proxy,
  329. "https://old-search.marginalia.nu/search",
  330. $params,
  331. //$anubis_key
  332. );
  333. }catch(Exception $error){
  334. throw new Exception("Failed to get HTML");
  335. }
  336. }
  337. $this->fuckhtml->load($html);
  338. $sections =
  339. $this->fuckhtml
  340. ->getElementsByClassName(
  341. "card search-result",
  342. "section"
  343. );
  344. foreach($sections as $section){
  345. $this->fuckhtml->load($section);
  346. $title =
  347. $this->fuckhtml
  348. ->getElementsByClassName(
  349. "title",
  350. "a"
  351. )[0];
  352. $description =
  353. $this->fuckhtml
  354. ->getElementsByClassName(
  355. "description",
  356. "p"
  357. );
  358. if(count($description) !== 0){
  359. $description =
  360. $this->fuckhtml
  361. ->getTextContent(
  362. $description[0]
  363. );
  364. }else{
  365. $description = null;
  366. }
  367. $sublinks = [];
  368. $sublink_html =
  369. $this->fuckhtml
  370. ->getElementsByClassName("additional-results");
  371. if(count($sublink_html) !== 0){
  372. $this->fuckhtml->load($sublink_html[0]);
  373. $links =
  374. $this->fuckhtml
  375. ->getElementsByTagName("a");
  376. foreach($links as $link){
  377. $sublinks[] = [
  378. "title" =>
  379. $this->fuckhtml
  380. ->getTextContent(
  381. $link
  382. ),
  383. "date" => null,
  384. "description" => null,
  385. "url" =>
  386. $this->fuckhtml
  387. ->getTextContent(
  388. $link["attributes"]["href"]
  389. )
  390. ];
  391. }
  392. }
  393. $out["web"][] = [
  394. "title" =>
  395. $this->fuckhtml
  396. ->getTextContent(
  397. $title
  398. ),
  399. "description" => $description,
  400. "url" =>
  401. $this->fuckhtml
  402. ->getTextContent(
  403. $title["attributes"]["href"]
  404. ),
  405. "date" => null,
  406. "type" => "web",
  407. "thumb" => [
  408. "url" => null,
  409. "ratio" => null
  410. ],
  411. "sublink" => $sublinks,
  412. "table" => []
  413. ];
  414. }
  415. // get next page
  416. $this->fuckhtml->load($html);
  417. $pagination =
  418. $this->fuckhtml
  419. ->getElementsByAttributeValue(
  420. "aria-label",
  421. "pagination",
  422. "nav"
  423. );
  424. if(count($pagination) === 0){
  425. // no pagination
  426. return $out;
  427. }
  428. $this->fuckhtml->load($pagination[0]);
  429. $pages =
  430. $this->fuckhtml
  431. ->getElementsByClassName(
  432. "page-link",
  433. "a"
  434. );
  435. $found_current_page = false;
  436. foreach($pages as $page){
  437. if(
  438. stripos(
  439. $page["attributes"]["class"],
  440. "active"
  441. ) !== false
  442. ){
  443. $found_current_page = true;
  444. continue;
  445. }
  446. if($found_current_page){
  447. // we found current page index, and we iterated over
  448. // the next page <a>
  449. $out["npt"] =
  450. $this->backend->store(
  451. parse_url(
  452. $page["attributes"]["href"],
  453. PHP_URL_QUERY
  454. ),
  455. "web",
  456. $proxy
  457. );
  458. break;
  459. }
  460. }
  461. return $out;
  462. }
  463. }