pub / yt2html

Fetches Youtube content via RSS and provides a chronological timeline
git clone https://src.jayvii.de/pub/yt2html.git
Home | Log | Files | Exports | Refs | README | RSS

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       "&amp;",
    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("&"), "&amp;", $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 &amp; 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>