vimeo.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  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: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  144. "Accept-Language: en-US,en;q=0.5",
  145. "Accept-Encoding: gzip, deflate, br, zstd",
  146. "Referer: https://vimeo.com/watch",
  147. "DNT: 1",
  148. "Sec-GPC: 1",
  149. "Connection: keep-alive",
  150. "Cookie: _dd_s=isExpired=1", // &aid=959c58f0-e5fe-48f6-bd54-e247d92d317a
  151. "Upgrade-Insecure-Requests: 1",
  152. "Sec-Fetch-Dest: document",
  153. "Sec-Fetch-Mode: navigate",
  154. "Sec-Fetch-Site: same-origin",
  155. "Priority: u=0, i"]
  156. );
  157. }else{
  158. curl_setopt(
  159. $curlproc,
  160. CURLOPT_HTTPHEADER,
  161. ["User-Agent: " . config::USER_AGENT,
  162. "Accept: application/vnd.vimeo.*+json;version=3.3",
  163. "Accept-Language: en",
  164. "Accept-Encoding: gzip, deflate, br, zstd",
  165. "Referer: https://vimeo.com/",
  166. "Content-Type: application/json",
  167. "Authorization: jwt $jwt",
  168. "Vimeo-Page: /search/[[...slug]]",
  169. "Origin: https://vimeo.com",
  170. "DNT: 1",
  171. "Sec-GPC: 1",
  172. "Connection: keep-alive",
  173. "Sec-Fetch-Dest: empty",
  174. "Sec-Fetch-Mode: cors",
  175. "Sec-Fetch-Site: same-site",
  176. "Priority: u=4"]
  177. );
  178. }
  179. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  180. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  181. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  182. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  183. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  184. $this->backend->assign_proxy($curlproc, $proxy);
  185. $data = curl_exec($curlproc);
  186. if(curl_errno($curlproc)){
  187. throw new Exception(curl_error($curlproc));
  188. }
  189. curl_close($curlproc);
  190. return $data;
  191. }
  192. public function video($get){
  193. // parse shit
  194. if($get["npt"]){
  195. [$npt, $proxy] =
  196. $this->backend
  197. ->get(
  198. $get["npt"],
  199. "videos"
  200. );
  201. $npt = json_decode($npt, true);
  202. $pagetype = $npt["pagetype"];
  203. $npt = $npt["npt"];
  204. $jwt = $this->get_jwt($proxy);
  205. try{
  206. $json =
  207. $this->get(
  208. $proxy,
  209. "https://api.vimeo.com" . $npt,
  210. [],
  211. $jwt
  212. );
  213. }catch(Exception $error){
  214. throw new Exception("Failed to fetch JSON");
  215. }
  216. }else{
  217. $proxy = null;
  218. $jwt = $this->get_jwt($proxy); // this gives us a proxy by reference
  219. // parse filters
  220. $npt = [
  221. "query" => $get["s"],
  222. "page" => 1,
  223. "per_page" => 24,
  224. "facets" => "type"
  225. ];
  226. switch($get["display"]){
  227. case "video":
  228. $npt["filter_type"] = "clip";
  229. $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";
  230. break;
  231. case "ondemand":
  232. $npt["filter_type"] = "ondemand";
  233. $npt["sizes"] = "296x744";
  234. $npt["fields"] = "ondemand.link,ondemand.name,ondemand.pictures.sizes,ondemand.metadata.interactions.buy,ondemand.metadata.interactions.rent,ondemand.uri";
  235. break;
  236. case "people":
  237. $npt["filter_type"] = "people";
  238. $npt["fetch_user_profile"] = "1";
  239. $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";
  240. break;
  241. case "channel":
  242. $npt["filter_type"] = "channel";
  243. $npt["fields"] = "channel.name,channel.metadata.connections.users.total,channel.metadata.connections.videos.total,channel.pictures.sizes,channel.link,channel.uri";
  244. break;
  245. case "group":
  246. $npt["filter_type"] = "group";
  247. $npt["fields"] = "group.name,group.metadata.connections.users.total,group.metadata.connections.videos.total,group.pictures.sizes,group.link,group.uri";
  248. break;
  249. }
  250. // only apply filters if we're searching for videos
  251. if($get["display"] == "video"){
  252. switch($get["sort"]){
  253. case "relevance": break; // do nothing
  254. case "recent":
  255. $npt["sort"] = "latest";
  256. $npt["direction"] = "desc";
  257. break;
  258. case "popular":
  259. $npt["sort"] = "popularity";
  260. $npt["direction"] = "desc";
  261. break;
  262. case "a_z":
  263. $npt["sort"] = "alphabetical";
  264. $npt["direction"] = "asc";
  265. break;
  266. case "z_a":
  267. $npt["sort"] = "alphabetical";
  268. $npt["direction"] = "desc";
  269. break;
  270. case "longest":
  271. $npt["sort"] = "duration";
  272. $npt["direction"] = "desc";
  273. break;
  274. case "shortest":
  275. $npt["sort"] = "duration";
  276. $npt["direction"] = "asc";
  277. break;
  278. }
  279. if($get["time"] != "any"){
  280. $npt["filter_uploaded"] = $get["time"];
  281. }
  282. if($get["duration"] != "any"){
  283. $npt["filter_duration"] = $get["duration"];
  284. }
  285. if($get["resolution"] != "any"){
  286. $npt["filter_resolution"] = $get["resolution"];
  287. }
  288. if($get["category"] != "any"){
  289. $npt["filter_category"] = $get["category"];
  290. }
  291. if($get["live"] != "any"){
  292. $npt["filter_live"] = "now";
  293. }
  294. if($get["hdr"] != "any"){
  295. $npt["filter_hdr"] = $get["hdr"];
  296. }
  297. if($get["vimeo_360"] != "any"){
  298. $npt["filter_vimeo_360"] = $get["vimeo_360"];
  299. }
  300. if($get["price"] != "any"){
  301. $npt["filter_price"] = $get["price"];
  302. }
  303. if($get["collection"] == "staff_pick"){
  304. $npt["filter_staffpicked"] = "true";
  305. }
  306. if($get["license"] != "any"){
  307. $npt["filter_license"] = $get["license"];
  308. }
  309. }
  310. $pagetype = $npt["filter_type"];
  311. try{
  312. $json =
  313. $this->get(
  314. $proxy,
  315. "https://api.vimeo.com/search",
  316. $npt,
  317. $jwt
  318. );
  319. }catch(Exception $error){
  320. throw new Exception("Failed to fetch JSON");
  321. }
  322. }
  323. $json = json_decode($json, true);
  324. if($json === null){
  325. throw new Exception("Failed to parse JSON");
  326. }
  327. $out = [
  328. "status" => "ok",
  329. "npt" => null,
  330. "video" => [],
  331. "author" => [],
  332. "livestream" => [],
  333. "playlist" => [],
  334. "reel" => []
  335. ];
  336. if(isset($json["error"])){
  337. $error = $json["error"];
  338. if(isset($json["developer_message"])){
  339. $error .= " ({$json["developer_message"]})";
  340. }
  341. throw new Exception("Vimeo returned an error: " . $error);
  342. }
  343. if(!isset($json["data"])){
  344. throw new Exception("Vimeo did not return a data object");
  345. }
  346. switch($pagetype){
  347. case "clip":
  348. foreach($json["data"] as $video){
  349. $video = $video["clip"];
  350. if(isset($video["user"]["pictures"]["sizes"])){
  351. $avatar = $video["user"]["pictures"]["sizes"][count($video["user"]["pictures"]["sizes"]) - 1]["link"];
  352. }else{
  353. $avatar = null;
  354. }
  355. $out["video"][] = [
  356. "title" => $video["name"],
  357. "description" =>
  358. $this->limitstrlen(
  359. $video["description"]
  360. ),
  361. "author" => [
  362. "name" => $video["user"]["name"],
  363. "url" => $video["user"]["link"],
  364. "avatar" => $avatar
  365. ],
  366. "date" => strtotime($video["created_time"]),
  367. "duration" => (int)$video["duration"],
  368. "views" => (int)$video["stats"]["plays"],
  369. "thumb" => [
  370. "ratio" => "16:9",
  371. "url" => $video["pictures"]["base_link"]
  372. ],
  373. "url" => $video["link"]
  374. ];
  375. }
  376. break;
  377. case "ondemand":
  378. foreach($json["data"] as $video){
  379. $video = $video["ondemand"];
  380. $description = [];
  381. if(isset($video["metadata"]["interactions"]["rent"]["display_price"])){
  382. $description[] = "Rent for " . $video["metadata"]["interactions"]["rent"]["display_price"];
  383. }
  384. if(isset($video["metadata"]["interactions"]["buy"]["display_price"])){
  385. $description[] = "Buy for " . $video["metadata"]["interactions"]["buy"]["display_price"];
  386. }
  387. $description = implode(", ", $description);
  388. $out["video"][] = [
  389. "title" => $video["name"],
  390. "description" => $description,
  391. "author" => [
  392. "name" => null,
  393. "url" => null,
  394. "avatar" => null
  395. ],
  396. "date" => null,
  397. "duration" => null,
  398. "views" => null,
  399. "thumb" => [
  400. "ratio" => "9:16",
  401. "url" => $video["pictures"]["sizes"][0]["link"]
  402. ],
  403. "url" => $video["link"]
  404. ];
  405. }
  406. break;
  407. case "people":
  408. foreach($json["data"] as $user){
  409. $user = $user["people"];
  410. if(
  411. isset($user["pictures"]["sizes"]) &&
  412. count($user["pictures"]["sizes"]) !== 0
  413. ){
  414. $thumb = [
  415. "ratio" => "1:1",
  416. "url" => $user["pictures"]["sizes"][count($user["pictures"]["sizes"]) - 1]["link"]
  417. ];
  418. }else{
  419. $thumb = [
  420. "ratio" => null,
  421. "url" => null
  422. ];
  423. }
  424. $out["author"][] = [
  425. "title" => $user["name"],
  426. "followers" => (int)$user["metadata"]["connections"]["followers"]["total"],
  427. "description" => $user["metadata"]["public_videos"]["total"] . " videos.",
  428. "thumb" => $thumb,
  429. "url" => $user["link"]
  430. ];
  431. }
  432. break;
  433. case "channel":
  434. case "group":
  435. foreach($json["data"] as $channel){
  436. $channel = $channel[$npt["filter_type"]];
  437. if(
  438. isset($channel["pictures"]["sizes"]) &&
  439. count($channel["pictures"]["sizes"]) !== 0
  440. ){
  441. $thumb = [
  442. "ratio" => "16:9",
  443. "url" => $channel["pictures"]["sizes"][count($channel["pictures"]["sizes"]) - 1]["link"]
  444. ];
  445. }else{
  446. $thumb = [
  447. "ratio" => null,
  448. "url" => null
  449. ];
  450. }
  451. $out["author"][] = [
  452. "title" => $channel["name"],
  453. "followers" => (int)$channel["metadata"]["connections"]["users"]["total"],
  454. "description" => $channel["metadata"]["connections"]["videos"]["total"] . " videos.",
  455. "thumb" => $thumb,
  456. "url" => $channel["link"]
  457. ];
  458. }
  459. break;
  460. }
  461. //
  462. // get next page
  463. //
  464. if(
  465. isset($json["paging"]["next"]) &&
  466. is_string($json["paging"]["next"])
  467. ){
  468. $out["npt"] =
  469. $this->backend
  470. ->store(
  471. json_encode([
  472. "npt" => $json["paging"]["next"],
  473. "pagetype" => $pagetype
  474. ]),
  475. "videos",
  476. $proxy
  477. );
  478. }
  479. return $out;
  480. }
  481. private function get_jwt(&$proxy){
  482. //
  483. // get jwt token
  484. // it's probably safe to cache this across proxies, cause the jwt doesnt contain an userID
  485. // only an appID, whatever shit that is
  486. // we can only cache it for 5 minutes though, otherwise vimeo cries about it
  487. //
  488. if($proxy === null){
  489. $proxy = $this->backend->get_ip();
  490. }
  491. $jwt = apcu_fetch("vimeo_jwt");
  492. if($jwt === false){
  493. $html =
  494. $this->get(
  495. $proxy,
  496. "https://vimeo.com/search",
  497. [],
  498. false
  499. );
  500. $html =
  501. explode(
  502. '<script id="viewer-bootstrap" type="application/json">',
  503. $html,
  504. 2
  505. );
  506. if(count($html) !== 2){
  507. throw new Exception("Failed to find JWT json");
  508. }
  509. $jwt =
  510. json_decode(
  511. $this->fuckhtml
  512. ->extract_json(
  513. $html[1]
  514. ),
  515. true
  516. );
  517. if($jwt === null){
  518. throw new Exception("Failed to decode JWT json");
  519. }
  520. if(!isset($jwt["jwt"])){
  521. throw new Exception("Failed to grep JWT");
  522. }
  523. $jwt = $jwt["jwt"];
  524. apcu_store("vimeo_jwt", $jwt, 300);
  525. }
  526. return $jwt;
  527. }
  528. private function titledots($title){
  529. $substr = substr($title, -3);
  530. if(
  531. $substr == "..." ||
  532. $substr == "…"
  533. ){
  534. return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
  535. }
  536. return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
  537. }
  538. private function limitstrlen($text){
  539. return
  540. explode(
  541. "\n",
  542. wordwrap(
  543. str_replace(
  544. ["\n\r", "\r\n", "\n", "\r"],
  545. " ",
  546. $text
  547. ),
  548. 300,
  549. "\n"
  550. ),
  551. 2
  552. )[0];
  553. }
  554. }