mullvad.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. class mullvad{
  3. public function __construct($engine){
  4. $this->engine = $engine;
  5. include "lib/backend.php";
  6. $this->backend = new backend("mullvad_{$this->engine}");
  7. }
  8. public function getfilters($page){
  9. return [
  10. "country" => [ // &country=
  11. "display" => "Country",
  12. "option" => [
  13. "any" => "Any country",
  14. "ar" => "Argentina",
  15. "au" => "Australia",
  16. "at" => "Austria",
  17. "be" => "Belgium",
  18. "br" => "Brazil",
  19. "ca" => "Canada",
  20. "cl" => "Chile",
  21. "cn" => "China",
  22. "dk" => "Denmark",
  23. "fi" => "Finland",
  24. "fr" => "France",
  25. "de" => "Germany",
  26. "hk" => "Hong Kong",
  27. "in" => "India",
  28. "id" => "Indonesia",
  29. "it" => "Italy",
  30. "jp" => "Japan",
  31. "kr" => "Korea, Republic",
  32. "my" => "Malaysia",
  33. "mx" => "Mexico",
  34. "nl" => "Netherlands",
  35. "nz" => "New Zealand",
  36. "no" => "Norway",
  37. "ph" => "Philippines",
  38. "pl" => "Poland",
  39. "pt" => "Portugal",
  40. "ru" => "Russian Federation",
  41. "sa" => "Saudi Arabia",
  42. "za" => "South Africa",
  43. "es" => "Spain",
  44. "se" => "Sweden",
  45. "ch" => "Switzerland",
  46. "tw" => "Taiwan",
  47. "tr" => "Turkey",
  48. "uk" => "United Kingdom",
  49. "us" => "United States"
  50. ]
  51. ],
  52. "language" => [ // &language=
  53. "display" => "Language",
  54. "option" => [
  55. "any" => "Any language",
  56. "ar" => "Arabic",
  57. "bg" => "Bulgarian",
  58. "ca" => "Catalan",
  59. "zh-hans" => "Chinese (Simplified)",
  60. "zh-hant" => "Chinese (Traditional)",
  61. "hr" => "Croatian",
  62. "cs" => "Czech",
  63. "da" => "Danish",
  64. "nl" => "Dutch",
  65. "en" => "English",
  66. "et" => "Estonian",
  67. "fi" => "Finnish",
  68. "fr" => "French",
  69. "de" => "German",
  70. "he" => "Hebrew",
  71. "hu" => "Hungarian",
  72. "is" => "Icelandic",
  73. "it" => "Italian",
  74. "jp" => "Japanese",
  75. "ko" => "Korean",
  76. "lv" => "Latvian",
  77. "lt" => "Lithuanian",
  78. "nb" => "Norwegian",
  79. "pl" => "Polish",
  80. "pt" => "Portuguese",
  81. "ro" => "Romanian",
  82. "ru" => "Russian",
  83. "sr" => "Serbian",
  84. "sk" => "Slovak",
  85. "sl" => "Slovenian",
  86. "es" => "Spanish",
  87. "sv" => "Swedish",
  88. "tr" => "Turkish"
  89. ]
  90. ],
  91. "time" => [ // &lastUpdated=
  92. "display" => "Time posted",
  93. "option" => [
  94. "any" => "Any time",
  95. "d" => "Past day",
  96. "w" => "Past week",
  97. "m" => "Past month",
  98. "y" => "Past year"
  99. ]
  100. ]
  101. ];
  102. }
  103. private function get($proxy, $url, $get = []){
  104. $curlproc = curl_init();
  105. if($get !== []){
  106. $get = http_build_query($get);
  107. $url .= "?" . $get;
  108. }
  109. curl_setopt($curlproc, CURLOPT_URL, $url);
  110. // http2 bypass
  111. curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  112. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  113. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  114. ["User-Agent: " . config::USER_AGENT,
  115. "Accept: */*",
  116. "Accept-Language: en-US,en;q=0.5",
  117. "Accept-Encoding: gzip, deflate, br, zstd",
  118. "Referer: https://leta.mullvad.net/search",
  119. "DNT: 1",
  120. "Sec-GPC: 1",
  121. "Connection: keep-alive",
  122. "Cookie: engine=brave",
  123. "Sec-Fetch-Dest: empty",
  124. "Sec-Fetch-Mode: cors",
  125. "Sec-Fetch-Site: same-origin",
  126. "Priority: u=0",
  127. "TE: trailers"]
  128. );
  129. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  130. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  131. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  132. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  133. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  134. $this->backend->assign_proxy($curlproc, $proxy);
  135. $data = curl_exec($curlproc);
  136. if(curl_errno($curlproc)){
  137. throw new Exception(curl_error($curlproc));
  138. }
  139. curl_close($curlproc);
  140. return $data;
  141. }
  142. public function web($get){
  143. if($get["npt"]){
  144. [$params, $proxy] = $this->backend->get($get["npt"], "web");
  145. $params = json_decode($params, true);
  146. }else{
  147. if(strlen($get["s"]) === 0){
  148. throw new Exception("Search term is empty!");
  149. }
  150. // generate filters
  151. $params = [
  152. "q" => $get["s"],
  153. "engine" => $this->engine,
  154. "page" => 1
  155. ];
  156. if($get["country"] != "any"){
  157. $params["country"] = $get["country"];
  158. }
  159. if($get["language"] != "any"){
  160. $params["language"] = $get["language"];
  161. }
  162. if($get["time"] != "any"){
  163. $params["lastUpdated"] = $get["time"];
  164. }
  165. $proxy = $this->backend->get_ip();
  166. }
  167. try{
  168. $json = $this->get(
  169. $proxy,
  170. "https://leta.mullvad.net/search/__data.json",
  171. $params
  172. );
  173. }catch(Exception $error){
  174. throw new Exception("Failed to fetch search page");
  175. }
  176. $json = json_decode($json, true);
  177. if($json === null){
  178. throw new Exception("Failed to decode JSON");
  179. }
  180. if(!isset($json["nodes"])){
  181. throw new Exception("Mullvad did not return a nodes object");
  182. }
  183. $out = [
  184. "status" => "ok",
  185. "spelling" => [
  186. "type" => "no_correction",
  187. "using" => null,
  188. "correction" => null
  189. ],
  190. "npt" => $nextpage,
  191. "answer" => [],
  192. "web" => [],
  193. "image" => [],
  194. "video" => [],
  195. "news" => [],
  196. "related" => []
  197. ];
  198. // parse json payload
  199. foreach($json["nodes"] as $node){
  200. if(!isset($node["data"][0]["q"])){
  201. // not iterating through the query object
  202. continue;
  203. }
  204. // node 0 contains pointers to what we need to iterate through
  205. $node0 = &$node["data"][0];
  206. if(!isset($node["data"][$node0["success"]])){
  207. throw new Exception("Mullvad did not return a success object");
  208. }
  209. $success = &$node["data"][$node0["success"]];
  210. if($success === false){
  211. throw new Exception("Mullvad flagged the response as unsuccessful");
  212. }
  213. if(!isset($node["data"][$node0["items"]])){
  214. throw new Exception("Mullvad did not return an items object");
  215. }
  216. $search_pointers = &$node["data"][$node0["items"]];
  217. //
  218. // Iterate over results
  219. //
  220. foreach($search_pointers as $pointer){
  221. $pointer = &$node["data"][$pointer];
  222. $link = &$node["data"][$pointer["link"]];
  223. $title = &$node["data"][$pointer["title"]];
  224. $description = &$node["data"][$pointer["snippet"]];
  225. $date = null;
  226. if($this->engine == "google"){
  227. // attempt to extract date
  228. // Jan 12, 2017
  229. $date_parts = explode(" ... ", $description, 2);
  230. if(
  231. count($date_parts) === 2 &&
  232. strlen($date_parts[0]) < 15
  233. ){
  234. $date = strtotime(trim($date_parts[0]));
  235. if($date === false){
  236. $date = null;
  237. }else{
  238. $description = trim($date_parts[1]);
  239. }
  240. }
  241. }
  242. $out["web"][] = [
  243. "title" => $this->titledots($title),
  244. "description" => $this->titledots($description),
  245. "url" => $link,
  246. "date" => $date,
  247. "type" => "web",
  248. "thumb" => [
  249. "url" => null,
  250. "ratio" => null
  251. ],
  252. "sublink" => [],
  253. "table" => []
  254. ];
  255. }
  256. //
  257. // Get nextpage
  258. //
  259. if(isset($node["data"][$node0["next"]])){
  260. $params["page"] = (int)$node["data"][$node0["next"]];
  261. $out["npt"] =
  262. $this->backend->store(
  263. json_encode($params),
  264. "web",
  265. $proxy
  266. );
  267. }
  268. }
  269. return $out;
  270. }
  271. private function titledots($title){
  272. return trim($title, " .\t\n\r\0\x0B…");
  273. }
  274. }