commit 4da0f2c1af9de7a6b4a2979158ad52f4b58b2b71
parent 7b74f244c238b329439bdf9baf92341b02f4cd19
Author: JayVii <jayvii[AT]posteo[DOT]de>
Date: Mon, 21 Oct 2024 17:19:50 +0200
feat: refactor
checking is done client-side and asynchronously via JS, which is A LOT faster.
Diffstat:
5 files changed, 325 insertions(+), 162 deletions(-)
diff --git a/README.md b/README.md
@@ -7,25 +7,12 @@ Checks online status of streams on twitch.tv and lets you watch them right here!
## Usage
-You can simply call the "?streams" endpoint with a comma-separated list of your
-favorite streams:
+Simply open the t2html page, scroll down to the "Streams" section, insert the account
+names you want to check on in a comma-separated list
+(`streamerA,streamerB,streamerC,...`) and click the "submit" button.
-```
-index.php?streams=gamingonlinux,linustechtips,...
-```
-
-Assuming you host this service under the URL "https://tw2html.example.com", you
-can open up the streamlist like so:
-
-```
-https://tw2html.example.com/?streams=gamingonlinux,linustechtips
-```
-
-The site will show you a loading screen, while the status of streams are fetched
-in the background. Once the process is finished, the site refreshes by itself
-and shows you a list of online streams. You can watch them directly on tw2html
-via iframe-embedding (this transfers your IP and user-agent information to
-twitch.tv).
+The list of streams will also be stored in a cookie, which is refreshed every time you
+open the page.
## Requirements
diff --git a/assets/css/custom.css b/assets/css/custom.css
@@ -1,7 +1,7 @@
* {
scroll-behavior: smooth;
}
-.preview {
+.preview, .player, .player_container {
height: auto;
width: 100%;
aspect-ratio: 16/9 !important;
@@ -12,7 +12,7 @@
#searchbar {
width: 100%;
}
-.chatwrapper, .chatwrapper > iframe {
+.chat_container, .chat_container > iframe {
width: 100%;
height:100%;
min-height: 800px;
@@ -29,3 +29,13 @@ a.button:hover
{
color: var(--bg) !important;
}
+
+.hidden {
+ display: none;
+}
+
+textarea[name=streams] {
+ width: 100%;
+ min-height: 400px;
+}
+
diff --git a/assets/js/darkmode.js b/assets/js/darkmode.js
@@ -1,29 +0,0 @@
-// Toggle Theme function
-function toggleTheme(set_mode = null) {
- // Set Mode and switch toggle icon
- if (set_mode === 0) {
- document.querySelector("body").classList = ["list"];
- document.querySelector("#darkmodetoggle").style="display:inherit;";
- document.querySelector("#lightmodetoggle").style="display:none;";
- } else {
- document.querySelector("body").classList = ["list dark"];
- document.querySelector("#darkmodetoggle").style="display:none;";
- document.querySelector("#lightmodetoggle").style="display:inherit;";
- }
- // Mark mode in session storage
- sessionStorage.setItem("dark-mode", set_mode);
-}
-// On Load: set dark mode if necessary
-function initialTheme() {
- if (
- // Case 1: Dark Mode is preferred and no cookie is set
- (window.matchMedia('(prefers-color-scheme: dark)').matches &&
- sessionStorage.getItem("dark-mode") === null) ||
- // Case 2: Dark Mode is set via session Storage
- sessionStorage.getItem("dark-mode") == 1
- ) {
- toggleTheme(set_mode = 1);
- } else {
- toggleTheme(set_mode = 0);
- }
-}
diff --git a/assets/js/twitch.js b/assets/js/twitch.js
@@ -1,55 +1,200 @@
-function toggle_player(stream) {
- var img = document.querySelector("#img-" + stream);
- var ply = document.querySelector("#play-" + stream);
- if (img != null) {
- var player = document.createElement("iframe");
- player.allowFullscreen = "true";
- player.width = "100%";
- player.height = "100%";
- player.frameborder = "0";
- player.scrolling = "no";
- player.src = "https://player.twitch.tv/?channel=" +
- stream +
- "&parent=" +
- window.location.hostname +
- "&muted=false&volume=1&quality=auto";
- player.id = "play-" + stream;
- player.classList.add("preview");
- img.replaceWith(player);
+async function twitch_check_online(stream) {
+
+ // Construct request
+ const status_url = new Request(
+ "https://static-cdn.jtvnw.net/previews-ttv/live_user_" + stream +
+ "-853x480.jpg"
+ );
+
+ // Get HTTP status code of status_url
+ const response_url = await fetch(status_url).then((response) => {
+ return response;
+ });
+
+ // refer online status from whether a redirect happened
+ const status = (status_url.url === response_url.url);
+
+ // return online status
+ return(status);
+
+}
+
+async function tw2html_toggle_hidden(stream) {
+
+ // get stream section
+ var section = document.getElementById("stream_" + stream);
+
+ // check online status of stream
+ const status = await twitch_check_online(stream);
+
+ // Debug output
+ console.log("Stream: " + stream + " | Status: " + status);
+
+ // If stream is online, unhide it. If it is offline, hide it
+ if (status) {
+
+ // Unhide section
+ section.classList.remove("hidden");
+
+ // Check whether player exists
+ var player = section.querySelectorAll("#player_" + stream);
+
+ // Update preview image if player does NOT exist
+ if (player.length < 1) {
+ tw2html_update_preview(stream);
+ }
+
+ } else {
+
+ // Hide section
+ section.classList.add("hidden");
+
+ // Ensure iframe is removed
+ section.querySelectorAll("#player_" + stream).forEach(function(x) {
+ x.remove();
+ });
+
}
- if (ply != null) {
- var image = document.createElement("img");
- image.src = "https://static-cdn.jtvnw.net/previews-ttv/live_user_" + stream + "-1280x720.jpg";
- image.id = "img-" + stream;
- image.classList.add("preview");
- ply.replaceWith(image);
+
+ // Mark Loading indicator as done (unhide it)
+ document.getElementById("loading_indicator_" + stream).classList.remove(
+ "hidden"
+ );
+
+}
+
+function tw2html_update_preview(stream) {
+
+ // Construct current time
+ var now = new Date();
+
+ // Get image object
+ var img = document.getElementById("preview_" + stream);
+
+ // Update preview image
+ img.src = "https://static-cdn.jtvnw.net/previews-ttv/live_user_" + stream +
+ "-1280x720.jpg" +
+ "?t=" + now.getTime(); // Add some index to force reload of image
+
+ // Ensure preview image is visible
+ img.classList.remove("hidden");
+
+}
+
+function tw2html_toggle_player(stream) {
+
+ // get stream section
+ var section = document.getElementById("stream_" + stream);
+
+ // if player-iframe exists, remove it and unhide preview image
+ var player = section.querySelectorAll("#player_" + stream);
+
+ if (player.length > 0) {
+
+ // unhide and update preview image
+ tw2html_update_preview(stream);
+
+ // remove player
+ player.forEach(function(x) { x.remove(); });
+
+ // Hide player container
+ section.querySelector(".player_container").classList.add("hidden");
+
+ } else {
+
+ // hide preview image
+ section.querySelector("#preview_" + stream).classList.add("hidden");
+
+ // unhide player container
+ section.querySelector(".player_container").classList.remove("hidden");
+
+ // Construct player iframe
+ var player = document.createElement("iframe");
+ player.id = "player_" + stream;
+ player.classList.add("player");
+ player.allowFullscreen = "true";
+ player.width = "100%";
+ player.height = "100%";
+ player.frameborder = "0";
+ player.scrolling = "no";
+ player.src = "https://player.twitch.tv/?channel=" +
+ stream +
+ "&parent=" +
+ window.location.hostname +
+ "&muted=false&volume=1&quality=auto";
+
+ // inject player into stream container
+ section.querySelector(".player_container").prepend(player);
+
}
+
}
-function toggle_chat(stream) {
- var plho = document.querySelector("#chat-placeholder-" + stream);
- var chat = document.querySelector("#chat-" + stream);
- if (chat != null) {
- var placeholder = document.createElement("div");
- placeholder.id = "chat-placeholder-" + stream;
- chat.replaceWith(placeholder);
+function tw2html_toggle_chat(stream) {
+
+ // get stream section
+ var section = document.getElementById("stream_" + stream);
+
+ // get chat object if it exists
+ var chat = section.querySelectorAll("#chat_" + stream);
+
+ if (chat.length > 0) {
+ var chat = chat[0];
+ } else {
+ var chat = null;
}
- if (plho != null) {
- var chatembed = document.createElement("iframe");
- chatembed.width = "100%";
- chatembed.height = "100%";
- chatembed.frameborder = "0";
- chatembed.scrolling = "auto";
- chatembed.src = "https://www.twitch.tv/embed/" +
- stream +
- "/chat?parent=" +
- window.location.hostname;
- // Check for dark mode
- if (window.matchMedia('(prefers-color-scheme: dark)')) {
- chatembed.src = chatembed.src + "&darkpopout";
- }
- chatembed.id = "chat-" + stream;
- plho.replaceWith(chatembed);
+
+ if (chat !== null) {
+
+ chat.remove();
+
+ } else {
+
+ // Create chat iframe
+ var chat = document.createElement("iframe");
+ chat.id = "chat-" + stream;
+ chat.width = "100%";
+ chat.height = "100%";
+ chat.frameborder = "0";
+ chat.scrolling = "auto";
+ chat.src = "https://www.twitch.tv/embed/" +
+ stream +
+ "/chat?parent=" +
+ window.location.hostname;
+
+ // If dark mode is used, apply it to the chat as well
+ if (window.matchMedia('(prefers-color-scheme: dark)')) {
+ chat.src = chat.src + "&darkpopout";
+ }
+
+ // inject chat into chat container
+ section.querySelector(".chat_container").prepend(chat);
}
+
}
+function tw2html_reload() {
+
+ // Debug output
+ console.log("Reloading list of online streams...")
+
+ // Hide loading indicator (hidden means "still loading")
+ document.querySelectorAll(".loading_indicator").forEach(function(x) {
+ x.classList.add("hidden");
+ })
+
+ // gather list of sections (streams)
+ var sections = document.querySelectorAll(".streams");
+
+ // Loop through each section
+ for (i = 0; i < sections.length; i++) {
+
+ // Gather stream name
+ var stream = sections[i].id.replace("stream_", "");
+
+ // check online status and untoggle hidden class asynchronously
+ tw2html_toggle_hidden(stream);
+
+ }
+
+}
diff --git a/index.php b/index.php
@@ -7,27 +7,31 @@
<?php
-// Fetch GET and POST arguments
-if (is_null($_POST["streams"])) {
- $loading_screen = true;
- if (!is_null($_GET["streams"])) {
- $channels = explode(",", $_GET["streams"]);
- } else {
- $channels = explode(",", $_COOKIE["streams"]);
+ /* Initilise streams array */
+ $streams = array();
+
+ // Fetch POST argument or the COOKIE
+ // Cookie should have lowest priority (fallback), POST first (intend)
+ if (!is_null($_COOKIE["streams"])) {
+ $streams = explode(",", $_COOKIE["streams"]);
+ }
+ if (!is_null($_POST["streams"])) {
+ $streams = urldecode($_POST["streams"]);
+ $streams = preg_replace('/[^a-z0-9\-\_\.\,]+/', '', $streams);
+ $streams = explode(",", $streams);
}
-} else {
- $loading_screen = false;
- $channels = unserialize($_POST["streams"]);
-}
-
-// Prepare streams array
-$streams = array(
- "stream" => $channels,
- "desc" => array(),
- "game" => array(),
- "status" => array(),
- "time" => array()
-);
+
+ // Sort streams by alphabet
+ sort($streams);
+
+ /* refresh cookie */
+ header(
+ "Set-Cookie: " .
+ "streams=" . implode(",", $streams) . ";" .
+ "Max-Age=" . 31536000 . "; " . /* 60 x 60 x 24 x 365 = 1 year */
+ "Domain=" . $_SERVER["SERVER_NAME"] . "; " .
+ "SameSite=Strict;"
+ );
?>
@@ -41,74 +45,120 @@ $streams = array(
<link rel="icon" type="image/png" sizes="128x128" href="assets/img/twitch_128.png">
<link rel="apple-touch-icon" href="assets/img/twitch.png">
<link rel="stylesheet" type="text/css" href="assets/css/simple.min.css"/>
- <link rel="stylesheet" href="assets/css/loading.css" />
<link rel="stylesheet" href="assets/css/custom.css" />
<script async src="assets/js/twitch.js"></script>
<link crossorigin="use-credentials" rel="manifest" href="manifest.json">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
- <body>
+ <body onload="tw2html_reload();">
<header>
+ <!-- Buttons -->
+ <nav>
+ <a href="#" onclick="tw2html_reload();">Reload</a>
+ <a href="#streams">Streams</a>
+ <a href="https://src.jayvii.de/pub/tw2html">Development</a>
+ </nav>
+ <!-- Headline -->
<h1>Streams</h1>
- <!-- Save Button -->
- <a
- id="savebtn"
- class="button"
- href="#"
- onclick="document.cookie='streams=<?php echo implode(",", $streams["stream"]); ?>;path=/;max-age=31536000;';document.querySelector('#savebtn').style.color='inherit';"
+ <!-- Loading Indicator -->
+ <div id="loading_indicators">
+ <?php
+ foreach ($streams as $stream) {
+ ?>
+ <span
+ class="loading_indicator hidden"
+ id="loading_indicator_<?php echo $stream; ?>"
+ title="<?php echo $stream; ?>"
+ >
+ ■
+ </span>
<?php
- if (implode(",", $streams["stream"]) != $_COOKIE["streams"]) {
- echo "style=color:#db4325";
}
?>
- >
- Save
- </a>
- <!-- Reload Button -->
- <a
- class="button"
- href="<?php echo "/?streams=" . implode(",", $streams["stream"]); ?>"
- >
- Reload
- </a>
- <!-- Live pseudo-button -->
- <a class="button" href="#streams">
- Live: <span id="num_streams">0</span>
- </a>
+ </div>
</header>
<!-- Search Bar -->
- <form action="https://www.twitch.tv/search" method="GET" target="_blank" style="width:100%;margin-top:1em;">
- <input id="searchbar" type="text" id="searchInput" name="term" placeholder="Search on twitch.tv">
+ <form
+ action="https://www.twitch.tv/search"
+ method="GET"
+ target="_blank"
+ style="width:100%;margin-top:1em;"
+ >
+ <input
+ id="searchbar"
+ type="text"
+ id="searchInput"
+ name="term"
+ placeholder="Search on twitch.tv"
+ >
</form>
- <div id="streams">
+ <!-- Streams List -->
+ <?php
-<?php
+ foreach ($streams as $stream) {
-// Streams or Loading Screen
-if (!$loading_screen) {
- // Load Stream Data
- include("lib/load_streams.php");
- // Build HTML from Stream Data
- include("lib/build_html.php");
-} else {
- // Load Loading Screen content
- include("lib/loadingscreen.php");
-}
+ ?>
-?>
+ <!-- Section for streamer <?php echo $stream; ?> (hidden by default) -->
+ <section class="hidden streams" id="stream_<?php echo $stream; ?>">
+
+ <!-- Headline: streamer name -->
+ <h2>
+ <a href="https://www.twitch.tv/<?php echo $stream; ?>" target="_blank">
+ <?php echo $stream; ?>
+ </a>
+ </h2>
+
+ <!-- player Container -->
+ <div class="player_container hidden"></div>
- </div>
+ <!-- Preview Image -->
+ <img
+ id="preview_<?php echo $stream; ?>"
+ class="preview"
+ src=""
+ loading="lazy"
+ >
+
+ <!-- Stream Button -->
+ <button onclick="tw2html_toggle_player('<?php echo $stream; ?>')">
+ Toggle Stream
+ </button>
+
+ <!-- Chat Collapsable -->
+ <details class="button">
+ <summary onclick="tw2html_toggle_chat('<?php echo $stream; ?>');">
+ Chat
+ </summary>
+ <div class="chat_container"></div>
+ </details>
+
+ </section>
+
+ <?php
+
+ }
+
+ ?>
+
+ <!-- Stream List Form -->
+ <h2 id="streams">List of Streams</h2>
+ <p>
+ Please enter the Twitch.TV usernames of streams you want to check here, each
+ separated with a ","
+ </p>
+ <form action="/" method="POST">
+ <textarea
+ name="streams"
+ placeholder="streamerA, streamerB, ..."
+ ><?php echo implode("," . PHP_EOL, $streams); ?></textarea>
+ <input type="submit" value="Submit & Reload">
+ </form>
</body>
- <!-- Script for counting active streams -->
- <script>
- var num_streams = document.querySelectorAll(".streamlisting").length;
- document.querySelector("#num_streams").innerText = num_streams;
- </script>
-
</html>