common.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <?php
  2. if ( file_exists( __DIR__ . '/config.inc.php' ) ) {
  3. require( __DIR__ . '/config.inc.php' );
  4. } else {
  5. require( __DIR__ . '/sample-config.inc.php' );
  6. }
  7. require( dirname( __DIR__ ) . '/vendor/autoload.php' );
  8. /**
  9. * Gets installation homepage URL.
  10. *
  11. * @return str
  12. */
  13. function get_base_url() {
  14. global $config;
  15. if ( ! isset( $config['base_url'] ) ) {
  16. $base_url = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
  17. $base_url .= dirname( $_SERVER['SCRIPT_NAME'] );
  18. return $base_url;
  19. } else {
  20. return $config['base_url'];
  21. }
  22. }
  23. /**
  24. * Returns a changed YT URL to work on local install.
  25. *
  26. * @param str $id Video/channel ID.
  27. * @param str $type Type of ID provided or URL to be generated. (video|channel)
  28. * @param array $extra_data Extra information to be included in the URL.
  29. * @return str
  30. */
  31. function get_local_url( $id, $type = 'video', $extra_data = null ) {
  32. if ( $type == 'video' ) {
  33. return get_base_url() . '/watch?v=' . $id;
  34. } elseif ( $type == 'channel' ) {
  35. return get_base_url() . '/channel/' . $id;
  36. }
  37. }
  38. /**
  39. * Replaces all occurrences of YT urls from a string.
  40. *
  41. * @param str $input_string String to process.
  42. * @return str
  43. */
  44. function replace_urls_with_local_urls( $input_string ) {
  45. $base_url = get_base_url();
  46. $patterns = array(
  47. // https://www.youtube.com/watch?v=C0DPdy98e4c&feature=feedrec_grec_index
  48. '/https*:\/\/(www.)*youtube.com\/watch(.*)v=([a-zA-Z0-9]*)/i',
  49. // http://youtu.be/C0DPdy98e4c
  50. '/https*:\/\/youtu.be\/([a-zA-Z0-9]*)/i',
  51. // https://www.youtube.com/embed/C0DPdy98e4c?rel=0
  52. '/https*:\/\/(www.)*youtube.com\/embed\/([a-zA-Z0-9]*)/i',
  53. // https://www.youtube.com/user/SomeUser_Name#p/a/u/1/QdK8U-VIH_o
  54. '/https*:\/\/(www.)*youtube.com\/user\/([a-zA-Z0-9_-]*)/i',
  55. // https://youtube.com/channel/UCHDm-DKoMyJxKVgwGmuTaQA
  56. '/https*:\/\/(www.)*youtube.com\/channel\/([a-zA-Z0-9_-]*)/i',
  57. );
  58. $replacements = array(
  59. "$base_url/watch?v=$3",
  60. "$base_url/watch?v=$1",
  61. "$base_url/watch?v=$2",
  62. "$base_url/user/$2",
  63. "$base_url/channel/$2",
  64. );
  65. return preg_replace( $patterns, $replacements, $input_string );
  66. }
  67. /**
  68. * Convert urls in the string into clickable links.
  69. *
  70. * @source https://stackoverflow.com/a/34732417
  71. *
  72. * @param str $input_string Input string to convert.
  73. * @return str
  74. */
  75. function urlify_string( $input_string ) {
  76. return preg_replace( '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]*(\/\S*)?/', '<a href="$0">$0</a>', $input_string );
  77. }
  78. /**
  79. * Returns a YT thumbnail image from a given video id
  80. *
  81. * @source https://stackoverflow.com/a/20542029
  82. *
  83. * @param str $video_id Video id to get the thumbnail for.
  84. * @param str $size Size of the thumbnail image to get url for. (small|medium|high)
  85. * @return str
  86. */
  87. function get_video_thumbnail_url( $video_id, $size = 'medium' ) {
  88. if ( $size == 'small') {
  89. return "https://i.ytimg.com/vi/{$video_id}/default.jpg";
  90. } elseif ( $size == 'medium') {
  91. return "https://i.ytimg.com/vi/{$video_id}/mqdefault.jpg";
  92. } elseif ( $size == 'high') {
  93. return "https://i.ytimg.com/vi/{$video_id}/hqdefault.jpg";
  94. }
  95. }
  96. /**
  97. * Returns proxy-enabled url for proxy (esp. for frontend).
  98. */
  99. // TODO: Implement proxy code and use throughout project.
  100. function proxify_url( $url ) {
  101. return get_base_url() . '/proxyasset.php?url=' . urlencode( $url );
  102. }
  103. /**
  104. * Gets the content of an URL.
  105. *
  106. * @param str $url The URL to get contents of.
  107. * @return str
  108. */
  109. function get_url_contents( $url ) {
  110. global $config;
  111. $ch = curl_init();
  112. $curl_options = array(
  113. CURLOPT_URL => $url,
  114. CURLOPT_FOLLOWLOCATION => 1,
  115. CURLOPT_RETURNTRANSFER => 1,
  116. );
  117. if ( $config['proxy_enabled'] ) {
  118. $curl_options[CURLOPT_PROXY] = "{$config['proxy_ip']}:{$config['proxy_port']}";
  119. if ( isset( $config['proxy_username'] ) ) {
  120. $curl_options[CURLOPT_PROXYUSERPWD] = "{$config['proxy_username']}:{$config['proxy_password']}";
  121. }
  122. switch( $config['proxy_type'] ) {
  123. case 'socks4':
  124. $curl_proxy_type = CURLPROXY_SOCKS4;
  125. break;
  126. case 'socks5':
  127. $curl_proxy_type = CURLPROXY_SOCKS5;
  128. break;
  129. case 'socks4a':
  130. $curl_proxy_type = CURLPROXY_SOCKS4A;
  131. break;
  132. default:
  133. $curl_proxy_type = CURLPROXY_HTTP;
  134. }
  135. $curl_options[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5;
  136. }
  137. curl_setopt_array( $ch, $curl_options );
  138. $curl_output = curl_exec( $ch );
  139. curl_close( $ch );
  140. return $curl_output;
  141. }
  142. /**
  143. * Extracts json information from YT HTML.
  144. *
  145. * @param str $html Input HTML.
  146. * @return str|bool
  147. */
  148. function get_initial_data( $html ) {
  149. if ( preg_match( '/window\["ytInitialData"\]\s*=\s*(?<info>.*?);+\n/i', $html, $matches ) ) {
  150. $json = $matches[1];
  151. $json = json_decode( $json );
  152. return $json;
  153. } else {
  154. return false;
  155. }
  156. }
  157. /**
  158. * Extracts items from extracted json (e.g. from get_initial_data()).
  159. *
  160. * @param str $json JSON string to be processed.
  161. * @return array
  162. */
  163. function extract_items( $json ) {
  164. // Temporary storage variable for json data in array
  165. $items_json = array();
  166. // Return array
  167. $items = array();
  168. // Search data
  169. if ( isset( $json->contents->twoColumnSearchResultsRenderer ) ) {
  170. $items_json = @$json->contents->twoColumnSearchResultsRenderer->primaryContents->sectionListRenderer->contents[0]->itemSectionRenderer->contents;
  171. // Go through contents inside sub-arrays (for trending data etc.)
  172. // Path: @$json->contents->twoColumnBrowseResultsRenderer->tabs[0]->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer->contents[0]->shelfRenderer->content->expandedShelfContentsRenderer->items;
  173. } elseif ( isset( $json->contents->twoColumnBrowseResultsRenderer ) ) {
  174. foreach ( @$json->contents->twoColumnBrowseResultsRenderer->tabs[0]->tabRenderer->content->sectionListRenderer->contents as $key => $sub_items ) {
  175. $nested_items = @$sub_items->itemSectionRenderer->contents[0]->shelfRenderer->content->expandedShelfContentsRenderer->items;
  176. if ( ! empty($nested_items) ) {
  177. $items_json = array_merge( $items_json, $nested_items );
  178. }
  179. }
  180. // Channel playlist (and also playlist??)
  181. } elseif ( isset( $json->contents->twoColumnWatchNextResults ) ) {
  182. //$items_json = @$json->contents->twoColumnWatchNextResults->results->results->contents; // not used
  183. $items_json = @$json->contents->twoColumnWatchNextResults->playlist->playlist->contents;
  184. }
  185. if ( ! empty( $items_json ) ) {
  186. foreach ( $items_json as $index => $item ) {
  187. $first_key = @array_keys( get_object_vars($item) )[0];
  188. if ( isset( $item->videoRenderer ) ) {
  189. $items[] = array(
  190. 'item_type' => 'video',
  191. 'video_id' => $item->videoRenderer->videoId,
  192. 'video_url' => get_local_url( $item->videoRenderer->videoId, 'video' ),
  193. 'thumbnail_url' => $item->videoRenderer->thumbnail->thumbnails[1]->url,
  194. 'title' => $item->videoRenderer->title->runs[0]->text,
  195. 'title_a11y' => $item->videoRenderer->title->accessibility->accessibilityData->label,
  196. 'description' => $item->videoRenderer->descriptionSnippet->runs[0]->text . $item->videoRenderer->descriptionSnippet->runs[2]->text,
  197. 'length' => $item->videoRenderer->lengthText->simpleText,
  198. 'author_name' => $item->videoRenderer->ownerText->runs[0]->text,
  199. 'author_url' => get_base_url() . $item->videoRenderer->ownerText->runs[0]->navigationEndpoint->browseEndpoint->canonicalBaseUrl,
  200. 'channel_id' => get_base_url() . $item->videoRenderer->ownerText->runs[0]->navigationEndpoint->browseEndpoint->browseId,
  201. 'views_text' => $item->videoRenderer->shortViewCountText->simpleText,
  202. );
  203. } elseif ( isset( $item->playlistPanelVideoRenderer ) ) {
  204. $video_id = @$item->{$first_key}->videoId;
  205. $items[] = array(
  206. 'item_type' => 'video',
  207. 'video_id' => $video_id,
  208. 'video_url' => get_local_url( $video_id, 'video' ),
  209. 'thumbnail_url' => $item->{$first_key}->thumbnail->thumbnails[1]->url,
  210. 'title' => $item->{$first_key}->title->simpleText,
  211. 'title_a11y' => $item->{$first_key}->title->accessibility->accessibilityData->label,
  212. 'length' => $item->{$first_key}->lengthText->simpleText,
  213. 'key_test' => $first_key,
  214. );
  215. } elseif ( isset( $item->channelRenderer ) ) {
  216. $items[] = array(
  217. 'item_type' => 'channel',
  218. 'channel_name' => $item->channelRenderer->title->simpleText,
  219. 'channel_id' => $item->channelRenderer->channelId,
  220. 'channel_url' => get_local_url( $item->channelRenderer->channelId, 'channel' ),
  221. 'channel_thumbnail_url' => $item->channelRenderer->thumbnail->thumbnails[1]->url,
  222. 'subscriber_count' => $item->channelRenderer->subscriberCountText->simpleText,
  223. 'auto_generated' => ( isset( $item->channelRenderer->videoCountText ) ) ? false : true,
  224. 'video_count' => $item->channelRenderer->videoCountText->runs[0]->text,
  225. 'description' => $item->channelRenderer->descriptionSnippet,
  226. );
  227. // TODO: Process more types such as playlists etc. (https://github.com/iv-org/invidious/blob/db83ede73ca38cd4c661e96b8f9823db99d9f5c2/src/invidious/helpers/helpers.cr#L250)
  228. }
  229. }
  230. } else {
  231. throw new Exception( 'Contents node not found on extracted JSON' );
  232. }
  233. return $items;
  234. }
  235. /**
  236. * Friendly display of date and time (e.g. 1 month ago).
  237. *
  238. * @source https://stackoverflow.com/a/38897003
  239. *
  240. * @param int $time_ago The time passed as input.
  241. * @return str
  242. */
  243. function time_ago( $time_ago ) {
  244. $time_ago = strtotime( $time_ago ) ? strtotime( $time_ago ) : $time_ago;
  245. $time = time() - $time_ago;
  246. switch( $time ):
  247. // seconds
  248. case $time <= 60;
  249. return 'less than a minute ago';
  250. // minutes
  251. case $time >= 60 && $time < 3600;
  252. return (round($time/60) == 1) ? 'a minute' : round($time/60) . ' minutes ago';
  253. // hours
  254. case $time >= 3600 && $time < 86400;
  255. return (round($time/3600) == 1) ? 'an hour ago' : round($time/3600) . ' hours ago';
  256. // days
  257. case $time >= 86400 && $time < 604800;
  258. return (round($time/86400) == 1) ? 'a day ago' : round($time/86400) . ' days ago';
  259. // weeks
  260. case $time >= 604800 && $time < 2600640;
  261. return (round($time/604800) == 1) ? 'a week ago' : round($time/604800) . ' weeks ago';
  262. // months
  263. case $time >= 2600640 && $time < 31207680;
  264. return (round($time/2600640) == 1) ? 'a month ago' : round($time/2600640) . ' months ago';
  265. // years
  266. case $time >= 31207680;
  267. return (round($time/31207680) == 1) ? 'a year ago' : round($time/31207680) . ' years ago' ;
  268. endswitch;
  269. }