248 lines
8.2 KiB
JavaScript
248 lines
8.2 KiB
JavaScript
/*Canopy - The next generation of stoner streaming software
|
||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU Affero General Public License as
|
||
published by the Free Software Foundation, either version 3 of the
|
||
License, or (at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU Affero General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Affero General Public License
|
||
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||
|
||
/**
|
||
* Class containing base code for the Canopy channel client.
|
||
*/
|
||
class channel{
|
||
/**
|
||
* Instantiates a new channel object
|
||
*/
|
||
constructor(){
|
||
//Establish connetion to the server via socket.io
|
||
this.connect();
|
||
//Define socket listeners
|
||
this.defineListeners();
|
||
|
||
/**
|
||
* Returns true once the ytEmbed API has loaded in from google (eww)
|
||
*/
|
||
this.ytEmbedAPILoaded = false;
|
||
|
||
/**
|
||
* Current connected channels name
|
||
*/
|
||
this.channelName = window.location.pathname.split('/c/')[1].split('/')[0];
|
||
|
||
/**
|
||
* Child Video Player object
|
||
*/
|
||
this.player = new player(this);
|
||
|
||
/**
|
||
* Child Chat Box Object
|
||
*/
|
||
this.chatBox = new chatBox(this);
|
||
|
||
/**
|
||
* Child User List Object
|
||
*/
|
||
this.userList = new userList(this);
|
||
|
||
/**
|
||
* Child Canopy Panel Object
|
||
*/
|
||
this.cPanel = new cPanel(this);
|
||
|
||
//Set defaults for any unset settings and run any required process steps for the current config
|
||
this.setDefaults(false, true);
|
||
|
||
//Freak out any weirdos who take a peek in the dev console for shits n gigs
|
||
console.log("👁️👄👁️ ℬℴ𝓊𝓃𝒿ℴ𝓊𝓇.");
|
||
}
|
||
|
||
/**
|
||
* Handles initial client connection
|
||
*/
|
||
connect(){
|
||
this.socket = io({
|
||
extraHeaders: {
|
||
//Include CSRF token
|
||
'x-csrf-token': utils.ajax.getCSRFToken()
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Defines network-related listeners
|
||
*/
|
||
defineListeners(){
|
||
this.socket.on("connect", () => {
|
||
document.title = `${this.channelName} - Connected`
|
||
});
|
||
|
||
this.socket.on("kick", async (data) => {
|
||
if(data.reason == "Invalid CSRF Token!"){
|
||
//Reload the CSRF token
|
||
await utils.ajax.reloadCSRFToken();
|
||
|
||
//Retry the connection
|
||
this.connect();
|
||
}else{
|
||
new canopyUXUtils.popup(`You have been ${data.type} from the channel for the following reason:<br>${data.reason}`);
|
||
}
|
||
});
|
||
|
||
this.socket.on("clientMetadata", this.handleClientInfo.bind(this));
|
||
|
||
this.socket.on("error", utils.ux.displayResponseError);
|
||
|
||
this.socket.on("queue", (data) => {
|
||
this.queue = new Map(data.queue);
|
||
});
|
||
|
||
this.socket.on("lock", (data) => {
|
||
this.queueLock = data.locked;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Handles initial client-metadata ingestion from server upon connection
|
||
* @param {Object} data - Data glob from server
|
||
*/
|
||
handleClientInfo(data){
|
||
//Ingest user data
|
||
this.user = data.user;
|
||
|
||
//Re-hydrate permission maps
|
||
this.user.permMap.site = new Map(data.user.permMap.site);
|
||
this.user.permMap.chan = new Map(data.user.permMap.chan);
|
||
|
||
//Tell the chatbox to handle client info
|
||
//should it have its own event listener instead? Guess it's a stylistic choice :P
|
||
this.chatBox.handleClientInfo(data);
|
||
|
||
//Store queue for use by the queue panel
|
||
this.queue = new Map(data.queue);
|
||
|
||
//Store queue lock status
|
||
this.queueLock = data.queueLock;
|
||
|
||
//For each chat held in the chat buffer
|
||
for(let chat of data.chatBuffer){
|
||
//Display the chat
|
||
this.chatBox.displayChat(chat);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Processes and applies default config on any unset settings
|
||
* @param {Boolean} force - Whether or not to forcefully reset already set settings
|
||
* @param {Boolean} processConfig - Whether or not to run the Process Config function once complete
|
||
*/
|
||
setDefaults(force = false, processConfig = false){
|
||
//Iterate through default config
|
||
for(let [key, value] of channel.defaultConfig){
|
||
//If the setting is unset or function was called forcefully
|
||
if(force || localStorage.getItem(key) == null){
|
||
//Set item from default map
|
||
localStorage.setItem(key, value);
|
||
}
|
||
|
||
//If we're running process steps for the config
|
||
if(processConfig){
|
||
//Process the current config value
|
||
this.processConfig(key, localStorage.getItem(key));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Run once every config change to ensure settings are properly set
|
||
* @param {String} key - Setting to change
|
||
* @param {*} value - Value to set setting to
|
||
*/
|
||
processConfig(key, value){
|
||
//Switch/case by config key
|
||
switch(key){
|
||
case 'ytPlayerType':
|
||
const embedScript = document.querySelector(".yt-embed-api");
|
||
//If the user is running the embedded player and we don't have en embed script loaded
|
||
if(value == 'embed' && embedScript == null){
|
||
//Find our footer
|
||
const footer = document.querySelector('footer');
|
||
|
||
//Create new script tag
|
||
const ytEmbedAPI = document.createElement('script');
|
||
//Link the iframe api from youtube
|
||
ytEmbedAPI.src = "https://www.youtube.com/iframe_api";
|
||
//set the iframe api script id
|
||
ytEmbedAPI.classList.add('yt-embed-api');
|
||
|
||
//Append the script tag to the top of the footer to give everything else access
|
||
footer.prepend(ytEmbedAPI);
|
||
//If we're not using the embed player but the script is loaded
|
||
}else if(embedScript != null){
|
||
//Pull all scripts since the main one might have pulled others
|
||
const scripts = document.querySelectorAll('script');
|
||
|
||
//Iterate through all script tags on the page
|
||
for(let script of scripts){
|
||
//If the script came from youtube
|
||
if(script.src.match(/youtube\.com|youtu\.be/)){
|
||
//Rip it out
|
||
script.remove();
|
||
}
|
||
}
|
||
}
|
||
|
||
//If the player or mediaHandler isn't loaded
|
||
if(this.player == null || this.player.mediaHandler == null){
|
||
//We're fuggin done here
|
||
return;
|
||
}
|
||
|
||
|
||
//Get current video
|
||
const nowPlaying = this.player.mediaHandler.nowPlaying;
|
||
|
||
//If we're playing a youtube video
|
||
if(nowPlaying != null && nowPlaying.type == 'yt'){
|
||
//Restart the video
|
||
this.player.start({media: nowPlaying});
|
||
}
|
||
|
||
//Stop while we're ahead
|
||
return;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Default channel config
|
||
*/
|
||
static defaultConfig = new Map([
|
||
["ytPlayerType","raw"]
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Youtube iframe-embed API entry point
|
||
*/
|
||
function onYouTubeIframeAPIReady(){
|
||
//Set embed api to true
|
||
client.ytEmbedAPILoaded = true;
|
||
|
||
//Get currently playing item
|
||
const nowPlaying = client.player.mediaHandler.nowPlaying;
|
||
|
||
//If we're playing a youtube video and the official embeds are enabled
|
||
if(nowPlaying.type == 'yt' && localStorage.getItem('ytPlayerType') == "embed"){
|
||
//Restart the video now that the embed api has loaded
|
||
client.player.start({media: nowPlaying});
|
||
}
|
||
}
|
||
|
||
const client = new channel(); |