vimeo.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. <?php
  2. class vimeo{
  3. public function __construct(){
  4. include "lib/backend.php";
  5. $this->backend = new backend("vimeo");
  6. include "lib/fuckhtml.php";
  7. $this->fuckhtml = new fuckhtml();
  8. }
  9. public function getfilters($page){
  10. return [
  11. "time" => [
  12. "display" => "Date uploaded", // &filter_uploaded=
  13. "option" => [
  14. "any" => "Any time",
  15. "today" => "Last 24 hours",
  16. "this-week" => "Last 7 days",
  17. "this-month" => "Last 30 days",
  18. "this-year" => "Last 365 days",
  19. ]
  20. ],
  21. "display" => [
  22. "display" => "Display",
  23. "option" => [
  24. "video" => "Videos",
  25. "ondemand" => "On-Demand ($$)",
  26. "people" => "People",
  27. "channel" => "Channels",
  28. "group" => "Groups"
  29. ]
  30. ],
  31. "sort" => [
  32. "display" => "Sort by",
  33. "option" => [
  34. "relevance" => "Relevance", // no param
  35. "recent" => "Newest", // &sort=latest&direction=desc
  36. "popular" => "Most popular", // &sort=popularity&direction=desc
  37. "a_z" => "Title, A to Z", // &sort=alphabetical&direction=asc
  38. "z_a" => "Title, Z to A", // &sort=alphabetical&direction=desc
  39. "longest" => "Longest", // &sort=duration&direction=desc
  40. "shortest" => "Shortest", // &sort=duration&direction=asc
  41. ]
  42. ],
  43. "duration" => [
  44. "display" => "Duration", // &filter_duration=
  45. "option" => [
  46. "any" => "Any duration",
  47. "short" => "Short (less than 4 minutes)",
  48. "medium" => "Medium (4-10 minutes)",
  49. "long" => "Long (over 10 minutes)"
  50. ]
  51. ],
  52. "resolution" => [
  53. "display" => "Resolution",
  54. "option" => [
  55. "any" => "Any resolution",
  56. "4k" => "4K" // &filter_resolution=4k
  57. ]
  58. ],
  59. "category" => [
  60. "display" => "Category", // &filter_category=
  61. "option" => [
  62. "any" => "Any category",
  63. "animation" => "Animation",
  64. "comedy" => "Comedy",
  65. "music" => "Music",
  66. "experimental" => "Experimental",
  67. "documentary" => "Documentary",
  68. "identsandanimatedlogos" => "Idents and Animated Logos",
  69. "industry" => "Industry",
  70. "instructionals" => "Instructionals",
  71. "narrative" => "Narrative",
  72. "personal" => "Personal"
  73. ]
  74. ],
  75. "live" => [
  76. "display" => "Live events",
  77. "option" => [
  78. "any" => "Any",
  79. "yes" => "Live now" // &filter_live=now
  80. ]
  81. ],
  82. "hdr" => [
  83. "display" => "HDR", // &filter_hdr=
  84. "option" => [
  85. "any" => "Any",
  86. "hdr" => "Any HDR",
  87. "dolby_vision" => "Dolby Vision",
  88. "hdr10" => "HDR10",
  89. "hdr10+" => "HDR10+"
  90. ]
  91. ],
  92. "vimeo_360" => [
  93. "display" => "Vimeo 360°", // &filter_vimeo_360
  94. "option" => [
  95. "any" => "Any",
  96. "spatial" => "Spatial",
  97. "360" => "360°"
  98. ]
  99. ],
  100. "price" => [ // &filter_price=
  101. "display" => "Price",
  102. "option" => [
  103. "any" => "Any price",
  104. "free" => "Free",
  105. "paid" => "Paid"
  106. ]
  107. ],
  108. "collection" => [
  109. "display" => "Vimeo collections",
  110. "option" => [
  111. "any" => "Any collection",
  112. "staff_pick" => "Staff picks" // &filter_staffpicked=true
  113. ]
  114. ],
  115. "license" => [ // &filter_license=
  116. "display" => "License",
  117. "option" => [
  118. "any" => "Any license",
  119. "by-nc-nd" => "CC BY-NC-ND",
  120. "by" => "CC BY",
  121. "by-nc" => "CC BY-NC",
  122. "by-nc-sa" => "CC BY-NC-SA",
  123. "by-nd" => "CC BY-ND",
  124. "by-sa" => "CC BY-SA",
  125. "cc0" => "CC0"
  126. ]
  127. ]
  128. ];
  129. }
  130. private function get($proxy, $url, $get = [], $jwt = false){
  131. $curlproc = curl_init();
  132. if($get !== []){
  133. $get = http_build_query($get);
  134. $url .= "?" . $get;
  135. }
  136. curl_setopt($curlproc, CURLOPT_URL, $url);
  137. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  138. if($jwt === false){
  139. curl_setopt(
  140. $curlproc,
  141. CURLOPT_HTTPHEADER,
  142. ["User-Agent: " . config::USER_AGENT,
  143. "Accept: */*",
  144. "Accept-Language: en-US,en;q=0.5",
  145. "Accept-Encoding: gzip, deflate, br, zstd",
  146. "Referer: https://vimeo.com/search",
  147. "X-Requested-With: XMLHttpRequest",
  148. "DNT: 1",
  149. "Sec-GPC: 1",
  150. "Connection: keep-alive",
  151. "Sec-Fetch-Dest: empty",
  152. "Sec-Fetch-Mode: cors",
  153. "Sec-Fetch-Site: same-origin",
  154. "Priority: u=4"]
  155. );
  156. }else{
  157. curl_setopt(
  158. $curlproc,
  159. CURLOPT_HTTPHEADER,
  160. ["User-Agent: " . config::USER_AGENT,
  161. "Accept: application/vnd.vimeo.*+json;version=3.3",
  162. "Accept-Language: en",
  163. "Accept-Encoding: gzip, deflate, br, zstd",
  164. "Referer: https://vimeo.com/",
  165. "Content-Type: application/json",
  166. "Authorization: jwt $jwt",
  167. "Vimeo-Page: /search/[[...slug]]",
  168. "Origin: https://vimeo.com",
  169. "DNT: 1",
  170. "Sec-GPC: 1",
  171. "Connection: keep-alive",
  172. "Sec-Fetch-Dest: empty",
  173. "Sec-Fetch-Mode: cors",
  174. "Sec-Fetch-Site: same-site",
  175. "Priority: u=4"]
  176. );
  177. }
  178. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  179. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  180. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  181. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  182. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  183. $this->backend->assign_proxy($curlproc, $proxy);
  184. $data = curl_exec($curlproc);
  185. if(curl_errno($curlproc)){
  186. throw new Exception(curl_error($curlproc));
  187. }
  188. curl_close($curlproc);
  189. return $data;
  190. }
  191. public function video($get){
  192. // parse shit
  193. if($get["npt"]){
  194. [$npt, $proxy] =
  195. $this->backend
  196. ->get(
  197. $get["npt"],
  198. "videos"
  199. );
  200. $npt = json_decode($npt, true);
  201. $pagetype = $npt["pagetype"];
  202. $npt = $npt["npt"];
  203. $jwt = $this->get_jwt($proxy);
  204. try{
  205. $json =
  206. $this->get(
  207. $proxy,
  208. "https://api.vimeo.com" . $npt,
  209. [],
  210. $jwt
  211. );
  212. }catch(Exception $error){
  213. throw new Exception("Failed to fetch JSON");
  214. }
  215. }else{
  216. $proxy = null;
  217. $jwt = $this->get_jwt($proxy); // this gives us a proxy by reference
  218. // parse filters
  219. $npt = [
  220. "query" => $get["s"],
  221. "page" => 1,
  222. "per_page" => 24,
  223. "facets" => "type"
  224. ];
  225. switch($get["display"]){
  226. case "video":
  227. $npt["filter_type"] = "clip";
  228. $npt["fields"] = "clip.name,stats.plays,clip.pictures,clip.user.name,clip.user.link,clip.user.pictures.sizes,clip.uri,clip.stats.plays,clip.duration,clip.created_time,clip.link,clip.description";
  229. break;
  230. case "ondemand":
  231. $npt["filter_type"] = "ondemand";
  232. $npt["sizes"] = "296x744";
  233. $npt["fields"] = "ondemand.link,ondemand.name,ondemand.pictures.sizes,ondemand.metadata.interactions.buy,ondemand.metadata.interactions.rent,ondemand.uri";
  234. break;
  235. case "people":
  236. $npt["filter_type"] = "people";
  237. $npt["fetch_user_profile"] = "1";
  238. $npt["fields"] = "people.name,people.location_details.formatted_address,people.metadata.public_videos.total,people.pictures.sizes,people.link,people.metadata.connections.followers.total,people.skills.name,people.skills.uri,people.background_video,people.uri";
  239. break;
  240. case "channel":
  241. $npt["filter_type"] = "channel";
  242. $npt["fields"] = "channel.name,channel.metadata.connections.users.total,channel.metadata.connections.videos.total,channel.pictures.sizes,channel.link,channel.uri";
  243. break;
  244. case "group":
  245. $npt["filter_type"] = "group";
  246. $npt["fields"] = "group.name,group.metadata.connections.users.total,group.metadata.connections.videos.total,group.pictures.sizes,group.link,group.uri";
  247. break;
  248. }
  249. // only apply filters if we're searching for videos
  250. if($get["display"] == "video"){
  251. switch($get["sort"]){
  252. case "relevance": break; // do nothing
  253. case "recent":
  254. $npt["sort"] = "latest";
  255. $npt["direction"] = "desc";
  256. break;
  257. case "popular":
  258. $npt["sort"] = "popularity";
  259. $npt["direction"] = "desc";
  260. break;
  261. case "a_z":
  262. $npt["sort"] = "alphabetical";
  263. $npt["direction"] = "asc";
  264. break;
  265. case "z_a":
  266. $npt["sort"] = "alphabetical";
  267. $npt["direction"] = "desc";
  268. break;
  269. case "longest":
  270. $npt["sort"] = "duration";
  271. $npt["direction"] = "desc";
  272. break;
  273. case "shortest":
  274. $npt["sort"] = "duration";
  275. $npt["direction"] = "asc";
  276. break;
  277. }
  278. if($get["time"] != "any"){
  279. $npt["filter_uploaded"] = $get["time"];
  280. }
  281. if($get["duration"] != "any"){
  282. $npt["filter_duration"] = $get["duration"];
  283. }
  284. if($get["resolution"] != "any"){
  285. $npt["filter_resolution"] = $get["resolution"];
  286. }
  287. if($get["category"] != "any"){
  288. $npt["filter_category"] = $get["category"];
  289. }
  290. if($get["live"] != "any"){
  291. $npt["filter_live"] = "now";
  292. }
  293. if($get["hdr"] != "any"){
  294. $npt["filter_hdr"] = $get["hdr"];
  295. }
  296. if($get["vimeo_360"] != "any"){
  297. $npt["filter_vimeo_360"] = $get["vimeo_360"];
  298. }
  299. if($get["price"] != "any"){
  300. $npt["filter_price"] = $get["price"];
  301. }
  302. if($get["collection"] == "staff_pick"){
  303. $npt["filter_staffpicked"] = "true";
  304. }
  305. if($get["license"] != "any"){
  306. $npt["filter_license"] = $get["license"];
  307. }
  308. }
  309. $pagetype = $npt["filter_type"];
  310. try{
  311. $json =
  312. $this->get(
  313. $proxy,
  314. "https://api.vimeo.com/search",
  315. $npt,
  316. $jwt
  317. );
  318. }catch(Exception $error){
  319. throw new Exception("Failed to fetch JSON");
  320. }
  321. }
  322. $json = json_decode($json, true);
  323. if($json === null){
  324. throw new Exception("Failed to parse JSON");
  325. }
  326. $out = [
  327. "status" => "ok",
  328. "npt" => null,
  329. "video" => [],
  330. "author" => [],
  331. "livestream" => [],
  332. "playlist" => [],
  333. "reel" => []
  334. ];
  335. if(isset($json["error"])){
  336. $error = $json["error"];
  337. if(isset($json["developer_message"])){
  338. $error .= " ({$json["developer_message"]})";
  339. }
  340. throw new Exception("Vimeo returned an error: " . $error);
  341. }
  342. if(!isset($json["data"])){
  343. throw new Exception("Vimeo did not return a data object");
  344. }
  345. switch($pagetype){
  346. case "clip":
  347. foreach($json["data"] as $video){
  348. $video = $video["clip"];
  349. if(isset($video["user"]["pictures"]["sizes"])){
  350. $avatar = $video["user"]["pictures"]["sizes"][count($video["user"]["pictures"]["sizes"]) - 1]["link"];
  351. }else{
  352. $avatar = null;
  353. }
  354. $out["video"][] = [
  355. "title" => $video["name"],
  356. "description" =>
  357. $this->limitstrlen(
  358. $video["description"]
  359. ),
  360. "author" => [
  361. "name" => $video["user"]["name"],
  362. "url" => $video["user"]["link"],
  363. "avatar" => $avatar
  364. ],
  365. "date" => strtotime($video["created_time"]),
  366. "duration" => (int)$video["duration"],
  367. "views" => (int)$video["stats"]["plays"],
  368. "thumb" => [
  369. "ratio" => "16:9",
  370. "url" => $video["pictures"]["base_link"]
  371. ],
  372. "url" => $video["link"]
  373. ];
  374. }
  375. break;
  376. case "ondemand":
  377. foreach($json["data"] as $video){
  378. $video = $video["ondemand"];
  379. $description = [];
  380. if(isset($video["metadata"]["interactions"]["rent"]["display_price"])){
  381. $description[] = "Rent for " . $video["metadata"]["interactions"]["rent"]["display_price"];
  382. }
  383. if(isset($video["metadata"]["interactions"]["buy"]["display_price"])){
  384. $description[] = "Buy for " . $video["metadata"]["interactions"]["buy"]["display_price"];
  385. }
  386. $description = implode(", ", $description);
  387. $out["video"][] = [
  388. "title" => $video["name"],
  389. "description" => $description,
  390. "author" => [
  391. "name" => null,
  392. "url" => null,
  393. "avatar" => null
  394. ],
  395. "date" => null,
  396. "duration" => null,
  397. "views" => null,
  398. "thumb" => [
  399. "ratio" => "9:16",
  400. "url" => $video["pictures"]["sizes"][0]["link"]
  401. ],
  402. "url" => $video["link"]
  403. ];
  404. }
  405. break;
  406. case "people":
  407. foreach($json["data"] as $user){
  408. $user = $user["people"];
  409. if(
  410. isset($user["pictures"]["sizes"]) &&
  411. count($user["pictures"]["sizes"]) !== 0
  412. ){
  413. $thumb = [
  414. "ratio" => "1:1",
  415. "url" => $user["pictures"]["sizes"][count($user["pictures"]["sizes"]) - 1]["link"]
  416. ];
  417. }else{
  418. $thumb = [
  419. "ratio" => null,
  420. "url" => null
  421. ];
  422. }
  423. $out["author"][] = [
  424. "title" => $user["name"],
  425. "followers" => (int)$user["metadata"]["connections"]["followers"]["total"],
  426. "description" => $user["metadata"]["public_videos"]["total"] . " videos.",
  427. "thumb" => $thumb,
  428. "url" => $user["link"]
  429. ];
  430. }
  431. break;
  432. case "channel":
  433. case "group":
  434. foreach($json["data"] as $channel){
  435. $channel = $channel[$npt["filter_type"]];
  436. if(
  437. isset($channel["pictures"]["sizes"]) &&
  438. count($channel["pictures"]["sizes"]) !== 0
  439. ){
  440. $thumb = [
  441. "ratio" => "16:9",
  442. "url" => $channel["pictures"]["sizes"][count($channel["pictures"]["sizes"]) - 1]["link"]
  443. ];
  444. }else{
  445. $thumb = [
  446. "ratio" => null,
  447. "url" => null
  448. ];
  449. }
  450. $out["author"][] = [
  451. "title" => $channel["name"],
  452. "followers" => (int)$channel["metadata"]["connections"]["users"]["total"],
  453. "description" => $channel["metadata"]["connections"]["videos"]["total"] . " videos.",
  454. "thumb" => $thumb,
  455. "url" => $channel["link"]
  456. ];
  457. }
  458. break;
  459. }
  460. //
  461. // get next page
  462. //
  463. if(
  464. isset($json["paging"]["next"]) &&
  465. is_string($json["paging"]["next"])
  466. ){
  467. $out["npt"] =
  468. $this->backend
  469. ->store(
  470. json_encode([
  471. "npt" => $json["paging"]["next"],
  472. "pagetype" => $pagetype
  473. ]),
  474. "videos",
  475. $proxy
  476. );
  477. }
  478. return $out;
  479. }
  480. private function get_jwt(&$proxy){
  481. //
  482. // get jwt token
  483. // it's probably safe to cache this across proxies, cause the jwt doesnt contain an userID
  484. // only an appID, whatever shit that is
  485. // we can only cache it for 5 minutes though, otherwise vimeo cries about it
  486. //
  487. if($proxy === null){
  488. $proxy = $this->backend->get_ip();
  489. }
  490. $jwt = apcu_fetch("vimeo_jwt");
  491. if($jwt === false){
  492. /*
  493. $html =
  494. $this->get(
  495. $proxy,
  496. "https://vimeo.com/search",
  497. [],
  498. false
  499. );
  500. $this->fuckhtml->load($html);
  501. $captcha =
  502. $this->fuckhtml
  503. ->getElementsByTagName(
  504. "title"
  505. );
  506. if(
  507. count($captcha) !== 0 &&
  508. $this->fuckhtml
  509. ->getTextContent(
  510. $captcha[0]
  511. ) == "Vimeo / CAPTCHA Challenge"
  512. ){
  513. throw new Exception("Vimeo returned a Captcha");
  514. }
  515. $html =
  516. explode(
  517. '<script id="viewer-bootstrap" type="application/json">',
  518. $html,
  519. 2
  520. );
  521. if(count($html) !== 2){
  522. throw new Exception("Failed to find JWT json");
  523. }
  524. $jwt =
  525. json_decode(
  526. $this->fuckhtml
  527. ->extract_json(
  528. $html[1]
  529. ),
  530. true
  531. );
  532. if($jwt === null){
  533. throw new Exception("Failed to decode JWT json");
  534. }
  535. if(!isset($jwt["jwt"])){
  536. throw new Exception("Failed to grep JWT");
  537. }
  538. $jwt = $jwt["jwt"];
  539. */
  540. $json =
  541. $this->get(
  542. $proxy,
  543. "https://vimeo.com/_next/jwt",
  544. [],
  545. false
  546. );
  547. $json = json_decode($json, true);
  548. if($json === null){
  549. throw new Exception("The JWT object could not be decoded");
  550. }
  551. if(!isset($json["token"])){
  552. throw new Exception("Vimeo did not return a JWT");
  553. }
  554. $jwt = $json["token"];
  555. apcu_store("vimeo_jwt", $jwt, 300);
  556. }
  557. return $jwt;
  558. }
  559. private function titledots($title){
  560. $substr = substr($title, -3);
  561. if(
  562. $substr == "..." ||
  563. $substr == "…"
  564. ){
  565. return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
  566. }
  567. return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
  568. }
  569. private function limitstrlen($text){
  570. return
  571. explode(
  572. "\n",
  573. wordwrap(
  574. str_replace(
  575. ["\n\r", "\r\n", "\n", "\r"],
  576. " ",
  577. $text
  578. ),
  579. 300,
  580. "\n"
  581. ),
  582. 2
  583. )[0];
  584. }
  585. }