async function generateFollowedStreamsList() {
		let followedStreamsListElement = document.getElementById("followedStreamsList");
		let followedStreamsListStatusElement = document.getElementById("followedStreamsListStatus");

		// If the twitchGameIds have not yet been retrieved
		if (twitchGameIds === undefined) {
			await getTwitchGameIds(); // Get the Twitch game ids from file
		}

		// If there is a twitchAuthToken already set and twitchGameIds is not undefined
		if (twitchAuthToken !== "" && twitchGameIds !== undefined) {
			// Show the streams list status element
			followedStreamsListStatusElement.classList.remove("hidden");

			// If no streams have been generated yet
			if (followedStreamsListElement.innerHTML === "") {
				// Let the user know a list is being generated
				followedStreamsListStatusElement.innerHTML = `Generating list...`;
			}
			// Let the user know the list is updating
			else {
				// Let the user know a list is being generated
				followedStreamsListStatusElement.innerHTML = `Updating list...`;
			}

			// Fetch user object
			let usersResult = await fetch(`https://api.twitch.tv/helix/users`, {
				headers: {
					"Client-ID": twitchClientID,
					Authorization: `Bearer ${twitchAuthToken}`,
				},
			});

			// The user object was successfully fetched
			if (usersResult.ok) {
				let userObject = await usersResult.json(); // Get user id

				let followedStreamsPlayingJG = []; // Array of followed streams which are playing Jackbox games
				let twitchCursor = ""; // Variable for holding the results cursor

				// Iterate through each page of query results
				do {
					//Followed streams in the current query
					let currentPageOfFollowedStreams = await fetch(`https://api.twitch.tv/helix/users/follows?from_id=${userObject.data[0].id}&first=30&after=${twitchCursor}`, {
						headers: {
							"Client-ID": twitchClientID,
							Authorization: `Bearer ${twitchAuthToken}`,
						},
					});

					let currentPageOfFollowedStreamsJSON = await currentPageOfFollowedStreams.json(); // Convert query result to json object
					let userIdParamString = ""; // Variable user ids parameters

					// If the user is following at least one stream
					if (currentPageOfFollowedStreamsJSON.data.length > 0) {
						// Add each user from the current page to the userIdParamString
						for (let u = 0; u < currentPageOfFollowedStreamsJSON.data.length; u++) {
							userIdParamString += "user_id=";
							userIdParamString += currentPageOfFollowedStreamsJSON.data[u].to_id;

							// If this is not the last param append an "&" after
							if (u + 1 < currentPageOfFollowedStreamsJSON.data.length) {
								userIdParamString += "&";
							}
						}

						// Fetch stream status info of the current page
						let streamInfo = await fetch(`https://api.twitch.tv/helix/streams?${userIdParamString}`, {
							headers: {
								"Client-ID": twitchClientID,
								Authorization: `Bearer ${twitchAuthToken}`,
							},
						});

						userIdParamString = ""; // Clear param string

						// Add each user from the current page to the userIdParamString
						for (let u = 0; u < currentPageOfFollowedStreamsJSON.data.length; u++) {
							userIdParamString += "id=";
							userIdParamString += currentPageOfFollowedStreamsJSON.data[u].to_id;

							// If this is not the last param append an "&" after
							if (u + 1 < currentPageOfFollowedStreamsJSON.data.length) {
								userIdParamString += "&";
							}
						}

						// Fetch user info of the current page
						let userInfo = await fetch(`https://api.twitch.tv/helix/users?${userIdParamString}`, {
							headers: {
								"Client-ID": twitchClientID,
								Authorization: `Bearer ${twitchAuthToken}`,
							},
						});

						// If fetch successfully got stream status and user info for the current page
						if (streamInfo.ok && userInfo.ok) {
							let streamInfoJSON = await streamInfo.json(); // Convert query result to json object
							let userInfoJSON = await userInfo.json(); // Convert query result to json object

							// Iterate through each stream result
							for (let s = 0; s < streamInfoJSON.data.length; s++) {
								// Iterate through each game id
								for (let g = 0; g < twitchGameIds.length; g++) {
									// If the stream is playing a game matching one from the list of game ids
									if (twitchGameIds[g].game_id === streamInfoJSON.data[s].game_id) {
										let tempObject = streamInfoJSON.data[s]; // Copy current stream to a temp object

										// Iterate through all the user info to find the matching profile image
										for (let p = 0; p < userInfoJSON.data.length; p++) {
											// If the user info id matches the stream info user id
											if (userInfoJSON.data[p].id === streamInfoJSON.data[s].user_id) tempObject.profile_image_url = userInfoJSON.data[p].profile_image_url; // Copy profile image from user info
										}
										followedStreamsPlayingJG.push(tempObject); // Push the stream object
									}
								}
							}
						}
					}

					// If there are more query pages
					if (currentPageOfFollowedStreamsJSON.pagination.cursor !== undefined) {
						twitchCursor = currentPageOfFollowedStreamsJSON.pagination.cursor; // Save the cursor
					}
					// Else there are no more query pages
					else {
						twitchCursor = ""; // Empty the cursor
					}
				} while (twitchCursor !== "");

				// If at least one stream is playing a valid game id
				if (followedStreamsPlayingJG.length > 0) {
					// If more than one stream
					if (followedStreamsPlayingJG.length > 1) {
						// sort based on view count
					}

					followedStreamsListElement.innerHTML = ""; // Clear followedStreamsList

					// For each stream add it to the dropdown list
					followedStreamsPlayingJG.forEach((s) => {
						let gameName = ""; // Variable for holding game name

						// Iterate through game ids
						for (let g = 0; g < twitchGameIds.length; g++) {
							// If the game id matches the stream's game id
							if (twitchGameIds[g].game_id === s.game_id) {
								gameName = twitchGameIds[g].game_name; // Grab the game name
							}
						}

						// Create a stream list component
						followedStreamsListElement.innerHTML += `
							<div 
								class="followedStream flex flex-row flex-no-wrap p-2 text-center align-middle cursor-pointer border-b border-solid border-gray-400 hover:bg-teal-100" 
								title="${s.user_name + " playing " + gameName + " for " + (s.viewer_count === 1 ? s.viewer_count + " viewer" : s.viewer_count === 0 ? "no one." : s.viewer_count.toLocaleString() + " viewers.")}" 
								data-stream-name="${s.user_name}"
							>
								<div class="flex-initial w-4/12 text-left truncate">
									<img src="${s.profile_image_url}" class="inline w-6 rounded">
									<span class="font-bold">${s.user_name}</span>
								</div>
								<div class="flex-grow text-left truncate">
									<span class="underline">${gameName}</span>
								</div>
								<div class="flex-initial w-3/12 text-right">
									${s.viewer_count === 1 ? s.viewer_count + " viewer" : s.viewer_count === 0 ? "No viewers" : s.viewer_count.toLocaleString() + " viewers"}
								</div>
							</div>
						`;
					});

					// Add event listener to each stream component
					document.querySelectorAll(".followedStream").forEach((e) => {
						e.addEventListener("click", () => {
							streamURLElement.value = `https://twitch.tv/${e.getAttribute("data-stream-name")}`; // Set the stream URL input
							updateStreamFrame(); // Update the stream frame
							updatePlayer(); // Update the player URL based on user input
						});
					});
				}
				// No followed streams are playing a valid game id
				else {
					// Let the user know
					followedStreamsListElement.innerHTML = `
						<div class="p-2 text-center">
							Sorry, none of your followed streams are playing a Jackbox game.
						</div>
					`;
				}
			}
			// Failed to fetch the user object from Twitch (probably the user revoked access)
			else {
				// Set followedStreamsList innerHTML to a notice
				followedStreamsListElement.innerHTML = `
					<div class="p-2 text-center">
						Want to see which of your followed Twitch streams are playing Jackbox
						games?<br />
						<button
							id="authorizeTwitchAccessButton"
							class="bg-purple-500 text-white rounded mt-1 px-4 py-1 hover:bg-purple-600"
						>
							Authorize <i class="fab fa-twitch"></i> access
						</button>
					</div>
				`;
			}

			// Hide the streams list status element
			followedStreamsListStatusElement.classList.add("hidden");
		}
		// Else let the user know
		else {
			// Set followedStreamsList innerHTML to a notice
			followedStreamsListElement.innerHTML = `
				<div class="p-2 text-center">
					Want to see which of your followed Twitch streams are playing Jackbox
					games?<br />
					<button
						id="authorizeTwitchAccessButton"
						class="bg-purple-500 text-white rounded mt-1 px-4 py-1 hover:bg-purple-600"
					>
						Authorize <i class="fab fa-twitch"></i> access
					</button>
				</div>
			`;

			// Add event listener for authorizeTwitchAccessButton
			document.getElementById("authorizeTwitchAccessButton").addEventListener("click", () => {
				redirectForTwitchAuthToken("stream"); // Send the user to get a Twitch auth token
			});
		}
	}
// Function for generating a list of the user's followed streams playing Jackbox games
Function for generating a list of the user's followed streams playing Jackbox games
Function for generating a list of the user ' s followed streams playing Jackbox games
async function generateFollowedStreamsList() {
		let followedStreamsListElement = document.getElementById("followedStreamsList");
		let followedStreamsListStatusElement = document.getElementById("followedStreamsListStatus");
		if (twitchGameIds === undefined) {
			await getTwitchGameIds(); 
		}
		if (twitchAuthToken !== "" && twitchGameIds !== undefined) {
			followedStreamsListStatusElement.classList.remove("hidden");
			if (followedStreamsListElement.innerHTML === "") {
				followedStreamsListStatusElement.innerHTML = `Generating list...`;
			}
			else {
				followedStreamsListStatusElement.innerHTML = `Updating list...`;
			}
			let usersResult = await fetch(`https://api.twitch.tv/helix/users`, {
				headers: {
					"Client-ID": twitchClientID,
					Authorization: `Bearer ${twitchAuthToken}`,
				},
			});
			if (usersResult.ok) {
				let userObject = await usersResult.json(); 
				let followedStreamsPlayingJG = []; 
				let twitchCursor = ""; 
				do {
					let currentPageOfFollowedStreams = await fetch(`https://api.twitch.tv/helix/users/follows?from_id=${userObject.data[0].id}&first=30&after=${twitchCursor}`, {
						headers: {
							"Client-ID": twitchClientID,
							Authorization: `Bearer ${twitchAuthToken}`,
						},
					});
					let currentPageOfFollowedStreamsJSON = await currentPageOfFollowedStreams.json(); 
					let userIdParamString = ""; 
					if (currentPageOfFollowedStreamsJSON.data.length > 0) {
						for (let u = 0; u < currentPageOfFollowedStreamsJSON.data.length; u++) {
							userIdParamString += "user_id=";
							userIdParamString += currentPageOfFollowedStreamsJSON.data[u].to_id;
							if (u + 1 < currentPageOfFollowedStreamsJSON.data.length) {
								userIdParamString += "&";
							}
						}
						let streamInfo = await fetch(`https://api.twitch.tv/helix/streams?${userIdParamString}`, {
							headers: {
								"Client-ID": twitchClientID,
								Authorization: `Bearer ${twitchAuthToken}`,
							},
						});
						userIdParamString = ""; 
						for (let u = 0; u < currentPageOfFollowedStreamsJSON.data.length; u++) {
							userIdParamString += "id=";
							userIdParamString += currentPageOfFollowedStreamsJSON.data[u].to_id;
							if (u + 1 < currentPageOfFollowedStreamsJSON.data.length) {
								userIdParamString += "&";
							}
						}
						let userInfo = await fetch(`https://api.twitch.tv/helix/users?${userIdParamString}`, {
							headers: {
								"Client-ID": twitchClientID,
								Authorization: `Bearer ${twitchAuthToken}`,
							},
						});
						if (streamInfo.ok && userInfo.ok) {
							let streamInfoJSON = await streamInfo.json(); 
							let userInfoJSON = await userInfo.json(); 
							for (let s = 0; s < streamInfoJSON.data.length; s++) {
								for (let g = 0; g < twitchGameIds.length; g++) {
									if (twitchGameIds[g].game_id === streamInfoJSON.data[s].game_id) {
										let tempObject = streamInfoJSON.data[s]; 
										for (let p = 0; p < userInfoJSON.data.length; p++) {
											if (userInfoJSON.data[p].id === streamInfoJSON.data[s].user_id) tempObject.profile_image_url = userInfoJSON.data[p].profile_image_url; 
										}
										followedStreamsPlayingJG.push(tempObject); 
									}
								}
							}
						}
					}
					if (currentPageOfFollowedStreamsJSON.pagination.cursor !== undefined) {
						twitchCursor = currentPageOfFollowedStreamsJSON.pagination.cursor; 
					}
					else {
						twitchCursor = ""; 
					}
				} while (twitchCursor !== "");
				if (followedStreamsPlayingJG.length > 0) {
					if (followedStreamsPlayingJG.length > 1) {
					}
					followedStreamsListElement.innerHTML = ""; 
					followedStreamsPlayingJG.forEach((s) => {
						let gameName = ""; 
						for (let g = 0; g < twitchGameIds.length; g++) {
							if (twitchGameIds[g].game_id === s.game_id) {
								gameName = twitchGameIds[g].game_name; 
							}
						}
						followedStreamsListElement.innerHTML += `
							<div 
								class="followedStream flex flex-row flex-no-wrap p-2 text-center align-middle cursor-pointer border-b border-solid border-gray-400 hover:bg-teal-100" 
								title="${s.user_name + " playing " + gameName + " for " + (s.viewer_count === 1 ? s.viewer_count + " viewer" : s.viewer_count === 0 ? "no one." : s.viewer_count.toLocaleString() + " viewers.")}" 
								data-stream-name="${s.user_name}"
							>
								<div class="flex-initial w-4/12 text-left truncate">
									<img src="${s.profile_image_url}" class="inline w-6 rounded">
									<span class="font-bold">${s.user_name}</span>
								</div>
								<div class="flex-grow text-left truncate">
									<span class="underline">${gameName}</span>
								</div>
								<div class="flex-initial w-3/12 text-right">
									${s.viewer_count === 1 ? s.viewer_count + " viewer" : s.viewer_count === 0 ? "No viewers" : s.viewer_count.toLocaleString() + " viewers"}
								</div>
							</div>
						`;
					});
					document.querySelectorAll(".followedStream").forEach((e) => {
						e.addEventListener("click", () => {
							streamURLElement.value = `https://twitch.tv/${e.getAttribute("data-stream-name")}`; 
							updateStreamFrame(); 
							updatePlayer(); 
						});
					});
				}
				else {
					followedStreamsListElement.innerHTML = `
						<div class="p-2 text-center">
							Sorry, none of your followed streams are playing a Jackbox game.
						</div>
					`;
				}
			}
			else {
				followedStreamsListElement.innerHTML = `
					<div class="p-2 text-center">
						Want to see which of your followed Twitch streams are playing Jackbox
						games?<br />
						<button
							id="authorizeTwitchAccessButton"
							class="bg-purple-500 text-white rounded mt-1 px-4 py-1 hover:bg-purple-600"
						>
							Authorize <i class="fab fa-twitch"></i> access
						</button>
					</div>
				`;
			}
			followedStreamsListStatusElement.classList.add("hidden");
		}
		else {
			followedStreamsListElement.innerHTML = `
				<div class="p-2 text-center">
					Want to see which of your followed Twitch streams are playing Jackbox
					games?<br />
					<button
						id="authorizeTwitchAccessButton"
						class="bg-purple-500 text-white rounded mt-1 px-4 py-1 hover:bg-purple-600"
					>
						Authorize <i class="fab fa-twitch"></i> access
					</button>
				</div>
			`;
			document.getElementById("authorizeTwitchAccessButton").addEventListener("click", () => {
				redirectForTwitchAuthToken("stream"); 
			});
		}
	}
async function generateFollowedStreamsList ( ) { let followedStreamsListElement = document . getElementById ( "followedStreamsList" ) ; let followedStreamsListStatusElement = document . getElementById ( "followedStreamsListStatus" ) ; if ( twitchGameIds === undefined ) { await getTwitchGameIds ( ) ; } if ( twitchAuthToken !== "" && twitchGameIds !== undefined ) { followedStreamsListStatusElement . classList . remove ( "hidden" ) ; if ( followedStreamsListElement . innerHTML === "" ) { followedStreamsListStatusElement . innerHTML = ` ` ; } else { followedStreamsListStatusElement . innerHTML = ` ` ; } let usersResult = await fetch ( ` ` , { headers : { "Client-ID" : twitchClientID , Authorization : ` ${ twitchAuthToken } ` , } , } ) ; if ( usersResult . ok ) { let userObject = await usersResult . json ( ) ; let followedStreamsPlayingJG = [ ] ; let twitchCursor = "" ; do { let currentPageOfFollowedStreams = await fetch ( ` ${ userObject . data [ 0 ] . id } ${ twitchCursor } ` , { headers : { "Client-ID" : twitchClientID , Authorization : ` ${ twitchAuthToken } ` , } , } ) ; let currentPageOfFollowedStreamsJSON = await currentPageOfFollowedStreams . json ( ) ; let userIdParamString = "" ; if ( currentPageOfFollowedStreamsJSON . data . length > 0 ) { for ( let u = 0 ; u < currentPageOfFollowedStreamsJSON . data . length ; u ++ ) { userIdParamString += "user_id=" ; userIdParamString += currentPageOfFollowedStreamsJSON . data [ u ] . to_id ; if ( u + 1 < currentPageOfFollowedStreamsJSON . data . length ) { userIdParamString += "&" ; } } let streamInfo = await fetch ( ` ${ userIdParamString } ` , { headers : { "Client-ID" : twitchClientID , Authorization : ` ${ twitchAuthToken } ` , } , } ) ; userIdParamString = "" ; for ( let u = 0 ; u < currentPageOfFollowedStreamsJSON . data . length ; u ++ ) { userIdParamString += "id=" ; userIdParamString += currentPageOfFollowedStreamsJSON . data [ u ] . to_id ; if ( u + 1 < currentPageOfFollowedStreamsJSON . data . length ) { userIdParamString += "&" ; } } let userInfo = await fetch ( ` ${ userIdParamString } ` , { headers : { "Client-ID" : twitchClientID , Authorization : ` ${ twitchAuthToken } ` , } , } ) ; if ( streamInfo . ok && userInfo . ok ) { let streamInfoJSON = await streamInfo . json ( ) ; let userInfoJSON = await userInfo . json ( ) ; for ( let s = 0 ; s < streamInfoJSON . data . length ; s ++ ) { for ( let g = 0 ; g < twitchGameIds . length ; g ++ ) { if ( twitchGameIds [ g ] . game_id === streamInfoJSON . data [ s ] . game_id ) { let tempObject = streamInfoJSON . data [ s ] ; for ( let p = 0 ; p < userInfoJSON . data . length ; p ++ ) { if ( userInfoJSON . data [ p ] . id === streamInfoJSON . data [ s ] . user_id ) tempObject . profile_image_url = userInfoJSON . data [ p ] . profile_image_url ; } followedStreamsPlayingJG . push ( tempObject ) ; } } } } } if ( currentPageOfFollowedStreamsJSON . pagination . cursor !== undefined ) { twitchCursor = currentPageOfFollowedStreamsJSON . pagination . cursor ; } else { twitchCursor = "" ; } } while ( twitchCursor !== "" ) ; if ( followedStreamsPlayingJG . length > 0 ) { if ( followedStreamsPlayingJG . length > 1 ) { } followedStreamsListElement . innerHTML = "" ; followedStreamsPlayingJG . forEach ( ( s ) => { let gameName = "" ; for ( let g = 0 ; g < twitchGameIds . length ; g ++ ) { if ( twitchGameIds [ g ] . game_id === s . game_id ) { gameName = twitchGameIds [ g ] . game_name ; } } followedStreamsListElement . innerHTML += ` ${ s . user_name + " playing " + gameName + " for " + ( s . viewer_count === 1 ? s . viewer_count + " viewer" : s . viewer_count === 0 ? "no one." : s . viewer_count . toLocaleString ( ) + " viewers." ) } ${ s . user_name } ${ s . profile_image_url } ${ s . user_name } ${ gameName } ${ s . viewer_count === 1 ? s . viewer_count + " viewer" : s . viewer_count === 0 ? "No viewers" : s . viewer_count . toLocaleString ( ) + " viewers" } ` ; } ) ; document . querySelectorAll ( ".followedStream" ) . forEach ( ( e ) => { e . addEventListener ( "click" , ( ) => { streamURLElement . value = ` ${ e . getAttribute ( "data-stream-name" ) } ` ; updateStreamFrame ( ) ; updatePlayer ( ) ; } ) ; } ) ; } else { followedStreamsListElement . innerHTML = ` ` ; } } else { followedStreamsListElement . innerHTML = ` ` ; } followedStreamsListStatusElement . classList . add ( "hidden" ) ; } else { followedStreamsListElement . innerHTML = ` ` ; document . getElementById ( "authorizeTwitchAccessButton" ) . addEventListener ( "click" , ( ) => { redirectForTwitchAuthToken ( "stream" ) ; } ) ; } }
Function for generating a list of the user's followed streams playing Jackbox games
Function for generating a list of the user ' s followed streams playing Jackbox games
// If the twitchGameIds have not yet been retrieved // Get the Twitch game ids from file // If there is a twitchAuthToken already set and twitchGameIds is not undefined // Show the streams list status element // If no streams have been generated yet // Let the user know a list is being generated // Let the user know the list is updating // Let the user know a list is being generated // Fetch user object // The user object was successfully fetched // Get user id // Array of followed streams which are playing Jackbox games // Variable for holding the results cursor // Iterate through each page of query results //Followed streams in the current query // Convert query result to json object // Variable user ids parameters // If the user is following at least one stream // Add each user from the current page to the userIdParamString // If this is not the last param append an "&" after // Fetch stream status info of the current page // Clear param string // Add each user from the current page to the userIdParamString // If this is not the last param append an "&" after // Fetch user info of the current page // If fetch successfully got stream status and user info for the current page // Convert query result to json object // Convert query result to json object // Iterate through each stream result // Iterate through each game id // If the stream is playing a game matching one from the list of game ids // Copy current stream to a temp object // Iterate through all the user info to find the matching profile image // If the user info id matches the stream info user id // Copy profile image from user info // Push the stream object // If there are more query pages // Save the cursor // Else there are no more query pages // Empty the cursor // If at least one stream is playing a valid game id // If more than one stream // sort based on view count // Clear followedStreamsList // For each stream add it to the dropdown list // Variable for holding game name // Iterate through game ids // If the game id matches the stream's game id // Grab the game name // Create a stream list component // Add event listener to each stream component // Set the stream URL input // Update the stream frame // Update the player URL based on user input // No followed streams are playing a valid game id // Let the user know // Failed to fetch the user object from Twitch (probably the user revoked access) // Set followedStreamsList innerHTML to a notice // Hide the streams list status element // Else let the user know // Set followedStreamsList innerHTML to a notice // Add event listener for authorizeTwitchAccessButton // Send the user to get a Twitch auth token