index.php (9825B)
1 <!-- SPDX-License-Identifier: AGPL-3.0-or-later
2 SPDX-FileCopyrightText: 2021-2024 JayVii <jayvii[AT]posteo[DOT]de>
3 -->
4
5 <!DOCTYPE html>
6 <html lang="en">
7 <head>
8 <meta charset="utf-8">
9 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
10 <title>YouTube Videos</title>
11 <link rel="icon" type="image/png" href="/assets/favicon.png">
12 <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon_16.png">
13 <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon_32.png">
14 <link rel="icon" type="image/png" sizes="64x64" href="/assets/favicon_64.png">
15 <link rel="icon" type="image/png" sizes="128x128" href="/assets/favicon_128.png">
16 <link rel="apple-touch-icon" href="/assets/favicon.png">
17 <link rel="stylesheet" type="text/css" href="/assets/css/simple.min.css"/>
18 <link rel="stylesheet" type="text/css" href="/assets/css/custom.css"/>
19 <script async src="assets/js/yt.js"></script>
20 <link crossorigin="use-credentials" rel="manifest" href="/manifest.json">
21 <meta name="viewport" content="width=device-width, initial-scale=1.0">
22 </head>
23
24 <?php
25
26 /* Comparing function for sorting */
27 function compare_numeric(array $a, array $b, string $col) {
28 return $b[$col] - $a[$col];
29 }
30 function compare_string(array $a, array $b, string $col) {
31 return strcmp(strtolower($a[$col]), strtolower($b[$col]));
32 }
33
34
35 /* Initilise videos template */
36 $video_template = array(
37 "vid" => array(),
38 "title" => array(),
39 "desc" => array(),
40 "time" => array(),
41 "aid" => array(),
42 "author" => array()
43 );
44
45 /* Initilise channels arrays */
46 $channels = array();
47 $channels_list = array();
48
49 /* Initilise videos array */
50 $videos = array();
51
52 // Fetch POST, GET arguments or the COOKIE
53 // Cookie should have lowest priority (fallback), POST first (intend)
54 // GET arguments are used for one-time usage (will not set cookie)
55 if (!is_null($_COOKIE["channels"])) {
56 $channels = explode(",", $_COOKIE["channels"]);
57 }
58 if (!is_null($_POST["channels"])) {
59 $channels = urldecode($_POST["channels"]);
60 $channels = preg_replace('/[^A-Za-z0-9\-\_\,]+/', '', $channels);
61 $channels = explode(",", $channels);
62 }
63 if (!is_null($_GET["channels"])) {
64 $channels = explode(",", $_GET["channels"]);
65 }
66
67 // Sort channels by alphabet and ensure each channel is unique
68 $channels = array_unique($channels);
69
70 /* refresh cookie */
71 if (is_null($_GET["channels"])) {
72 header(
73 "Set-Cookie: " .
74 "channels=" . implode(",", $channels) . ";" .
75 "Max-Age=" . 31536000 . "; " . /* 60 x 60 x 24 x 365 = 1 year */
76 "Domain=" . $_SERVER["SERVER_NAME"] . "; " .
77 "SameSite=Strict;"
78 );
79 }
80
81 for ($i = 0; $i < count($channels); $i++) {
82 $channel = $channels[$i];
83
84 // Fetch Youtube XML Feed
85 $channel_xml = file(
86 "https://www.youtube.com/feeds/videos.xml?channel_id=" . $channel
87 );
88
89 /* Skip to next entry, if channel could not be found */
90 if ($channel_xml === false) {
91 /* Remove entry from channels list */
92 array_splice($channels, $i, 1);
93 /* Skip item within loop */
94 continue;
95 }
96
97 // Replace un-parsable items
98 $channel_xml = str_replace(
99 array("yt:", "media:"),
100 array("yt_", "media_"),
101 $channel_xml
102 );
103
104 // Cast Array to string
105 $channel_xml = implode(PHP_EOL, $channel_xml);
106
107 // Parse XML
108 $channel_xml = simplexml_load_string($channel_xml);
109 $channel_xml = json_encode($channel_xml);
110 $channel_xml = json_decode($channel_xml, true);
111
112 // Get Channel name
113 $author = str_replace(
114 array("&"),
115 "&",
116 $channel_xml["entry"][0]["author"]["name"]
117 );
118
119 /* Fill channels list array */
120 array_push($channels_list, array("aid" => $channel, "author" => $author));
121
122 // Process Entries
123 foreach ($channel_xml["entry"] as $entry) {
124
125 /* Copy video template array */
126 $video = $video_template;
127
128 // Get Video ID
129 $video["vid"] = str_replace(array("yt_video:"), "", $entry["id"]);
130
131 // Get Video Title
132 $video["title"] = str_replace(array("&"), "&", $entry["title"]);
133
134 // Get Video Description
135 $video["desc"] = str_replace(
136 array("\n"),
137 "<br>",
138 preg_replace("/\n+/", "\n", $entry["media_group"]["media_description"])
139 );
140 if (empty($video["desc"])) {
141 $video["desc"] = "";
142 }
143
144 // Get Time
145 $video["time"] = strtotime($entry["published"]);
146
147 // Get Channel ID
148 $video["aid"] = $channel;
149
150 // Get Channel Name
151 $video["author"] = $author;
152
153 /* check whether indications of a YouTube-Short exist, skip if it is
154 * We assume video is a short if any of the following applies:
155 * has #short/#shorts in the title
156 * has #short #shorts in the description
157 * has no description at all
158 */
159 if (
160 preg_match('/#short(s)*/', $video["title"]) > 0 ||
161 preg_match('/#short(s)*/', $video["desc"]) > 0 ||
162 strlen($video["desc"]) == 0
163 ) {
164 continue;
165 }
166
167 /* Add video to videos array */
168 array_push($videos, $video);
169
170 }
171
172 }
173
174 /* Sort videos and channels arrays */
175 function cmp_time(array $a, array $b) {
176 return compare_numeric($a, $b, "time");
177 }
178 function cmp_name(array $a, array $b) {
179 return compare_string($a, $b, "author");
180 }
181 usort($videos, "cmp_time");
182 usort($channels_list, "cmp_name");
183
184 ?>
185
186 <body>
187
188 <header>
189 <nav>
190 <a href="/">All Videos</a>
191 <?php
192 /* For GET-requests, draw simple reload button,
193 * otherwise re-submit channels form
194 */
195 if (is_null($_GET["channels"])) {
196 $jsfun = "document.getElementById('channel_form').submit();";
197 } else {
198 $jsfun = "window.location.reload();";
199 }
200 ?>
201 <a href="#" onclick="<?php echo $jsfun; ?>">
202 Refresh
203 </a>
204 <a href="https://src.jayvii.de/pub/yt2html" target="_blank">
205 Development
206 </a>
207 </nav>
208 <h1>Videos</h1>
209 </header>
210
211 <form action="https://www.youtube.com/results" method="GET">
212 <input
213 id="searchbar"
214 type="text"
215 id="searchInput"
216 name="search_query"
217 placeholder="Search on YouTube..."
218 >
219 </form>
220
221 <!-- Channels List Form -->
222 <details id="channels">
223 <summary>List of Channels</summary>
224 <form action="/" method="POST" id="channel_form">
225 <details>
226 <summary>Import / Export</summary>
227 <p>
228 Please enter the YouTube channel-IDs you want to check here, each
229 separated with a <code>,</code>:
230 </p>
231 <input
232 name="channels"
233 type="text"
234 value="<?php echo implode("," . PHP_EOL, $channels); ?>"
235 >
236 </details>
237 <div id="channels_list">
238 <?php
239 /* Draw Input fields for each channel ID */
240 for ($i = 0; $i < count($channels_list); $i++) {
241 ?>
242 <label for="channel_<?php echo $i; ?>">
243 <?php
244 if ($channels_list[$i]["author"] != "") {
245 echo "<a href=\"/?channels=" . $channels_list[$i]["aid"] .
246 "\" target=\"_blank\">" . $channels_list[$i]["author"] .
247 "</a>";
248 } else {
249 echo "<mark class=\"error\">Error</mark>";
250 }
251 ?>
252 </label>
253 <input
254 name="channel_<?php echo $i; ?>"
255 class="channels_input"
256 type="text"
257 value="<?php echo $channels_list[$i]["aid"]; ?>"
258 oninput="yt2html_update_channels_list();"
259 >
260 <?php
261 }
262 ?>
263 <!-- Draw one empty input field for new entry by the user -->
264 <label for="channel_<?php echo ($i); ?>">
265 <mark>Add a new channel here</mark>
266 </label>
267 <input
268 name="channel<?php echo ($i); ?>"
269 class="channels_input"
270 type="text"
271 oninput="yt2html_update_channels_list();"
272 >
273 </div>
274 <!-- Submit Button -->
275 <input type="submit" value="Submit & Reload">
276 </form>
277 </details>
278
279 <!-- Video List -->
280 <?php
281 $index = 0;
282 foreach ($videos as $video) {
283
284 /* Stop if 500 videos are listed */
285 if ($index >= 500) {
286 break;
287 }
288 ?>
289 <section id="video_<?php echo $index; ?>">
290
291 <!-- Headline: streamer name -->
292 <h2><?php echo $video["title"]; ?></h2>
293
294 <!-- player Container -->
295 <div class="player_container hidden"></div>
296
297 <!-- Preview Image -->
298 <img
299 id="preview_<?php echo $index; ?>"
300 class="preview"
301 src="https://i4.ytimg.com/vi/<?php echo $video["vid"]; ?>/hqdefault.jpg"
302 loading="lazy"
303 >
304
305 <!-- Video Buttons -->
306 <?php $js_args = $index . ",'" . $video["vid"] . "'"; ?>
307 <button onclick="yt2html_toggle_player(<?php echo $js_args; ?>)">
308 Toggle Player
309 </button>
310 <a
311 class="button"
312 href="/?channels=<?php echo $video["aid"]; ?>"
313 >
314 <?php echo $video["author"]; ?>
315 </a>
316 <a
317 class="button"
318 href="https://www.youtube.com/watch?v=<?php echo $video["vid"]; ?>"
319 target="_blank"
320 >
321 Open on YouTube
322 </a>
323
324 <!-- Chat Collapsable -->
325 <details class="button">
326 <summary>
327 Description
328 </summary>
329 <?php
330 echo "Date: " . date("Y-m-d, H:i:s", $video["time"]) . "<br><br>" .
331 $video["desc"];
332 ?>
333 </details>
334
335 </section>
336
337 <?php
338
339 $index++;
340
341 }
342
343 ?>
344
345 </body>
346 </html>