ddg.php 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700
  1. <?php
  2. class ddg{
  3. public function __construct(){
  4. include "lib/backend.php";
  5. $this->backend = new backend("ddg");
  6. include "lib/fuckhtml.php";
  7. $this->fuckhtml = new fuckhtml();
  8. }
  9. /*
  10. curl functions
  11. */
  12. private const req_web = 0;
  13. private const req_xhr = 1;
  14. private function get($proxy, $url, $get = [], $reqtype = self::req_web){
  15. $curlproc = curl_init();
  16. if($get !== []){
  17. $get = http_build_query($get);
  18. $url .= "?" . $get;
  19. }
  20. curl_setopt($curlproc, CURLOPT_URL, $url);
  21. // http2 bypass
  22. curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2);
  23. switch($reqtype){
  24. case self::req_web:
  25. $headers =
  26. ["User-Agent: " . config::USER_AGENT,
  27. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  28. "Accept-Language: en-US,en;q=0.9",
  29. "Accept-Encoding: gzip",
  30. "DNT: 1",
  31. "Sec-GPC: 1",
  32. "Connection: keep-alive",
  33. "Upgrade-Insecure-Requests: 1",
  34. "Sec-Fetch-Dest: document",
  35. "Sec-Fetch-Mode: navigate",
  36. "Sec-Fetch-Site: same-origin",
  37. "Sec-Fetch-User: ?1",
  38. "Priority: u=0, i"];
  39. break;
  40. case self::req_xhr:
  41. $headers =
  42. ["User-Agent: " . config::USER_AGENT,
  43. "Accept: */*",
  44. "Accept-Language: en-US,en;q=0.9",
  45. "Accept-Encoding: gzip, deflate, br, zstd",
  46. "Referer: https://duckduckgo.com/",
  47. "DNT: 1",
  48. "Sec-GPC: 1",
  49. "Connection: keep-alive",
  50. "Sec-Fetch-Dest: script",
  51. "Sec-Fetch-Mode: no-cors",
  52. "Sec-Fetch-Site: same-site",
  53. "Priority: u=1"];
  54. break;
  55. }
  56. $this->backend->assign_proxy($curlproc, $proxy);
  57. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  58. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  59. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  60. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  61. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  62. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  63. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  64. $data = curl_exec($curlproc);
  65. if(curl_errno($curlproc)){
  66. throw new Exception(curl_error($curlproc));
  67. }
  68. curl_close($curlproc);
  69. return $data;
  70. }
  71. public function getfilters($pagetype){
  72. $base = [
  73. "country" => [
  74. "display" => "Country",
  75. "option" => [
  76. "us-en" => "US (English)",
  77. "ar-es" => "Argentina",
  78. "au-en" => "Australia",
  79. "at-de" => "Austria",
  80. "be-fr" => "Belgium (fr)",
  81. "be-nl" => "Belgium (nl)",
  82. "br-pt" => "Brazil",
  83. "bg-bg" => "Bulgaria",
  84. "ca-en" => "Canada (en)",
  85. "ca-fr" => "Canada (fr)",
  86. "ct-ca" => "Catalonia",
  87. "cl-es" => "Chile",
  88. "cn-zh" => "China",
  89. "co-es" => "Colombia",
  90. "hr-hr" => "Croatia",
  91. "cz-cs" => "Czech Republic",
  92. "dk-da" => "Denmark",
  93. "ee-et" => "Estonia",
  94. "fi-fi" => "Finland",
  95. "fr-fr" => "France",
  96. "de-de" => "Germany",
  97. "gr-el" => "Greece",
  98. "hk-tzh" => "Hong Kong",
  99. "hu-hu" => "Hungary",
  100. "in-en" => "India (en)",
  101. "id-en" => "Indonesia (en)",
  102. "ie-en" => "Ireland",
  103. "il-en" => "Israel (en)",
  104. "it-it" => "Italy",
  105. "jp-jp" => "Japan",
  106. "kr-kr" => "Korea",
  107. "lv-lv" => "Latvia",
  108. "lt-lt" => "Lithuania",
  109. "my-en" => "Malaysia (en)",
  110. "mx-es" => "Mexico",
  111. "nl-nl" => "Netherlands",
  112. "nz-en" => "New Zealand",
  113. "no-no" => "Norway",
  114. "pk-en" => "Pakistan (en)",
  115. "pe-es" => "Peru",
  116. "ph-en" => "Philippines (en)",
  117. "pl-pl" => "Poland",
  118. "pt-pt" => "Portugal",
  119. "ro-ro" => "Romania",
  120. "ru-ru" => "Russia",
  121. "xa-ar" => "Saudi Arabia",
  122. "sg-en" => "Singapore",
  123. "sk-sk" => "Slovakia",
  124. "sl-sl" => "Slovenia",
  125. "za-en" => "South Africa",
  126. "es-ca" => "Spain (ca)",
  127. "es-es" => "Spain (es)",
  128. "se-sv" => "Sweden",
  129. "ch-de" => "Switzerland (de)",
  130. "ch-fr" => "Switzerland (fr)",
  131. "tw-tzh" => "Taiwan",
  132. "th-en" => "Thailand (en)",
  133. "tr-tr" => "Turkey",
  134. "us-es" => "US (Spanish)",
  135. "ua-uk" => "Ukraine",
  136. "uk-en" => "United Kingdom",
  137. "vn-en" => "Vietnam (en)"
  138. ]
  139. ]
  140. ];
  141. switch($pagetype){
  142. case "web":
  143. $base["country"]["option"] =
  144. array_merge(["any" => "All Regions"], $base["country"]["option"]);
  145. return array_merge($base,
  146. [
  147. "nsfw" => [
  148. "display" => "NSFW",
  149. "option" => [
  150. "yes" => "Yes",
  151. "maybe" => "Maybe",
  152. "no" => "No"
  153. ]
  154. ],
  155. "newer" => [
  156. "display" => "Newer than",
  157. "option" => "_DATE"
  158. ],
  159. "older" => [
  160. "display" => "Older than",
  161. "option" => "_DATE"
  162. ],
  163. "extendedsearch" => [
  164. // undefined display
  165. "option" => [
  166. "yes" => "Yes",
  167. "no" => "No",
  168. ]
  169. ]
  170. ]
  171. );
  172. break;
  173. case "images":
  174. return array_merge($base,
  175. [
  176. "nsfw" => [
  177. "display" => "NSFW",
  178. "option" => [
  179. "yes" => "Yes",
  180. "no" => "No"
  181. ]
  182. ],
  183. "date" => [
  184. "display" => "Time posted",
  185. "option" => [
  186. "any" => "Any time",
  187. "Day" => "Past day",
  188. "Week" => "Past week",
  189. "Month" => "Past month"
  190. ]
  191. ],
  192. "size" => [
  193. "display" => "Size",
  194. "option" => [
  195. "any" => "Any size",
  196. "Small" => "Small",
  197. "Medium" => "Medium",
  198. "Large" => "Large",
  199. "Wallpaper" => "Wallpaper"
  200. ]
  201. ],
  202. "color" => [
  203. "display" => "Colors",
  204. "option" => [
  205. "any" => "All colors",
  206. "Monochrome" => "Black and white",
  207. "Red" => "Red",
  208. "Orange" => "Orange",
  209. "Yellow" => "Yellow",
  210. "Green" => "Green",
  211. "Blue" => "Blue",
  212. "Purple" => "Purple",
  213. "Pink" => "Pink",
  214. "Brown" => "Brown",
  215. "Black" => "Black",
  216. "Gray" => "Gray",
  217. "Teal" => "Teal",
  218. "White" => "White"
  219. ]
  220. ],
  221. "type" => [
  222. "display" => "Type",
  223. "option" => [
  224. "any" => "All types",
  225. "photo" => "Photograph",
  226. "clipart" => "Clipart",
  227. "gif" => "Animated GIF",
  228. "transparent" => "Transparent"
  229. ]
  230. ],
  231. "layout" => [
  232. "display" => "Layout",
  233. "option" => [
  234. "any" => "All layouts",
  235. "Square" => "Square",
  236. "Tall" => "Tall",
  237. "Wide" => "Wide"
  238. ]
  239. ],
  240. "license" => [
  241. "display" => "License",
  242. "option" => [
  243. "any" => "All licenses",
  244. "Any" => "All Creative Commons",
  245. "Public" => "Public domain",
  246. "Share" => "Free to Share and Use",
  247. "ShareCommercially" => "Free to Share and Use Commercially",
  248. "Modify" => "Free to Modify, Share, and Use",
  249. "ModifyCommercially" => "Free to Modify, Share, and Use Commercially"
  250. ]
  251. ]
  252. ]
  253. );
  254. break;
  255. case "videos":
  256. return array_merge($base,
  257. [
  258. "nsfw" => [
  259. "display" => "NSFW",
  260. "option" => [
  261. "yes" => "Yes",
  262. "maybe" => "Maybe",
  263. "no" => "No"
  264. ]
  265. ],
  266. "date" => [
  267. "display" => "Time fetched",
  268. "option" => [
  269. "any" => "Any time",
  270. "d" => "Past day",
  271. "w" => "Past week",
  272. "m" => "Past month"
  273. ]
  274. ],
  275. "resolution" => [ //videoDefinition
  276. "display" => "Resolution",
  277. "option" => [
  278. "any" => "Any resolution",
  279. "high" => "High definition",
  280. "standard" => "Standard definition"
  281. ]
  282. ],
  283. "duration" => [ // videoDuration
  284. "display" => "Duration",
  285. "option" => [
  286. "any" => "Any duration",
  287. "short" => "Short (>5min)",
  288. "medium" => "Medium (5-20min)",
  289. "long" => "Long (<20min)"
  290. ]
  291. ],
  292. "license" => [
  293. "display" => "License",
  294. "option" => [
  295. "any" => "Any license",
  296. "creativeCommon" => "Creative Commons",
  297. "youtube" => "YouTube Standard"
  298. ]
  299. ]
  300. ]
  301. );
  302. break;
  303. case "news":
  304. return array_merge($base,
  305. [
  306. "nsfw" => [
  307. "display" => "NSFW",
  308. "option" => [
  309. "yes" => "Yes",
  310. "maybe" => "Maybe",
  311. "no" => "No"
  312. ]
  313. ],
  314. "date" => [
  315. "display" => "Time posted",
  316. "option" => [
  317. "any" => "Any time",
  318. "d" => "Past day",
  319. "w" => "Past week",
  320. "m" => "Past month"
  321. ]
  322. ]
  323. ]
  324. );
  325. break;
  326. }
  327. }
  328. public function web($get){
  329. throw new Exception("failure");
  330. if($get["npt"]){
  331. [$raw_data, $proxy] = $this->backend->get($get["npt"], "web");
  332. $raw_data = explode(",", $raw_data, 2);
  333. if($raw_data[0] == "0"){
  334. return $this->web_html($get, [$raw_data[1], $proxy]);
  335. }
  336. return $this->web_full($get, [$raw_data[1], $proxy]);
  337. }else{
  338. // we have $get["s"]
  339. if(
  340. strpos($get["s"], "email") !== false || // contains word "email" ?? lol
  341. strpos($get["s"], "\"") !== false || // contains quotes
  342. strpos($get["s"], ":") !== false // contains potential site: operator or whatever the fuck
  343. ){
  344. return $this->web_html($get);
  345. }
  346. // no quotes sent, do full web search
  347. return $this->web_full($get);
  348. }
  349. }
  350. public function web_html($get, $npt = null){
  351. $out = [
  352. "status" => "ok",
  353. "spelling" => [
  354. "type" => "no_correction",
  355. "using" => null,
  356. "correction" => null
  357. ],
  358. "npt" => null,
  359. "answer" => [],
  360. "web" => [],
  361. "image" => [],
  362. "video" => [],
  363. "news" => [],
  364. "related" => []
  365. ];
  366. if($npt !== null){
  367. [$get_filters, $proxy] = $npt;
  368. $get_filters = json_decode($get_filters, true);
  369. }else{
  370. if(strlen($get["s"]) === 0){
  371. throw new Exception("Search term is empty!");
  372. }
  373. $proxy = $this->backend->get_ip();
  374. // generate filters
  375. $get_filters = [
  376. "q" => $get["s"]
  377. ];
  378. if($get["country"] == "any"){
  379. $get_filters["kl"] = "wt-wt";
  380. }else{
  381. $get_filters["kl"] = $get["country"];
  382. }
  383. switch($get["nsfw"]){
  384. case "yes": $get_filters["kp"] = "-2"; break;
  385. case "maybe": $get_filters["kp"] = "-1"; break;
  386. case "no": $get_filters["kp"] = "1"; break;
  387. }
  388. $df = true;
  389. if($get["newer"] === false){
  390. if($get["older"] !== false){
  391. $start = 36000;
  392. $end = $get["older"];
  393. }else{
  394. $df = false;
  395. }
  396. }else{
  397. $start = $get["newer"];
  398. if($get["older"] !== false){
  399. $end = $get["older"];
  400. }else{
  401. $end = time();
  402. }
  403. }
  404. if($df === true){
  405. $get_filters["df"] = date("Y-m-d", $start) . ".." . date("Y-m-d", $end);
  406. }
  407. }
  408. //
  409. // Get HTML
  410. //
  411. try{
  412. $html = $this->get(
  413. $proxy,
  414. "https://html.duckduckgo.com/html/",
  415. $get_filters
  416. );
  417. }catch(Exception $e){
  418. throw new Exception("Failed to fetch search page");
  419. }
  420. //$html = file_get_contents("scraper/ddg.html");
  421. $this->fuckhtml->load($html);
  422. //
  423. // Detect captcha
  424. //
  425. $forms =
  426. $this->fuckhtml
  427. ->getElementsByTagName(
  428. "form"
  429. );
  430. foreach($forms as $form){
  431. if(
  432. isset($form["attributes"]["id"]) &&
  433. $form["attributes"]["id"] == "challenge-form"
  434. ){
  435. throw new Exception("DuckDuckGo returned an image captcha");
  436. }
  437. }
  438. foreach(array_reverse($forms) as $form){
  439. $this->fuckhtml->load($form);
  440. $input_probe =
  441. $this->fuckhtml
  442. ->getElementsByAttributeValue(
  443. "value",
  444. "Next",
  445. "input"
  446. );
  447. if(count($input_probe) !== 0){
  448. // found next page!
  449. $inputs =
  450. $this->fuckhtml
  451. ->getElementsByAttributeValue(
  452. "type",
  453. "hidden",
  454. "input"
  455. );
  456. $query = [];
  457. foreach($inputs as $q){
  458. $query[
  459. $this->fuckhtml
  460. ->getTextContent(
  461. $q["attributes"]["name"]
  462. )
  463. ] =
  464. $this->fuckhtml
  465. ->getTextContent(
  466. $q["attributes"]["value"]
  467. );
  468. }
  469. $out["npt"] =
  470. $this->backend->store(
  471. "0," . json_encode($query),
  472. "web",
  473. $proxy
  474. );
  475. break;
  476. }
  477. }
  478. // reset
  479. $this->fuckhtml->load($html);
  480. //
  481. // parse wikipedia answer
  482. //
  483. $wiki_wrapper =
  484. $this->fuckhtml
  485. ->getElementsByClassName(
  486. "zci-wrapper",
  487. "div"
  488. );
  489. if(count($wiki_wrapper) !== 0){
  490. $this->fuckhtml->load($wiki_wrapper[0]);
  491. $a =
  492. $this->fuckhtml
  493. ->getElementsByTagName(
  494. "a"
  495. );
  496. if(count($a) !== 0){
  497. $link =
  498. $this->unshiturl(
  499. $this->fuckhtml
  500. ->getTextContent(
  501. $a[0]["attributes"]["href"]
  502. )
  503. );
  504. }else{
  505. $link = null;
  506. }
  507. $title =
  508. $this->fuckhtml
  509. ->getElementsByTagName(
  510. "h1"
  511. );
  512. if(count($title) !== 0){
  513. $title =
  514. $this->fuckhtml
  515. ->getTextContent(
  516. $title[0]
  517. );
  518. }else{
  519. $title = null;
  520. }
  521. $description =
  522. $this->fuckhtml
  523. ->getElementById(
  524. "zero_click_abstract",
  525. "div"
  526. );
  527. if($description !== false){
  528. $this->fuckhtml->load($description);
  529. $thumb =
  530. $this->fuckhtml
  531. ->getElementsByTagName(
  532. "img"
  533. );
  534. if(count($thumb) !== 0){
  535. $thumb =
  536. $this->fuckhtml
  537. ->getTextContent(
  538. $thumb[0]["attributes"]["src"]
  539. );
  540. }else{
  541. $thumb = null;
  542. }
  543. $as =
  544. $this->fuckhtml
  545. ->getElementsByTagName(
  546. "a"
  547. );
  548. foreach($as as $a){
  549. $description["innerHTML"] =
  550. str_replace(
  551. $a["outerHTML"],
  552. "",
  553. $description["innerHTML"]
  554. );
  555. }
  556. $description =
  557. $this->fuckhtml
  558. ->getTextContent(
  559. $description
  560. );
  561. $out["answer"][] = [
  562. "title" => $title,
  563. "description" => [
  564. [
  565. "type" => "text",
  566. "value" => $description
  567. ]
  568. ],
  569. "url" => $link,
  570. "thumb" => $thumb,
  571. "table" => [],
  572. "sublink" => []
  573. ];
  574. }
  575. // reset
  576. $this->fuckhtml->load($html);
  577. }
  578. //
  579. // Get results
  580. //
  581. $results =
  582. $this->fuckhtml
  583. ->getElementsByClassName(
  584. "result",
  585. "div"
  586. );
  587. foreach($results as $result){
  588. $this->fuckhtml->load($result);
  589. if(stripos($result["attributes"]["class"], "result--ad") !== false){
  590. // found an ad
  591. continue;
  592. }
  593. $title =
  594. $this->fuckhtml
  595. ->getElementsByTagName(
  596. "h2"
  597. );
  598. if(count($title) === 0){
  599. // should not happen
  600. continue;
  601. }
  602. $title =
  603. $this->fuckhtml
  604. ->getTextContent(
  605. $title[0]
  606. );
  607. $description_obj =
  608. $this->fuckhtml
  609. ->getElementsByClassName(
  610. "result__snippet",
  611. "a"
  612. );
  613. if(count($description_obj) === 0){
  614. $description = null;
  615. }else{
  616. $description =
  617. $this->titledots(
  618. $this->fuckhtml
  619. ->getTextContent(
  620. $description_obj[0]
  621. )
  622. );
  623. }
  624. $url =
  625. $this->fuckhtml
  626. ->getTextContent(
  627. $description_obj[0]["attributes"]["href"]
  628. );
  629. $out["web"][] = [
  630. "title" => $this->titledots($title),
  631. "description" => $description,
  632. "url" => $this->unshiturl($url),
  633. "date" => null,
  634. "type" => "web",
  635. "thumb" => [
  636. "ratio" => null,
  637. "url" => null
  638. ],
  639. "sublink" => [],
  640. "table" => []
  641. ];
  642. }
  643. return $out;
  644. }
  645. public function web_full($get, $npt = null){
  646. $out = [
  647. "status" => "ok",
  648. "spelling" => [
  649. "type" => "no_correction",
  650. "using" => null,
  651. "correction" => null
  652. ],
  653. "npt" => null,
  654. "answer" => [],
  655. "web" => [],
  656. "image" => [],
  657. "video" => [],
  658. "news" => [],
  659. "related" => []
  660. ];
  661. if($npt !== null){
  662. [$js_link, $proxy] = $npt;
  663. $js_link = "https://links.duckduckgo.com" . $js_link;
  664. $html = "";
  665. $get["extendedsearch"] = "no";
  666. }else{
  667. if(strlen($get["s"]) === 0){
  668. throw new Exception("Search term is empty!");
  669. }
  670. $proxy = $this->backend->get_ip();
  671. // generate filters
  672. $get_filters = [
  673. "q" => $get["s"]
  674. ];
  675. if($get["country"] == "any"){
  676. $get_filters["kl"] = "wt-wt";
  677. }else{
  678. $get_filters["kl"] = $get["country"];
  679. }
  680. switch($get["nsfw"]){
  681. case "yes": $get_filters["kp"] = "-2"; break;
  682. case "maybe": $get_filters["kp"] = "-1"; break;
  683. case "no": $get_filters["kp"] = "1"; break;
  684. }
  685. $df = true;
  686. if($get["newer"] === false){
  687. if($get["older"] !== false){
  688. $start = 36000;
  689. $end = $get["older"];
  690. }else{
  691. $df = false;
  692. }
  693. }else{
  694. $start = $get["newer"];
  695. if($get["older"] !== false){
  696. $end = $get["older"];
  697. }else{
  698. $end = time();
  699. }
  700. }
  701. if($df === true){
  702. $get_filters["df"] = date("Y-m-d", $start) . ".." . date("Y-m-d", $end);
  703. }
  704. //
  705. // Get HTML
  706. //
  707. try{
  708. $html = $this->get(
  709. $proxy,
  710. "https://duckduckgo.com/",
  711. $get_filters
  712. );
  713. }catch(Exception $e){
  714. throw new Exception("Failed to fetch search page");
  715. }
  716. $this->fuckhtml->load($html);
  717. $script =
  718. $this->fuckhtml
  719. ->getElementById(
  720. "deep_preload_link",
  721. "link"
  722. );
  723. if(
  724. $script === null ||
  725. !isset($script["attributes"]["href"])
  726. ){
  727. throw new Exception("Failed to grep d.js");
  728. }
  729. $js_link =
  730. $this->fuckhtml
  731. ->getTextContent(
  732. $script["attributes"]["href"]
  733. );
  734. }
  735. //
  736. // Get d.js
  737. //
  738. try{
  739. $js = $this->get(
  740. $proxy,
  741. $js_link,
  742. [],
  743. ddg::req_xhr
  744. );
  745. }catch(Exception $e){
  746. throw new Exception("Failed to fetch d.js");
  747. }
  748. //$js = file_get_contents("scraper/fuck.js");
  749. //echo htmlspecialchars($js);
  750. $js_tmp =
  751. preg_split(
  752. '/DDG\.pageLayout\.load\(\s*\'d\'\s*,\s*/',
  753. $js,
  754. 2
  755. );
  756. if(count($js_tmp) <= 1){
  757. //
  758. // Detect javascript challenge
  759. //
  760. if(
  761. preg_match(
  762. '/DDG\.deep\.initialize\(\'([^\']+)\'\ *\+ *jsa/i',
  763. $js,
  764. $challenge_url
  765. )
  766. ){
  767. throw new Exception("DuckDuckGo returned a JSA challenge");
  768. }
  769. //
  770. // Detect JavaScript anomaly failure thingy
  771. //
  772. if(
  773. preg_match(
  774. '/DDG.deep.anomalyDetectionBlock\({/',
  775. $js
  776. )
  777. ){
  778. throw new Exception("DuckDuckGo detected an anomaly in the Javascript challenge response");
  779. }
  780. throw new Exception("Failed to grep pageLayout(d)");
  781. }
  782. $json =
  783. json_decode(
  784. $this->fuckhtml
  785. ->extract_json(
  786. $js_tmp[1]
  787. ),
  788. true
  789. );
  790. if($json === null){
  791. throw new Exception("Failed to decode JSON");
  792. }
  793. //
  794. // Get search results + NPT token
  795. //
  796. foreach($json as $item){
  797. if(isset($item["c"])){
  798. if(
  799. !isset($item["s"]) &&
  800. isset($item["t"]) &&
  801. (
  802. $item["t"] == "DEEP_ERROR_NO_RESULTS" ||
  803. $item["t"] == "DEEP_SIMPLE_NO_RESULTS"
  804. )
  805. ){
  806. return $out;
  807. }
  808. $table = [];
  809. // get youtube video information
  810. if(isset($item["video"]["thumbnail_url_template"])){
  811. $thumb =
  812. [
  813. "ratio" => "16:9",
  814. "url" => $this->bingimg($item["video"]["thumbnail_url_template"])
  815. ];
  816. }else{
  817. $thumb =
  818. [
  819. "ratio" => null,
  820. "url" => null
  821. ];
  822. }
  823. // get table items
  824. if(isset($item["rf"])){
  825. foreach($item["rf"] as $hint){
  826. if(
  827. !isset($hint["label"]["text"]) ||
  828. !isset($hint["items"][0]["text"])
  829. ){
  830. continue;
  831. }
  832. $text = [];
  833. foreach($hint["items"] as $text_part){
  834. $text[] = $text_part["text"];
  835. }
  836. $text = implode(", ", $text);
  837. if(is_numeric($text)){
  838. $text = number_format((string)$text);
  839. }
  840. $table[$hint["label"]["text"]] = $text;
  841. }
  842. }
  843. // get ratings
  844. if(isset($item["ar"])){
  845. foreach($item["ar"] as $rating){
  846. if(
  847. isset($rating["aggregateRating"]["bestRating"]) &&
  848. isset($rating["aggregateRating"]["ratingValue"])
  849. ){
  850. $text = $rating["aggregateRating"]["ratingValue"] . "/" . $rating["aggregateRating"]["bestRating"];
  851. if(isset($rating["aggregateRating"]["reviewCount"])){
  852. $text .= " (" . number_format($rating["aggregateRating"]["reviewCount"]) . " votes)";
  853. }
  854. $table["Rating"] = $text;
  855. }
  856. }
  857. }
  858. // get sublinks
  859. $sublinks = [];
  860. if(isset($item["l"])){
  861. foreach($item["l"] as $sublink){
  862. $sublinks[] = [
  863. "title" => $this->titledots($sublink["text"]),
  864. "description" => $this->titledots($sublink["snippet"]),
  865. "url" => $sublink["targetUrl"],
  866. "date" => null
  867. ];
  868. }
  869. }
  870. $title =
  871. $this->titledots(
  872. $this->fuckhtml
  873. ->getTextContent(
  874. $item["t"]
  875. )
  876. );
  877. if(
  878. $title == "EOF" &&
  879. strpos(
  880. $item["c"],
  881. "google"
  882. )
  883. ){
  884. continue;
  885. }
  886. // parse search result
  887. $out["web"][] = [
  888. "title" =>
  889. $this->titledots(
  890. $this->fuckhtml
  891. ->getTextContent(
  892. $item["t"]
  893. )
  894. ),
  895. "description" =>
  896. isset($item["a"]) ?
  897. $this->titledots(
  898. $this->fuckhtml
  899. ->getTextContent(
  900. $item["a"]
  901. )
  902. ) : null,
  903. "url" => $this->unshiturl($item["c"]),
  904. "date" =>
  905. isset($item["e"]) ?
  906. strtotime($item["e"]) : null,
  907. "type" => "web",
  908. "thumb" => $thumb,
  909. "sublink" => $sublinks,
  910. "table" => $table
  911. ];
  912. continue;
  913. }
  914. if(isset($item["n"])){
  915. // get NPT
  916. $out["npt"] =
  917. $this->backend->store(
  918. "1," . $item["n"],
  919. "web",
  920. $proxy
  921. );
  922. continue;
  923. }
  924. }
  925. //
  926. // Get spelling
  927. //
  928. $js_tmp =
  929. preg_split(
  930. '/DDG\.page\.showMessage\(\s*\'spelling\'\s*,\s*/',
  931. $js,
  932. 2
  933. );
  934. if(count($js_tmp) > 1){
  935. $json =
  936. json_decode(
  937. $this->fuckhtml
  938. ->extract_json(
  939. $js_tmp[1]
  940. ),
  941. true
  942. );
  943. if($json !== null){
  944. // parse spelling
  945. // qc=2: including
  946. switch((int)$json["qc"]){
  947. case 2:
  948. $type = "including";
  949. break;
  950. default:
  951. $type = "not_many";
  952. break;
  953. }
  954. $out["spelling"] = [
  955. "type" => $type,
  956. "using" =>
  957. $this->fuckhtml
  958. ->getTextContent(
  959. $json["suggestion"]
  960. ),
  961. "correction" => html_entity_decode($json["recourseText"])
  962. ];
  963. }
  964. }
  965. //
  966. // Get images
  967. //
  968. $js_tmp =
  969. preg_split(
  970. '/DDG\.duckbar\.load\(\s*\'images\'\s*,\s*/',
  971. $js,
  972. 2
  973. );
  974. if(count($js_tmp) > 1){
  975. $json =
  976. json_decode(
  977. $this->fuckhtml
  978. ->extract_json(
  979. $js_tmp[1]
  980. ),
  981. true
  982. );
  983. if($json !== null){
  984. foreach($json["results"] as $image){
  985. $ratio = $this->bingratio((int)$image["width"], (int)$image["height"]);
  986. $out["image"][] = [
  987. "title" => $image["title"],
  988. "source" => [
  989. [
  990. "url" => $image["image"],
  991. "width" => (int)$image["width"],
  992. "height" => (int)$image["height"]
  993. ],
  994. [
  995. "url" => $this->bingimg($image["thumbnail"]),
  996. "width" => $ratio[0],
  997. "height" => $ratio[1]
  998. ]
  999. ],
  1000. "url" => $this->unshiturl($image["url"])
  1001. ];
  1002. }
  1003. }
  1004. }
  1005. //
  1006. // Get videos
  1007. //
  1008. $js_tmp =
  1009. preg_split(
  1010. '/DDG\.duckbar\.load\(\s*\'videos\'\s*,\s*/',
  1011. $js,
  1012. 2
  1013. );
  1014. if(count($js_tmp) > 1){
  1015. $json =
  1016. json_decode(
  1017. $this->fuckhtml
  1018. ->extract_json(
  1019. $js_tmp[1]
  1020. ),
  1021. true
  1022. );
  1023. if($json !== null){
  1024. foreach($json["results"] as $video){
  1025. $thumb = [
  1026. "ratio" => null,
  1027. "url" => null
  1028. ];
  1029. foreach(["large", "medium", "small"] as $contender){
  1030. if(isset($video["images"][$contender])){
  1031. $thumb = [
  1032. "ratio" => "16:9",
  1033. "url" => $this->bingimg($video["images"][$contender])
  1034. ];
  1035. break;
  1036. }
  1037. }
  1038. $out["video"][] = [
  1039. "title" => $this->titledots($video["title"]),
  1040. "description" =>
  1041. $video["description"] != "" ?
  1042. $this->titledots($video["description"]) : null,
  1043. "date" =>
  1044. isset($video["published"]) ?
  1045. strtotime($video["published"]) : null,
  1046. "duration" =>
  1047. $video["duration"] != "" ?
  1048. $this->hms2int($video["duration"]) : null,
  1049. "views" =>
  1050. isset($video["statistics"]["viewCount"]) ?
  1051. (int)$video["statistics"]["viewCount"] : null,
  1052. "thumb" => $thumb,
  1053. "url" => $this->unshiturl($video["content"])
  1054. ];
  1055. }
  1056. }
  1057. }
  1058. //
  1059. // Get news
  1060. //
  1061. $js_tmp =
  1062. preg_split(
  1063. '/DDG\.duckbar\.load\(\s*\'news\'\s*,\s*/',
  1064. $js,
  1065. 2
  1066. );
  1067. if(count($js_tmp) > 1){
  1068. $json =
  1069. json_decode(
  1070. $this->fuckhtml
  1071. ->extract_json(
  1072. $js_tmp[1]
  1073. ),
  1074. true
  1075. );
  1076. if($json !== null){
  1077. foreach($json["results"] as $news){
  1078. if(isset($news["image"])){
  1079. $thumb = [
  1080. "ratio" => "16:9",
  1081. "url" => $news["image"]
  1082. ];
  1083. }else{
  1084. $thumb = [
  1085. "ratio" => null,
  1086. "url" => null
  1087. ];
  1088. }
  1089. $out["news"][] = [
  1090. "title" => $news["title"],
  1091. "description" =>
  1092. $this->fuckhtml
  1093. ->getTextContent(
  1094. $news["excerpt"]
  1095. ),
  1096. "date" => (int)$news["date"],
  1097. "thumb" => $thumb,
  1098. "url" => $news["url"]
  1099. ];
  1100. }
  1101. }
  1102. }
  1103. //
  1104. // Get related searches
  1105. //
  1106. $js_tmp =
  1107. preg_split(
  1108. '/DDG\.duckbar\.loadModule\(\s*\'related_searches\'\s*,\s*/',
  1109. $js,
  1110. 2
  1111. );
  1112. if(count($js_tmp) > 1){
  1113. $json =
  1114. json_decode(
  1115. $this->fuckhtml
  1116. ->extract_json(
  1117. $js_tmp[1]
  1118. ),
  1119. true
  1120. );
  1121. if($json !== null){
  1122. foreach($json["results"] as $related){
  1123. $out["related"][] = $related["text"];
  1124. }
  1125. }
  1126. }
  1127. //
  1128. // Get instant answers
  1129. //
  1130. $js_tmp =
  1131. preg_split(
  1132. '/DDG\.duckbar\.add\(\s*/',
  1133. $html . $js,
  1134. 2
  1135. );
  1136. if(count($js_tmp) > 1){
  1137. $json =
  1138. json_decode(
  1139. $this->fuckhtml
  1140. ->extract_json(
  1141. $js_tmp[1]
  1142. ),
  1143. true
  1144. );
  1145. if($json !== null){
  1146. $json = $json["data"];
  1147. $table = [];
  1148. $sublinks = [];
  1149. $description = [];
  1150. // get official website
  1151. if(
  1152. isset($json["OfficialWebsite"]) &&
  1153. $json["OfficialWebsite"] !== null
  1154. ){
  1155. $sublinks["Website"] = $json["OfficialWebsite"];
  1156. }
  1157. // get sublinks & table elements
  1158. if(isset($json["Infobox"]["content"])){
  1159. foreach($json["Infobox"]["content"] as $info){
  1160. if($info["data_type"] == "string"){
  1161. // add table element
  1162. $table[$info["label"]] = $info["value"];
  1163. continue;
  1164. }
  1165. if($info["data_type"] == "wd_description"){
  1166. $description[] = [
  1167. "type" => "quote",
  1168. "value" => $info["value"]
  1169. ];
  1170. continue;
  1171. }
  1172. // add sublink
  1173. switch($info["data_type"]){
  1174. case "official_site":
  1175. case "official_website":
  1176. $type = "Website";
  1177. break;
  1178. case "wikipedia": $type = "Wikipedia"; break;
  1179. case "itunes": $type = "iTunes"; break;
  1180. case "amazon": $type = "Amazon"; break;
  1181. case "imdb_title_id":
  1182. case "imdb_id":
  1183. case "imdb_name_id":
  1184. $type = "IMDb";
  1185. $delim = substr($info["value"], 0, 2);
  1186. if($delim == "nm"){
  1187. $prefix = "https://www.imdb.com/name/";
  1188. }elseif($delim == "tt"){
  1189. $prefix = "https://www.imdb.com/title/";
  1190. }elseif($delim == "co"){
  1191. $prefix = "https://www.imdb.com/search/title/?companies=";
  1192. }else{
  1193. $prefix = "https://www.imdb.com/title/";
  1194. }
  1195. break;
  1196. case "imdb_name_id": $prefix = "https://www.imdb.com/name/"; $type = "IMDb"; break;
  1197. case "twitter_profile": $prefix = "https://twitter.com/"; $type = "Twitter"; break;
  1198. case "instagram_profile": $prefix = "https://instagram.com/"; $type = "Instagram"; break;
  1199. case "facebook_profile": $prefix = "https://facebook.com/"; $type = "Facebook"; break;
  1200. case "spotify_artist_id": $prefix = "https://open.spotify.com/artist/"; $type = "Spotify"; break;
  1201. case "itunes_artist_id": $prefix = "https://music.apple.com/us/artist/"; $type = "iTunes"; break;
  1202. case "rotten_tomatoes": $prefix = "https://rottentomatoes.com/"; $type = "Rotten Tomatoes"; break;
  1203. case "youtube_channel": $prefix = "https://youtube.com/channel/"; $type = "YouTube"; break;
  1204. case "soundcloud_id": $prefix = "https://soundcloud.com/"; $type = "SoundCloud"; break;
  1205. default:
  1206. $prefix = null;
  1207. $type = false;
  1208. }
  1209. if($type !== false){
  1210. if($prefix === null){
  1211. $sublinks[$type] = $info["value"];
  1212. }else{
  1213. $sublinks[$type] = $prefix . $info["value"];
  1214. }
  1215. }
  1216. }
  1217. }
  1218. if(isset($json["Abstract"])){
  1219. $description = $this->parse_rich_text($json["Abstract"]);
  1220. }
  1221. if(
  1222. !isset($json["Image"]) ||
  1223. $json["Image"] == "" ||
  1224. $json["Image"] === null ||
  1225. $json["Image"] == "https://duckduckgo.com/i/"
  1226. ){
  1227. $image = null;
  1228. }else{
  1229. if(
  1230. preg_match(
  1231. '/^https?:\/\//',
  1232. $json["Image"]
  1233. )
  1234. ){
  1235. $image = $json["Image"];
  1236. }else{
  1237. $image = "https://duckduckgo.com" . $json["Image"];
  1238. }
  1239. }
  1240. $out["answer"][] = [
  1241. "title" => $json["Heading"],
  1242. "description" => $description,
  1243. "url" => $json["AbstractURL"],
  1244. "thumb" => $image,
  1245. "table" => $table,
  1246. "sublink" => $sublinks
  1247. ];
  1248. }
  1249. }
  1250. if($get["extendedsearch"] == "no"){
  1251. return $out;
  1252. }
  1253. //
  1254. // Parse additional data endpoints
  1255. //
  1256. //nrj('/js/spice/dictionary/definition/create', null, null, null, null, 'dictionary_definition');
  1257. preg_match_all(
  1258. '/nrj\(\s*\'([^\']+)\'/',
  1259. $js,
  1260. $nrj
  1261. );
  1262. if(isset($nrj[1])){
  1263. foreach($nrj[1] as $potential_endpoint){
  1264. //
  1265. // Probe for wordnik definition
  1266. //
  1267. preg_match(
  1268. '/\/js\/spice\/dictionary\/definition\/([^\/]+)/',
  1269. $potential_endpoint,
  1270. $word
  1271. );
  1272. if(isset($word[1])){
  1273. $word = $word[1];
  1274. // found wordnik definition & word
  1275. try{
  1276. $nik =
  1277. $this->get(
  1278. $proxy,
  1279. "https://duckduckgo.com/js/spice/dictionary/definition/" . $word,
  1280. [],
  1281. ddg::req_xhr
  1282. );
  1283. }catch(Exception $e){
  1284. // fail gracefully
  1285. return $out;
  1286. }
  1287. // remove javascript
  1288. $js_tmp =
  1289. preg_split(
  1290. '/ddg_spice_dictionary_definition\(\s*/',
  1291. $nik,
  1292. 2
  1293. );
  1294. if(count($js_tmp) > 1){
  1295. $nik =
  1296. json_decode(
  1297. $this->fuckhtml
  1298. ->extract_json(
  1299. $js_tmp[1]
  1300. ),
  1301. true
  1302. );
  1303. }
  1304. if($nik === null){
  1305. return $out;
  1306. }
  1307. $answer_cat = [];
  1308. $answer = [];
  1309. foreach($nik as $snippet){
  1310. if(!isset($snippet["partOfSpeech"])){ continue; }
  1311. $push = [];
  1312. // add text snippet
  1313. if(isset($snippet["text"])){
  1314. $push[] = [
  1315. "type" => "text",
  1316. "value" =>
  1317. $this->fuckhtml
  1318. ->getTextContent(
  1319. $snippet["text"]
  1320. )
  1321. ];
  1322. }
  1323. // add example uses
  1324. if(isset($snippet["exampleUses"])){
  1325. foreach($snippet["exampleUses"] as $example){
  1326. $push[] = [
  1327. "type" => "quote",
  1328. "value" => "\"" .
  1329. $this->fuckhtml
  1330. ->getTextContent(
  1331. $example["text"]
  1332. ) . "\""
  1333. ];
  1334. }
  1335. }
  1336. // add citations
  1337. if(isset($snippet["citations"])){
  1338. foreach($snippet["citations"] as $citation){
  1339. if(!isset($citation["cite"])){ continue; }
  1340. $text =
  1341. $this->fuckhtml
  1342. ->getTextContent(
  1343. $citation["cite"]
  1344. );
  1345. if(isset($citation["source"])){
  1346. $text .=
  1347. " - " .
  1348. $this->fuckhtml
  1349. ->getTextContent(
  1350. $citation["source"]
  1351. );
  1352. }
  1353. $push[] = [
  1354. "type" => "quote",
  1355. "value" => $text
  1356. ];
  1357. }
  1358. }
  1359. // add related words
  1360. if(isset($snippet["relatedWords"])){
  1361. $relations = [];
  1362. foreach($snippet["relatedWords"] as $related){
  1363. $words = [];
  1364. foreach($related["words"] as $wrd){
  1365. $words[] =
  1366. $this->fuckhtml
  1367. ->getTextContent(
  1368. $wrd
  1369. );
  1370. }
  1371. if(
  1372. count($words) !== 0 &&
  1373. isset($related["relationshipType"])
  1374. ){
  1375. $relations[ucfirst($related["relationshipType"]) . "s"] =
  1376. implode(", ", $words);
  1377. }
  1378. }
  1379. foreach($relations as $relation_title => $relation_content){
  1380. $push[] = [
  1381. "type" => "quote",
  1382. "value" => $relation_title . ": " . $relation_content
  1383. ];
  1384. }
  1385. }
  1386. if(count($push) !== 0){
  1387. // push data to answer_cat
  1388. if(!isset($answer_cat[$snippet["partOfSpeech"]])){
  1389. $answer_cat[$snippet["partOfSpeech"]] = [];
  1390. }
  1391. $answer_cat[$snippet["partOfSpeech"]] =
  1392. array_merge(
  1393. $answer_cat[$snippet["partOfSpeech"]],
  1394. $push
  1395. );
  1396. }
  1397. }
  1398. foreach($answer_cat as $answer_title => $answer_content){
  1399. $i = 0;
  1400. $answer[] = [
  1401. "type" => "title",
  1402. "value" => $answer_title
  1403. ];
  1404. $old_type = $answer[count($answer) - 1]["type"];
  1405. foreach($answer_content as $ans){
  1406. if(
  1407. $ans["type"] == "text" &&
  1408. $old_type == "text"
  1409. ){
  1410. $i++;
  1411. $c = count($answer) - 1;
  1412. // append text to existing textfield
  1413. $answer[$c] = [
  1414. "type" => "text",
  1415. "value" => $answer[$c]["value"] . "\n" . $i . ". " . $ans["value"]
  1416. ];
  1417. }elseif($ans["type"] == "text"){
  1418. $i++;
  1419. $answer[] = [
  1420. "type" => "text",
  1421. "value" => $i . ". " . $ans["value"]
  1422. ];
  1423. }else{
  1424. // append normally
  1425. $answer[] = $ans;
  1426. }
  1427. $old_type = $ans["type"];
  1428. }
  1429. }
  1430. // yeah.. sometimes duckduckgo doesnt give us a definition back
  1431. if(count($answer) !== 0){
  1432. $out["answer"][] = [
  1433. "title" => ucfirst(rawurldecode($word)),
  1434. "description" => $answer,
  1435. "url" => "https://www.wordnik.com/words/" . $word,
  1436. "thumb" => null,
  1437. "table" => [],
  1438. "sublink" => []
  1439. ];
  1440. }
  1441. }
  1442. //
  1443. // Parse stackoverflow answer
  1444. //
  1445. if(
  1446. preg_match(
  1447. '/^\/a\.js.*src_id=stack_overflow/',
  1448. $potential_endpoint
  1449. )
  1450. ){
  1451. // found stackoverflow answer
  1452. try{
  1453. $json =
  1454. $this->get(
  1455. $proxy,
  1456. "https://duckduckgo.com" . $potential_endpoint,
  1457. [],
  1458. ddg::req_xhr
  1459. );
  1460. }catch(Exception $e){
  1461. // fail gracefully
  1462. return $out;
  1463. }
  1464. $json = explode("DDG.duckbar.add_array(", $json, 2);
  1465. if(count($json) === 2){
  1466. $json =
  1467. json_decode(
  1468. $this->fuckhtml
  1469. ->extract_json(
  1470. $json[1]
  1471. ),
  1472. true
  1473. );
  1474. if(
  1475. $json !== null &&
  1476. isset($json[0]["data"])
  1477. ){
  1478. $json = $json[0]["data"];
  1479. foreach($json as $answer){
  1480. if(isset($answer["Heading"])){
  1481. $title = $answer["Heading"];
  1482. }elseif(isset($answer["title"])){
  1483. $title = $answer["title"];
  1484. }else{
  1485. $title = null;
  1486. }
  1487. if(
  1488. $title !== null &&
  1489. isset($answer["Abstract"])
  1490. ){
  1491. $description = $this->parse_rich_text($answer["Abstract"]);
  1492. $out["answer"][] = [
  1493. "title" => $title,
  1494. "description" => $description,
  1495. "url" => $answer["AbstractURL"],
  1496. "thumb" => null,
  1497. "table" => [],
  1498. "sublink" => []
  1499. ];
  1500. }
  1501. }
  1502. }
  1503. }
  1504. }
  1505. }
  1506. }
  1507. return $out;
  1508. }
  1509. public function image($get){
  1510. if($get["npt"]){
  1511. [$js_link, $proxy] = $this->backend->get($get["npt"], "images");
  1512. }else{
  1513. if(strlen($get["s"]) === 0){
  1514. throw new Exception("Search term is empty!");
  1515. }
  1516. $proxy = $this->backend->get_ip();
  1517. $filters = [];
  1518. if($get["date"] != "any"){ $filters[] = "time:{$get["date"]}"; }
  1519. if($get["size"] != "any"){ $filters[] = "size:{$get["size"]}"; }
  1520. if($get["color"] != "any"){ $filters[] = "color:{$get["color"]}"; }
  1521. if($get["type"] != "any"){ $filters[] = "type:{$get["type"]}"; }
  1522. if($get["layout"] != "any"){ $filters[] = "layout:{$get["layout"]}"; }
  1523. if($get["license"] != "any"){ $filters[] = "license:{$get["license"]}"; }
  1524. $filters = implode(",", $filters);
  1525. $get_filters = [
  1526. "q" => $get["s"],
  1527. "iax" => "images",
  1528. "ia" => "images"
  1529. ];
  1530. if($filters != ""){
  1531. $get_filters["iaf"] = $filters;
  1532. }
  1533. $nsfw = $get["nsfw"] == "yes" ? "-1" : "1";
  1534. $get_filters["kp"] = $nsfw;
  1535. try{
  1536. $html = $this->get(
  1537. $proxy,
  1538. "https://duckduckgo.com",
  1539. $get_filters,
  1540. ddg::req_web
  1541. );
  1542. }catch(Exception $err){
  1543. throw new Exception("Failed to fetch search page");
  1544. }
  1545. preg_match(
  1546. '/vqd="([0-9-]+)"/',
  1547. $html,
  1548. $vqd
  1549. );
  1550. if(!isset($vqd[1])){
  1551. throw new Exception("Failed to grep VQD token");
  1552. }
  1553. $js_link =
  1554. "i.js?" .
  1555. http_build_query([
  1556. "l" => $get["country"],
  1557. "o" => "json",
  1558. "q" => $get["s"],
  1559. "vqd" => $vqd[1],
  1560. "f" => $filters,
  1561. "p" => $nsfw
  1562. ]);
  1563. }
  1564. try{
  1565. $json =
  1566. $this->get(
  1567. $proxy,
  1568. "https://duckduckgo.com/" . $js_link,
  1569. [],
  1570. ddg::req_xhr
  1571. );
  1572. }catch(Exception $error){
  1573. throw new Exception("Failed to get i.js");
  1574. }
  1575. $json = json_decode($json, true);
  1576. if($json === null){
  1577. throw new Exception("Failed to decode JSON");
  1578. }
  1579. $out = [
  1580. "status" => "ok",
  1581. "npt" => null,
  1582. "image" => []
  1583. ];
  1584. if(!isset($json["results"])){
  1585. return $out;
  1586. }
  1587. // get npt
  1588. if(
  1589. isset($json["next"]) &&
  1590. $json["next"] !== null
  1591. ){
  1592. $vqd = null;
  1593. if(isset($vqd[1])){
  1594. $vqd = $vqd[1];
  1595. }else{
  1596. $vqd = array_values($json["vqd"]);
  1597. if(count($vqd) > 0){
  1598. $vqd = $vqd[0];
  1599. }
  1600. }
  1601. if($vqd !== null){
  1602. $out["npt"] =
  1603. $this->backend->store(
  1604. $json["next"] . "&vqd=" . $vqd,
  1605. "images",
  1606. $proxy
  1607. );
  1608. }
  1609. }
  1610. // get images
  1611. foreach($json["results"] as $image){
  1612. $ratio =
  1613. $this->bingratio(
  1614. (int)$image["width"],
  1615. (int)$image["height"]
  1616. );
  1617. $out["image"][] = [
  1618. "title" => $this->titledots($image["title"]),
  1619. "source" => [
  1620. [
  1621. "url" => $image["image"],
  1622. "width" => (int)$image["width"],
  1623. "height" => (int)$image["height"]
  1624. ],
  1625. [
  1626. "url" => $this->bingimg($image["thumbnail"]),
  1627. "width" => $ratio[0],
  1628. "height" => $ratio[1]
  1629. ]
  1630. ],
  1631. "url" => $this->unshiturl($image["url"])
  1632. ];
  1633. }
  1634. return $out;
  1635. }
  1636. public function video($get){
  1637. if($get["npt"]){
  1638. [$js_link, $proxy] = $this->backend->get($get["npt"], "videos");
  1639. }else{
  1640. if(strlen($get["s"]) === 0){
  1641. throw new Exception("Search term is empty!");
  1642. }
  1643. $proxy = $this->backend->get_ip();
  1644. $get_filters = [
  1645. "q" => $get["s"],
  1646. "iax" => "videos",
  1647. "ia" => "videos"
  1648. ];
  1649. switch($get["nsfw"]){
  1650. case "yes": $nsfw = "-2"; break;
  1651. case "maybe": $nsfw = "-1"; break;
  1652. case "no": $nsfw = "1"; break;
  1653. }
  1654. $filters = [];
  1655. if($get["date"] != "any"){ $filters[] = "publishedAfter:{$date}"; }
  1656. if($get["resolution"] != "any"){ $filters[] = "videoDefinition:{$resolution}"; }
  1657. if($get["duration"] != "any"){ $filters[] = "videoDuration:{$duration}"; }
  1658. if($get["license"] != "any"){ $filters[] = "videoLicense:{$license}"; }
  1659. $filters = implode(",", $filters);
  1660. if($filters != ""){
  1661. $get_filters["iaf"] = $filters;
  1662. }
  1663. try{
  1664. $html =
  1665. $this->get(
  1666. $proxy,
  1667. "https://duckduckgo.com/",
  1668. $get_filters,
  1669. ddg::req_web
  1670. );
  1671. }catch(Exception $error){
  1672. throw new Exception("Failed to fetch search page");
  1673. }
  1674. preg_match(
  1675. '/vqd="([0-9-]+)"/',
  1676. $html,
  1677. $vqd
  1678. );
  1679. if(!isset($vqd[1])){
  1680. throw new Exception("Failed to grep VQD token");
  1681. }
  1682. $js_link =
  1683. "v.js?" .
  1684. http_build_query([
  1685. "l" => $get["country"],
  1686. "o" => "json",
  1687. "sr" => "1",
  1688. "q" => $get["s"],
  1689. "vqd" => $vqd[1],
  1690. "f" => $filters,
  1691. "p" => $nsfw
  1692. ]);
  1693. }
  1694. try{
  1695. $json =
  1696. $this->get(
  1697. $proxy,
  1698. "https://duckduckgo.com/" . $js_link,
  1699. [],
  1700. ddg::req_xhr
  1701. );
  1702. }catch(Exception $error){
  1703. throw new Exception("Failed to fetch JSON");
  1704. }
  1705. $json = json_decode($json, true);
  1706. if($json === null){
  1707. throw new Exception("Failed to decode JSON");
  1708. }
  1709. $out = [
  1710. "status" => "ok",
  1711. "npt" => null,
  1712. "video" => [],
  1713. "author" => [],
  1714. "livestream" => [],
  1715. "playlist" => [],
  1716. "reel" => []
  1717. ];
  1718. if(!isset($json["results"])){
  1719. return $out;
  1720. }
  1721. // get NPT
  1722. if(
  1723. isset($json["next"]) &&
  1724. $json["next"] !== null
  1725. ){
  1726. $out["npt"] =
  1727. $this->backend->store(
  1728. $json["next"],
  1729. "videos",
  1730. $proxy
  1731. );
  1732. }
  1733. foreach($json["results"] as $video){
  1734. $thumb = [
  1735. "ratio" => null,
  1736. "url" => null
  1737. ];
  1738. foreach(["large", "medium", "small"] as $contender){
  1739. if(isset($video["images"][$contender])){
  1740. $thumb = [
  1741. "ratio" => "16:9",
  1742. "url" => $this->bingimg($video["images"][$contender])
  1743. ];
  1744. break;
  1745. }
  1746. }
  1747. $out["video"][] = [
  1748. "title" => $this->titledots($video["title"]),
  1749. "description" => $this->titledots($video["description"]),
  1750. "author" => [
  1751. "name" =>
  1752. (
  1753. isset($video["uploader"]) &&
  1754. $video["uploader"] != ""
  1755. ) ?
  1756. $video["uploader"] : null,
  1757. "url" => null,
  1758. "avatar" => null
  1759. ],
  1760. "date" =>
  1761. (
  1762. isset($video["published"]) &&
  1763. $video["published"] != ""
  1764. ) ?
  1765. strtotime($video["published"]) : null,
  1766. "duration" =>
  1767. (
  1768. isset($video["duration"]) &&
  1769. $video["duration"] != ""
  1770. ) ?
  1771. $this->hms2int($video["duration"]) : null,
  1772. "views" =>
  1773. isset($video["statistics"]["viewCount"]) ?
  1774. (int)$video["statistics"]["viewCount"] : null,
  1775. "thumb" => $thumb,
  1776. "url" => $this->unshiturl($video["content"])
  1777. ];
  1778. }
  1779. return $out;
  1780. }
  1781. public function news($get){
  1782. if($get["npt"]){
  1783. [$js_link, $proxy] = $this->backend->get($get["npt"], "news");
  1784. }else{
  1785. if(strlen($get["s"]) === 0){
  1786. throw new Exception("Search term is empty!");
  1787. }
  1788. $proxy = $this->backend->get_ip();
  1789. $get_filters = [
  1790. "q" => $get["s"],
  1791. "iar" => "news",
  1792. "ia" => "news"
  1793. ];
  1794. if($get["date"] != "any"){
  1795. $date = $get["date"];
  1796. $get_filters["df"] = $date;
  1797. }else{
  1798. $date = "";
  1799. }
  1800. switch($get["nsfw"]){
  1801. case "yes": $get_filters["kp"] = "-2"; break;
  1802. case "maybe": $get_filters["kp"] = "-1"; break;
  1803. case "no": $get_filters["kp"] = "1"; break;
  1804. }
  1805. try{
  1806. $html =
  1807. $this->get(
  1808. $proxy,
  1809. "https://duckduckgo.com/",
  1810. $get_filters,
  1811. ddg::req_web
  1812. );
  1813. }catch(Exception $error){
  1814. throw new Exception("Failed to fetch search page");
  1815. }
  1816. preg_match(
  1817. '/vqd="([0-9-]+)"/',
  1818. $html,
  1819. $vqd
  1820. );
  1821. if(!isset($vqd[1])){
  1822. throw new Exception("Failed to grep VQD token");
  1823. }
  1824. $js_link =
  1825. "news.js?" .
  1826. http_build_query([
  1827. "l" => $get["country"],
  1828. "o" => "json",
  1829. "noamp" => "1",
  1830. "m" => "30",
  1831. "q" => $get["s"],
  1832. "vqd" => $vqd[1],
  1833. "p" => $get_filters["kp"],
  1834. "df" => $date,
  1835. "u" => "bing"
  1836. ]);
  1837. }
  1838. try{
  1839. $json =
  1840. $this->get(
  1841. $proxy,
  1842. "https://duckduckgo.com/" . $js_link,
  1843. [],
  1844. ddg::req_xhr
  1845. );
  1846. }catch(Exception $error){
  1847. throw new Exception("Failed to fetch JSON");
  1848. }
  1849. $json = json_decode($json, true);
  1850. if($json === null){
  1851. throw new Exception("Failed to decode JSON");
  1852. }
  1853. $out = [
  1854. "status" => "ok",
  1855. "npt" => null,
  1856. "news" => []
  1857. ];
  1858. if(!isset($json["results"])){
  1859. return $out;
  1860. }
  1861. // get NPT
  1862. if(
  1863. isset($json["next"]) &&
  1864. $json["next"] !== null
  1865. ){
  1866. $out["npt"] =
  1867. $this->backend->store(
  1868. $json["next"],
  1869. "news",
  1870. $proxy
  1871. );
  1872. }
  1873. foreach($json["results"] as $news){
  1874. if(
  1875. isset($news["image"]) &&
  1876. $news["image"] != ""
  1877. ){
  1878. $thumb = [
  1879. "ratio" => "16:9",
  1880. "url" => $news["image"]
  1881. ];
  1882. }else{
  1883. $thumb = [
  1884. "ratio" => null,
  1885. "url" => null
  1886. ];
  1887. }
  1888. $out["news"][] = [
  1889. "title" => $news["title"],
  1890. "author" =>
  1891. (
  1892. isset($news["source"]) &&
  1893. $news["source"] != ""
  1894. ) ?
  1895. $news["source"] : null,
  1896. "description" =>
  1897. (
  1898. isset($news["excerpt"]) &&
  1899. $news["excerpt"] != ""
  1900. ) ?
  1901. $this->fuckhtml
  1902. ->getTextContent(
  1903. $news["excerpt"]
  1904. ) : null,
  1905. "date" =>
  1906. isset($news["date"]) ?
  1907. (int)$news["date"] : null,
  1908. "thumb" => $thumb,
  1909. "url" => $this->unshiturl($news["url"])
  1910. ];
  1911. }
  1912. return $out;
  1913. }
  1914. private function parse_rich_text($html){
  1915. $description = [];
  1916. // pre-process the html, remove useless elements
  1917. $html =
  1918. strip_tags(
  1919. $html,
  1920. [
  1921. "h1", "h2", "h3", "h4", "h5", "h6", "h7",
  1922. "pre", "code"
  1923. ]
  1924. );
  1925. $html =
  1926. preg_replace(
  1927. '/<(\/?)pre *[^>]*>\s*<\/?code *[^>]*>/i',
  1928. '<$1pre>',
  1929. $html
  1930. );
  1931. $this->fuckhtml->load($html);
  1932. $tags =
  1933. $this->fuckhtml
  1934. ->getElementsByTagName(
  1935. "*"
  1936. );
  1937. if(count($tags) === 0){
  1938. $description[] = [
  1939. "type" => "text",
  1940. "value" =>
  1941. trim(
  1942. $this->fuckhtml
  1943. ->getTextContent(
  1944. $html,
  1945. true,
  1946. false
  1947. )
  1948. )
  1949. ];
  1950. }else{
  1951. $start = 0;
  1952. $was_code_block = true;
  1953. foreach($tags as $tag){
  1954. $text =
  1955. $this->fuckhtml
  1956. ->getTextContent(
  1957. substr(
  1958. $html,
  1959. $start,
  1960. $tag["startPos"] - $start
  1961. ),
  1962. true,
  1963. false
  1964. );
  1965. if($was_code_block){
  1966. $text = ltrim($text);
  1967. $was_code_block = false;
  1968. }
  1969. $description[] = [
  1970. "type" => "text",
  1971. "value" => $text
  1972. ];
  1973. switch($tag["tagName"]){
  1974. case "pre":
  1975. $append = "code";
  1976. $was_code_block = true;
  1977. $c = count($description) - 1;
  1978. $description[$c]["value"] =
  1979. rtrim($description[$c]["value"]);
  1980. break;
  1981. case "code":
  1982. $append = "inline_code";
  1983. $c = count($description) - 1;
  1984. $description[$c]["value"] =
  1985. rtrim($description[$c]["value"]) . " ";
  1986. break;
  1987. case "h1":
  1988. case "h2":
  1989. case "h3":
  1990. case "h4":
  1991. case "h5":
  1992. case "h6":
  1993. case "h7":
  1994. $append = "title";
  1995. $c = count($description) - 1;
  1996. $description[$c]["value"] =
  1997. rtrim($description[$c]["value"]);
  1998. break;
  1999. }
  2000. $description[] = [
  2001. "type" => $append,
  2002. "value" =>
  2003. trim(
  2004. $this->fuckhtml
  2005. ->getTextContent(
  2006. $tag,
  2007. true,
  2008. false
  2009. )
  2010. )
  2011. ];
  2012. $start = $tag["endPos"];
  2013. }
  2014. // shit out remainder
  2015. $description[] = [
  2016. "type" => "text",
  2017. "value" =>
  2018. trim(
  2019. $this->fuckhtml
  2020. ->getTextContent(
  2021. substr(
  2022. $html,
  2023. $start
  2024. ),
  2025. true,
  2026. false
  2027. )
  2028. )
  2029. ];
  2030. }
  2031. return $description;
  2032. }
  2033. private function titledots($title){
  2034. $substr = substr($title, -3);
  2035. if(
  2036. $substr == "..." ||
  2037. $substr == "…"
  2038. ){
  2039. return trim(substr($title, 0, -3));
  2040. }
  2041. return trim($title);
  2042. }
  2043. private function hms2int($time){
  2044. $parts = explode(":", $time, 3);
  2045. $time = 0;
  2046. if(count($parts) === 3){
  2047. // hours
  2048. $time = $time + ((int)$parts[0] * 3600);
  2049. array_shift($parts);
  2050. }
  2051. if(count($parts) === 2){
  2052. // minutes
  2053. $time = $time + ((int)$parts[0] * 60);
  2054. array_shift($parts);
  2055. }
  2056. // seconds
  2057. $time = $time + (int)$parts[0];
  2058. return $time;
  2059. }
  2060. private function unshiturl($url){
  2061. // remove tracking redirect
  2062. // yes, the privacy search engine has click-out tracking. great!
  2063. $domain = parse_url($url, PHP_URL_HOST);
  2064. if($domain == "duckduckgo.com"){
  2065. $query = parse_url($url, PHP_URL_QUERY);
  2066. parse_str($query, $query);
  2067. if(isset($query["uddg"])){
  2068. $url = $query["uddg"];
  2069. $domain = parse_url($url, PHP_URL_HOST);
  2070. }
  2071. }
  2072. // check for domains w/out first short subdomain (ex: www.)
  2073. $subdomain = preg_replace(
  2074. '/^[A-z0-9]{1,3}\./',
  2075. "",
  2076. $domain
  2077. );
  2078. switch($subdomain){
  2079. case "ebay.com.au":
  2080. case "ebay.at":
  2081. case "ebay.ca":
  2082. case "ebay.fr":
  2083. case "ebay.de":
  2084. case "ebay.com.hk":
  2085. case "ebay.ie":
  2086. case "ebay.it":
  2087. case "ebay.com.my":
  2088. case "ebay.nl":
  2089. case "ebay.ph":
  2090. case "ebay.pl":
  2091. case "ebay.com.sg":
  2092. case "ebay.es":
  2093. case "ebay.ch":
  2094. case "ebay.co.uk":
  2095. case "cafr.ebay.ca":
  2096. case "ebay.com":
  2097. case "community.ebay.com":
  2098. case "pages.ebay.com":
  2099. // remove ebay tracking elements
  2100. $old_params = parse_url($url, PHP_URL_QUERY);
  2101. parse_str($old_params, $params);
  2102. if(isset($params["mkevt"])){ unset($params["mkevt"]); }
  2103. if(isset($params["mkcid"])){ unset($params["mkcid"]); }
  2104. if(isset($params["mkrid"])){ unset($params["mkrid"]); }
  2105. if(isset($params["campid"])){ unset($params["campid"]); }
  2106. if(isset($params["customid"])){ unset($params["customid"]); }
  2107. if(isset($params["toolid"])){ unset($params["toolid"]); }
  2108. if(isset($params["_sop"])){ unset($params["_sop"]); }
  2109. if(isset($params["_dcat"])){ unset($params["_dcat"]); }
  2110. if(isset($params["epid"])){ unset($params["epid"]); }
  2111. if(isset($params["epid"])){ unset($params["oid"]); }
  2112. $params = http_build_query($params);
  2113. if(strlen($params) === 0){
  2114. $replace = "\?";
  2115. }else{
  2116. $replace = "";
  2117. }
  2118. $url = preg_replace(
  2119. "/" . $replace . preg_quote($old_params, "/") . "$/",
  2120. $params,
  2121. $url
  2122. );
  2123. break;
  2124. }
  2125. return $url;
  2126. }
  2127. private function bingimg($url){
  2128. $image = parse_url($url);
  2129. $id = null;
  2130. if(isset($image["query"])){
  2131. parse_str($image["query"], $str);
  2132. if(isset($str["id"])){
  2133. $id = $str["id"];
  2134. }
  2135. }
  2136. if($id === null){
  2137. $id = explode("/th/id/", $image["path"], 2);
  2138. if(count($id) !== 2){
  2139. // malformed
  2140. return $url;
  2141. }
  2142. $id = $id[1];
  2143. }
  2144. return "https://" . $image["host"] . "/th?id=" . rawurlencode($id);
  2145. }
  2146. private function bingratio($width, $height){
  2147. $ratio = [
  2148. 474 / $width,
  2149. 474 / $height
  2150. ];
  2151. if($ratio[0] < $ratio[1]){
  2152. $ratio = $ratio[0];
  2153. }else{
  2154. $ratio = $ratio[1];
  2155. }
  2156. return [
  2157. floor($width * $ratio),
  2158. floor($height * $ratio)
  2159. ];
  2160. }
  2161. }