Changed out spliced ID's for dataset in admin panel. Removed auto-generated documentation from build step.

This commit is contained in:
rainbow napkin 2025-09-07 09:43:45 -04:00
parent cbd2136ca6
commit 3ab6c6c715
151 changed files with 31 additions and 142267 deletions

View file

@ -20,8 +20,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(permList).forEach((key)=>{ %> <% Object.keys(permList).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %> <% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label id="admin-perm-list-label-<%- key %>" class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%- key %>"><%- key %>: </label> <label class="admin-list-label admin-perm-list" for="admin-perm-list-rank-select-<%- key %>"><%- key %>: </label>
<select id="admin-perm-list-rank-select-<%- key %>" name="admin-perm-list-rank-select-<%- key %>" class="admin-list-select admin-perm-list-rank-select"> <select name="admin-perm-list-rank-select-<%- key %>" data-key="<%- key %>" class="admin-list-select admin-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(permList[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option> <option <%if(permList[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
<% }); %> <% }); %>
@ -33,8 +33,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(permList.channelOverrides).forEach((key)=>{ %> <% Object.keys(permList.channelOverrides).forEach((key)=>{ %>
<% if(key != "channelOverrides"){ %> <% if(key != "channelOverrides"){ %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label id="admin-chan-perm-list-label-<%- key %>" class="admin-list-label admin-chan-perm-list" for="admin-chan-perm-list-rank-select-<%- key %>"><%- key %>: </label> <label class="admin-list-label admin-chan-perm-list" for="admin-chan-perm-list-rank-select-<%- key %>"><%- key %>: </label>
<select id="admin-chan-perm-list-rank-select-<%- key %>" name="admin-chan-perm-list-rank-select-<%- key %>" class="admin-list-select admin-chan-perm-list-rank-select"> <select name="admin-chan-perm-list-rank-select-<%- key %>" data-key="<%- key %>" class="admin-list-select admin-chan-perm-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
<option <%if(permList.channelOverrides[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option> <option <%if(permList.channelOverrides[key] == rank){%> selected <%}%> value="<%- rank %>"><%- rank %></option>
<% }); %> <% }); %>

View file

@ -41,23 +41,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
</td> </td>
</tr> </tr>
<% userList.forEach((curUser) => { %> <% userList.forEach((curUser) => { %>
<tr id="admin-user-list-entry-<%- curUser.user %>" class="admin-list-entry"> <tr id="admin-user-list-entry-<%- curUser.user %>" class="admin-list-entry" data-name="<%- curUser.user %>" >
<td id="admin-user-list-entry-img-<%- curUser.user %>" class="admin-list-entry-item"> <td class="admin-list-entry-item">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item"> <a href="/profile/<%- curUser.user %>" class="admin-list-entry-item">
<img id="admin-user-list-entry-img-<%- curUser.user %>" class="admin-list-entry-item" src="<%- curUser.img %>"> <img class="admin-list-entry-item" src="<%- curUser.img %>">
</a> </a>
</td> </td>
<td id="admin-user-list-entry-id-<%- curUser.user %>" class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item"> <a href="/profile/<%- curUser.user %>" class="admin-list-entry-item">
<%- curUser.id %> <%- curUser.id %>
</a> </a>
</td> </td>
<td id="admin-user-list-entry-name-<%- curUser.user %>" class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<a href="/profile/<%- curUser.user %>" class="admin-list-entry-item admin-user-list-name" id="admin-user-list-name-<%- curUser.user %>"> <a href="/profile/<%- curUser.user %>" class="admin-list-entry-item admin-user-list-name">
<%- curUser.user %> <%- curUser.user %>
</a> </a>
</td> </td>
<td id="admin-user-list-entry-rank-<%- curUser.user %>" class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%> <% if(rankEnum.indexOf(curUser.rank) < rankEnum.indexOf(user.rank)){%>
<select id="admin-user-list-rank-select-<%- curUser.user %>" class="admin-user-list-rank-select"> <select id="admin-user-list-rank-select-<%- curUser.user %>" class="admin-user-list-rank-select">
<%rankEnum.slice().reverse().forEach((rank)=>{ %> <%rankEnum.slice().reverse().forEach((rank)=>{ %>
@ -68,15 +68,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<%- curUser.rank %> <%- curUser.rank %>
<% } %> <% } %>
</td> </td>
<td id="admin-user-list-entry-email-<%- curUser.user %>" class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<%- curUser.email ? curUser.email : "N/A" %> <%- curUser.email ? curUser.email : "N/A" %>
</td> </td>
<td id="admin-user-list-entry-date-<%- curUser.user %>" class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<%- curUser.date.toUTCString() %> <%- curUser.date.toUTCString() %>
</td> </td>
<td id="admin-user-list-entry-action-<%- curUser.user %>" class="admin-list-entry-item not-first-col"> <td class="admin-list-entry-item not-first-col">
<%# It's either this or add whitespce >:( %> <%# It's either this or add whitespce >:( %>
<i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" id="admin-user-list-nuke-icon-<%- curUser.user %>" title="Nuke Account: <%- curUser.user %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" id="admin-user-list-ban-icon-<%- curUser.user %>" title="Ban User: <%- curUser.user %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" id="admin-user-list-pw-reset-icon-<%- curUser.user %>" title="Generate Password Reset Link for <%- curUser.user %>"></i> <i class="bi-radioactive admin-user-list-icon admin-user-list-nuke-icon" title="Nuke Account: <%- curUser.user %>"></i><i class="bi-fire admin-user-list-icon admin-user-list-ban-icon" title="Ban User: <%- curUser.user %>"></i><i class="bi-arrow-clockwise admin-user-list-icon admin-user-list-pw-reset-icon" title="Generate Password Reset Link for <%- curUser.user %>"></i>
</td> </td>
</tr> </tr>
<% }); %> <% }); %>

View file

@ -1,884 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: addURLPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: addURLPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>addURLPopup<span class="signature">(event, playlist, location, client, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing pop-up dialogue which adds media to a given playlist</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="addURLPopup"><span class="type-signature"></span>new addURLPopup<span class="signature">(event, playlist, location, client, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Add URL Pop-up
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>playlist</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Playlist name</td>
</tr>
<tr>
<td class="name"><code>location</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Location of playlist, either Channel or User</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line708">line 708</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line721">line 721</a>
</li></ul></dd>
</dl>
<h4 class="name" id="location"><span class="type-signature"></span>location<span class="type-signature"></span></h4>
<div class="description">
Location of playlist, either Channel or User
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line731">line 731</a>
</li></ul></dd>
</dl>
<h4 class="name" id="playlist"><span class="type-signature"></span>playlist<span class="type-signature"></span></h4>
<div class="description">
Playlist Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line726">line 726</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line738">line 738</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="addToPlaylist"><span class="type-signature"></span>addToPlaylist<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to add to a playlist to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line764">line 764</a>
</li></ul></dd>
</dl>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line744">line 744</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line754">line 754</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,385 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: channel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: channel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;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:&lt;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){
//Unfortunately we can't scope constants to switch-cases so this is the best we got if we wanna re-use the name
let nowPlaying;
//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' &amp;&amp; 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
nowPlaying = this.player.mediaHandler.nowPlaying;
//If we're playing a youtube video
if(nowPlaying != null &amp;&amp; nowPlaying.type == 'yt'){
//Restart the video
this.player.hardReload();
}
//Stop while we're ahead
return;
case 'IACDN':
//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
nowPlaying = this.player.mediaHandler.nowPlaying;
//If we're playing a video from Internet Archive
if(nowPlaying != null &amp;&amp; nowPlaying.type == 'ia'){
//Hard reload the media, forcing media handler re-creation
this.player.hardReload();
}
return;
case 'syncTolerance':
//If the player isn't loaded
if(this.player == null){
//We're fuckin' done here
return;
}
//Set syncronization tolerance
this.player.syncTolerance = value;
return;
case 'liveSyncTolerance':
//If the player isn't loaded
if(this.player == null){
//We're fuckin' done here
return;
}
//Set syncronization tolerance
this.player.streamSyncTolerance = value;
return;
case 'syncDelta':
//If the player isn't loaded
if(this.player == null){
//We're fuckin' done here
return;
}
//Set syncronization delta
this.player.syncDelta = value;
return;
case 'chatWidthMin':
//If the chat isn't loaded
if(this.chatBox == null){
//We're fuckin' done here
return;
}
//Set Chat Box Width minimum while Locked to Aspect-Ratio
this.chatBox.chatWidthMinimum = value / 100;
return;
case 'userlistHidden':
//If the userlist class isn't loaded in yet
if(this.userList == null){
//We're fuckin' done here
return;
}
//Pass value down to UI toggle, making sure to allow for string conversion
this.userList.toggleUI(!(value == true || value == "true"));
return;
case 'cinemaMode':
//If the userlist class isn't loaded in yet
if(this.player == null){
//We're fuckin' done here
return;
}
//Pass value down to UI toggle, making sure to allow for string conversion
this.player.toggleCinemaMode(value == true || value == "true");
return;
}
}
/**
* Default channel config
*/
static defaultConfig = new Map([
["ytPlayerType","raw"],
["IACDN",""],
["syncTolerance",0.4],
["liveSyncTolerance", 2],
["syncDelta", 6],
["chatWidthMin", 20],
["userlistHidden", false],
["cinemaMode", false]
]);
}
/**
* 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' &amp;&amp; localStorage.getItem('ytPlayerType') == "embed"){
//Restart the video now that the embed api has loaded
client.player.start({media: nowPlaying});
}
}
const client = new channel();</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,676 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: chat.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: chat.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class which represents Canopy Chat Box UI
*/
class chatBox{
/**
* Instantiates a new Chat Box object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client
/**
* Whether or not chat-size should be locked to current media aspect ratio
*/
this.aspectLock = true;
/**
* Whether or not the chat box should auto-scroll on new chat
*/
this.autoScroll = true;
/**
* Chat-Width Minimum while sized to media Aspect-Ratio
*/
this.chatWidthMinimum = localStorage.getItem('chatWidthMin') / 100;
/**
* Chat Buffer Scroll Top on last scroll
*/
this.lastPos = 0;
/**
* Height of Chat Buffer on last scroll
*/
this.lastHeight = 0;
/**
* Width of Chat Buffer on last scroll
*/
this.lastWidth = 0;
/**
* Click-Dragger Object for handling dynamic chat/video split re-sizing
*/
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-drag-handle", "#chat-panel-div");
/**
* Command Pre-Processor Object
*/
this.commandPreprocessor = new commandPreprocessor(client);
/**
* Chat Post-Processor Object
*/
this.chatPostprocessor = new chatPostprocessor(client);
//Element Nodes
/**
* Chat Panel Container Div
*/
this.chatPanel = document.querySelector("#chat-panel-div");
/**
* High Level Selector
*/
this.highSelect = document.querySelector("#chat-panel-high-level-select");
/**
* Flair Selector
*/
this.flairSelect = document.querySelector("#chat-panel-flair-select");
/**
* Chat Buffer Div
*/
this.chatBuffer = document.querySelector("#chat-panel-buffer-div");
/**
* Chat Prompt
*/
this.chatPrompt = document.querySelector("#chat-panel-prompt");
/**
* Auto-Complete Placeholder
*/
this.autocompletePlaceholder = document.querySelector("#chat-panel-prompt-autocomplete-filler");
/**
* Auto-Complete Display
*/
this.autocompleteDisplay = document.querySelector("#chat-panel-prompt-autocomplete-display");
/**
* Settings Panel Icon
*/
this.settingsIcon = document.querySelector("#chat-panel-settings-icon");
/**
* Admin Panel Icon
*/
this.adminIcon = document.querySelector("#chat-panel-admin-icon");
/**
* Emote Icon
*/
this.emoteIcon = document.querySelector("#chat-panel-emote-icon");
/**
* Send Chat/Command Button
*/
this.sendButton = document.querySelector("#chat-panel-send-button");
/**
* Aspect Lock Icon
* Seems weird to stick this in here, but the split is dictated by chat width :P
*/
this.aspectLockIcon = document.querySelector("#media-panel-aspect-lock-icon");
/**
* Hide Chat Icon
*/
this.hideChatIcon = document.querySelector("#chat-panel-div-hide");
/**
* Show Chat Icon
*/
this.showChatIcon = document.querySelector("#media-panel-show-chat-icon");
//Setup functions
this.setupInput();
this.defineListeners();
this.sizeToAspect();
}
/**
* Defines input-related event listeners
*/
setupInput(){
//Chat bar
this.chatPrompt.addEventListener("keydown", this.send.bind(this));
this.chatPrompt.addEventListener("keydown", this.tabComplete.bind(this));
this.chatPrompt.addEventListener("input", this.displayAutocomplete.bind(this));
this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this));
this.sendButton.addEventListener("click", this.send.bind(this));
this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new settingsPanel(client))});
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new queuePanel(client))});
this.emoteIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new emotePanel(client))});
//Header icons
this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this));
this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.highSelect.addEventListener("change", this.setHighLevel.bind(this));
this.flairSelect.addEventListener("change", this.setFlair.bind(this));
//Clickdragger/Resize
this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this));
this.clickDragger.handle.addEventListener("clickdrag", this.handleAutoScroll.bind(this));
window.addEventListener("resize", this.resizeAspect.bind(this));
//chatbuffer
this.chatBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
}
/**
* Defines network-related event listners
*/
defineListeners(){
this.client.socket.on("chatMessage", this.displayChat.bind(this));
this.client.socket.on("clearChat", this.clearChat.bind(this));
}
/**
* Clears chat on command from server
* @param {Object} data - Data from server
*/
clearChat(data){
//If we where passed a user to check
if(data.user != null){
var clearedChats = document.querySelectorAll(`.chat-entry-${data.user}`);
}else{
var clearedChats = document.querySelectorAll('.chat-entry');
}
//For each chat found
clearedChats.forEach((chat) => {
//fuckin' nukem!
chat.remove();
});
}
/**
* Receives, Post-Processes, and Displays chat messages from server
* @param {Object} data De-hydrated chat object from server
*/
displayChat(data){
//Create chat-entry span
var chatEntry = document.createElement('span');
chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${data.user}`);
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("chat-panel-buffer","chat-entry-high-level","high-level");
highLevel.textContent = utils.unescapeEntities(`${data.highLevel}`);
chatEntry.appendChild(highLevel);
//If we're not using classic flair
if(data.flair != "classic"){
//Use flair
var flair = `flair-${data.flair}`;
//Otherwise
}else{
//Pull user's assigned color from the color map
var flair = this.client.userList.colorMap.get(data.user);
}
//Create username label
var userLabel = document.createElement('p');
userLabel.classList.add("chat-panel-buffer", "chat-entry-username", );
//Create color span
var flairSpan = document.createElement('span');
flairSpan.classList.add("chat-entry-flair-span", flair);
flairSpan.innerHTML = data.user;
//Inject flair span into user label before the colon
userLabel.innerHTML = `${flairSpan.outerHTML}: `;
//Append user label
chatEntry.appendChild(userLabel);
//Create chat body
var chatBody = document.createElement('p');
chatBody.classList.add("chat-panel-buffer","chat-entry-body");
chatEntry.appendChild(chatBody);
//Append the post-processed chat-body to the chat buffer
this.chatBuffer.appendChild(this.chatPostprocessor.postprocess(chatEntry, data));
//Set size to aspect on launch
this.resizeAspect();
}
/**
* Concatenate Text into Chat Prompt
* @param {String} text - Text to Concatenate
*/
catChat(text){
this.chatPrompt.value += text;
this.displayAutocomplete();
}
/**
* Calls a toke command out with a specified user
* @param {String} user - User to toke with
*/
tokeWith(user){
this.commandPreprocessor.preprocess(user == this.client.user.user ? "!toke up fuckers" : `!toke up ${user}`);
}
/**
* Pre-processes and sends text from chat prompt to server
* @param {Event} event - Event passed down from Event Handler
*/
send(event){
if((!event || !event.key || event.key == "Enter") &amp;&amp; this.chatPrompt.value){
this.commandPreprocessor.preprocess(this.chatPrompt.value);
//Clear our prompt and autocomplete nodes
this.chatPrompt.value = "";
this.autocompletePlaceholder.innerHTML = '';
this.autocompleteDisplay.innerHTML = '';
}
}
/**
* Displays auto-complete text against current prompt input
* @param {Event} event - Event passed down from Event Handler
*/
displayAutocomplete(event){
//Find current match
const match = this.checkAutocomplete();
//Set placeholder to space out the autocomplete display
//Use text content because it's unescaped, and while this only effects local users, it'll keep someone from noticing and whinging about it
this.autocompletePlaceholder.textContent = this.chatPrompt.value;
//Set the autocomplete display
this.autocompleteDisplay.textContent = match.match.replace(match.word, '');
}
/**
* Called upon tab-complete
* @param {Event} event - Event passed down from Event Handler
*/
tabComplete(event){
//If we hit tab or this isn't a keyboard event
if(event.key == "Tab" || event.key == null){
//Prevent default action
event.preventDefault();
//return focus to the chat prompt
this.chatPrompt.focus();
//Grab autocompletion match
const match = this.checkAutocomplete();
//If we have a match
if(match.match != ''){
//Autocomplete the current word
this.chatPrompt.value += match.match.replace(match.word, '');
//Clear out the autocomplete display
this.autocompleteDisplay.innerHTML = '';
}
}
}
/**
* Checks string input against auto-complete dictionary to generate the best guess as to what the user is typing
* @param {String} input - Current input from Chat Prompt
* @returns {Object} Object containing word we where handed and the match we found
*/
checkAutocomplete(input = this.chatPrompt.value){
//Rebuild this fucker every time because it really doesn't take that much compute power and emotes/used tokes change
//Worst case we could store it persistantly and update as needed but I think that might be much
const dictionary = this.commandPreprocessor.buildAutocompleteDictionary();
//Split our input by whitespace
const splitInput = input.split(/\s/g);
//Get the current word we're working on
const word = splitInput[splitInput.length - 1];
let matches = [];
//Run through dictionary sets
for(let set of Object.keys(dictionary)){
//Go through the current definitions of the current dictionary set
//I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix
//and also directly push it into a shared array :P
for(let cmd of dictionary[set].cmds){
//Append the proper prefix/postfix to the current command
const definition = (`${dictionary[set].prefix}${cmd[0]}${dictionary[set].postfix}`);
//if definition starts with the current word and the command is enabled
if((word == '' ? false : definition.indexOf(word) == 0) &amp;&amp; cmd[1]){
//Add definition to match list
matches.push(definition);
}
}
}
//If we found jack shit
if(matches.length == 0){
//Return jack shit
return {
match: '',
word
};
//If we got something
}else{
//return our top match
return {
match: matches[0],
word
};
}
}
/**
* Handles initial client meta-data dump from server upon connection
* @param {Object} data - Data dump from server
*/
handleClientInfo(data){
this.updateFlairSelect(data.flairList, data.user.flair);
this.updateHighSelect(data.user.highLevel);
}
/**
* Sets user high-level
* @param {Event} event - Event passed down from Event Handler
*/
setHighLevel(event){
const highLevel = event.target.value;
this.client.socket.emit("setHighLevel", {highLevel});
}
/**
* Sets user flair
* @param {Event} event - Event passed down from Event Handler
*/
setFlair(event){
const flair = event.target.value;
this.client.socket.emit("setFlair", {flair});
}
/**
* Handles High-Level updates from the server
* @param {Number} highLevel - High Level to Set
*/
updateHighSelect(highLevel){
this.highSelect.value = highLevel;
}
/**
* Handles flair updates from the server
* @param {Array} fliarList - List of flairs to put into flair select
* @param {String} fliar - Flair to set
*/
updateFlairSelect(flairList, flair){
//clear current flair select
this.flairSelect.innerHTML = "";
//For each flair in flairlist
flairList.forEach((flair) => {
//Create an option
var flairOption = document.createElement('option');
//Set the name and innerHTML
flairOption.value = flair.name;
flairOption.textContent = utils.unescapeEntities(flair.displayName);
//Append it to the select
this.flairSelect.appendChild(flairOption);
});
//Set the selected flair in the UI
this.flairSelect.value = flair;
//Re-style the UI, do this in two seperate steps in-case we're running for the first time and have nothing to replace.
this.flairSelect.className = this.flairSelect.className.replace(/flair-\S*/, "");
this.flairSelect.classList.add(`flair-${flair}`);
}
/**
* Locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
lockAspect(event){
//prevent the user from breaking shit :P
if(this.chatPanel.style.display != "none"){
this.aspectLock = true;
this.aspectLockIcon.style.display = "none";
this.sizeToAspect();
}
}
/**
* Un-locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
unlockAspect(event){
//Disable aspect lock
this.aspectLock = false;
//Show aspect lock icon
this.aspectLockIcon.style.display = "inline";
}
/**
* Re-sizes chat back to aspect ratio on window re-size when chat box is aspect locked
* Also prevents horizontal scroll-bars from chat/window resizing
* @param {Event} event - Event passed down from Event Handler
*/
resizeAspect(event){
const playerHidden = this.client.player.playerDiv.style.display == "none";
//If the aspect is locked and the player is hidden
if(this.aspectLock &amp;&amp; !playerHidden){
this.sizeToAspect();
//Otherwise
}else{
//Fix the clickDragger on userlist
this.client.userList.clickDragger.fixCutoff();
}
//Autoscroll chat in-case we fucked it up
this.handleAutoScroll();
}
/**
* Re-sizes chat box relative to media aspect ratio
*/
sizeToAspect(){
if(this.chatPanel.style.display != "none"){
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
const targetChatWidth = window.innerWidth - targetVidWidth;
//This should be changeable in settings later on, for now it defaults to 20%
const limit = window.innerWidth * this.chatWidthMinimum;
//Set width to target or 20vw depending on whether or not we've hit the width limit
this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : `${this.chatWidthMinimum * 100}vw`;
//Fix busted layout
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
//This sometimes gets called before userList ahs been initiated :p
if(this.client.userList != null){
this.client.userList.clickDragger.fixCutoff();
}
}
}
/**
* Toggles Chat Box UX
* @param {Boolean} show - Whether or not to show Chat Box UX
*/
toggleUI(show = !this.chatPanel.checkVisibility()){
if(show){
this.chatPanel.style.display = "flex";
this.showChatIcon.style.display = "none";
this.client.player.hideVideoIcon.style.display = "flex";
this.client.userList.clickDragger.fixCutoff();
}else{
this.chatPanel.style.display = "none";
this.showChatIcon.style.display = "flex";
this.client.player.hideVideoIcon.style.display = "none";
}
}
/**
* Handles Video Toggling
* @param {Boolean} show - Whether or not the video is currently being hidden
*/
handleVideoToggle(show){
//If we're enabling the video
if(show){
//Show hide chat icon
this.hideChatIcon.style.display = "flex";
//Re-enable the click dragger
this.clickDragger.enabled = true;
//Lock the chat to aspect ratio of the video, to make sure the chat width isn't breaking shit
this.lockAspect();
//If we're disabling the video
}else{
//Hide hide hide hide hide hide chat icon
this.hideChatIcon.style.display = "none";
//Need to clear the width from the split, or else it doesn't display properly
this.chatPanel.style.flexBasis = "100%";
//Disable the click dragger
this.clickDragger.enabled = false;
}
}
/**
* Handles scrolling within the chat buffer
* @param {Event} event - Event passed down from Event Handler
*/
scrollHandler(event){
//If we're just starting out
if(this.lastPos == 0){
//Set last pos for the first time
this.lastPos = this.chatBuffer.scrollTop;
}
//Calculate scroll delta
const deltaY = this.chatBuffer.scrollTop - this.lastPos;
//Grab visible bounding rect so we don't have to do it again (can't use offset because someone might zoom in :P)
const bufferRect = this.chatBuffer.getBoundingClientRect();
const bufferHeight = Math.round(bufferRect.height);
const bufferWidth = Math.round(bufferRect.width);
if(this.lastHeight == 0){
this.lastHeight = bufferHeight;
}
if(this.lastWidth == 0){
this.lastWidth = bufferWidth;
}
//If we're scrolling up
if(deltaY &lt; 0){
//If we have room to scroll, and we didn't resize
if(this.chatBuffer.scrollHeight > bufferHeight &amp;&amp; (this.lastWidth == bufferWidth &amp;&amp; this.lastHeight == bufferHeight)){
//Disable auto scrolling
this.autoScroll = false;
}else{
this.handleAutoScroll();
}
//Otherwise if the difference between the chat buffers scroll height and offset height is equal to the scroll top
//(Because it is scrolled all the way down)
}else if((this.chatBuffer.scrollHeight - bufferHeight) == this.chatBuffer.scrollTop){
this.autoScroll = true;
}
//Set last post/size for next the run
this.lastPos = this.chatBuffer.scrollTop;
this.lastHeight = bufferHeight;
this.lastWidth = bufferWidth;
}
/**
* Auto-scrolls chat buffer when new chats are entered.
*/
handleAutoScroll(){
//If autoscroll is enabled
if(this.autoScroll){
//Set chatBuffer scrollTop to the difference between scrollHeight and buffer height (scroll to the bottom)
this.chatBuffer.scrollTop = this.chatBuffer.scrollHeight - Math.round(this.chatBuffer.getBoundingClientRect().height);
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,680 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: chatPostprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: chatPostprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class contianing client-side message post-processing code
*/
class chatPostprocessor{
/**
* Instantiates a new Chat Post-Processor object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client;
}
/**
* Post-Processes a single message from the server and returns a presntable DOM Node
* @param {Node} chatEntry - Chat entry generated by initial chatBox method
* @param {Object} rawData - Raw data from server
* @returns {Node} Post-Processed Chat Entry
*/
postprocess(chatEntry, rawData){
//Create empty array to hold filter spans
this.filterSpans = [];
//Set raw message data
this.rawData = rawData;
//Set current chat nodes
this.chatEntry = chatEntry;
this.chatBody = this.chatEntry.querySelector(".chat-entry-body");
//Split the chat message into an array of objects representing each word/chunk
this.splitMessage();
//Process Qoutes
this.processQoute();
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
this.processLinks();
//Inject clickable command examples
this.processCommandExamples();
//Inject clickable channel names
this.processChannelNames();
//Inject clickable usernames
this.processUsernames();
//Detect inline spoilers
this.processSpoilers();
//Detect inline strikethrough
this.processStrikethrough();
//Detect inline bold text
this.processBold();
//Detect inline italics
this.processItalics();
//Inject whitespace into long ass-words
this.addWhitespace();
//Handle non-standard chat types
this.handleChatType();
//Inject the pre-processed chat hyper-text into the chatEntry node
this.injectBody();
//Return the pre-processed node
return this.chatEntry;
}
/**
* Splits message into an array of Word Objects for further processing
*/
splitMessage(){
//Create an empty array to hold the body
this.messageArray = [];
//Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes
//Split string by word-boundries on words and non-word boundries around whitespace, with negative lookaheads to exclude file seperators so we don't split link placeholders, and dashes so we dont split usernames and other things
//Also split by any invisble whitespace as a crutch to handle mushed links/emotes
//If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet.
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?&lt;!-)(?&lt;!␜)(?=\w)\b|(?!-)(?&lt;=\w)\b|(?=\s)\B|(?&lt;=\s)\B|/g);
//for each word in the splitstring
splitString.forEach((string) => {
//create a word object
const wordObj = {
string: string,
filterClasses: [],
type: "word"
}
//Add it to our body array
this.messageArray.push(wordObj);
});
}
/**
* Injects word objects into chat-entry as proper DOM Nodes
*/
injectBody(){
//Create an empty array to hold the objects to inject
const injectionArray = [];
//For each word object
this.messageArray.forEach((wordObj) => {
if(wordObj.type == 'word'){
//Create span node
const span = document.createElement('span');
//Set span filter classes
span.classList.add(...wordObj.filterClasses);
//Set span text
span.textContent = wordObj.string;
//Inject node into array
injectionArray.push(span);
}else if(wordObj.type == 'link'){
//Create a link node from our link
const link = document.createElement('a');
link.classList.add('chat-link', ...wordObj.filterClasses);
link.href = wordObj.link;
link.target = "_blank";
//Use textContent to be safe since links can't be escaped serverside
link.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, link);
}else if(wordObj.type == 'deadLink'){
//Create a text span node from our link
const badLink = document.createElement('a');
badLink.classList.add('chat-dead-link', 'danger-link', ...wordObj.filterClasses);
badLink.href = wordObj.link;
badLink.target = "_blank";
//Use textContent to be safe since links can't be escaped serverside
badLink.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, badLink);
}else if(wordObj.type == 'malformedLink'){
//Create a text span node from our link
const malformedLink = document.createElement('span');
malformedLink.classList.add('chat-malformed-link', ...wordObj.filterClasses);
//Use textContent to be safe since links can't be escaped (this is why we don't just add it using injectString)
//arguably we could sanatize malformed links serverside since they're never actually used as links
malformedLink.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, malformedLink);
}else if(wordObj.type == 'image'){
//Create an img node from our link
const img = document.createElement('img');
img.classList.add('chat-img', ...wordObj.filterClasses);
img.src = wordObj.link;
//Look for an emote by link since emotes are tx'd as bare links
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
//If this is a known emote
if(emote != null){
//Set the hover text to the emote's name
img.title = `[${emote.name}]`;
}
//Append node to chatBody
combineNode(wordObj, img);
}else if(wordObj.type == 'video'){
//Create a video node from our link
const vid = document.createElement('video');
vid.classList.add('chat-video', ...wordObj.filterClasses);
vid.src = wordObj.link;
vid.controls = false;
vid.autoplay = true;
vid.loop = true;
vid.muted = true;
//Look for an emote by link since emotes are tx'd as bare links
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
//If this is a known emote
if(emote != null){
//Set the hover text to the emote's name
vid.title = `[${emote.name}]`;
}
combineNode(wordObj, vid);
}else if(wordObj.type == 'command'){
//Create link node
const link = document.createElement('a');
//Set class
link.classList.add('chat-link', ...wordObj.filterClasses);
//Set href and inner text
link.href = "javascript:";
link.textContent = wordObj.command;
//Add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(wordObj.command)});
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else if(wordObj.type == "username"){
//Create link node
const link = document.createElement('a');
//set class
link.classList.add(wordObj.color, ...wordObj.filterClasses);
//Set href and inner text
link.href = "javascript:";
link.textContent = wordObj.string;
//add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.chatPrompt.value += `${wordObj.string} `});
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else if(wordObj.type == "channel"){
//Create link node
const link = document.createElement('a');
//set class
link.classList.add('chat-link', ...wordObj.filterClasses);
//Set href and inner text
link.href = `/c/${wordObj.chan}`;
link.target = "_blank"
link.textContent = wordObj.string;
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else{
console.warn("Unknown chat postprocessor word type:");
console.warn(wordObj);
}
});
//For each item found in the injection array
for(let itemIndex in injectionArray){
const item = injectionArray[itemIndex];
//Currently this doesnt support multiple overlapping span-type filters
//not a huge deal since we only have once (spoiler)
//All others can be applied per-node without any usability side effects
const curFilter = this.filterSpans.filter(filterFilters)[0];
let appendBody = this.chatBody;
//If we have a filter span
if(curFilter != null){
//If we're beggining the array
if(itemIndex == curFilter.index[0]){
//Create the span
appendBody = document.createElement('span');
//Label it for what it is
appendBody.classList.add(curFilter.class);
//Add it to the chat body
this.chatBody.appendChild(appendBody);
//Otherwise
}else{
//Use the existing span
appendBody = (this.chatBody.children[this.chatBody.children.length - 1]);
}
}
//Append the node to our chat body
appendBody.appendChild(item);
function filterFilters(filter){
//If the index is within the filter span
return filter.index[0] &lt;= itemIndex &amp;&amp; filter.index[1] >= itemIndex;
}
}
//Like string.replace except it actually injects the node so we can keep things like event handlers
function combineNode(wordObj, node, placeholder = '␜'){
//Split string by the placeholder so we can keep surrounding whitespace
const splitWord = wordObj.string.split(placeholder, 2);
//Create combined node
const combinedSpan = document.createElement('span');
//Add the first part of the text
combinedSpan.textContent = splitWord[0];
//Add in the requestd node
combinedSpan.appendChild(node);
//Finish it off with the last bit of text
combinedSpan.insertAdjacentText('beforeend', splitWord[1]);
//Add to injection array as three nested items to keep arrays lined up
injectionArray.push(combinedSpan);
}
}
/**
* Processes qouted text in chat
*/
processQoute(){
//If the message starts off with '>'
if(this.messageArray[0].string[0] == '>'){
this.chatBody.classList.add("qoute");
}
}
/**
* Processes clickable command examples in chat
*/
processCommandExamples(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Get last char of current word
const lastChar = wordObj.string[wordObj.string.length - 1];
//if the last char is !
if(lastChar == '!' || lastChar == '/'){
//get next word
const nextWord = this.messageArray[wordIndex + 1];
//if we have another word
if(nextWord != null){
const command = lastChar + nextWord.string;
//Take out the command marker
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
const commandObj = {
type: "command",
string: nextWord.string,
filterClasses: [],
command: command
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
/**
* Processes clickable channel names in chat
*/
processChannelNames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Get last char of current word with slashes pounds
const lastChar = wordObj.string[wordObj.string.length - 1];
const secondLastChar = wordObj.string[wordObj.string.length - 2];
//if the last char is # and the second to last char isn't &amp; or # (avoid spoilers)
if(lastChar == '#' &amp;&amp; secondLastChar != '#'){
//get next word
const nextWord = this.messageArray[wordIndex + 1];
//if we have another word
if(nextWord != null){
//Take out the chan marker
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
const commandObj = {
type: "channel",
string: lastChar + nextWord.string,
filterClasses: [],
chan: nextWord.string
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
/**
* Processes clickable username callouts in chat
*/
processUsernames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Check for user and get their color
const color = this.client.userList.colorMap.get(wordObj.string);
//If the current word is the username of a connected user
if(color != null){
//Mark it as so
this.messageArray[wordIndex].type = "username";
//Store their color
this.messageArray[wordIndex].color = color;
}
}
});
}
/**
* Injects invisible whitespace in long-ass words to prevent fucking up the chat buffer size
*/
addWhitespace(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Create an empty array to hold our word
var wordArray = [];
//For each character in the string of the current word object
this.messageArray[wordIndex].string.split("").forEach((char, charIndex) => {
//push the current character to the wordArray
wordArray.push(char);
//After eight characters
if(charIndex > 8){
//Push an invisible line-break character between every character
wordArray.push("");
}
});
//Join the wordArray into a single string, and use it to set the current wordObject's string
this.messageArray[wordIndex].string = wordArray.join("");
}
});
}
/**
* Searches for text in-between a specific delimiter and runs a given callback against it
*
* Internal command used by several text filters to prevent code re-writes
* @param {String} delimiter - delimiter to search string by
* @param {Function} cb - Callback function to run against found strings
* @returns {Array} - list of found instances of filter
*/
processFilter(delimiter, cb){
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
const foundFilters = [];
//Spoiler detection stage
//For each word object in the message array
main: for(let wordIndex = 0; wordIndex &lt; this.messageArray.length; wordIndex++){
//Get the current word object
const wordObj = this.messageArray[wordIndex];
//If its a regular word and contains '##'
if(wordObj.type == 'word' &amp;&amp; wordObj.string.match(utils.escapeRegex(delimiter))){
//Crawl through detected spoilers
for(let spoiler of foundFilters){
//If the current word object is part of a detected spoiler
if(wordIndex == spoiler[0] || wordIndex == spoiler[1]){
//ignore it and continue on to the next word object
continue main;
}
}
//Crawl throw word objects after the current one
for(let endIndex = (wordIndex + 1); endIndex &lt; this.messageArray.length; endIndex++){
//Get the current end object
const endObj = this.messageArray[endIndex];
//If its a regular word and contains '##'
if(endObj.type == 'word' &amp;&amp; endObj.string.match(utils.escapeRegex(delimiter))){
//Setup the found filter array
const foundFilter = [wordIndex, endIndex];
//Scrape out delimiters
wordObj.string = wordObj.string.replaceAll(delimiter,'');
endObj.string = endObj.string.replaceAll(delimiter,'');
//Add it to the list of detected filters
foundFilters.push(foundFilter);
//Run the filter callback
cb(foundFilter)
//Break the nested end-detection loop
break;
}
}
}
}
return foundFilters;
}
/**
* Processes in-line spoilers
*/
processSpoilers(){
//Process spoilers using '##' delimiter
this.processFilter('##', (foundSpoiler)=>{
//For each found spoiler add it to the list of found filter spans
this.filterSpans.push({class: "spoiler", index: [foundSpoiler[0] + 1, foundSpoiler[1] - 1], delimiters: [foundSpoiler[0], foundSpoiler[1]]});
});
}
/**
* Processes in-line Strike-through
*/
processStrikethrough(){
//Process strikethrough's using '~~' delimiter
this.processFilter('~~', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("strikethrough");
}
})
}
/**
* Processes in-line Bold/Strong text
*/
processBold(){
//Process strong text using '*' delimiter
this.processFilter('**', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("bold");
}
})
}
/**
* Processes in-line Italics
*/
processItalics(){
//Process italics using '__' delimiter
this.processFilter('*', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("italics");
}
})
}
/**
* Processes clickable links and embedded media
*/
processLinks(){
//If we don't have links
if(this.rawData.links == null){
//Don't bother
return;
}
//For every link received in this message
this.rawData.links.forEach((link, linkIndex) => {
//For every word obj in the message array
this.messageArray.forEach((wordObj, wordIndex) => {
//Check current wordobj for link (placeholder may contain whitespace with it)
if(wordObj.string.match(`␜${linkIndex}`)){
//Set current word object in the body array to the new link object
this.messageArray[wordIndex] = {
//Don't want to use a numbered placeholder to make this easier during body injection
//but we also don't want to clobber any surrounding whitespace
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
link: link.link,
type: link.type,
filterClasses: []
}
}
})
});
}
/**
* Marks chat nodes in-case of non-standard chat types
*/
handleChatType(){
if(this.rawData.type == "whisper"){
//add whisper class
this.chatBody.classList.add('whisper');
}else if(this.rawData.type == "announcement"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//Get the username and make it into an announcement title (little hacky but this *IS* postprocessing)
const userNode = this.chatEntry.querySelector('.chat-entry-username');
userNode.textContent = `${userNode.textContent.slice(0,-2)} Announcement`;
//Add/remove relevant classes
userNode.classList.remove('chat-entry-username');
userNode.classList.add('announcement-title');
this.chatBody.classList.add('announcement-body');
this.chatEntry.classList.add('announcement');
}else if(this.rawData.type == "toke"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//remove the username
this.chatEntry.querySelector('.chat-entry-username').remove();
//Add toke/tokewhisper class
this.chatBody.classList.add("toke");
}else if(this.rawData.type == "tokewhisper"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//remove the username
this.chatEntry.querySelector('.chat-entry-username').remove();
//Add toke/tokewhisper class
this.chatBody.classList.add("tokewhisper","serverwhisper");
}else if(this.rawData.type == "spoiler"){
//Set whole-body spoiler
this.chatBody.classList.add("spoiler");
}else if(this.rawData.type == "strikethrough"){
//Set whole-body spoiler
this.chatBody.classList.add("strikethrough");
}else if(this.rawData.type == "bold"){
//Set whole-body spoiler
this.chatBody.classList.add("bold");
}else if(this.rawData.type == "italics"){
//Set whole-body spoiler
this.chatBody.classList.add("italics");
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,799 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: clearPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: clearPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>clearPopup<span class="signature">(event, client, cb, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class represneting pop-up dialogue for clearing queue between a range of two dates</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="clearPopup"><span class="type-signature"></span>new clearPopup<span class="signature">(event, client, cb, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new queue Clear Popup
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>cb</code></td>
<td class="type">
<span class="param-type">function</span>
</td>
<td class="description last">Callback function, passed upon pop-up creation</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1554">line 1554</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="cb"><span class="type-signature"></span>cb<span class="type-signature"></span></h4>
<div class="description">
Callback function, passed upon pop-up creation
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1571">line 1571</a>
</li></ul></dd>
</dl>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1566">line 1566</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1578">line 1578</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1584">line 1584</a>
</li></ul></dd>
</dl>
<h4 class="name" id="clear"><span class="type-signature"></span>clear<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to clear playlist between two dates to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1624">line 1624</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1614">line 1614</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,380 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: commandPreprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: commandPreprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing chat and command pre-processing logic
*/
class commandPreprocessor{
/**
* Instantiates a new commandPreprocessor object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client;
/**
* Child Command Processor object
*/
this.commandProcessor = new commandProcessor(client);
/**
* Set of arrays containing site-wide, channel-wide, and user-specific emotes
*/
this.emotes = {
site: [],
chan: [],
personal: []
}
//define listeners
this.defineListeners();
}
/**
* Defines Network-Related Listeners
*/
defineListeners(){
//When we receive site-wide emote list
this.client.socket.on("siteEmotes", this.setSiteEmotes.bind(this));
this.client.socket.on("chanEmotes", this.setChanEmotes.bind(this));
this.client.socket.on("personalEmotes", this.setPersonalEmotes.bind(this));
this.client.socket.on("usedTokes", this.setUsedTokes.bind(this));
}
/**
* Pre-Processes a single chat/command before sending it off to the server
* @param {String} command - Chat/Command to pre-process
*/
preprocess(command){
//Set command and sendFlag
this.command = command;
this.sendFlag = true;
//Attempt to process as local command
this.processLocalCommand();
//If we made it through the local command processor
if(this.sendFlag){
//Set the message to the command
this.message = command;
//Process message emotes into links
this.processEmotes();
//Process unmarked links into marked links
this.processLinks();
//Send command off to server
this.sendRemoteCommand();
}
}
/**
* Processes local commands, starting with '/'
*/
processLocalCommand(){
//Create an empty array to hold the command
this.commandArray = [];
//Split string by words
this.commandArray = this.command.split(/\b/g);//Split by word-borders
this.argumentArray = this.command.match(/\b\w+\b/g);//Match by words surrounded by borders
//If this is a local command
if(this.commandArray[0] == '/'){
//If the command exists
if(this.argumentArray != null &amp;&amp; this.commandProcessor[this.argumentArray[0].toLowerCase()] != null){
//Don't send it to the server
this.sendFlag = false;
//Call the command with the argument array
this.commandProcessor[this.argumentArray[0].toLowerCase()](this.argumentArray, this.commandArray);
}
}
}
/**
* Processes emotes refrences in loaded message into links to be further processed by processLinks()
*/
processEmotes(){
//inject invisible whitespace in-between emotes to prevent from mushing links together
this.message = this.message.replaceAll('][','][');
//For each list of emotes
Object.keys(this.emotes).forEach((key) => {
//For each emote in the current list
this.emotes[key].forEach((emote) => {
//Inject emote links into the message, pad with invisible whitespace to keep link from getting mushed
this.message = this.message.replaceAll(`[${emote.name}]`, `${emote.link}`);
});
});
}
/**
* Processes links into numbered file seperators, putting links into a dedicated array.
*/
processLinks(){
//Strip out file seperators in-case the user is being a smart-ass
this.message = this.message.replaceAll('␜','');
//Split message by links
var splitMessage = this.message.split(/(https?:\/\/[^\s]+)/g);
//Create an empty array to hold links
this.links = [];
splitMessage.forEach((chunk, chunkIndex) => {
//For each chunk that is a link
if(chunk.match(/(https?:\/\/[^\s]+)/g)){
//I looked online for obscure characters that no one would use to prevent people from chatting embed placeholders
//Then I found this fucker, turns out it's literally made for the job lmao (even if it was originally intended for paper/magnetic tape)
//Replace link with indexed placeholder
splitMessage[chunkIndex] = `␜${this.links.length}`
//push current chunk as link
this.links.push(chunk);
}
});
//Join the message back together
this.message = splitMessage.join('');
}
/**
* Transmits message/command off to server
*/
sendRemoteCommand(){
this.client.socket.emit("chatMessage",{msg: this.message, links: this.links});
}
/**
* Sets site emotes
* @param {Object} data - Emote data from server
*/
setSiteEmotes(data){
this.emotes.site = data;
}
/**
* Sets channel emotes
* @param {Object} data - Emote data from server
*/
setChanEmotes(data){
this.emotes.chan = data;
}
/**
* Sets personal emotes
* @param {Object} data - Emote data from server
*/
setPersonalEmotes(data){
this.emotes.personal = data;
}
/**
* Sets used tokes
* @param {Object} data - Used toke data from server
*/
setUsedTokes(data){
this.usedTokes = data.tokes;
}
/**
* Fetches emote by link
* @param {String} link - Link to fetch emote with
* @returns {Object} found emote
*/
getEmoteByLink(link){
//Create an empty variable to hold the found emote
var foundEmote = null;
//For each list of emotes
Object.keys(this.emotes).forEach((key) => {
//For each emote in the current list
this.emotes[key].forEach((emote) => {
//if we found a match
if(emote.link == link){
//return the match
foundEmote = emote;
}
});
});
return foundEmote;
}
/**
* Generates flat list of emote names
* @returns {Array} List of strings containing emote names
*/
getEmoteNames(){
//Create an empty array to hold names
let names = [];
//For every set of emotes
for(let set of Object.keys(this.emotes)){
//for every emote in the current set of emotes
for(let emote of this.emotes[set]){
//push the name of the emote to the name list
names.push(emote.name);
}
}
//return our list of names
return names;
}
/**
* Generates auto-complete dictionary from pre-written commands, emotes, and used tokes from servers for use with autocomplete
* @returns {Object} Generated Dictionary object
*/
buildAutocompleteDictionary(){
let dictionary = {
tokes: {
prefix: '!',
postfix: '',
cmds: [
['toke', true]
].concat(injectPerms(this.usedTokes))
},
//Make sure to add spaces at the end for commands that take arguments
//Not necissary but definitely nice to have
serverCMD: {
prefix: '!',
postfix: '',
cmds: [
["whisper ", true],
["announce ", client.user.permMap.chan.get('announce')],
["serverannounce ", client.user.permMap.site.get('announce')],
["clear ", client.user.permMap.chan.get('clearChat')],
["kick ", client.user.permMap.chan.get('kickUser')],
]
},
localCMD:{
prefix: '/',
postfix: '',
cmds: [
["high ", true]
]
},
usernames:{
prefix: '',
postfix: '',
cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
},
emotes:{
prefix:'[',
postfix:']',
cmds: injectPerms(this.getEmoteNames())
}
};
//return our dictionary object
return dictionary;
function injectPerms(cmds, perm = true){
//Create empty array to hold cmds
let cmdSet = [];
//For each cmd
for(let cmd of cmds){
//Add the cmd with its perm to the cmdset
cmdSet.push([cmd, perm]);
}
//return the cmd set
return cmdSet;
}
}
}
/**
* Class which contains logic for client-side commands
*/
class commandProcessor{
/**
* Instantiates a new Command Processor object
* @param {channel} client - Parent client mgmt object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client
}
/**
* Method handling /high client command
* @param {Array} argumentArray - Array of arguments passed down from Command Pre-Processor
*/
high(argumentArray){
//If we have an argument
if(argumentArray[1]){
//Use it to set our high level
//Technically this is less of a local command than it would be if it where telling the select to do this
//but TTN used to treat this as a local command so fuck it
this.client.socket.emit("setHighLevel", {highLevel: argumentArray[1]});
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,430 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: commandProcessor</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: commandProcessor</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>commandProcessor<span class="signature">(client)</span><span class="type-signature"></span></h2>
<div class="class-description">Class which contains logic for client-side commands</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="commandProcessor"><span class="type-signature"></span>new commandProcessor<span class="signature">(client)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Command Processor object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent client mgmt object</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="commandPreprocessor.js.html">commandPreprocessor.js</a>, <a href="commandPreprocessor.js.html#line305">line 305</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="commandPreprocessor.js.html">commandPreprocessor.js</a>, <a href="commandPreprocessor.js.html#line314">line 314</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="high"><span class="type-signature"></span>high<span class="signature">(argumentArray)</span><span class="type-signature"></span></h4>
<div class="description">
Method handling /high client command
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>argumentArray</code></td>
<td class="type">
<span class="param-type">Array</span>
</td>
<td class="description last">Array of arguments passed down from Command Pre-Processor</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="commandPreprocessor.js.html">commandPreprocessor.js</a>, <a href="commandPreprocessor.js.html#line321">line 321</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,524 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: cpanel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: cpanel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing code for managing the Canopy Panel UX
*/
class cPanel{
/**
* Instantiates a new Canopy Panel Management object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client;
/**
* Active Panel Object
*/
this.activePanel = null;
/**
* Pinned Panel Object
*/
this.pinnedPanel = null;
/**
* Popped Panel Objects
*/
this.poppedPanels = [];
/**
* Click-Dragger object for re-sizable active panel
*/
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false, null, false);
/**
* Click-Dragger object for re-sizable pinned panel
*/
this.pinnedPanelDragger = new canopyUXUtils.clickDragger("#cpanel-pinned-drag-handle", "#cpanel-pinned-div", false, this.client.chatBox.clickDragger);
//Element Nodes
//Active Panel
/**
* Active Panel Container
*/
this.activePanelDiv = document.querySelector("#cpanel-active-div");
/**
* Active Panel Title
*/
this.activePanelTitle = document.querySelector("#cpanel-active-title");
/**
* Active Title Document Div
*/
this.activePanelDoc = document.querySelector("#cpanel-active-doc");
/**
* Active Panel Pin Icon
*/
this.activePanelPinIcon = document.querySelector("#cpanel-active-pin-icon");
/**
* Active Panel Pop-Out Icon
*/
this.activePanelPopoutIcon = document.querySelector("#cpanel-active-popout-icon");
/**
* Active Panel Close Icon
*/
this.activePanelCloseIcon = document.querySelector("#cpanel-active-close-icon");
//Pinned Panel
/**
* Pinned Panel Contianer
*/
this.pinnedPanelDiv = document.querySelector("#cpanel-pinned-div");
/**
* Pinned Panel Title
*/
this.pinnedPanelTitle = document.querySelector("#cpanel-pinned-title");
/**
* Pinned Panel Document Div
*/
this.pinnedPanelDoc = document.querySelector("#cpanel-pinned-doc");
/**
* Pinned Panel Un-Pin Icon
*/
this.pinnedPanelUnpinIcon = document.querySelector("#cpanel-pinned-unpin-icon");
/**
* Pinned Panel Pop-Out Icon
*/
this.pinnedPanelPopoutIcon = document.querySelector("#cpanel-pinned-popout-icon");
/**
* Pinned Panel Close Icon
*/
this.pinnedPanelCloseIcon = document.querySelector("#cpanel-pinned-close-icon");
this.setupInput();
}
/**
* Defines input-related event listeners
*/
setupInput(){
this.activePanelCloseIcon.addEventListener("click", this.hideActivePanel.bind(this));
this.activePanelPinIcon.addEventListener("click", this.pinPanel.bind(this));
this.activePanelPopoutIcon.addEventListener("click", this.popActivePanel.bind(this));
this.pinnedPanelCloseIcon.addEventListener("click", this.hidePinnedPanel.bind(this));
this.pinnedPanelUnpinIcon.addEventListener("click", this.unpinPanel.bind(this));
this.pinnedPanelPopoutIcon.addEventListener("click", this.popPinnedPanel.bind(this));
}
/**
* Sets Active Panel
* @param {panelObj} panel - Panel Object to set as active
* @param {String} panelBody - innerHTML of Panel, pulls from panelObj.getPage() if empty
*/
async setActivePanel(panel, panelBody){
//Set active panel
this.activePanel = panel;
//Grab panel hypertext content and load it into div
this.activePanelDoc.innerHTML = (panelBody == null || panelBody == "") ? await this.activePanel.getPage() : panelBody;
//Display panel
this.activePanelDiv.style.display = "flex";
this.activePanelTitle.textContent = this.activePanel.name;
//Call panel initialization function
this.activePanel.panelDocument = this.activePanelDoc;
this.activePanel.docSwitch();
}
/**
* Hides active panel
* @param {Event} event - Event passed down from Input Handler
* @param {Boolean} keepAlive - Prevents closing panel if true
*/
hideActivePanel(event, keepAlive = false){
if(!keepAlive){
this.activePanel.closer();
}
//Hide the panel
this.activePanelDiv.style.display = "none";
//Clear out the panel
this.activePanelDoc.innerHTML = '';
//Set active panel to null
this.activePanel = null;
}
/**
* Pins active panel
*/
pinPanel(){
this.setPinnedPanel(this.activePanel, this.activePanelDoc.innerHTML);
this.hideActivePanel(null, true);
}
/**
* Pop's out active panel
*/
popActivePanel(){
this.popPanel(this.activePanel, this.activePanelDoc.innerHTML);
this.hideActivePanel(null, true);
}
/**
* Sets pinned panel
* @param {panelObj} panel - Panel Object to apply to panel
* @param {String} panelBody - Raw HTML to inject into panel body, defaults to panel page if null
*/
async setPinnedPanel(panel, panelBody){
//Set pinned panel
this.pinnedPanel = panel;
//Set Title
this.pinnedPanelTitle.textContent = this.pinnedPanel.name;
//Grab panel hypertext content and load it into div
this.pinnedPanelDoc.innerHTML = (panelBody == null || panelBody == "") ? await this.pinnedPanel.getPage() : panelBody;
//Display panel
this.pinnedPanelDiv.style.display = "flex";
//Call panel initialization function
this.pinnedPanel.panelDocument = this.pinnedPanelDoc;
this.pinnedPanel.docSwitch();
//Resize to window/content
this.pinnedPanelDragger.fixCutoff();
}
/**
* Hides pinned panel
* @param {Event} event - Passed down input event
* @param {Boolean} keepAlive - Prevents panel.closer() from running if true
*/
hidePinnedPanel(event, keepAlive = false){
this.pinnedPanelDiv.style.display = "none";
if(!keepAlive){
this.pinnedPanel.closer();
}
this.pinnedPanel = null;
}
/**
* Sets pinned panel to active
*/
unpinPanel(){
this.setActivePanel(this.pinnedPanel, this.pinnedPanelDoc.innerHTML);
this.hidePinnedPanel(null, true);
}
/**
* Pops pinned panel
*/
popPinnedPanel(){
this.popPanel(this.pinnedPanel, this.pinnedPanelDoc.innerHTML);
this.hidePinnedPanel(null, true);
}
/**
* Pops a new pop-out panel
* @param {panelObj} panel - panelObj to apply to the panel
* @param {String} panelBody - Raw HTML to inject into panel body, injects panel default if left to null
*/
popPanel(panel, panelBody){
var newPanel = new poppedPanel(panel, panelBody, this)
this.poppedPanels.push(newPanel);
}
}
/**
* Template Class for other Classes for Objects which represent a single Canopy Panel
*/
class panelObj{
/**
* Instantiates a new Panel Object
* @param {channel} client - Parent client Management Object
* @param {String} name - Panel Name
* @param {String} pageURL - Panel Default Page URL
* @param {Document} panelDocument - Panel Document
*/
constructor(client, name = "Placeholder Panel", pageURL = "/panel/placeholder", panelDocument = window.document){
/**
* Panel Name
*/
this.name = name;
/**
* Panel Default Page URL
*/
this.pageURL = pageURL;
/**
* Panel Document
*/
this.panelDocument = panelDocument;
/**
* Current root document panel doc lives within
*/
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
/**
* Parent Client Management object
*/
this.client = client;
}
/**
* Fetches panel page from the server
* @returns {String} Raw panel doc HTML
*/
async getPage(){
var response = await fetch(this.pageURL,{
method: "GET",
});
return await response.text();
}
/**
* Handles Document/Panel Changes
*/
docSwitch(){
//Set owner doc
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
}
/**
* Called upon panel close/exit
*/
closer(){
}
}
/**
* Class which represents a single instance of a popped-out panel
*/
class poppedPanel{
/**
* Instantiates a new Popped Panel Object
* @param {panelObj} panel - Panel Object to apply to Popped Panel
* @param {String} panelBody - Raw HTML to inject into panel body, defaults to panel page if null
* @param {cPanel} cPanel - Parent Canopy Panel Management Object
*/
constructor(panel, panelBody, cPanel){
/**
* Panel Object to apply to Popped Panel
*/
this.panel = panel;
/**
* Raw HTML to inject into panel body, defaults to panel page if null
*/
this.panelBody = panelBody;
/**
* Browser Window taken up by the Popped Panel
*/
this.window = null;
/**
* Popped Panel Container Div
*/
this.pinnedPanelDiv = null;
/**
* Popped Panel Title
*/
this.pinnedPanelTitle = null;
/**
* Popped Panel Document Div
*/
this.pinnedPanelDoc = null;
/**
* Popped Panel Close Icon
*/
this.pinnedPanelCloseIcon = null;
/**
* Parent Canopy Panel Management Object
*/
this.cPanel = cPanel;
/**
* Disables this.panel.closer() calls from this.closer()
*/
this.keepAlive = false;
//Continue constructor asynchrnously
this.asyncConstructor();
}
/**
* Continuation of constructor method for asynchronous function calls
*/
async asyncConstructor(){
//Set panel body properly
this.panelBody = (this.panelBody == null || this.panelBody == "") ? await this.panel.getPage() : this.panelBody;
//Pop the panel
this.popContainer();
}
/**
* Pops/Opens container window upon start
*/
popContainer(){
//Set Window Object
this.window = window.open("/panel/popoutContainer","",`menubar=no,height=850,width=600`);
this.window.addEventListener("load", this.fillContainer.bind(this));
}
/**
* Fills container window with Popped Panel container elements
*/
fillContainer(){
//Set Element Nodes
this.panelDiv = this.window.document.querySelector("#cpanel-div");
this.panelTitle = this.window.document.querySelector("#cpanel-title");
this.panelDoc = this.window.document.querySelector("#cpanel-doc");
this.panelPopinIcon = this.window.document.querySelector("#cpanel-popin-icon");
this.panelPinIcon = this.window.document.querySelector("#cpanel-pin-icon");
//Set Window Title
this.window.document.title = this.window.document.title.replace("NULL_POPOUT", `${this.panel.name} (${client.channelName})`);
//Set Panel Content
this.panelTitle.innerText = this.panel.name;
this.panelDoc.innerHTML = this.panelBody;
//Set panel object document and call the related function
this.panel.panelDocument = this.window.document;
this.panel.docSwitch();
this.setupInput();
}
/**
* Defines default input-related popped-panel Event Listeners
*/
setupInput(){
this.panelPopinIcon.addEventListener("click", this.unpop.bind(this));
this.panelPinIcon.addEventListener("click", this.pin.bind(this));
this.window.addEventListener("unload", this.closer.bind(this));
}
/**
* Called upon close/exit of panel
*/
closer(){
if(!this.keepAlive){
this.panel.closer();
}
this.cPanel.poppedPanels.splice(this.cPanel.poppedPanels.indexOf(this),1);
}
/**
* Un-pops panel into active-panel slot
*/
unpop(){
//Set active panel
this.cPanel.setActivePanel(this.panel, this.panelDoc.innerHTML);
this.keepAlive = true;
//Close the popped window
this.window.close();
}
/**
* Pins panel next to chat
*/
pin(){
this.cPanel.setPinnedPanel(this.panel, this.panelDoc.innerHTML);
this.keepAlive = true;
this.window.close();
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,969 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: defaultTitlesPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: defaultTitlesPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>defaultTitlesPopup<span class="signature">(event, playlist, titles, location, client, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class Representing popup dialogue for changing playlists defualt titles</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="defaultTitlesPopup"><span class="type-signature"></span>new defaultTitlesPopup<span class="signature">(event, playlist, titles, location, client, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Default Titles Popup
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>playlist</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Playlist name</td>
</tr>
<tr>
<td class="name"><code>titles</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">List of titles, denoted by newlines</td>
</tr>
<tr>
<td class="name"><code>location</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Location of playlist, either Channel or User</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line784">line 784</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line798">line 798</a>
</li></ul></dd>
</dl>
<h4 class="name" id="location"><span class="type-signature"></span>location<span class="type-signature"></span></h4>
<div class="description">
Location of playlist, either Channel or User
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line808">line 808</a>
</li></ul></dd>
</dl>
<h4 class="name" id="playlist"><span class="type-signature"></span>playlist<span class="type-signature"></span></h4>
<div class="description">
Playlist Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line803">line 803</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line820">line 820</a>
</li></ul></dd>
</dl>
<h4 class="name" id="titles"><span class="type-signature"></span>titles<span class="type-signature"></span></h4>
<div class="description">
Array of titles to set
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line813">line 813</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line826">line 826</a>
</li></ul></dd>
</dl>
<h4 class="name" id="changeDefaultTitles"><span class="type-signature"></span>changeDefaultTitles<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to change default titles of playlist to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line847">line 847</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line837">line 837</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 117 KiB

View file

@ -1,217 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Global</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Global</h1>
<section>
<header>
<h2></h2>
</header>
<article>
<div class="container-overview">
<dl class="details">
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="onYouTubeIframeAPIReady"><span class="type-signature"></span>onYouTubeIframeAPIReady<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Youtube iframe-embed API entry point
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="channel.js.html">channel.js</a>, <a href="channel.js.html#line321">line 321</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,96 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Home</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Home</h1>
<h3> </h3>
<section>
<article><h1>Canopy - 0.3-INDEV</h1>
<p>Canopy - /ˈkæ.nə.pi/:</p>
<ul>
<li>The upper layer of foliage and branches of a forest, containing the majority of animal life.</li>
</ul>
<p>Canopy is a community chat &amp; synced video embedding web application, intended to replace fore.st as the server software for ourfore.st.
This new codebase intends to solve the following issues with the current CyTube based software:</p>
<ul>
<li>Unmaintained upstream codebase.</li>
<li>Different goals.</li>
<li>Different coding styles.</li>
<li>Obsolete Technology and Dependencies.</li>
<li>General Clunk</li>
<li>Less Unique Community Identity</li>
</ul>
<p>Canopy intends to be a simple node/express.js app. It leverages yt-dlp and the internet archive REST api for metadata gathering. Persistant storage is handled by mongodb, as it's document based nature inherintly works well for cleanly storing large config documents for user/channel settings, and the low use of inter-collection references within the canopy software. All hardcore security functions like server-side input sanatization, session handling, CSRF mitigation, and password hashing are handled by industry-standard open source libraries such as validator/express-validator, express-sessions, csrf-sync, and bcrypt, however it IS hobbiest software, and it should be treated as such.</p>
<p>The Canopy codebase does not, nor will it ever contain:</p>
<ul>
<li>Advertisements (targetted or otherwise)</li>
<li>Proprietary Code</li>
<li>Cryptocurrency/Blockchain integration</li>
<li>'Analytics/Telemtry' spyware</li>
<li>The use of video sources which require proprietary 'Digital <s>Rights Management</s> Ristricitons Malware' such as Widevine.</li>
</ul>
<p>Thirdparty media providers may or may not contain all of the above atrocities :P (though browser-side DRM extensions will never be required), always use an ad-blocker!</p>
<p>Our current goal is to create a cleaner, more modern, purpose-built codebase that has feature-parity with the current version of fore.st, while writing improvements where possible. Once this is accomplished, and ourfore.st has been migrated, work will continue to re-create features from TTN, while also building completely new ones as well.</p>
<h2>License</h2>
<p>Canopy is written by the community, and provided under the GNU Affero General Public License v3 in order to prevent Canopy from being used in proprietary software or shitcoin scams.</p></article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,844 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: mediaHandler.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: mediaHandler.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/*
* Base class for all Canopy Media Handlers
*
* This is little more than a interface class
*/
class mediaHandler{
/**
* Instantiates a new Media Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Parent Canopy Player Object
*/
this.player = player;
/**
* Media Handler Source Type
*/
this.type = type
/*
* Denotes wether a seek call was made by the syncing function
*/
this.selfAct = false;
/*
* Contains the last received time stamp
*/
this.lastTimestamp = 0;
//Ingest media object from server
this.startMedia(media);
}
/**
* Ingests media nd starts playback
* @param {Object} media - Media object from server
*/
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
//Build the video player
this.buildPlayer();
//Call the start function
this.start();
}
}
/**
* Builds video player element
*/
buildPlayer(){
//Reset player lock
this.lock = false;
}
/**
* Destroys video player element
*/
destroyPlayer(){
//Null out video property
this.video = null;
}
/**
* Ingests media object from server
* @param {Object} media - Media object from the server
* @returns {Boolean} True upon success
*/
ingestMedia(media){
//Set now playing
this.nowPlaying = media;
//return true to signify success
return true;
}
/**
* Starts video playback
*/
start(){
this.setVideoTitle(this.nowPlaying.title);
}
/**
* Syncronizes timestamp based on timestamp received from server
* @param {Number} timestamp - Current video timestamp in seconds
*/
sync(timestamp = this.lastTimestamp){
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
//Set self act flag
this.selfAct = true;
}
}
/**
* Reloads media player
*/
reload(){
//Get current timestamp
const timestamp = this.video.currentTime;
//Throw self act flag to make sure we don't un-sync the player
this.selfAct = true;
}
/**
* Handles media end
*/
end(){
//Null out current media
this.nowPlaying = null;
//Throw self act to prevent unlock on video end
this.selfAct = true;
//Destroy the player
this.destroyPlayer();
}
/**
* Plays video
*/
play(){
}
/**
* Pauses video
*/
pause(){
}
/**
* Toggles player control lockout
* @param {Boolean} lock - Whether or not to lock-out user control of video
*/
setPlayerLock(lock){
//set lock property
this.lock = lock;
}
/**
* Calculates Aspect Ratio of media
* @returns {Number} Media Aspect Ratio as Floating Point number
*/
getRatio(){
return 4/3;
}
/**
* Gets current timestamp from video
* @returns {Number} Media Timestamp in seconds
*/
getTimestamp(){
return 0;
}
/**
* Sets player title
* @param {String} title - Title to set
*/
setVideoTitle(title){
this.player.title.textContent = `Currently Playing: ${title}`;
}
/**
* Called once all video metadata has properly been fetched
* @param {Event} event - Event passed down by event handler
*/
onMetadataLoad(event){
//Resize aspect (if locked), since the video doesn't properly report it's resolution until it's been loaded
this.client.chatBox.resizeAspect();
}
/**
* Called on media pause
* @param {Event} event - Event passed down by event handler
*/
onPause(event){
//If the video was paused out-side of code
if(!this.selfAct){
this.player.unlockSync();
}
this.selfAct = false;
}
/**
* Called on media volume change
* @param {Event} event - Event passed down by event handler
*/
onVolumeChange(event){
}
/**
* Called on media seek
* @param {Event} event - Event passed down by event handler
*/
onSeek(event){
//If the video was seeked out-side of code
if(!this.selfAct){
this.player.unlockSync();
}
//reset self act flag
this.selfAct = false;
}
/**
* Called on media buffer
* @param {Event} event - Event passed down by event handler
*/
onBuffer(){
this.selfAct = true;
}
}
/**
* Class containing basic building blocks for anything that touches a &lt;video> tag
* @extends mediaHandler
*/
class rawFileBase extends mediaHandler{
/**
* Instantiates a new rawFileBase object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
super(client, player, media, type);
}
/**
* Defines input-related event listeners
*/
defineListeners(){
//Resize to aspect on metadata load
this.video.addEventListener('loadedmetadata', this.onMetadataLoad.bind(this));
this.video.addEventListener('volumechange', this.onVolumeChange.bind(this));
}
buildPlayer(){
//Create player
this.video = document.createElement('video');
//Enable controls
this.video.controls = true;
//Append it to page
this.player.videoContainer.appendChild(this.video);
//Run derived method
super.buildPlayer();
}
destroyPlayer(){
//Stops playback
this.video.pause();
//Remove player from page
this.video.remove();
//Run derived method
super.destroyPlayer();
}
reload(){
//Call derived method
super.reload();
//Load video from source
this.video.load();
//Set it back to the proper time
this.video.currentTime = this.lastTimestamp;
//Play the video
this.video.play();
}
setPlayerLock(lock){
//toggle controls
this.video.controls = !lock;
//Only toggle mute if we're locking, or if we're unlocking after being locked
//If this is ran twice without locking we don't want to surprise unmute on the user
if(lock || this.lock){
//toggle mute
this.video.muted = lock;
}
//toggle looping
this.video.loop = lock;
//Run derived method
super.setPlayerLock(lock);
}
getRatio(){
return this.video.videoWidth / this.video.videoHeight;
}
onVolumeChange(event){
//Pull volume from video
this.player.volume = this.video.volume;
}
}
/**
* Off air static 'player'
* @extends rawFileBase
*/
class nullHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
*/
constructor(client, player){
//Call derived constructor
super(client, player, {}, null);
this.defineListeners();
}
defineListeners(){
//Run derived method
super.defineListeners();
//Disable right clicking
this.video.addEventListener('contextmenu', (e)=>{e.preventDefault()});
}
start(){
//call derived start function
super.start();
//Lock the player
this.setPlayerLock(true);
//Set the static placeholder
this.video.src = '/video/static.webm';
//play the placeholder video
this.video.play();
}
setVideoTitle(title){
this.player.title.textContent = `Channel Off Air`;
}
}
/**
* Basic building blocks needed for proper time-synchronized raw-file playback
* @extends rawFileBase
*/
class rawFileHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'raw');
//Define listeners
this.defineListeners();
}
defineListeners(){
//Run derived method
super.defineListeners();
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeked', this.onSeek.bind(this));
this.video.addEventListener('waiting', this.onBuffer.bind(this));
}
start(){
//Call derived start
super.start();
//Set video
this.video.src = this.nowPlaying.rawLink;
//Set video volume
this.video.volume = this.player.volume;
//Unlock player
this.setPlayerLock(false);
//play video
this.video.play();
}
play(){
this.video.play();
}
pause(){
this.video.pause();
}
sync(timestamp = this.lastTimestamp){
//Call derived sync
super.sync(timestamp);
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
//Set current video time based on timestamp received from server
this.video.currentTime = timestamp;
}
}
getTimestamp(){
//Return current timestamp
return this.video.currentTime;
}
}
/**
* Handles Youtube playback via the official YT embed (gross)
* @extends mediaHandler
*/
class youtubeEmbedHandler extends mediaHandler{
/**
* Instantiates a new Youtube Embed Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'ytEmbed');
//Set flag to notify functions when the player is actually ready
this.ready = false;
//Create property to hold video iframe for easy access
this.iframe = null;
}
//custom start media function since we want the youtube player to call the start function once it's ready
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
//Build the video player
this.buildPlayer();
}
}
buildPlayer(){
//If the embed API hasn't loaded
if(!this.client.ytEmbedAPILoaded){
//Complain and stop
return console.warn("youtubeEmbedHandler.buildPlayer() Called before YT Iframe API Loaded, waiting on refresh to rebuild...");
}
//Create temp div for yt api to replace
const tempDiv = document.createElement('div');
//Name the div
tempDiv.id = "youtube-embed-player"
//Append it to the video container
this.player.videoContainer.appendChild(tempDiv);
//Create a new youtube player using the official YT iframe-embed api
this.video = new YT.Player('youtube-embed-player', {
//Inject video id
videoId: this.nowPlaying.id,
events: {
'onReady': this.start.bind(this),
'onStateChange': this.onStateChange.bind(this)
}
});
//Call derived function
super.buildPlayer();
}
start(){
//Call derived start function
super.start();
//Set volume based on player volume
this.video.setVolume(this.player.volume * 100);
//Kick the video off
this.video.playVideo();
//Pull iframe
this.iframe = this.video.getIframe()
//Throw the ready flag
this.ready = true;
}
destroyPlayer(){
//If we've had enough time to create a player frame
if(this.ready){
//Pull volume from player before destroying since google didn't give us a volume change event like a bunch of dicks
this.player.volume = (this.video.getVolume() / 100);
//Use the embed api's built in destroy function
this.video.destroy();
}
//Check the f̶r̶i̶d̶g̶e video container for leftovers
const leftovers = this.player.videoContainer.querySelector("#youtube-embed-player");
//If we have any leftovers
if(leftovers != null){
//Nukem like last nights chicken
leftovers.remove();
}
//Call derived destroy function
super.destroyPlayer();
}
sync(timestamp = this.lastTimestamp){
//If we're not ready
if(!this.ready){
//Kick off a timer to wait it out and try again l8r
setTimeout(this.sync.bind(this), 100);
//If it failed, tell randy to fuck off
return;
}
//Seek to timestamp, allow buffering
this.video.seekTo(timestamp, true);
}
reload(){
//if we're ready
if(this.ready){
//re-load the video by id
this.video.loadVideoById(this.nowPlaying.id);
}
}
play(){
//If we're ready
if(this.ready){
//play the video
this.video.playVideo();
}
}
pause(){
//If we're ready
if(this.ready){
//pause the video
this.video.pauseVideo();
}
}
getRatio(){
//TODO: Implement a type-specific metadata property object in the media class to hold type-sepecifc meta-data
//Alternatively we could fill in resolution information from the raw link
//However keeping embedded functionality dependant on raw-links seems like bad practice
}
getTimestamp(){
//If we're ready
if(this.ready){
//Return the timestamp
return this.video.getCurrentTime();
}
//If we fall through, simply report that the video hasn't gone anywhere yet
return 0;
}
setVideoTitle(){
//Clear out the player title so that youtube's baked in title can do it's thing.
//This will be replaced once we complete the full player control and remove the defualt youtube UI
this.player.title.textContent = "";
}
/**
* Generic handler for state changes since google is a dick
*/
onStateChange(event){
switch(event.data){
//video unstarted
case -1:
return;
//video ended
case 0:
return;
//video playing
case 1:
return;
//video paused
case 2:
super.onPause(event);
return;
//video buffering
case 3:
//There is no good way to tell slow connections apart from user seeking
//This will be easier to implement once we get custom player controls up
//super.onSeek(event);
return;
//video queued
case 5:
return;
//bad status code
default:
return;
}
}
setPlayerLock(lock){
super.setPlayerLock(lock);
if(this.ready){
this.iframe.style.pointerEvents = (lock ? "none" : "");
}
}
}
/**
* Base HLS Media handler for handling all HLS related media
* @extends rawFileBase
*/
class hlsBase extends rawFileBase{
/**
* Instantiates a new HLS Base object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
//Call derived constructor
super(client, player, media, type);
}
buildPlayer(){
//Call derived buildPlayer function
super.buildPlayer();
//Instantiate HLS object
this.hls = new Hls();
//Load HLS Stream
this.hls.loadSource(this.nowPlaying.url);
//Attatch hls object to video element
this.hls.attachMedia(this.video);
//Bind onMetadataLoad to MANIFEST_PARSED
this.hls.on(Hls.Events.MANIFEST_PARSED, this.onMetadataLoad.bind(this));
}
end(){
//Stop hls.js from loading any more of the stream
this.hls.stopLoad();
//Call derived method
super.end();
}
onMetadataLoad(){
//Call derived method
super.onMetadataLoad();
}
start(){
//Call derived method
super.start();
//Start the video
this.video.play();
}
}
/**
* HLS Livestream Handler
* @extends hlsBase
*/
class hlsLiveStreamHandler extends hlsBase{
/**
* Instantiates a new HLS Live Stream Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, "livehls");
//Create variable to determine if we need to resync after next seek
this.reSync = false;
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeked', this.onSeek.bind(this));
this.video.addEventListener('waiting', this.onBuffer.bind(this));
}
sync(){
//Kick the video back on if it was paused
this.video.play();
//Pull video duration
const duration = this.video.duration;
//Ignore bad timestamps
if(duration > 0){
//Seek to the end to sync up w/ the livestream
this.video.currentTime = duration;
}
}
setVideoTitle(title){
//Add title as text content for security :P
this.player.title.textContent = `: ${title}`;
//Create glow span
const glowSpan = document.createElement('span');
//Fill glow span content
glowSpan.textContent = "🔴LIVE";
//Set glowspan class
glowSpan.classList.add('critical-danger-text');
//Inject glowspan into title in a way that allows it to be easily replaced
this.player.title.prepend(glowSpan);
}
onBuffer(event){
//Call derived function
super.onBuffer(event);
//If we're supposed to be synced by the end of buffering
if(this.player.syncLock){
//Throw flag to manually sync since this works entirely differently from literally every other fucking media source
this.reSync = true;
}
}
onSeek(event){
//Call derived method
super.onSeek(event);
//If we stopped playing the video
if(this.video == null){
//Don't worry about it
return;
}
//Calculate distance to end of stream
const difference = this.video.duration - this.video.currentTime;
//If we where buffering under sync lock
if(this.reSync){
//Set reSync to false
this.reSync = false;
//If the difference is bigger than streamSyncTolerance
if(difference > this.player.streamSyncTolerance){
//Sync manually since we have no timestamp, and therefore the player won't do it for us
this.sync();
}
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,714 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: newPlaylistPopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: newPlaylistPopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>newPlaylistPopup<span class="signature">(event, client, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing pop-up dialogue for creating a new playlist</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="newPlaylistPopup"><span class="type-signature"></span>new newPlaylistPopup<span class="signature">(event, client, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a New Playlist Popup
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line643">line 643</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line654">line 654</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line661">line 661</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line667">line 667</a>
</li></ul></dd>
</dl>
<h4 class="name" id="createPlaylist"><span class="type-signature"></span>createPlaylist<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Sends request to create a playlist off to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line689">line 689</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line679">line 679</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,918 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: panelObj</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: panelObj</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>panelObj<span class="signature">(client, name, pageURL, panelDocument)</span><span class="type-signature"></span></h2>
<div class="class-description">Template Class for other Classes for Objects which represent a single Canopy Panel</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="panelObj"><span class="type-signature"></span>new panelObj<span class="signature">(client, name, pageURL, panelDocument)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Panel Object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="default">
</td>
<td class="description last">Parent client Management Object</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="default">
Placeholder Panel
</td>
<td class="description last">Panel Name</td>
</tr>
<tr>
<td class="name"><code>pageURL</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="default">
/panel/placeholder
</td>
<td class="description last">Panel Default Page URL</td>
</tr>
<tr>
<td class="name"><code>panelDocument</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="default">
</td>
<td class="description last">Panel Document</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line295">line 295</a>
</li></ul></dd>
</dl>
<h4 class="name" id="name"><span class="type-signature"></span>name<span class="type-signature"></span></h4>
<div class="description">
Panel Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line275">line 275</a>
</li></ul></dd>
</dl>
<h4 class="name" id="ownerDoc"><span class="type-signature"></span>ownerDoc<span class="type-signature"></span></h4>
<div class="description">
Current root document panel doc lives within
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line290">line 290</a>
</li></ul></dd>
</dl>
<h4 class="name" id="pageURL"><span class="type-signature"></span>pageURL<span class="type-signature"></span></h4>
<div class="description">
Panel Default Page URL
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line280">line 280</a>
</li></ul></dd>
</dl>
<h4 class="name" id="panelDocument"><span class="type-signature"></span>panelDocument<span class="type-signature"></span></h4>
<div class="description">
Panel Document
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line285">line 285</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="closer"><span class="type-signature"></span>closer<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Called upon panel close/exit
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line321">line 321</a>
</li></ul></dd>
</dl>
<h4 class="name" id="docSwitch"><span class="type-signature"></span>docSwitch<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Handles Document/Panel Changes
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line313">line 313</a>
</li></ul></dd>
</dl>
<h4 class="name" id="getPage"><span class="type-signature">(async) </span>getPage<span class="signature">()</span><span class="type-signature"> &rarr; {String}</span></h4>
<div class="description">
Fetches panel page from the server
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line302">line 302</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
Raw panel doc HTML
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">String</span>
</dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:58 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,362 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: panels/emotePanel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: panels/emotePanel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class representing Emote Panel UX
* @extends panelObj
*/
class emotePanel extends panelObj{
/**
* Instantiates a new Panel Object
* @param {channel} client - Parent client Management Object
* @param {Document} panelDocument - Panel Document
*/
constructor(client, panelDocument){
super(client, "Emote Palette", "/panel/emote", panelDocument);
this.client.socket.on("personalEmotes", this.renderEmoteLists.bind(this));
}
closer(){
this.client.socket.off("personalEmotes", this.renderEmoteLists.bind(this));
}
docSwitch(){
this.siteEmoteTitle = this.panelDocument.querySelector('#site-emotes-title');
this.chanEmoteTitle = this.panelDocument.querySelector('#chan-emotes-title');
this.personalEmoteTitle = this.panelDocument.querySelector('#personal-emotes-title');
this.siteEmoteToggle = this.panelDocument.querySelector('#site-emotes-toggle');
this.chanEmoteToggle = this.panelDocument.querySelector('#chan-emotes-toggle');
this.personalEmoteToggle = this.panelDocument.querySelector('#personal-emotes-toggle');
this.siteEmoteList = this.panelDocument.querySelector('#emote-panel-site-list');
this.chanEmoteList = this.panelDocument.querySelector('#emote-panel-chan-list');
this.personalEmoteSection = this.panelDocument.querySelector('#emote-panel-personal-section');
this.personalEmoteList = this.panelDocument.querySelector('#emote-panel-personal-list');
this.searchPrompt = this.panelDocument.querySelector('#emote-panel-search-prompt');
this.personalEmoteLinkPrompt = this.panelDocument.querySelector('#new-emote-link-input');
this.personalEmoteNamePrompt = this.panelDocument.querySelector('#new-emote-name-input');
this.personalEmoteAddButton = this.panelDocument.querySelector('#new-emote-button');
this.setupInput();
this.renderEmoteLists();
}
/**
* Defines input-related event handlers
*/
setupInput(){
//Make sure to remove any event listeners in-case we moving an already instantiated panel
this.siteEmoteToggle.removeEventListener("click", this.toggleSiteEmotes.bind(this));
this.siteEmoteToggle.addEventListener("click", this.toggleSiteEmotes.bind(this));
this.chanEmoteToggle.removeEventListener("click", this.toggleChanEmotes.bind(this));
this.chanEmoteToggle.addEventListener("click", this.toggleChanEmotes.bind(this));
this.personalEmoteToggle.removeEventListener("click", this.togglePersonalEmotes.bind(this));
this.personalEmoteToggle.addEventListener("click", this.togglePersonalEmotes.bind(this));
this.siteEmoteTitle.removeEventListener("click", this.toggleSiteEmotes.bind(this));
this.siteEmoteTitle.addEventListener("click", this.toggleSiteEmotes.bind(this));
this.chanEmoteTitle.removeEventListener("click", this.toggleChanEmotes.bind(this));
this.chanEmoteTitle.addEventListener("click", this.toggleChanEmotes.bind(this));
this.personalEmoteTitle.removeEventListener("click", this.togglePersonalEmotes.bind(this));
this.personalEmoteTitle.addEventListener("click", this.togglePersonalEmotes.bind(this));
this.searchPrompt.removeEventListener('keyup', this.renderEmoteLists.bind(this));
this.searchPrompt.addEventListener('keyup', this.renderEmoteLists.bind(this));
this.personalEmoteAddButton.removeEventListener("click", this.addPersonalEmote.bind(this));
this.personalEmoteAddButton.addEventListener("click", this.addPersonalEmote.bind(this));
}
/**
* Toggles Site emote display
* @param {Event} event - Event passed down by event listener
*/
toggleSiteEmotes(event){
this.toggleEmotes(this.siteEmoteToggle, this.siteEmoteList);
}
/**
* Toggles Channel emote display
* @param {Event} event - Event passed down by event listener
*/
toggleChanEmotes(event){
this.toggleEmotes(this.chanEmoteToggle, this.chanEmoteList);
}
/**
* Toggles Personal emote display
* @param {Event} event - Event passed down by event listener
*/
togglePersonalEmotes(event){
this.toggleEmotes(this.personalEmoteToggle, this.personalEmoteSection);
}
/**
* Toggles a specified emote list on or off
* @param {Node} icon - Toggle Icon for given list
* @param {Node} list - Emote list container to toggle
*/
toggleEmotes(icon, list){
if(list.checkVisibility()){
icon.classList.replace('bi-caret-down-fill','bi-caret-left-fill');
list.style.display = 'none';
}else{
icon.classList.replace('bi-caret-left-fill', 'bi-caret-down-fill');
list.style.display = 'grid';
}
}
/**
* Concatenates specified emote into chat prompt input
* @param {String} emote - Emote to concat into chat
*/
useEmote(emote){
//If we're using this from the active panel
if(this.client.cPanel.activePanel == this){
//Close it
this.client.cPanel.hideActivePanel();
}
//Add the emote to the chatbox prompt
this.client.chatBox.catChat(`[${emote}]`);
}
/**
* Requests server to add emote to list of personal emotes
* @param {Event} event - Event passed down by event listener
*/
addPersonalEmote(event){
//Collect input
const name = this.personalEmoteNamePrompt.value;
const link = this.personalEmoteLinkPrompt.value;
//Empty out prompts
this.personalEmoteNamePrompt.value = '';
this.personalEmoteLinkPrompt.value = '';
//Send emote to server
this.client.socket.emit("addPersonalEmote", {name, link});
}
/**
* Requests server to remove emote from list of personal emotes
* @param {String} name - Name of emote to delete
*/
deletePersonalEmote(name){
//send out delete
this.client.socket.emit('deletePersonalEmote', {name});
}
/**
* Renders out emote list to panel document
*/
renderEmoteLists(){
//if we've initialized the search prompt (wont happen yet first run)
if(this.searchPrompt != null){
//Get the search value
var search = this.searchPrompt.value;
}
//pull emote lists from the command preprocessor
var siteEmotes = this.client.chatBox.commandPreprocessor.emotes.site;
var chanEmotes = this.client.chatBox.commandPreprocessor.emotes.chan;
var personalEmotes = this.client.chatBox.commandPreprocessor.emotes.personal;
//If we have a search bar and a search in the search bar
if(search != null &amp;&amp; search != ''){
//filter emote lists using the filterQuery function
siteEmotes = siteEmotes.filter(filterQuery);
chanEmotes = chanEmotes.filter(filterQuery);
personalEmotes = personalEmotes.filter(filterQuery);
function filterQuery(emote){
//return true for anyany case-insensitive matches
return (emote.name.toLowerCase().match(search.toLowerCase())) != null;
}
}
//render out the emote lists
this.renderEmotes(siteEmotes, this.siteEmoteList);
this.renderEmotes(chanEmotes, this.chanEmoteList);
this.renderEmotes(personalEmotes, this.personalEmoteList, true);
}
/**
* Renders out emotes to emote lists
* @param {Array} emoteList - list of emotes to render
* @param {Node} container - Container to render emotes out to
* @param {Boolean} personal - Denotes whether or not we're rendering personal emotes
*/
renderEmotes(emoteList, container, personal = false){
//Clear out the container
container.innerHTML = '';
//If we have two or less emotes
if(emoteList.length &lt;= 2){
//Set the container display to flex
container.style.display = 'flex';
//otherwise
}else{
//Set the container display to grid
container.style.display = 'grid';
}
//For each emote
emoteList.forEach((emote) => {
//Create div to hold emote span
const emoteDiv = document.createElement('div');
emoteDiv.classList.add('emote-panel-list-emote');
const emoteSpan = document.createElement('span');
emoteSpan.classList.add('emote-panel-list-emote');
//If we have a low emote count
if(emoteList.length &lt;= 2){
//render them huuuuuge
emoteDiv.classList.add('emote-panel-list-big-emote');
emoteSpan.classList.add('emote-panel-list-big-emote');
}
//If the emote is an image
if(emote.type == 'image'){
//Create image node
var emoteMedia = document.createElement('img');
//if emote is a video
}else if(emote.type == 'video'){
//create video node
var emoteMedia = document.createElement('video');
//Set video properties
emoteMedia.autoplay = true;
emoteMedia.muted = true;
emoteMedia.controls = false;
emoteMedia.loop = true;
}
//set media link as source
emoteMedia.src = emote.link;
//Set media class
emoteMedia.classList.add('emote-list-media');
//if we have a low emote count
if(emoteList.length &lt;= 2){
//render them huuuuuge
emoteMedia.classList.add('emote-list-big-media');
}
//Create paragraph tag
const emoteTitle = document.createElement('p');
//Set title class
emoteTitle.classList.add('emote-list-title');
//Set emote title
emoteTitle.textContent = utils.unescapeEntities(`[${emote.name}]`);
//if we're rendering personal emotes
if(personal){
//create span to hold trash icon
const trashSpan = document.createElement('span');
trashSpan.classList.add('emote-list-trash-icon');
//Create trash icon
const trashIcon = document.createElement('i');
trashIcon.classList.add('emote-list-trash-icon', 'bi-trash-fill');
trashIcon.id = `emote-list-trash-icon-${emote.name}`;
//add deletePersonalEmote event listener
trashIcon.addEventListener('click', ()=>{this.deletePersonalEmote(emote.name)});
//Add trash icon to trash span
trashSpan.appendChild(trashIcon);
//append trash span to emote div
emoteDiv.appendChild(trashSpan);
}
//Add the emote media to the emote span
emoteSpan.appendChild(emoteMedia);
//Add title paragraph node
emoteSpan.appendChild(emoteTitle);
//Add useEmote event listener
emoteSpan.addEventListener('click', ()=>{this.useEmote(emote.name)});
//Add emote span to the emote div
emoteDiv.appendChild(emoteSpan);
//Append the mote span to the emote list
container.appendChild(emoteDiv);
})
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,992 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: panels/queuePanel/playlistManager.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: panels/queuePanel/playlistManager.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class representing Playlist Manager UX within the Queue Panel
*/
class playlistManager{
/**
* Instantiates a new playlist manager
* @param {channel} client - Parent Client Management Object
* @param {Document} panelDocument - Panel Document
* @param {queuePanel} queuePanel - Parent Queue Panel Object
*/
constructor(client, panelDocument, queuePanel){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Panel Document
*/
this.panelDocument = panelDocument;
/**
* Parent Queue Panel Object
*/
this.queuePanel = queuePanel;
/**
* Map of which playlists are open and which are not, for better refresh handling
*/
this.openMap = {
Channel: new Map(),
User: new Map()
};
//Define Listeners
this.defineListeners();
}
/**
* Handles Network-Related Event Listeners
*/
defineListeners(){
this.client.socket.on("chanPlaylists", this.renderChannelPlaylists.bind(this));
this.client.socket.on("userPlaylists", this.renderUserPlaylists.bind(this));
}
/**
* Handles Up-stream Document/Panel Changes from the parent Queue Panel object
*/
docSwitch(){
//Grab menus
this.channelPlaylistDiv = this.panelDocument.querySelector("#queue-channel-playlist-div");
this.userPlaylistDiv = this.panelDocument.querySelector("#queue-user-playlist-div");
//Grab controls
this.createPlaylistSpan = this.panelDocument.querySelector('#queue-add-playlist-span');
this.channelPlaylistLabel = this.panelDocument.querySelector('#queue-channel-playlist-span');
this.channelPlaylistCaret = this.panelDocument.querySelector('#queue-channel-playlist-toggle');
this.userPlaylistLabel = this.panelDocument.querySelector('#queue-user-playlist-span');
this.userPlaylistCaret = this.panelDocument.querySelector('#queue-user-playlist-toggle');
//Force playlist re-render to fix controls
this.client.socket.emit('getChannelPlaylists');
this.client.socket.emit('getUserPlaylists');
//Setup Input
this.setupInput();
}
/**
* Handles Input-Related Event Listeners
*/
setupInput(){
this.createPlaylistSpan.addEventListener('click', (event)=>{new newPlaylistPopup(event, this.client, this.queuePanel.ownerDoc)})
this.channelPlaylistLabel.addEventListener('click', this.toggleChannelPlaylists.bind(this));
this.userPlaylistLabel.addEventListener('click', this.toggleUserPlaylists.bind(this));
}
/* queue control button functions */
/**
* Toggle Channel Playlists
* @param {Event} event - Event passed down from Event Listener
*/
toggleChannelPlaylists(event){
//If the div is hidden
if(this.channelPlaylistDiv.style.display == 'none'){
//Light up the button
this.channelPlaylistLabel.classList.add('positive');
//Flip the caret
this.channelPlaylistCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
//Show the div
this.channelPlaylistDiv.style.display = '';
}else{
//Unlight the button
this.channelPlaylistLabel.classList.remove('positive');
//Flip the caret
this.channelPlaylistCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
//Hide the div
this.channelPlaylistDiv.style.display = 'none';
}
}
/**
* Toggle User Playlists
* @param {Event} event - Event passed down from Event Listener
*/
toggleUserPlaylists(event){
//If the div is hidden
if(this.userPlaylistDiv.style.display == 'none'){
//Light up the button
this.userPlaylistLabel.classList.add('positive');
//Flip the caret
this.userPlaylistCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
//Show the div
this.userPlaylistDiv.style.display = '';
}else{
//Unlight the button
this.userPlaylistLabel.classList.remove('positive');
//Flip the caret
this.userPlaylistCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
//Hide the div
this.userPlaylistDiv.style.display = 'none';
}
}
/**
* Checks which playlists where open before a refresh and re-opens them
* @param {String} location - Whether or not we're dealing with user or channel playlists
*/
checkOpenPlaylists(location){
//If open map is a string, indicating we just renamed a playlist with it's media open
if(typeof this.openMap[location] == 'string'){
//Create new map to hold status with the new name of the renamed playlist already added
this.openMap[location] = new Map([[this.openMap[location], true]]);
}else{
//Create new map to hold status
this.openMap[location] = new Map();
}
let mediaContainerDivs = [];
if(location == 'Channel'){
mediaContainerDivs = this.channelPlaylistDiv.querySelectorAll('.queue-playlist-media-container-div')
}else{
mediaContainerDivs = this.userPlaylistDiv.querySelectorAll('.queue-playlist-media-container-div')
}
//For each container Div rendered
for(let containerDiv of mediaContainerDivs){
//Set whether or not it's visible in the map
this.openMap[location].set(containerDiv.dataset['playlist'], (containerDiv.style.display != 'none'));
}
}
//Main playlist rendering functions
/**
* Renders Channel Playlist list
* @param {Object} data - Data from server
*/
renderChannelPlaylists(data){
//Check for open playlists
this.checkOpenPlaylists('Channel');
//Clear channel playlist div
this.channelPlaylistDiv.innerHTML = '';
//Append rendered playlists
this.channelPlaylistDiv.append(...this.renderPlaylists(data, 'Channel'));
}
/**
* Renders User Playlist list
* @param {Object} data - Data from server
*/
renderUserPlaylists(data){
//Check for open playlists
this.checkOpenPlaylists('User');
//Clear channel playlist div
this.userPlaylistDiv.innerHTML = '';
//Append rendered playlists
this.userPlaylistDiv.append(...this.renderPlaylists(data, 'User'));
}
/**
* Render set of playlists out to Playlist Management Menu
* @param {Object} data - Data from server
* @param {String} location - Location to load from, either Channel or User
* @returns {Node} Rendered out playlist list
*/
renderPlaylists(data, location){
const playlists = [];
//For every playlist sent down from the server
for(let playlistIndex in data){
//Get playlist from data
const playlist = data[playlistIndex];
//Create a new playlist div
const playlistDiv = document.createElement('div');
//Set it's class
playlistDiv.classList.add('queue-playlist-div');
//Create span to hold playlist entry line contents
const playlistSpan = document.createElement('span');
//Set classes
playlistSpan.classList.add('queue-playlist-span');
//If this isn't our first rodeo
if(playlistIndex != 0){
//make note
playlistSpan.classList.add('not-first');
}
//assemble playlist entry line
playlistSpan.append(
this.renderLabels(playlist, location),
this.renderControls(playlist, location)
);
//assemble playlist div
playlistDiv.append(
playlistSpan,
this.renderMedia(playlist, location),
);
//add playlist div to playlists array
playlists.push(playlistDiv);
}
return playlists;
}
//aux rendering functions
/**
* Renders Playlist labels
* @param {Object} playlist - Playlist from server to render label for
* @param {String} location - Location of playlist (Channel or User)
* @returns {Node} Rendered out playlist label
*/
renderLabels(playlist, location){
//Create playlist label span
const playlistLabels = document.createElement('span');
//Set it's class
playlistLabels.classList.add('queue-playlist-labels-span');
//create playlist title span
const playlistTitleSpan = document.createElement('span');
//Set class
playlistTitleSpan.classList.add('queue-playlist-title-span', 'interactive');
//Create playlist title caret
const playlistTitleCaret = document.createElement('i');
//If this is supposed to be open
if(this.openMap[location].get(playlist.name)){
//Set class accordingly
playlistTitleSpan.classList.add('positive');
playlistTitleCaret.classList.add('bi-caret-down-fill');
//otherwise
}else{
//Set class accordingly
playlistTitleCaret.classList.add('bi-caret-right-fill');
}
//Create playlist title label
const playlistTitle = document.createElement('p');
//Set it's class
playlistTitle.classList.add('queue-playlist-title');
//Unescape Sanatized Enteties and safely inject as plaintext
playlistTitle.innerText = utils.unescapeEntities(playlist.name);
//Construct playlist title span
playlistTitleSpan.appendChild(playlistTitleCaret);
playlistTitleSpan.appendChild(playlistTitle);
//Create playlist count label
const playlistCount = document.createElement('p');
//Set it's class
playlistCount.classList.add('queue-playlist-count');
//List video count
playlistCount.innerText = `Count: ${playlist.media.length}`;
//Append items to playlist labels span
playlistLabels.appendChild(playlistTitleSpan);
playlistLabels.appendChild(playlistCount);
//Define input listeners
playlistTitleSpan.addEventListener('click', this.toggleMedia.bind(this));
return playlistLabels;
}
/**
* Renders out Playlist Controls
* @param {Object} playlist - Playlist from server to render label for
* @param {String} location - Location of playlist (Channel or User)
* @returns {Node} Rendered out playlist controls
*/
renderControls(playlist, location){
//Create playlist control span
const playlistControls = document.createElement('span');
//Set it's class
playlistControls.classList.add('queue-playlist-control-span');
//Set dataset
playlistControls.dataset['playlist'] = playlist.name;
playlistControls.dataset['location'] = location;
//Create queue all button
const playlistQueueRandomButton = document.createElement('button');
//Set it's classes
playlistQueueRandomButton.classList.add('queue-playlist-queue-random-button', 'queue-playlist-control');
//Inject text content
playlistQueueRandomButton.textContent = 'Random';
//Set title
playlistQueueRandomButton.title = 'Queue Random Item from Playlist';
//Create queue all button
const playlistQueueAllButton = document.createElement('button');
//Set it's classes
playlistQueueAllButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control', 'not-first');
//Inject text content
playlistQueueAllButton.textContent = 'All';
//Set title
playlistQueueAllButton.title = 'Queue Entire Playlist';
//Create add from URL button
const playlistAddURLButton = document.createElement('button');
//Set it's classes
playlistAddURLButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'positive-button', 'not-first');
//Set Tile
playlistAddURLButton.title = 'Add To Playlist From URL'
//Create playlist icons (we're using two so we're putting them inside the button :P)
const playlistAddIcon = document.createElement('i');
const playlistLinkIcon = document.createElement('i');
//set classes
playlistAddIcon.classList.add('bi-plus-lg');
playlistLinkIcon.classList.add('bi-link-45deg');
//Append icons to URL button
playlistAddURLButton.appendChild(playlistAddIcon);
playlistAddURLButton.appendChild(playlistLinkIcon);
//Create default titles button
const playlistDefaultTitlesButton = document.createElement('button');
//Set classes
playlistDefaultTitlesButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-tags-fill', 'positive-button', 'not-first');
//Set title
playlistDefaultTitlesButton.title = 'Change Default Titles'
//Set dataset
playlistDefaultTitlesButton.dataset['titles'] = JSON.stringify(playlist.defaultTitles);
//Create rename button
const playlistRenameButton = document.createElement('button');
//Set it's classes
playlistRenameButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-input-cursor-text', 'positive-button', 'not-first');
//Set title
playlistRenameButton.title = 'Rename Playlist'
//Create delete button
const playlistDeleteButton = document.createElement('button');
//Set it's classes
playlistDeleteButton.classList.add('queue-playlist-delete-button', 'queue-playlist-control', 'danger-button', 'bi-trash-fill', 'not-first');
//Set title
playlistDeleteButton.title = 'Delete Playlist'
//Append items to playlist control span
playlistControls.append(
playlistQueueRandomButton,
playlistQueueAllButton,
playlistAddURLButton,
playlistDefaultTitlesButton,
playlistRenameButton,
playlistDeleteButton
);
//Define input event listeners
playlistAddURLButton.addEventListener('click', this.addURL.bind(this));
playlistDefaultTitlesButton.addEventListener('click', this.editDefaultTitles.bind(this));
playlistRenameButton.addEventListener('click', this.renamePlaylist.bind(this));
playlistQueueRandomButton.addEventListener('click', this.queueRandom.bind(this));
playlistQueueAllButton.addEventListener('click', this.queueAll.bind(this));
playlistDeleteButton.addEventListener('click', this.deletePlaylist.bind(this));
return playlistControls;
}
/**
* Renders media object out for an entire playlist
* @param {Object} playlist - Playlist from server to render label for
* @param {String} location - Location of playlist (Channel or User)
* @returns {Node} Rendered out playlist
*/
renderMedia(playlist, location){
//Create media container div
const mediaContainer = document.createElement('div');
//Set classes
mediaContainer.classList.add('queue-playlist-media-container-div');
//If the playlist wasn't set to open in the open map
if(!this.openMap[location].get(playlist.name)){
//Auto-hide media container
mediaContainer.style.display = 'none';
}
//Set dataset
mediaContainer.dataset['playlist'] = playlist.name;
for(let mediaIndex in playlist.media){
//Grab media object from playlist
const media = playlist.media[mediaIndex];
//Sanatize title text
const title = utils.unescapeEntities(media.title);
//Create media div
const mediaDiv = document.createElement('div');
//Set class
mediaDiv.classList.add('queue-playlist-media-div');
//Inject title
mediaDiv.title = title;
//If this isn't our first rodeo
if(mediaIndex != 0){
mediaDiv.classList.add('not-first');
}
//Create media title
const mediaTitle = document.createElement('p');
//Set class
mediaTitle.classList.add('queue-playlist-media-title');
//Inject text content
mediaTitle.innerText = title;
//Append items to media div
mediaDiv.append(
mediaTitle,
this.renderMediaControls(media, playlist, location)
);
//Append media div to media container
mediaContainer.appendChild(mediaDiv);
}
//return media container
return mediaContainer;
}
/**
* Renders controls out for a single media entry within a playlist
* @param {Object} media - Media object from playlist to render controls for
* @param {Object} playlist - Playlist from server to render label for
* @param {String} location - Location of playlist (Channel or User)
* @returns {Node} Rendered out playlist
*/
renderMediaControls(media, playlist, location){
//Create media control span
const mediaControlSpan = document.createElement('span');
//Set it's class
mediaControlSpan.classList.add('queue-playlist-media-control-span');
//Set dataset
mediaControlSpan.dataset['playlist'] = playlist.name;
mediaControlSpan.dataset['uuid'] = media.uuid;
mediaControlSpan.dataset['location'] = location;
//Create Queue Media icon
const queueMediaIcon = document.createElement('i');
//set class
queueMediaIcon.classList.add('queue-playlist-control', 'queue-playlist-media-queue-icon', 'bi-play-circle');
//Set title
queueMediaIcon.title = (`Queue '${media.title}'`);
//Create delete media icon
const deleteMediaIcon = document.createElement('i');
//set class
deleteMediaIcon.classList.add('queue-playlist-control', 'queue-playlist-media-delete-icon', 'danger-text', 'bi-trash-fill');
//Set title
deleteMediaIcon.title = `Delete '${media.title}' from playlist '${playlist.name}'`;
//Append items to media control span
mediaControlSpan.appendChild(queueMediaIcon);
mediaControlSpan.appendChild(deleteMediaIcon);
//Handle input event listeners
queueMediaIcon.addEventListener('click', this.queueMedia.bind(this));
deleteMediaIcon.addEventListener('click', this.deleteMedia.bind(this));
//Return media control span
return mediaControlSpan;
}
/**
* Toggle Media List
* @param {Event} event - Event passed down from Event Listener
*/
toggleMedia(event){
//Grab playlist title caret
const playlistTitleCaret = event.target.querySelector('i');
//I hope my mother doesn't see this next line, god I hate dot crawling...
const mediaContainer = event.target.parentNode.parentNode.nextElementSibling;
//If the div is hidden
if(mediaContainer.style.display == 'none'){
//Light up the button
event.target.classList.add('positive');
//Flip the caret
playlistTitleCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
//Show the div
mediaContainer.style.display = '';
}else{
//Unlight the button
event.target.classList.remove('positive');
//Flip the caret
playlistTitleCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
//Hide the div
mediaContainer.style.display = 'none';
}
}
/**
* Add URL to playlist
* @param {Event} event - Event passed down from Event Listener
*/
addURL(event){
new addURLPopup(
event,
event.target.parentNode.dataset['playlist'],
event.target.parentNode.dataset['location'],
this.client,
this.queuePanel.ownerDoc
);
}
//playlist control functions
/**
* Sends request to server to edit default titles
* @param {Event} event - Event passed down from Event Listener
*/
editDefaultTitles(event){
new defaultTitlesPopup(
event,
event.target.parentNode.dataset['playlist'],
JSON.parse(event.target.dataset['titles']),
event.target.parentNode.dataset['location'],
this.client,
this.queuePanel.ownerDoc
);
}
/**
* Sends request to server to rename playlists
* @param {Event} event - Event passed down from Event Listener
*/
renamePlaylist(event){
new renamePopup(
event,
event.target.parentNode.dataset['playlist'],
this.client,
this.queuePanel.ownerDoc,
handleOpenedMedia.bind(this)
);
function handleOpenedMedia(newName){
//do an ugly dot crawl to get the media container div
const mediaContainer = event.target.parentNode.parentNode.nextElementSibling;
//If the media container is visible
if(mediaContainer.style.display != 'none'){
//Set openMap to new name indicating the new playlist has it's media opened
this.openMap[event.target.parentNode.dataset['location']] = newName;
}
}
}
/**
* Sends request to server to queue all playlist items
* @param {Event} event - Event passed down from Event Listener
*/
queueAll(event){
this.client.socket.emit(`queue${event.target.parentNode.dataset['location']}Playlist`, {playlist: event.target.parentNode.dataset['playlist']});
}
/**
* Sends request to server to queue a playlist item
* @param {Event} event - Event passed down from Event Listener
*/
queueMedia(event){
this.client.socket.emit(`queueFrom${event.target.parentNode.dataset['location']}Playlist`,{playlist: event.target.parentNode.dataset['playlist'], uuid: event.target.parentNode.dataset['uuid']});
}
/**
* Sends request to server to queue a random playlist item
* @param {Event} event - Event passed down from Event Listener
*/
queueRandom(event){
this.client.socket.emit(`queueRandomFrom${event.target.parentNode.dataset['location']}Playlist`,{playlist: event.target.parentNode.dataset['playlist']});
}
/**
* Sends request to server to delete a playlist
* @param {Event} event - Event passed down from Event Listener
*/
deletePlaylist(event){
this.client.socket.emit(`delete${event.target.parentNode.dataset['location']}Playlist`, {playlist: event.target.parentNode.dataset['playlist']});
}
/**
* Sends request to server to delete a playlist item
* @param {Event} event - Event passed down from Event Listener
*/
deleteMedia(event ){
this.client.socket.emit(`delete${event.target.parentNode.dataset['location']}PlaylistMedia`, {playlist: event.target.parentNode.dataset['playlist'], uuid: event.target.parentNode.dataset['uuid']});
}
}
/**
* Class representing pop-up dialogue for creating a new playlist
*/
class newPlaylistPopup{
/**
* Instantiates a New Playlist Popup
* @param {Event} event - Event passed down from Event Listener
* @param {channel} client - Parent Client Management Object
* @param {Document} doc - Current owner documnet of the panel, so we know where to drop our pop-up
*/
constructor(event, client, doc){
/**
* Parent Client Management Object
*/
this.client = client;
//Create media popup and call async constructor when done
//unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :(
/**
* canopyUXUtils.popup() object
*/
this.popup = new canopyUXUtils.popup('/newPlaylist', true, this.asyncConstructor.bind(this), doc, false);
}
/**
* Continuation of object construction, called after child popup object construction
*/
asyncConstructor(){
this.name = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-name');
this.defaultTitles = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-default-titles');
this.location = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-location');
this.saveButton = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-save');
this.setupInput();
}
/**
* Defines input-related Event Handlers
*/
setupInput(){
//Setup input
this.saveButton.addEventListener('click', this.createPlaylist.bind(this));
this.popup.popupDiv.addEventListener('keydown', this.createPlaylist.bind(this));
}
/**
* Sends request to create a playlist off to the server
* @param {Event} event - Event passed down from Event Listener
*/
createPlaylist(event){
//If we clicked or hit enter
if(event.key == null || (event.key == "Enter" &amp;&amp; this.defaultTitles !== this.popup.doc.activeElement)){
//Tell the server to create a new playlist
this.client.socket.emit(`create${this.location.value}Playlist`, {
playlist: this.name.value,
defaultTitles: this.defaultTitles.value.split('\n')
});
//Close the popup
this.popup.closePopup();
}
}
}
/**
* Class representing pop-up dialogue which adds media to a given playlist
*/
class addURLPopup{
/**
* Instantiates a new Add URL Pop-up
* @param {Event} event - Event passed down from Event Listener
* @param {String} playlist - Playlist name
* @param {String} location - Location of playlist, either Channel or User
* @param {channel} client - Parent Client Management Object
* @param {Document} doc - Current owner documnet of the panel, so we know where to drop our pop-up
*/
constructor(event, playlist, location, client, doc){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Playlist Name
*/
this.playlist = playlist
/**
* Location of playlist, either Channel or User
*/
this.location = location;
//Create media popup and call async constructor when done
//unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :(
/**
* canopyUXUtils.popup() object
*/
this.popup = new canopyUXUtils.popup('/addToPlaylist', true, this.asyncConstructor.bind(this), doc);
}
/**
* Continuation of object construction, called after child popup object construction
*/
asyncConstructor(){
this.urlPrompt = this.popup.contentDiv.querySelector('#playlist-add-media-popup-prompt');
this.addButton = this.popup.contentDiv.querySelector('#playlist-add-media-popup-button');
this.setupInput();
}
/**
* Defines input-related Event Handlers
*/
setupInput(){
//Setup input
this.addButton.addEventListener('click', this.addToPlaylist.bind(this));
this.popup.popupDiv.addEventListener('keydown', this.addToPlaylist.bind(this));
}
/**
* Handles sending request to add to a playlist to the server
* @param {Event} event - Event passed down from Event Listener
*/
addToPlaylist(event){
//If we clicked or hit enter
if(event.key == null || event.key == "Enter"){
//Tell the server to add url to the playlist
this.client.socket.emit(`addTo${this.location}Playlist`, {
//this.client.socket.emit(`addToChannelPlaylist`, {
playlist: this.playlist,
url: this.urlPrompt.value
});
//Close the popup
this.popup.closePopup();
}
}
}
/**
* Class Representing popup dialogue for changing playlists defualt titles
*/
class defaultTitlesPopup{
/**
* Instantiates a new Default Titles Popup
* @param {Event} event - Event passed down from Event Listener
* @param {String} playlist - Playlist name
* @param {String} titles - List of titles, denoted by newlines
* @param {String} location - Location of playlist, either Channel or User
* @param {channel} client - Parent Client Management Object
* @param {Document} doc - Current owner documnet of the panel, so we know where to drop our pop-up
*/
constructor(event, playlist, titles, location, client, doc){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Playlist Name
*/
this.playlist = playlist
/**
* Location of playlist, either Channel or User
*/
this.location = location;
/**
* Array of titles to set
*/
this.titles = titles.join('\n');
//Create media popup and call async constructor when done
//unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :(
/**
* canopyUXUtils.popup() object
*/
this.popup = new canopyUXUtils.popup('/playlistDefaultTitles', true, this.asyncConstructor.bind(this), doc, false);
}
/**
* Continuation of object construction, called after child popup object construction
*/
asyncConstructor(){
this.titlePrompt = this.popup.contentDiv.querySelector('#playlist-default-titles-popup-prompt');
this.titleButton = this.popup.contentDiv.querySelector('#playlist-default-media-popup-button');
this.titlePrompt.textContent = utils.unescapeEntities(this.titles);
this.setupInput();
}
/**
* Defines input-related Event Handlers
*/
setupInput(){
//Setup input
this.titleButton.addEventListener('click', this.changeDefaultTitles.bind(this));
this.popup.popupDiv.addEventListener('keydown', this.changeDefaultTitles.bind(this));
}
/**
* Handles sending request to change default titles of playlist to the server
* @param {Event} event - Event passed down from Event Listener
*/
changeDefaultTitles(event){
//If we clicked or hit enter while the prompt wasn't active
if(event.key == null || (event.key == "Enter" &amp;&amp; this.titlePrompt !== this.popup.doc.activeElement)){
//Tell the server to change the titles
this.client.socket.emit(`changeDefaultTitles${this.location}Playlist`, {
playlist: this.playlist,
defaultTitles: this.titlePrompt.value.split('\n')
});
//Close the popup
this.popup.closePopup();
}
}
}
/**
* Class representing pop-up dialogue to rename a playlist
*/
class renamePopup{
/**
* Instantiates a new Rename Pop-up
* @param {Event} event - Event passed down from Event Listener
* @param {String} playlist - Playlist name
* @param {channel} client - Parent Client Management Object
* @param {Document} doc - Current owner documnet of the panel, so we know where to drop our pop-up
* @param {Function} cb - Callback function, passed new name upon rename
*/
constructor(event, playlist, client, doc, cb){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Playlist Name
*/
this.playlist = playlist
/**
* Callback Function, passed new name upon rename
*/
this.cb = cb;
//Create media popup and call async constructor when done
//unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :(
/**
* canopyUXUtils.popup() object
*/
this.popup = new canopyUXUtils.popup('/renamePlaylist', true, this.asyncConstructor.bind(this), doc);
}
/**
* Continuation of object construction, called after child popup object construction
*/
asyncConstructor(){
this.renamePrompt = this.popup.contentDiv.querySelector('#playlist-rename-popup-prompt');
this.renameButton = this.popup.contentDiv.querySelector('#playlist-rename-popup-button');
this.setupInput();
}
/**
* Defines input-related Event Handlers
*/
setupInput(){
//Setup input
this.renameButton.addEventListener('click', this.renamePlaylist.bind(this));
this.popup.popupDiv.addEventListener('keydown', this.renamePlaylist.bind(this));
}
/**
* Handles sending request to rename playlist to the server
* @param {Event} event - Event passed down from Event Listener
*/
renamePlaylist(event){
//If we clicked or hit enter while the prompt wasn't active
if(event.key == null || event.key == "Enter"){
//Tell the server to change the titles
this.client.socket.emit('renameChannelPlaylist', {
playlist: this.playlist,
name: this.renamePrompt.value
});
//if CB is a function
if(typeof this.cb == 'function'){
//Hand it back the new name
this.cb(this.renamePrompt.value);
}
//Close the popup
this.popup.closePopup();
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,239 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: panels/settingsPanel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: panels/settingsPanel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class representing the settings panel
* @extends panelObj
*/
class settingsPanel extends panelObj{
/**
* Instantiates a new Panel Object
* @param {channel} client - Parent client Management Object
* @param {Document} panelDocument - Panel Document
*/
constructor(client, panelDocument){
super(client, "Client Settings", "/panel/settings", panelDocument);
}
closer(){
}
docSwitch(){
/**
* Youtube Source Selector
*/
this.youtubeSource = this.panelDocument.querySelector("#settings-panel-youtube-source select");
/**
* Internet Archive CDN Server Input
*/
this.iaCDN = this.panelDocument.querySelector("#settings-panel-ia-server input");
/**
* Syncronization Tolerance Input
*/
this.syncTolerance = this.panelDocument.querySelector("#settings-panel-sync-tolerance input");
/**
* Livestream Syncronization Tolerance Input
*/
this.liveSyncTolerance = this.panelDocument.querySelector("#settings-panel-live-sync-tolerance input");
/**
* Syncronization Tolerance Delta
*/
this.syncDelta = this.panelDocument.querySelector("#settings-panel-sync-delta input");
/**
* Chat Width Minimum while Size-Locked to Media Aspect Ratio
*/
this.chatWidthMinimum = this.panelDocument.querySelector("#settings-panel-min-chat-width input");
this.renderSettings();
this.setupInput();
}
/**
* Defines input-related event handlers
*/
setupInput(){
this.youtubeSource.addEventListener('change', this.updateYoutubeSource.bind(this));
this.iaCDN.addEventListener('keydown', this.updateIACDN.bind(this));
this.syncTolerance.addEventListener('change', this.updateSyncTolerance.bind(this));
this.liveSyncTolerance.addEventListener('change', this.updateLiveSyncTolerance.bind(this));
this.syncDelta.addEventListener('change', this.updateSyncDelta.bind(this));
this.chatWidthMinimum.addEventListener('change', this.updateChatWidthMinimum.bind(this));
}
/**
* Renders actual user settings state into panel display
*/
renderSettings(){
this.youtubeSource.value = localStorage.getItem("ytPlayerType");
this.iaCDN.value = localStorage.getItem("IACDN");
this.syncTolerance.value = localStorage.getItem("syncTolerance");
this.liveSyncTolerance.value = localStorage.getItem("liveSyncTolerance");
this.syncDelta.value = localStorage.getItem("syncDelta");
this.chatWidthMinimum.value = localStorage.getItem("chatWidthMin");
}
/**
* Event handler for Youtube Source selector
*/
updateYoutubeSource(){
localStorage.setItem("ytPlayerType", this.youtubeSource.value);
client.processConfig("ytPlayerType", this.youtubeSource.value);
}
/**
* Event handler for Internet Archive CDN Server input
* @param {Event} event - Event handed down by event listener
*/
updateIACDN(event){
//If we hit enter
if(event.key == "Enter"){
//If we have an invalid server string
if(!(this.iaCDN.value.match(/^ia[0-9]{6}\...$/g) || this.iaCDN.value == "")){
//reset back to what was set before
this.iaCDN.value = localStorage.getItem('IACDN');
//BAIL!
return;
}
localStorage.setItem("IACDN", this.iaCDN.value);
client.processConfig("IACDN", this.iaCDN.value);
}
}
/**
* Handles Sync Tolerance Changes
*/
updateSyncTolerance(){
//If sync tolerance was set to a negative number
if(this.syncTolerance.value &lt; 0){
//Reset setting back to stored value
this.syncTolerance.value = localStorage.getItem('syncTolerance');
//BAIL!
return;
}
localStorage.setItem("syncTolerance", this.syncTolerance.value);
client.processConfig("syncTolerance", this.syncTolerance.value);
}
/**
* Handles Live Sync Tolerance Changes
*/
updateLiveSyncTolerance(){
//If sync tolerance was set to a negative number
if(this.liveSyncTolerance.value &lt; 0){
//Reset setting back to stored value
this.liveSyncTolerance.value = localStorage.getItem('liveSyncTolerance');
//BAIL!
return;
}
localStorage.setItem("liveSyncTolerance", this.liveSyncTolerance.value);
client.processConfig("liveSyncTolerance", this.liveSyncTolerance.value);
}
/**
* Handles Sync Delta Changes
*/
updateSyncDelta(){
//If sync tolerance was set to a negative number
if(this.syncDelta.value &lt; 0){
//Reset setting back to stored value
this.syncDelta.value = localStorage.getItem('syncDelta');
//BAIL!
return;
}
localStorage.setItem("syncDelta", this.syncDelta.value);
client.processConfig("syncDelta", this.syncDelta.value);
}
/**
* Handles Chat Width minimum Changes
*/
updateChatWidthMinimum(){
//If sync tolerance was set to a negative number
if(this.chatWidthMinimum.value &lt; 0 || this.chatWidthMinimum > 100){
//Reset setting back to stored value
this.syncDelta.value = localStorage.getItem('chatWidthMin');
//BAIL!
return;
}
localStorage.setItem("chatWidthMin", this.chatWidthMinimum.value);
client.processConfig("chatWidthMin", this.chatWidthMinimum.value);
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,518 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: player.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: player.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class which represents Canopy Player UX
*/
class player{
/**
* Instantiates a new Canopy Player object
* @param {channel} client - Parent client Management Object
*/
constructor (client){
/**
* Parent CLient Management Object
*/
this.client = client;
/**
* Whether or not the mouse cursor is floating over player UX
*/
this.onUI = false;
/**
* Whether or not player scrub is locked to sync signal from the server
*/
this.syncLock = true;
/**
* Player UX Stow-Away timer
*/
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
//elements
/**
* Top-Level Player Container Div
*/
this.playerDiv = document.querySelector("#media-panel-div");
/**
* Player Element Container Div
*/
this.videoContainer = document.querySelector("#media-panel-video-container")
/**
* Page Nav-Par
*/
this.navBar = document.querySelector("#navbar");
/**
* Auto-Hiding Player UI
*/
this.uiBar = document.querySelector("#media-panel-head-div");
/**
* Player Title Label
*/
this.title = document.querySelector("#media-panel-title-paragraph");
/**
* Player Show Video Icon
*/
this.showVideoIcon = document.querySelector("#chat-panel-show-video-icon");
/**
* Player Hide Video Icon
*/
this.hideVideoIcon = document.querySelector("#media-panel-div-toggle-icon");
/**
* Player Syncronization Icon
*/
this.syncIcon = document.querySelector("#media-panel-sync-icon");
/**
* Player Cinema-Mode Icon
*/
this.cinemaModeIcon = document.querySelector("#media-panel-cinema-mode-icon");
/**
* Player Filp Video Y Icon
*/
this.flipYIcon = document.querySelector("#media-panel-flip-vertical-icon")
/**
* Player Flip Video X Icon
*/
this.flipXIcon = document.querySelector("#media-panel-flip-horizontal-icon")
/**
* Player Media Reload Icon
*/
this.reloadIcon = document.querySelector("#media-panel-reload-icon");
/**
* Tolerance between timestamp from server and actual media before corrective seek for pre-recorded media
*/
this.syncTolerance = localStorage.getItem('syncTolerance');
/**
* Tolerance in livestream delay before corrective seek to live.
*
* Might seem weird to keep this here instead of the HLS handler, but remember we may want to support other livestream services in the future...
*/
this.streamSyncTolerance = localStorage.getItem('liveSyncTolerance');
/**
* Forced time to wait between sync checks, heavily decreases chance of seek-banging without reducing syncornization accuracy
*/
this.syncDelta = localStorage.getItem('syncDelta');
/**
* Current Player Volume
*/
this.volume = 1;
//run setup functions
this.setupInput();
this.defineListeners();
}
/**
* Defines Input-Related Event Listeners for the player
*/
setupInput(){
//UIBar Movement Detection
this.playerDiv.addEventListener("mousemove", this.popUI.bind(this));
this.uiBar.addEventListener("mouseenter", ()=>{this.setOnUI(true)});
this.uiBar.addEventListener("mouseleave", ()=>{this.setOnUI(false)});
//UIBar/header icons
//Don't bind these, they want an argument that isn't an event :P
this.showVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
this.hideVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
this.syncIcon.addEventListener("click", this.lockSync.bind(this));
this.cinemaModeIcon.addEventListener("click", ()=>{this.toggleCinemaMode()});
this.flipYIcon.addEventListener('click', this.flipY.bind(this));
this.flipXIcon.addEventListener('click', this.flipX.bind(this));
this.reloadIcon.addEventListener("click", this.reload.bind(this));
}
/**
* Define Network-Related Event Listeners for the player
*/
defineListeners(){
this.client.socket.on("start", this.start.bind(this));
this.client.socket.on("sync", this.sync.bind(this));
this.client.socket.on("end", this.end.bind(this));
this.client.socket.on("updateCurrentRawFile", this.updateCurrentRawFile.bind(this));
}
/**
* Handles command from server to start media
* @param {Object} data - Media Metadata from server
*/
start(data){
//If we have an active media handler
if(this.mediaHandler != null){
//End the media handler
this.mediaHandler.end();
}
//Ignore null media
if(data.media == null){
//Set null handler
this.mediaHandler = new nullHandler(client, this);
//Otherwise
}else{
//If we have a youtube video and the official embedded iframe player is selected
if(data.media.type == 'yt' &amp;&amp; localStorage.getItem("ytPlayerType") == 'embed'){
//Create a new yt handler for it
this.mediaHandler = new youtubeEmbedHandler(this.client, this, data.media);
//Sync to time stamp
this.mediaHandler.sync(data.timestamp);
//If we have an HLS Livestream
}else if(data.media.type == "livehls"){
//Create a new HLS Livestream Handler for it
this.mediaHandler = new hlsLiveStreamHandler(this.client, this, data.media);
}else if(data.media.type == 'dm'){
this.mediaHandler = new hlsDailymotionHandler(this.client, this, data.media);
//Otherwise, if we have a raw-file compatible source
}else if(data.media.type == 'ia' || data.media.type == 'raw' || data.media.type == 'yt' || data.media.type == 'dm'){
//If we're running a source from IA
if(data.media.type == 'ia'){
//Replace specified CDN with generic URL, in-case of hard reload
data.media.rawLink = data.media.rawLink.replace(/^https(.*)archive\.org(.*)items/g, "https://archive.org/download")
//If we have an IA source and a custom IA CDN Server set
if(data.media.type == 'ia' &amp;&amp; localStorage.getItem("IACDN") != ""){
//Generate and set new link
data.media.rawLink = data.media.rawLink.replace("https://archive.org/download", `https://${localStorage.getItem("IACDN")}.archive.org/0/items`);
}
}
//Create a new raw file handler for it
this.mediaHandler = new rawFileHandler(client, this, data.media);
//Sync to time stamp
this.mediaHandler.sync(data.timestamp);
}else{
this.mediaHandler = new nullHandler(client, this);
}
}
//Lock synchronization since everyone starts at 0, and update the UI
this.lockSync();
//Re-size to aspect since video may now be a different size
this.client.chatBox.resizeAspect();
//Sync off of starter time stamp
this.mediaHandler.sync(data.timestamp);
}
/**
* Handles synchronization command from server
* @param {Object} data - Syncrhonization Data from Server
*/
sync(data){
if(this.mediaHandler != null){
//Get timestamp
const timestamp = data.timestamp;
//Get difference between server and local timestamp
const difference = Math.abs(timestamp - this.mediaHandler.getTimestamp());
//Check if timestamp evenly devides into sync delta, effectively only checking for sync every X seconds
//Check if the difference between timestamps is larger than the sync tolerance
//Lastly, check to make sure we have sync lock
if(timestamp % this.syncDelta == 0 &amp;&amp; difference > this.syncTolerance &amp;&amp; this.syncLock){
//If we need to sync, then sync the video!
this.mediaHandler.sync(timestamp);
}
//Collect last timestamp
this.mediaHandler.lastTimestamp = timestamp;
}
}
/**
* Reloads the media player
*/
reload(){
if(this.mediaHandler != null){
this.mediaHandler.reload();
}
}
/**
* Destroys and Re-Creates media handler
*/
hardReload(){
if(this.mediaHandler != null){
//Re-create data we'd get from server
const data = {
media: this.mediaHandler.nowPlaying,
timestamp: this.mediaHandler.getTimestamp()
}
//End current media handler
this.end();
console.log(data);
//Restart from last media handlers
this.start(data);
}
}
/**
* Handles End-Media Commands from the Server
*/
end(){
//Call the media handler finisher
this.mediaHandler.end();
//Replace it with a null handler
this.mediaHandler = new nullHandler(client, this);
//Re-lock sync since we're probably gonna start new media soon anywho, and we need to update the UI anywho
this.lockSync();
}
/**
* Handles Raw-File Metadata Updates from the Server
* @param {Object} data - Updadated Raw-File link from Server
*/
updateCurrentRawFile(data){
//typecheck the media handler to see if we really need to do any of this shit, if not...
if(this.mediaHandler.type == 'ytEmbed'){
//Ignore it
return;
}
//Grab current item from media handler
const currentItem = this.mediaHandler.nowPlaying;
//Update raw link
currentItem.rawLink = data.file;
//Re-start the item
this.start({media: currentItem});
}
/**
* Locks player seek to synced timestamp from the server
*/
lockSync(){
//Enable syncing
this.syncLock = true;
if(this.mediaHandler != null &amp;&amp; this.mediaHandler.type != null){
//Light up the sync icon to show that we're actively synchronized
this.syncIcon.classList.add('positive');
//Sync to last timestamp
this.mediaHandler.sync();
//Play
this.mediaHandler.play();
}else{
//Unlight the sync icon since there is nothing to sync
this.syncIcon.classList.remove('positive');
}
}
/**
* Un-locks player seek to synced timestamp from the server
*/
unlockSync(){
//Unlight the sync icon since we're no longer actively synced
this.syncIcon.classList.remove('positive');
//Disable syncing
this.syncLock = false;
}
/**
* Flips the video horizontally
*/
flipX(){
//I'm lazy
const transform = this.videoContainer.style.transform;
//If we we're specifically set to un-mirrored
if(transform.match("scaleX(1)")){
//mirror it
this.videoContainer.style.transfrom = transform.replace('scaleX(1)', 'scaleX(-1)');
//If we're currently mirrored
}else if(transform.match("scaleX(-1)")){
//Un-mirror
this.videoContainer.style.transfrom = transform.replace('scaleX(-1)', 'scaleX(1)');
//Otherwise, if it's untouched
}else{
//Mirror it
this.videoContainer.style.transform += 'scaleX(-1)';
}
}
/**
* Flips the video vertically
*/
flipY(){
//I'm lazy
const transform = this.videoContainer.style.transform;
//If we we're specifically set to un-mirrored
if(transform.match("scaleY(1)")){
//mirror it
this.videoContainer.style.transfrom = transform.replace('scaleY(1)', 'scaleY(-1)');
//If we're currently mirrored
}else if(transform.match("scaleY(-1)")){
//Un-mirror
this.videoContainer.style.transfrom = transform.replace('scaleY(-1)', 'scaleY(1)');
//Otherwise, if it's untouched
}else{
//Mirror it
this.videoContainer.style.transform += 'scaleY(-1)';
}
}
/**
* Displays UI after player-related input
* @param {Event} event - Event passed through by event handler
*/
popUI(event){
this.toggleUI(true);
clearTimeout(this.uiTimer);
if(!this.onUI){
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
}
}
/**
* Toggles UI-Bar on or off
* @param {Boolean} show - Whether or not to show the UI-Bar. Defaults to toggle if left unspecified.
*/
toggleUI(show = this.uiBar.style.display == "none"){
this.uiBar.style.display = show ? "flex" : "none";
}
/**
* Toggles video on or off
* @param {Boolean} show - Whether or not to show the video player. Defaults to toggle if left unspecified
*/
toggleVideo(show = !this.playerDiv.checkVisibility()){
if(show){
this.playerDiv.style.display = "flex";
this.showVideoIcon.style.display = "none";
}else{
this.playerDiv.style.display = "none";
this.showVideoIcon.style.display = "flex";
}
//Tell chatbox to handle this shit
this.client.chatBox.handleVideoToggle(show);
}
/**
* Toggles Cinema Mode on or off
* @param {Boolean} cinema - Whether or not to enter Cinema Mode. Defaults to toggle if left unspecified
*/
toggleCinemaMode(cinema = this.navBar.checkVisibility()){
localStorage.setItem("cinemaMode", cinema);
if(cinema){
this.navBar.style.display = "none";
}else{
this.navBar.style.display = "flex";
}
//Resize the video if we're aspect locked
this.client.chatBox.resizeAspect();
}
/**
* Informs the class when the user's mouse curosr enters and leaves the UI area
* @param {Boolean} onUI - Whether or not onUI should be toggled true
*/
setOnUI(onUI){
this.onUI = onUI;
this.popUI();
}
/**
* Calculates ratio of current media object
* @returns {Number} Current media aspect ratio as a single floating point number
*/
getRatio(){
//If we have no media handler
if(this.mediaHandler == null){
//Return a 4/3 aspect to get a decent chat size
return 4/3;
}else{
return this.mediaHandler.getRatio();
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,884 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: renamePopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: renamePopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>renamePopup<span class="signature">(event, playlist, client, doc, cb)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing pop-up dialogue to rename a playlist</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="renamePopup"><span class="type-signature"></span>new renamePopup<span class="signature">(event, playlist, client, doc, cb)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Rename Pop-up
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>playlist</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Playlist name</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
<tr>
<td class="name"><code>cb</code></td>
<td class="type">
<span class="param-type">function</span>
</td>
<td class="description last">Callback function, passed new name upon rename</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line866">line 866</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="cb"><span class="type-signature"></span>cb<span class="type-signature"></span></h4>
<div class="description">
Callback Function, passed new name upon rename
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line889">line 889</a>
</li></ul></dd>
</dl>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line879">line 879</a>
</li></ul></dd>
</dl>
<h4 class="name" id="playlist"><span class="type-signature"></span>playlist<span class="type-signature"></span></h4>
<div class="description">
Playlist Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line884">line 884</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line896">line 896</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line902">line 902</a>
</li></ul></dd>
</dl>
<h4 class="name" id="renamePlaylist"><span class="type-signature"></span>renamePlaylist<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to rename playlist to the server
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line922">line 922</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_playlistManager.js.html">panels/queuePanel/playlistManager.js</a>, <a href="panels_queuePanel_playlistManager.js.html#line912">line 912</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:58 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,969 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: schedulePopup</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: schedulePopup</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>schedulePopup<span class="signature">(event, client, url, title, cb, doc)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing pop-up dialogue to schedule a piece of media</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="schedulePopup"><span class="type-signature"></span>new schedulePopup<span class="signature">(event, client, url, title, cb, doc)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new schedule media Pop-up
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="description last">Parent Client Management Object</td>
</tr>
<tr>
<td class="name"><code>url</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">URL/link to media to queue</td>
</tr>
<tr>
<td class="name"><code>title</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Title of media to queue</td>
</tr>
<tr>
<td class="name"><code>cb</code></td>
<td class="type">
<span class="param-type">function</span>
</td>
<td class="description last">Callback function, passed upon pop-up creation</td>
</tr>
<tr>
<td class="name"><code>doc</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="description last">Current owner documnet of the panel, so we know where to drop our pop-up</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1421">line 1421</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="cb"><span class="type-signature"></span>cb<span class="type-signature"></span></h4>
<div class="description">
Callback function, passed upon pop-up creation
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1450">line 1450</a>
</li></ul></dd>
</dl>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management Object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1435">line 1435</a>
</li></ul></dd>
</dl>
<h4 class="name" id="popup"><span class="type-signature"></span>popup<span class="type-signature"></span></h4>
<div class="description">
canopyUXUtils.popup() object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1457">line 1457</a>
</li></ul></dd>
</dl>
<h4 class="name" id="title"><span class="type-signature"></span>title<span class="type-signature"></span></h4>
<div class="description">
Title of media to queue
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1445">line 1445</a>
</li></ul></dd>
</dl>
<h4 class="name" id="url"><span class="type-signature"></span>url<span class="type-signature"></span></h4>
<div class="description">
URL/Link to media to queue
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1440">line 1440</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="asyncConstructor"><span class="type-signature"></span>asyncConstructor<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Continuation of object construction, called after child popup object construction
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1463">line 1463</a>
</li></ul></dd>
</dl>
<h4 class="name" id="schedule"><span class="type-signature"></span>schedule<span class="signature">(event)</span><span class="type-signature"></span></h4>
<div class="description">
Handles sending request to schedule item to the queue
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>event</code></td>
<td class="type">
<span class="param-type">Event</span>
</td>
<td class="description last">Event passed down from Event Listener</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1498">line 1498</a>
</li></ul></dd>
</dl>
<h4 class="name" id="setupInput"><span class="type-signature"></span>setupInput<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Defines input-related Event Handlers
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="panels_queuePanel_queuePanel.js.html">panels/queuePanel/queuePanel.js</a>, <a href="panels_queuePanel_queuePanel.js.html#line1488">line 1488</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:58 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,25 +0,0 @@
/*global document */
(() => {
const source = document.getElementsByClassName('prettyprint source linenums');
let i = 0;
let lineNumber = 0;
let lineId;
let lines;
let totalLines;
let anchorHash;
if (source && source[0]) {
anchorHash = document.location.hash.substring(1);
lines = source[0].getElementsByTagName('li');
totalLines = lines.length;
for (; i < totalLines; i++) {
lineNumber++;
lineId = `line${lineNumber}`;
lines[i].id = lineId;
if (lineId === anchorHash) {
lines[i].className += ' selected';
}
}
}
})();

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,2 +0,0 @@
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);

View file

@ -1,28 +0,0 @@
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();

File diff suppressed because it is too large Load diff

View file

@ -1,358 +0,0 @@
@font-face {
font-family: 'Open Sans';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Regular-webfont.eot');
src:
local('Open Sans'),
local('OpenSans'),
url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
}
@font-face {
font-family: 'Open Sans Light';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Light-webfont.eot');
src:
local('Open Sans Light'),
local('OpenSans Light'),
url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
}
html
{
overflow: auto;
background-color: #fff;
font-size: 14px;
}
body
{
font-family: 'Open Sans', sans-serif;
line-height: 1.5;
color: #4d4e53;
background-color: white;
}
a, a:visited, a:active {
color: #0095dd;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header
{
display: block;
padding: 0px 4px;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0;
}
#main {
float: left;
width: 70%;
}
article dl {
margin-bottom: 40px;
}
article img {
max-width: 100%;
}
section
{
display: block;
background-color: #fff;
padding: 12px 24px;
border-bottom: 1px solid #ccc;
margin-right: 30px;
}
.variation {
display: none;
}
.signature-attributes {
font-size: 60%;
color: #aaa;
font-style: italic;
font-weight: lighter;
}
nav
{
display: block;
float: right;
margin-top: 28px;
width: 30%;
box-sizing: border-box;
border-left: 1px solid #ccc;
padding-left: 16px;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a, nav ul a:visited, nav ul a:active {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
line-height: 18px;
color: #4D4E53;
}
nav h3 {
margin-top: 12px;
}
nav li {
margin-top: 6px;
}
footer {
display: block;
padding: 6px;
margin-top: 12px;
font-style: italic;
font-size: 90%;
}
h1, h2, h3, h4 {
font-weight: 200;
margin: 0;
}
h1
{
font-family: 'Open Sans Light', sans-serif;
font-size: 48px;
letter-spacing: -2px;
margin: 12px 24px 20px;
}
h2, h3.subsection-title
{
font-size: 30px;
font-weight: 700;
letter-spacing: -1px;
margin-bottom: 12px;
}
h3
{
font-size: 24px;
letter-spacing: -0.5px;
margin-bottom: 12px;
}
h4
{
font-size: 18px;
letter-spacing: -0.33px;
margin-bottom: 12px;
color: #4d4e53;
}
h5, .container-overview .subsection-title
{
font-size: 120%;
font-weight: bold;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6
{
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
table
{
border-spacing: 0;
border: 0;
border-collapse: collapse;
}
td, th
{
border: 1px solid #ddd;
margin: 0px;
text-align: left;
vertical-align: top;
padding: 4px 6px;
display: table-cell;
}
thead tr
{
background-color: #ddd;
font-weight: bold;
}
th { border-right: 1px solid #aaa; }
tr > th:last-child { border-right: 1px solid #ddd; }
.ancestors, .attribs { color: #999; }
.ancestors a, .attribs a
{
color: #999 !important;
text-decoration: none;
}
.clear
{
clear: both;
}
.important
{
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px;
}
.type-signature {
color: #aaa;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.details { margin-top: 14px; border-left: 2px solid #DDD; }
.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
.details dd { margin-left: 70px; }
.details ul { margin: 0; }
.details ul { list-style-type: none; }
.details li { margin-left: 30px; padding-top: 6px; }
.details pre.prettyprint { margin: 0 }
.details .object-value { padding-top: 0; }
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption
{
font-style: italic;
font-size: 107%;
margin: 0;
}
.source
{
border: 1px solid #ddd;
width: 80%;
overflow: auto;
}
.prettyprint.source {
width: inherit;
}
.source code
{
font-size: 100%;
line-height: 18px;
display: block;
padding: 4px 12px;
margin: 0;
background-color: #fff;
color: #4D4E53;
}
.prettyprint code span.line
{
display: inline-block;
}
.prettyprint.linenums
{
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol
{
padding-left: 0;
}
.prettyprint.linenums li
{
border-left: 3px #ddd solid;
}
.prettyprint.linenums li.selected,
.prettyprint.linenums li.selected *
{
background-color: lightyellow;
}
.prettyprint.linenums li *
{
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td.description > p:first-child,
.props td.description > p:first-child
{
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child,
.props td.description > p:last-child
{
margin-bottom: 0;
padding-bottom: 0;
}
.disabled {
color: #454545;
}

View file

@ -1,111 +0,0 @@
/* JSDoc prettify.js theme */
/* plain text */
.pln {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* string content */
.str {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a keyword */
.kwd {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a comment */
.com {
font-weight: normal;
font-style: italic;
}
/* a type name */
.typ {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a literal value */
.lit {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* punctuation */
.pun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp open bracket */
.opn {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp close bracket */
.clo {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a markup tag name */
.tag {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute name */
.atn {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute value */
.atv {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a declaration */
.dec {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a variable name */
.var {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a function name */
.fun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}

View file

@ -1,132 +0,0 @@
/* Tomorrow Theme */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* Pretty printing styles. Used with prettify.js. */
/* SPAN elements with the classes below are added by prettyprint. */
/* plain text */
.pln {
color: #4d4d4c; }
@media screen {
/* string content */
.str {
color: #718c00; }
/* a keyword */
.kwd {
color: #8959a8; }
/* a comment */
.com {
color: #8e908c; }
/* a type name */
.typ {
color: #4271ae; }
/* a literal value */
.lit {
color: #f5871f; }
/* punctuation */
.pun {
color: #4d4d4c; }
/* lisp open bracket */
.opn {
color: #4d4d4c; }
/* lisp close bracket */
.clo {
color: #4d4d4c; }
/* a markup tag name */
.tag {
color: #c82829; }
/* a markup attribute name */
.atn {
color: #f5871f; }
/* a markup attribute value */
.atv {
color: #3e999f; }
/* a declaration */
.dec {
color: #f5871f; }
/* a variable name */
.var {
color: #c82829; }
/* a function name */
.fun {
color: #4271ae; } }
/* Use higher contrast and text-weight for printable form. */
@media print, projection {
.str {
color: #060; }
.kwd {
color: #006;
font-weight: bold; }
.com {
color: #600;
font-style: italic; }
.typ {
color: #404;
font-weight: bold; }
.lit {
color: #044; }
.pun, .opn, .clo {
color: #440; }
.tag {
color: #006;
font-weight: bold; }
.atn {
color: #404; }
.atv {
color: #060; } }
/* Style */
/*
pre.prettyprint {
background: white;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #ccc;
padding: 10px; }
*/
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0; }
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */ }
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */ }

File diff suppressed because it is too large Load diff

View file

@ -1,263 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: userlist.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: userlist.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing logic behind userlist UX
*/
class userList{
/**
* Instantiates a new userList object
* @param {channel} client - Parent client mgmt object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client
/**
* Click Dragger object for handling userlist resizes
*/
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div", true, this.client.chatBox.clickDragger);
/**
* Userlist color array (Maps to css classes)
*/
this.userColors = [
"userlist-color0",
"userlist-color1",
"userlist-color2",
"userlist-color3",
"userlist-color4",
"userlist-color5",
"userlist-color6"];
/**
* Map of usernames to assigned username color
*/
this.colorMap = new Map();
/**
* users div
*/
this.userDiv = document.querySelector("#chat-panel-users-div");
/**
* userlist div
*/
this.userList = document.querySelector("#chat-panel-users-list-div");
/**
* user count label
*/
this.userCount = document.querySelector("#chat-panel-user-count");
/**
* userlist toggle button
*/
this.toggleIcon = document.querySelector("#chat-panel-users-toggle");
//Call setup functions
this.setupInput();
this.defineListeners();
}
/**
* Defines input-related event listeners
*/
setupInput(){
this.toggleIcon.addEventListener("click", ()=>{this.toggleUI()});
this.userCount.addEventListener("click", ()=>{this.toggleUI()});
}
/**
* Defines network-related event listeners
*/
defineListeners(){
this.client.socket.on('userList', (data) => {
this.updateList(data);
});
this.client.socket.on("disconnect", () => {
this.updateList([]);
})
}
/**
* Updates UX after user list change
* @param {Array} list - Userlist data from server
*/
updateList(list){
//Clear list and set user count
this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`;
this.userList.innerHTML = null;
//create a new map
var newMap = new Map();
//for each user
list.forEach((user) => {
//randomly pick a color
var color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
//if this user was in the previous colormap
if(this.colorMap.get(user.user) != null){
//Override with previous color
color = this.colorMap.get(user.user);
}
newMap.set(user.user, color);
this.renderUser(user, color);
});
this.colorMap = newMap;
//Make sure we're not cutting the ux off
this.clickDragger.fixCutoff();
}
/**
* Renders out a single username to the userlist
* @param {String} user - Username to render
* @param {String} flair - Flair to render as
*/
renderUser(user, flair){
//Create user span
var userSpan = document.createElement('span');
userSpan.classList.add('chat-panel-users', 'user-entry');
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("user-list-high-level","high-level");
highLevel.textContent = `${user.highLevel}`;
//Create nameplate
var userEntry = document.createElement('p');
userEntry.innerText = user.user;
userEntry.id = `user-entry-${user.user}`;
//Override color with flair
if(user.flair != "classic"){
flair = `flair-${user.flair}`;
}
//Add classes to classList
userEntry.classList.add("chat-panel-users","user-entry",flair);
//Add high-level username to nameplate
userSpan.appendChild(highLevel);
userSpan.appendChild(userEntry);
//Setup profile tooltip
userSpan.addEventListener('mouseenter',(event)=>{utils.ux.displayTooltip(event, `profile?user=${user.user}`, true, null, true);});
//Setup profile context menu
userSpan.addEventListener('click', renderContextMenu.bind(this));
userSpan.addEventListener('contextmenu', renderContextMenu.bind(this));
this.userList.appendChild(userSpan);
function renderContextMenu(event){
//Setup menu map
let menuMap = new Map([
["Profile", ()=>{this.client.cPanel.setActivePanel(new panelObj(this.client, `${user.user}`, `/panel/profile?user=${user.user}`))}],
["Mention", ()=>{this.client.chatBox.catChat(`${user.user} `)}],
["Toke With", ()=>{this.client.chatBox.tokeWith(user.user)}],
]);
if(user.user != "Tokebot" &amp;&amp; user.user != this.client.user.user){
if(this.client.user.permMap.chan.get("kickUser")){
menuMap.set("Kick", ()=>{this.client.chatBox.commandPreprocessor.preprocess(`!kick ${user.user}`)});
}
if(this.client.user.permMap.chan.get("banUser")){
menuMap.set("Channel Ban", ()=>{new chanBanUserPopup(this.client.channelName, user.user);});
}
if(this.client.user.permMap.site.get("banUser")){
menuMap.set("Site Ban", ()=>{new banUserPopup(user.user);});
}
}
//Display the menu
utils.ux.displayContextMenu(event, user.user, menuMap);
}
}
toggleUI(show = !this.userDiv.checkVisibility()){
localStorage.setItem("userlistHidden", !show);
if(show){
this.userDiv.style.display = "flex";
this.toggleIcon.classList.replace("bi-caret-left-fill","bi-caret-down-fill");
this.clickDragger.fixCutoff();
}else{
this.userDiv.style.display = "none";
this.toggleIcon.classList.replace("bi-caret-down-fill","bi-caret-left-fill");
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="addURLPopup.html">addURLPopup</a></li><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="newPlaylistPopup.html">newPlaylistPopup</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:57 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,230 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/activeChannel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/activeChannel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//local imports
const connectedUser = require('./connectedUser');
const chatBuffer = require('./chatBuffer');
const queue = require('./media/queue');
const channelModel = require('../../schemas/channel/channelSchema');
const playlistHandler = require('./media/playlistHandler')
/**
* Class representing a single active channel
*/
class activeChannel{
/**
* Instantiates an activeChannel object
* @param {channelManager} server - Parent Server Object
* @param {Mongoose.Document} chanDB - chanDB to rehydrate buffer from
*/
constructor(server, chanDB){
/**
* Parent Server Object
*/
this.server = server;
/**
* Current Channel Name
*/
this.name = chanDB.name;
/**
* List of channel-wide toke commands
*/
this.tokeCommands = chanDB.tokeCommands;
/**
* List of connected users
*/
this.userList = new Map();
/**
* Child Queue Object
*/
this.queue = new queue(server, chanDB, this);
/**
* Child Playlist Handler Object
*/
this.playlistHandler = new playlistHandler(server, this);
/**
* Child Chat Buffer Object
*/
this.chatBuffer = new chatBuffer(server, chanDB, this);
}
/**
* Handles server-side initialization for new connections to the channel
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
* @param {Socket} socket - Requesting Socket
*/
async handleConnection(userDB, chanDB, socket){
//get current user object from the userlist
var userObj = this.userList.get(userDB.user);
//get channel rank for current user
const chanRank = await chanDB.getChannelRankByUserDoc(userDB);
//If user is already connected
if(userObj){
//Add this socket on to the userobject
userObj.sockets.push(socket.id);
//If the user is joining the channel
}else{
//Grab flair
await userDB.populate('flair');
//Set user object
userObj = new connectedUser(userDB, chanRank, this, socket);
}
//Set user entry in userlist
this.userList.set(userDB.user, userObj);
//if everything looks good, admit the connection to the channel
socket.join(socket.chan);
//Define per-channel event listeners
this.queue.defineListeners(socket);
this.playlistHandler.defineListeners(socket);
//Hand off the connection initiation to it's user object
await userObj.handleConnection(userDB, chanDB, socket)
//Send out the userlist
this.broadcastUserList(socket.chan);
}
/**
* Handles server-side initialization for disconnecting from the channel
* @param {Socket} socket - Requesting Socket
*/
handleDisconnect(socket){
//If we have more than one active connection
if(this.userList.get(socket.user.user).sockets.length > 1){
//temporarily store userObj
var userObj = this.userList.get(socket.user.user);
//Filter out disconnecting socket from socket list, and set as current socket list for user
userObj.sockets = userObj.sockets.filter((id) => {
return id != socket.id;
});
//Update the userlist
this.userList.set(socket.user.user, userObj);
}else{
//If this is the last connection for this user, remove them from the userlist
this.userList.delete(socket.user.user);
}
//and send out the filtered list
this.broadcastUserList(socket.chan);
}
/**
* Broadcasts user list to all users
*/
broadcastUserList(){
//Create a userlist object with the tokebot user pre-loaded
var userList = [{
user: "Tokebot",
flair: "classic",
highLevel: "∞",
}];
this.userList.forEach((userObj, user) => {
userList.push({
user: user,
flair: userObj.flair,
highLevel: userObj.highLevel
});
});
this.server.io.in(this.name).emit("userList", userList);
}
/**
* Broadcasts channel emote list to connected users
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
*/
async broadcastChanEmotes(chanDB){
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.name});
}
//Get emote list from channel document
const emoteList = chanDB.getEmotes();
//Broadcast that sumbitch
this.server.io.in(this.name).emit('chanEmotes', emoteList);
}
}
module.exports = activeChannel;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,361 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/channelManager.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/channelManager.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//Config
const config = require('../../../config.json');
//Local Imports
const channelModel = require('../../schemas/channel/channelSchema');
const emoteModel = require('../../schemas/emoteSchema');
const {userModel} = require('../../schemas/user/userSchema');
const userBanModel = require('../../schemas/user/userBanSchema');
const loggerUtils = require('../../utils/loggerUtils');
const csrfUtils = require('../../utils/csrfUtils');
const activeChannel = require('./activeChannel');
const chatHandler = require('./chatHandler');
/**
* Class containing global server-side channel connection management logic
*/
class channelManager{
/**
* Instantiates object containing global server-side channel conection management logic
* @param {Server} io - Socket.io server instanced passed down from server.js
*/
constructor(io){
/**
* Socket.io server instance passed down from server.js
*/
this.io = io;
/**
* Map containing all active channels running on the server
*/
this.activeChannels = new Map;
/**
* Global Chat Handler Object
*/
this.chatHandler = new chatHandler(this);
//Handle connections from socket.io
io.on("connection", this.handleConnection.bind(this) );
}
/**
* Handles global server-side initialization for new connections to any channel
* @param {Socket} socket - Requesting Socket
*/
async handleConnection(socket){
try{
//ensure unbanned ip and valid CSRF token
if(!(await this.validateSocket(socket))){
socket.disconnect();
return;
}
//Prevent logged out connections and authenticate socket
if(socket.request.session.user != null){
//Authenticate socket
const userDB = await this.authSocket(socket);
//Get the active channel based on the socket
var {activeChan, chanDB} = await this.getActiveChan(socket);
//Check for chan ban
const ban = await chanDB.checkBanByUserDoc(userDB);
if(ban != null){
//Toss out banned user's
if(ban.expirationDays &lt; 0){
socket.emit("kick", {type: "kicked", reason: "You have been permanently banned from this channel!"});
}else{
socket.emit("kick", {type: "kicked", reason: `You have been temporarily banned from this channel, and will be unbanned in ${ban.getDaysUntilExpiration()} day(s)!`});
}
socket.disconnect();
return;
}
//Define listeners for inter-channel classes
this.defineListeners(socket);
this.chatHandler.defineListeners(socket);
//Hand off the connection to it's given active channel object
//Lil' hacky to pass chanDB like that, but why double up on DB calls?
activeChan.handleConnection(userDB, chanDB, socket);
}else{
//Toss out anon's
socket.emit("kick", {type: "disconnected", reason: "You must log-in to join this channel!"});
socket.disconnect();
return;
}
}catch(err){
//Flip a table if something fucks up
return loggerUtils.socketCriticalExceptionHandler(socket, err);
}
}
/**
* Global server-side validation logic for new connections to any channel
* @param {Socket} socket - Requesting Socket
* @returns {Boolean} true on success
*/
async validateSocket(socket){
//If we're proxied use passthrough IP
const ip = config.proxied ? socket.handshake.headers['x-forwarded-for'] : socket.handshake.address;
//Look for ban by IP
const ipBanDB = await userBanModel.checkBanByIP(ip);
//If this ip is randy bobandy
if(ipBanDB != null){
//Make the number a little prettier despite the lack of precision since we're not doing calculations here :P
const expiration = ipBanDB.getDaysUntilExpiration() &lt; 1 ? 0 : ipBanDB.getDaysUntilExpiration();
//If the ban is permanent
if(ipBanDB.permanent){
//tell it to fuck off
socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been permanently banned. Your cleartext IP has been saved to the database. Any associated accounts will be nuked in ${expiration} day(s).`});
//Otherwise
}else{
//tell it to fuck off
socket.emit("kick", {type: "kicked", reason: `The IP address you are trying to connect from has been temporarily banned. Your cleartext IP has been saved to the database until the ban expires in ${expiration} day(s).`});
}
return false;
}
//Check for Cross-Site Request Forgery
if(!csrfUtils.isRequestValid(socket.request)){
socket.emit("kick", {type: "disconnected", reason: "Invalid CSRF Token!"});
return false;
}
return true;
}
/**
* Global server-side authorization logic for new connections to any channel
* @param {Socket} socket - Requesting Socket
* @returns {Mongoose.Document} - Authorized User Document upon success
*/
async authSocket(socket){
//Find the user in the Database since the session won't store enough data to fulfill our needs :P
const userDB = await userModel.findOne({user: socket.request.session.user.user});
if(userDB == null){
throw loggerUtils.exceptionSmith("User not found!", "unauthorized");
}
//Set socket user and channel values
socket.user = {
id: userDB.id,
user: userDB.user,
};
return userDB;
}
/**
* Gets active channel from a given socket
* @param {Socket} socket - Socket to check
* @returns {Object} Object containing users active channel name and channel document object
*/
async getActiveChan(socket){
socket.chan = socket.handshake.headers.referer.split('/c/')[1].split('/')[0];
const chanDB = (await channelModel.findOne({name: socket.chan}));
//Check if channel exists
if(chanDB == null){
throw loggerUtils.exceptionSmith("Channel not found", "validation");
}
//Check if current channel is active
var activeChan = this.activeChannels.get(socket.chan);
if(!activeChan){
//If not, make it so
activeChan = new activeChannel(this, chanDB);
this.activeChannels.set(socket.chan, activeChan);
}
//Return whatever the active channel is (new or old)
return {activeChan, chanDB};
}
/**
* Define Global Server-Side socket event listeners
* @param {Socket} socket - Socket to check
*/
defineListeners(socket){
//Socket Listeners
socket.conn.on("close", (reason) => {this.handleDisconnect(socket, reason)});
}
/**
* Global server-side logic for handling disconncted sockets
* @param {Socket} socket - Socket to check
* @param {String} reason - Reason for disconnection
*/
handleDisconnect(socket, reason){
var activeChan = this.activeChannels.get(socket.chan);
activeChan.handleDisconnect(socket, reason);
}
/**
* Pulls user information by socket
* @param {Socket} socket - Socket to check
* @return returns related user info
*/
getSocketInfo(socket){
const channel = this.activeChannels.get(socket.chan);
return channel.userList.get(socket.user.user);
}
/**
* Pulls user information by socket
* @param {Socket} socket - Socket to check
* @return returns related user info
*/
getConnectedChannels(socket){
//Create a list to hold connected channels
var chanList = [];
//For each channel
this.activeChannels.forEach((channel) => {
//Check and see if the user is connected
const foundUser = channel.userList.get(socket.user.user);
//If we found a user and this channel hasn't been added to the list
if(foundUser){
chanList.push(channel);
}
});
//return the channels this user is connected to
return chanList;
}
/**
* Iterates through connections by a given username, and runs them through a given callback function/method
* @param {String} user - Username to crawl connections against
* @param {Function} cb - Callback function to run active connections of a given user against
*/
crawlConnections(user, cb){
//For each channel
this.activeChannels.forEach((channel) => {
//Check and see if the user is connected
const foundUser = channel.userList.get(user);
//If we found a user and this channel hasn't been added to the list
if(foundUser){
cb(foundUser);
}
});
}
/**
* Iterates through connections by a given username, and runs them through a given callback function/method
* @param {String} user - Username to crawl connections against
* @param {Function} cb - Callback function to run active connections of a given user against
*/
getConnections(user){
//Create a list to store our connections
var connections = [];
//crawl through connections
//this.crawlConnections(user,(foundUser)=>{connections.push(foundUser)});
this.crawlConnections(user,(foundUser)=>{connections.push(foundUser)});
//return connects
return connections;
}
/**
* Kicks a user from all channels by username
* @param {String} user - Username to kick from the server
* @param {String} reason - Reason for kick
*/
kickConnections(user, reason){
//crawl through connections and kick user
this.crawlConnections(user,(foundUser)=>{foundUser.disconnect(reason)});
}
/**
* Broadcast global emote list
*/
async broadcastSiteEmotes(){
//Get emote list from DB
const emoteList = await emoteModel.getEmotes();
//Broadcast that sumbitch
this.io.sockets.emit('siteEmotes', emoteList);
}
}
module.exports = channelManager;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,113 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/chat.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/chat.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Class representing a single chat message
*/
class chat{
/**
* Instantiates a chat message object
* @param {connectedUser} user - User who sent the message
* @param {String} flair - Flair ID String for the flair used to send the message
* @param {Number} highLevel - Number representing current high level
* @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers
* @param {String} type - Message Type Identifier, used for client-side processing.
* @param {Array} links - Array of URLs/Links included in the message.
*/
constructor(user, flair, highLevel, msg, type, links){
/**
* User who sent the message
*/
this.user = user;
/**
* Flair ID String for the flair used to send the message
*/
this.flair = flair;
/**
* Number representing current high level
*/
this.highLevel = highLevel;
/**
* COntents of the message, with links replaced with numbered file-seperator marks
*/
this.msg = msg;
/**
* Message Type Identifier, used for client-side processing.
*/
this.type = type;
/**
* Array of URLs/Links included in the message.
*/
this.links = links;
}
}
module.exports = chat;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,209 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/chatBuffer.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/chatBuffer.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
const config = require('../../../config.json');
const channelModel = require('../../schemas/channel/channelSchema');
/**
* Class representing a stored chat buffer
*/
class chatBuffer{
/**
* Instantiates a new chat buffer for a given channel
* @param {channelManager} server - Parent Server Object
* @param {Mongoose.Document} chanDB - chanDB to rehydrate buffer from
* @param {activeChannel} channel - Parent Channel Object
*/
constructor(server, chanDB, channel){
/**
* Parent Server Object
*/
this.server = server;
/**
* Parent CHannel Object
*/
this.channel = channel;
//If we have no chanDB.chatBuffer
if(chanDB == null || chanDB.chatBuffer == null){
/**
* RAM-Based buffer containing array of previous chats
*/
this.buffer = [];
//Otherwise
}else{
/**
* RAM-Based buffer containing array of previous chats
*/
this.buffer = chanDB.chatBuffer;
}
/**
* Inactivity Timer, goes off after x seconds of chat inactivity
*/
this.inactivityTimer = null;
/**
* Inactivity Timer Delay
*/
this.inactivityDelay = 10;
/**
* Goes off after x minutes of solid chatroom activity (no inactivityTimer call in x minutes)
*/
this.busyTimer = null;
/**
* Busy Timer Delay
*/
this.busyDelay = 5;
}
/**
* Adds a given chat to the chat buffer in RAM and sets any appropriate timers for DB transactions
* @param {chat} chat - Chat object to commit to buffer
*/
push(chat){
//push chat into RAM buffer
this.buffer.push(chat);
//clear existing inactivity timer
clearTimeout(this.inactivityTimer);
//reset inactivity timer
this.inactivityTimer = setTimeout(this.handleInactivity.bind(this), 1000 * this.inactivityDelay);
//If busy timer is unset
if(this.busyTimer == null){
this.busyTimer = setTimeout(this.handleBusyRoom.bind(this), 1000 * 60 * this.busyDelay);
}
}
/**
* Removes the oldest item from the chat buffer
*
* Was originally created in-case we needed to trigger timing functions
*
* Left here since it seems like good form anywho, since this would be a private, or at least protected member in another language
*/
shift(){
this.buffer.shift();
}
/**
* Called after 10 seconds of chat room inactivity
*/
handleInactivity(){
this.saveDB(`${this.inactivityDelay} seconds of inactivity.`);
}
/**
* Called after 5 minutes of solid activity
*/
handleBusyRoom(){
this.saveDB(`${this.busyDelay} minutes of activity.`);
}
/**
* Saves RAM-Based buffer to Channel Document in DB
* @param {String} reason - Reason for DB save, formatted as 'x minutes/seconds of in/activity', used for logging purposes
* @param {Mongoose.Document} chanDB - Channel Doc to work with, can be left empty for method to auto-find through channel name.
*/
async saveDB(reason, chanDB){
//clear existing timers
clearTimeout(this.inactivityTimer);
clearTimeout(this.busyTimer);
this.inactivityTimer = null;
this.busyTimer = null;
//if the server is in screamy boi mode
if(config.verbose){
//This should eventually be replaced by a per-channel logging feature that provides access to chan admins via web front-end
console.log(`Saving chat buffer to channel ${this.channel.name} after ${reason}.`);
}
//If we wheren't handed a channel
if(chanDB == null){
//Now that everything is clean, we can take our time with the DB :P
chanDB = await channelModel.findOne({name:this.channel.name});
}
//If we couldn't find the channel
if(chanDB == null){
//FUCK
throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while saving chat buffer!`, "chat");
}
//Set chan doc buffer to RAM buffer
chanDB.chatBuffer = this.buffer;
//save chan doc to DB.
await chanDB.save();
}
}
module.exports = chatBuffer;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,393 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/chatHandler.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/chatHandler.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//NPM imports
const validator = require('validator')
//local imports
const commandPreprocessor = require('./commandPreprocessor');
const loggerUtils = require('../../utils/loggerUtils');
const linkUtils = require('../../utils/linkUtils');
const emoteValidator = require('../../validators/emoteValidator');
const chat = require('./chat');
const {userModel} = require('../../schemas/user/userSchema');
/**
* Class containing global server-side chat relay logic
*/
class chatHandler{
/**
* Instantiates a chatHandler object
* @param {channelManager} server - Parent Server Object
*/
constructor(server){
/**
* Parent Server Object
*/
this.server = server;
/**
* Child Command Pre-Processor Object
*/
this.commandPreprocessor = new commandPreprocessor(server, this)
/**
* Max chat buffer message count
*/
this.chatBufferSize = 50;
}
/**
* Defines global server-side chat relay event listeners
* @param {Socket} socket - Requesting Socket
*/
defineListeners(socket){
socket.on("chatMessage", (data) => {this.handleChat(socket, data)});
socket.on("setFlair", (data) => {this.setFlair(socket, data)});
socket.on("setHighLevel", (data) => {this.setHighLevel(socket, data)});
socket.on("addPersonalEmote", (data) => {this.addPersonalEmote(socket, data)});
socket.on("deletePersonalEmote", (data) => {this.deletePersonalEmote(socket, data)});
}
/**
* Handles incoming chat messages from client connections
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
handleChat(socket, data){
this.commandPreprocessor.preprocess(socket, data);
}
/**
* Handles incoming client request to change flair
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
async setFlair(socket, data){
var userDB = await userModel.findOne({user: socket.user.user});
if(userDB){
try{
//We can take this data raw since our schema checks it against existing flairs, and mongoose sanatizes queries
const flairDB = await userDB.setFlair(data.flair);
//Crawl through users active connections
this.server.crawlConnections(socket.user.user, (conn)=>{
//Update flair
conn.updateFlair(flairDB.name);
});
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
}
}
}
/**
* Handles incoming client request to change high level
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
async setHighLevel(socket, data){
var userDB = await userModel.findOne({user: socket.user.user});
if(userDB){
try{
//Floor input to an integer and set high level
userDB.highLevel = Math.floor(data.highLevel);
//Save user DB Document
await userDB.save();
//GetConnects across all channels
const connections = this.server.getConnections(socket.user.user);
//For each connection
connections.forEach((conn) => {
conn.updateHighLevel(userDB.highLevel);
});
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
}
}
}
/**
* Handles incoming client request to add a personal emote
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
async addPersonalEmote(socket, data){
//Sanatize and Validate input
const name = emoteValidator.manualName(data.name);
const link = emoteValidator.manualLink(data.link);
//If we received good input
if(link &amp;&amp; name){
//Generate marked link object
var emote = await linkUtils.markLink(link);
//If the link we have is an image or video
if(emote.type == 'image' || emote.type == 'video'){
//Get user document from DB
const userDB = await userModel.findOne({user: socket.user.user})
//if we have a user in the DB
if(userDB != null){
//Convert marked link into emote object with 1 ez step for only $19.95
emote.name = name;
//add emote to user document emotes list
userDB.emotes.push(emote);
//Save user doc
await userDB.save();
}
}
}
}
/**
* Handles incoming client request to delete a personal emote
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
async deletePersonalEmote(socket, data){
//Get user doc from DB based on socket
const userDB = await userModel.findOne({user: socket.user.user});
//if we found a user
if(userDB != null){
await userDB.deleteEmote(data.name);
}
}
//Base chat functions
/**
* Creates a new chatObject and relays the resulting message to the given channel
* @param {String} user - Originating user
* @param {String} flair - Flair ID to mark chat with
* @param {Number} highLevel - High Level to mark chat with
* @param {String} msg - Message Text Content
* @param {String} type - Message Type, used for client-side chat post-processing.
* @param {String} chan - Channel to broadcast message within
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayChat(user, flair, highLevel, msg, type = 'chat', chan, links){
this.relayChatObject(chan, new chat(user, flair, highLevel, msg, type, links));
}
/**
* Relays an existing chat object to a channel
* @param {String} chan - Channel to broadcast message within
* @param {chat} chat - Chat Object representing the message to broadcast to the given channel
*/
relayChatObject(chan, chat){
//Send out chat
this.server.io.in(chan).emit("chatMessage", chat);
const channel = this.server.activeChannels.get(chan);
//If chat buffer length is over mandated size
if(channel.chatBuffer.buffer.length >= this.chatBufferSize){
//Take out oldest chat
channel.chatBuffer.shift();
}
//Add buffer to chat
channel.chatBuffer.push(chat);
}
/**
* Creates a new chatObject and relays the resulting message to the given socket
* @param {Socket} socket - Socket we're sending a message to (sounds menacing, huh?)
* @param {String} user - Originating user
* @param {String} flair - Flair ID to mark chat with
* @param {Number} highLevel - High Level to mark chat with
* @param {String} msg - Message Text Content
* @param {String} type - Message Type, used for client-side chat post-processing.
* @param {String} chan - Channel to broadcast message within
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayPrivateChat(socket, user, flair, highLevel, msg, type, links){
this.relayPrivateChatObject(socket , new chat(user, flair, highLevel, msg, type, links));
}
/**
* Handles incoming client request to delete a personal emote
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
relayPrivateChatObject(socket, chat){
socket.emit("chatMessage", chat);
}
/**
* Creates a new chatObject and relays the resulting message to the entire server
* @param {String} user - Originating user
* @param {String} flair - Flair ID to mark chat with
* @param {Number} highLevel - High Level to mark chat with
* @param {String} msg - Message Text Content
* @param {String} type - Message Type, used for client-side chat post-processing.
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayGlobalChat(user, flair, highLevel, msg, type = 'chat', links){
this.relayGlobalChatObject(new chat(user, flair, highLevel, msg, type, links));
}
/**
* Relays an existing chat object to the entire server
* @param {chat} chat - Chat Object representing the message to broadcast throughout the server
*/
relayGlobalChatObject(chat){
this.server.io.emit("chatMessage", chat);
}
//User Chat Functions
/**
* Relays a chat message from a user to the rest of the channel based on socket
* @param {Socket} socket - Socket we're receiving the request from
* @param {String} msg - Message Text Content
* @param {String} type - Message Type, used for client-side chat post-processing.
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayUserChat(socket, msg, type, links){
const user = this.server.getSocketInfo(socket);
this.relayChat(user.user, user.flair, user.highLevel, msg, type, socket.chan, links);
}
//Toke Chat Functions
/**
* Broadcasts toke callout to the server
* @param {String} msg - Message Text Content
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayTokeCallout(msg, links){
this.relayGlobalChat("Tokebot", "", '∞', msg, "toke", links);
}
/**
* Broadcasts toke callout to the server
* @param {Socket} socket - Socket we're sending the whisper to
* @param {String} msg - Message Text Content
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayTokeWhisper(socket, msg, links){
this.relayPrivateChat(socket, "Tokebot", "", '∞', msg, "tokewhisper", links);
}
/**
* Broadcasts toke whisper to the server
* @param {String} msg - Message Text Content
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayGlobalTokeWhisper(msg, links){
this.relayGlobalChat("Tokebot", "", '∞', msg, "tokewhisper", links);
}
//Announcement Functions
/**
* Broadcasts announcement to the server
* @param {String} msg - Message Text Content
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayServerAnnouncement(msg, links){
this.relayGlobalChat("Server", "", '∞', msg, "announcement", links);
}
/**
* Broadcasts announcement to a given channel
* @param {String} msg - Message Text Content
* @param {Array} links - Array of URLs/Links to hand to the client-side chat post-processor to inject into the final message.
*/
relayChannelAnnouncement(chan, msg, links){
const activeChan = this.server.activeChannels.get(chan);
//If channel isn't null
if(activeChan != null){
this.relayChat("Channel", "", '∞', msg, "announcement", chan, links);
}
}
//Misc Functions
/**
* Clears chat for a given channel, targets specified user or entire channel if none found/specified.
* @param {String} user - User chats to clear
* @param {String} chan - Channel to broadcast message within
*/
clearChat(chan, user){
const activeChan = this.server.activeChannels.get(chan);
//If channel isn't null
if(activeChan != null){
const target = activeChan.userList.get(user);
//If no user was entered OR the user was found
if(user == null || target != null){
this.server.io.in(chan).emit("clearChat", {user});
}
}
}
}
module.exports = chatHandler;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,497 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/commandPreprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/commandPreprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!
//Local Imports
const tokebot = require('./tokebot');
const linkUtils = require('../../utils/linkUtils');
const permissionModel = require('../../schemas/permissionSchema');
const channelModel = require('../../schemas/channel/channelSchema');
/**
* Class containing global server-side chat/command pre-processing logic
*/
class commandPreprocessor{
/**
* Instantiates a commandPreprocessor object
* @param {channelManager} server - Parent Server Object
* @param {chatHandler} chatHandler - Parent Chat Handler Object
*/
constructor(server, chatHandler){
/**
* Parent Server Object
*/
this.server = server;
/**
* Parent Chat Handler Object
*/
this.chatHandler = chatHandler;
/**
* Child Command Processor Object
*/
this.commandProcessor = new commandProcessor(server, chatHandler);
/**
* Child Tokebot Object
*/
this.tokebot = new tokebot(server, chatHandler);
}
/**
* Ingests a command/chat request from Chat Handler and pre-processes and processes it accordingly
* @param {Socket} socket - Socket we're receiving the request from
* @param {Object} data - Event payload
*/
async preprocess(socket, data){
//Set command object
const commandObj = {
socket,
sendFlag: true,
rawData: data,
chatType: 'chat'
}
//If we don't pass sanatization/validation turn this car around
if(!this.sanatizeCommand(commandObj)){
return;
}
//split the command
this.splitCommand(commandObj);
//Process the command
await this.processServerCommand(commandObj);
//If we're going to relay this command as a message, continue on to chat processing
if(commandObj.sendFlag){
//Prep the message
await this.prepMessage(commandObj);
//Send the chat
this.sendChat(commandObj);
}
}
/**
* Sanatizes and Validates a single user chat message/command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} false if Command/Message is too long to send
*/
sanatizeCommand(commandObj){
//Trim and Sanatize for XSS
commandObj.command = validator.trim(validator.escape(commandObj.rawData.msg));
//Return whether or not the shit was long enough
return (validator.isLength(commandObj.rawData.msg, {min: 1, max: 255}));
}
/**
* Splits raw chat/command data into seperate arrays, one by word-borders and words surrounded by word-borders
* These arrays are used to handle further command/chat processing
* @param {Object} commandObj - Object representing a single given command/chat request
*/
splitCommand(commandObj){
//Split string by words
commandObj.commandArray = commandObj.command.split(/\b/g);//Split by word-borders
commandObj.argumentArray = commandObj.command.match(/\b\w+\b/g);//Match by words surrounded by borders
}
/**
* Uses the server's Command Processor object to process the chat/command request.
* @param {Object} commandObj - Object representing a single given command/chat request
*/
async processServerCommand(commandObj){
//If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat)
if(commandObj.rawData.msg[0] == '!'){
//if it isn't just an exclimation point, and we have a real command
if(commandObj.argumentArray != null){
//If the command processor knows what to do with whatever the fuck the user sent us
if(this.commandProcessor[commandObj.argumentArray[0].toLowerCase()] != null){
//Process the command and use the return value to set the sendflag (true if command valid)
commandObj.sendFlag = await this.commandProcessor[commandObj.argumentArray[0].toLowerCase()](commandObj, this);
}else{
//Process as toke command if we didnt get a match from the standard server-side command processor
commandObj.sendFlag = await this.tokebot.tokeProcessor(commandObj);
}
}
}
}
/**
* Iterates through links in message and marks them by link type for later use by client-side post-processing
* @param {Object} commandObj - Object representing a single given command/chat request
*/
async markLinks(commandObj){
//Setup the links array
commandObj.links = [];
//For each link sent from the client
//this.rawData.links.forEach((link) => {
for (const link of commandObj.rawData.links){
//Add a marked link object to our links array
commandObj.links.push(await linkUtils.markLink(link));
}
}
/**
* Re-creates message string from processed Command Array
* @param {Object} commandObj - Object representing a single given command/chat request
*/
async prepMessage(commandObj){
//Create message from commandArray
commandObj.message = commandObj.commandArray.join('').trimStart();
//Validate links and mark them by embed type
await this.markLinks(commandObj);
}
/**
* Relays chat to channel via parent Chat Handler object
* @param {Object} commandObj - Object representing a single given command/chat request
*/
sendChat(commandObj){
//FUCKIN' SEND IT!
this.chatHandler.relayUserChat(commandObj.socket, commandObj.message, commandObj.chatType, commandObj.links);
}
}
/**
* Class representing global server-side chat/command processing logic
*/
class commandProcessor{
/**
* Instantiates a commandProcessor object
* @param {channelManager} server - Parent Server Object
* @param {chatHandler} chatHandler - Parent Chat Handler Object
*/
constructor(server, chatHandler){
this.server = server;
this.chatHandler = chatHandler;
}
//Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods
/**
* Command Processor method to handle the '!whisper' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
whisper(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a whisper
commandObj.chatType = 'whisper';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!spoiler' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
spoiler(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'spoiler';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!strikethrough' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
strikethrough(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'strikethrough';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!bold' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
bold(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'bold';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!italics' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag
*/
italics(commandObj){
//splice out our command
commandObj.commandArray.splice(0,2);
//Mark out the current message as a spoiler
commandObj.chatType = 'italics';
//Make sure to throw the send flag
return true
}
/**
* Command Processor method to handle the '!announce' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async announce(commandObj, preprocessor){
//Get the current channel from the database
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(chanDB != null &amp;&amp; await chanDB.permCheck(commandObj.socket.user, 'announce')){
//splice out our command
commandObj.commandArray.splice(0,2);
//Prep the message using pre-processor functions chat-handling
await preprocessor.prepMessage(commandObj);
//send it
this.chatHandler.relayChannelAnnouncement(commandObj.socket.chan, commandObj.message, commandObj.links);
//throw send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!serverannounce' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async serverannounce(commandObj, preprocessor){
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await permissionModel.permCheck(commandObj.socket.user, 'announce')){
//splice out our command
commandObj.commandArray.splice(0,2);
//Prep the message using pre-processor functions for chat-handling
await preprocessor.prepMessage(commandObj);
//send it
this.chatHandler.relayServerAnnouncement(commandObj.message, commandObj.links);
//disble send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!resettoke' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async resettoke(commandObj, preprocessor){
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await permissionModel.permCheck(commandObj.socket.user, 'resetToke')){
//Acknowledge command
this.chatHandler.relayTokeWhisper(commandObj.socket, 'Toke cooldown reset.');
//Tell tokebot to reset the toke
preprocessor.tokebot.resetToke();
//disable send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!clear' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async clear(commandObj){
//Get the current channel from the database
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await chanDB.permCheck(commandObj.socket.user, 'clearChat')){
//Send off the command
this.chatHandler.clearChat(commandObj.socket.chan, commandObj.argumentArray[1]);
//disable send flag
return false;
}
//throw send flag
return true;
}
/**
* Command Processor method to handle the '!kick' command
* @param {Object} commandObj - Object representing a single given command/chat request
* @returns {Boolean} True to enable send flag on un-authorized call to shame the user
*/
async kick(commandObj){
//Get the current channel from the database
const chanDB = await channelModel.findOne({name: commandObj.socket.chan});
//Check if the user has permission, and publicly shame them if they don't (lmao)
if(await chanDB.permCheck(commandObj.socket.user, 'kickUser')){
//Get username from argument array
const username = commandObj.argumentArray[1];
//Get channel
const channel = this.server.activeChannels.get(commandObj.socket.chan);
//get initiator and target user objects
const initiator = channel.userList.get(commandObj.socket.user.user);
const target = channel.userList.get(username);
//get initiator and target override abilities
const override = await permissionModel.overrideCheck(commandObj.socket.user, 'kickUser');
const targetOverride = await permissionModel.overrideCheck(target, 'kickUser');
//If there is no user
if(target == null){
//silently drop the command
return false;
}
//If the user is capable of overriding this permission based on site permissions
if(override || targetOverride){
//If the site rank is equal
if(permissionModel.rankToNum(initiator.rank) == permissionModel.rankToNum(target.rank)){
//compare chan rank
if(permissionModel.rankToNum(initiator.chanRank) &lt;= permissionModel.rankToNum(target.chanRank)){
//shame the person running it
return true;
}
//otherwise
}else{
//compare site rank
if(permissionModel.rankToNum(initiator.rank) &lt;= permissionModel.rankToNum(target.rank)){
//shame the person running it
return true;
}
}
}else{
//If the target has a higher chan rank than the initiator
if(permissionModel.rankToNum(initiator.chanRank) &lt;= permissionModel.rankToNum(target.chanRank)){
//shame the person running it
return true;
}
}
//Splice out kick
commandObj.commandArray.splice(0,4)
//Get collect reason
var reason = commandObj.commandArray.join('');
//If no reason was given
if(reason == ''){
//Fill in a generic reason
reason = "You have been kicked from the channel!";
}
//Kick the user
target.disconnect(reason);
//throw send flag
return false;
}
//throw send flag
return true;
}
}
module.exports = commandPreprocessor;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,375 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/connectedUser.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/connectedUser.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//local imports
const config = require('../../../config.json');
const channelModel = require('../../schemas/channel/channelSchema');
const permissionModel = require('../../schemas/permissionSchema');
const flairModel = require('../../schemas/flairSchema');
const emoteModel = require('../../schemas/emoteSchema');
const { userModel } = require('../../schemas/user/userSchema');
/**
* Class representing a single user connected to a channel
*/
class connectedUser{
/**
* Instantiates a connectedUser object
* @param {Mongoose.Document} userDB - User document to re-hydrate user from
* @param {PemissionModel.chanRank} chanRank - Enum representing user channel rank
* @param {String} - Channel the user is connecting to
* @param {Socket} socket - Socket associated with the users connection
*/
constructor(userDB, chanRank, channel, socket){
/**
* User ID Number
*/
this.id = userDB.id;
/**
* User Name
*/
this.user = userDB.user;
/**
* User Rank
*/
this.rank = userDB.rank;
/**
* User High-Level
*/
this.highLevel = userDB.highLevel;
//Check to make sure users flair entry from DB is good
if(userDB.flair != null){
//Set flair from DB
/**
* User Flair
*/
this.flair = userDB.flair.name;
//Otherwise
}else{
//Gracefully default to classic
/**
* User Flair
*/
this.flair = 'classic';
}
/**
* User Channel-Rank
*/
this.chanRank = chanRank;
/**
* Connected Channel
*/
this.channel = channel;
/**
* List of active sockets to current channel
*/
this.sockets = [socket.id];
}
/**
* Handles server-side initialization for new connections from a specific user
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
* @param {Socket} socket - Requesting Socket
*/
async handleConnection(userDB, chanDB, socket){
//send metadata to client
this.sendClientMetadata();
//Send out emotes
this.sendSiteEmotes();
this.sendChanEmotes(chanDB);
this.sendPersonalEmotes(userDB);
//Send out used tokes
this.sendUsedTokes(userDB);
//Send out the currently playing item
this.channel.queue.sendMedia(socket);
//If we're proxied
if(config.proxied){
//Tattoo hashed IP address from reverse proxy to user account for seven days
await userDB.tattooIPRecord(socket.handshake.headers['x-forwarded-for']);
}else{
//Tattoo hashed IP address to user account for seven days
await userDB.tattooIPRecord(socket.handshake.address);
}
}
/**
* Iterates through all known connections for a given user, running them through a supplied callback function
* @param {Function} cb - Callback to call against found sockets for a given user
*/
socketCrawl(cb){
//Crawl through user's sockets (lol)
this.sockets.forEach((sockid) => {
//get socket based on ID
const socket = this.channel.server.io.sockets.sockets.get(sockid);
//Callback with socket
cb(socket);
});
}
/**
* Emits an event to all known sockets for a given user
*
* My brain keeps going back to using dynamic per-user namespaces for this
* but everytime i look into it I come to the conclusion that it's a bad idea, then I toy with making chans namespaces
* and using per-user channels for this, but what of gold or mod-only features? or games?
* No matter what it'd probably end up hacky, as namespaces where meant for splitting app logic not user comms (like rooms).
* at the end of the day there has to be some penance for decent multi-session handling on-top of a library that doesn't do it.
* Having to crawl through these sockets is that. Because the other ways seem more gross somehow.
* @param {String} eventName - Event name to emit to client sockets
* @param {Object} data - Data to emit to client sockets
*/
emit(eventName, data){
this.socketCrawl((socket)=>{
//Ensure our socket is initialized
if(socket != null){
socket.emit(eventName, data);
}
});
}
/**
* Disconnects all sockets for a given user
* @param {String} reason - Reason for being disconnected
* @param {String} type - Disconnection Type
*/
disconnect(reason, type = "Disconnected"){
this.emit("kick",{type, reason});
this.socketCrawl((socket)=>{socket.disconnect()});
}
//This is the big first push upon connection
//It should only fire once, so things that only need to be sent once can be slapped into here
/**
* Sends glut of required initial metadata to the client upon a new connection
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
*/
async sendClientMetadata(userDB, chanDB){
//Get flairList from DB and setup flairList array
const flairListDB = await flairModel.find({});
var flairList = [];
//if we wherent handed a user document
if(userDB == null){
//Pull it based on user name
userDB = await userModel.findOne({user: this.user});
}
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//If our perm map is un-initiated
//can't set this in constructor easily since it's asyncornous
//need to wait for it to complete before sending this off, but shouldnt re-do the wait for later connections
if(this.permMap == null){
//Grab perm map
this.permMap = await chanDB.getPermMapByUserDoc(userDB);
}
//Setup our userObj
const userObj = {
id: this.id,
user: this.user,
rank: this.rank,
chanRank: this.chanRank,
highLevel: this.highLevel,
permMap: {
site: Array.from(this.permMap.site),
chan: Array.from(this.permMap.chan),
},
flair: this.flair,
}
//For each flair listed in the Database
flairListDB.forEach((flair)=>{
//Check if the user has permission to use the current flair
if(permissionModel.rankToNum(flair.rank) &lt;= permissionModel.rankToNum(this.rank)){
//If so push a light version of the flair object into our final flair list
flairList.push({
name: flair.name,
displayName: flair.displayName
});
}
});
//Get schedule as a temporary array
const queue = await this.channel.queue.prepQueue(chanDB);
//Get schedule lock status
const queueLock = this.channel.queue.locked;
//Get chat buffer
const chatBuffer = this.channel.chatBuffer.buffer;
//Send off the metadata to our user's clients
this.emit("clientMetadata", {user: userObj, flairList, queue, queueLock, chatBuffer});
}
/**
* Send copy of site emotes to the user
*/
async sendSiteEmotes(){
//Get emote list from DB
const emoteList = await emoteModel.getEmotes();
//Send it off to the user
this.emit('siteEmotes', emoteList);
}
/**
* Send copy of channel emotes to the user
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
*/
async sendChanEmotes(chanDB){
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//Pull emotes from channel
const emoteList = chanDB.getEmotes();
//Send it off to the user
this.emit('chanEmotes', emoteList);
}
/**
* Send copy of channel emotes to the user
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
*/
async sendPersonalEmotes(userDB){
//if we wherent handed a user document
if(userDB == null){
//Pull it based on user name
userDB = await userModel.findOne({user: this.user});
}
//Pull emotes from channel
const emoteList = userDB.getEmotes();
//Send it off to the user
this.emit('personalEmotes', emoteList);
}
/**
* Send copy of channel emotes to the user
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
*/
async sendUsedTokes(userDB){
//if we wherent handed a user document
if(userDB == null){
//Pull it based on user name
userDB = await userModel.findOne({user: this.user});
}
//Create array of used toks from toke map and send it out to the user
this.emit('usedTokes',{
tokes: Array.from(userDB.tokes.keys())
});
}
/**
* Set flair for a given user and broadcast update to clients
* @param {String} flair - Flair string to update user's flair to
*/
updateFlair(flair){
this.flair = flair;
this.channel.broadcastUserList();
this.sendClientMetadata();
}
/**
* Set high level for a given user and broadcast update to clients
* @param {Number} highLevel - Number to update user's high-level to
*/
updateHighLevel(highLevel){
this.highLevel = highLevel;
//TODO: show high-level in userlist
this.channel.broadcastUserList();
this.sendClientMetadata();
}
}
module.exports = connectedUser;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,119 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/media/media.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/media/media.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
/**
* Object representing a piece of media
*/
class media{
/**
* Creates a new media object from scraped information
* @param {String} title - Chosen title of media
* @param {String} fileName - Original filename/title of media provided by source
* @param {String} url - Original URL to file
* @param {String} id - Video ID from source (IE: youtube watch code/archive.org file path)
* @param {String} type - Original video source
* @param {Number} duration - Length of media in seconds
* @param {String} rawLink - URL to raw file copy of media, not applicable to all sources
*/
constructor(title, fileName, url, id, type, duration, rawLink = url){
/**
* Chosen title of media
*/
this.title = title;
/**
* Original filename/title of media provided by source
*/
this.fileName = fileName
/**
* Original URL to file
*/
this.url = url;
/**
* Video ID from source (IE: youtube watch code/archive.org file path)
*/
this.id = id;
/**
* Original video source
*/
this.type = type;
/**
* Length of media in seconds
*/
this.duration = duration;
/**
* URL to raw file copy of media, not applicable to all sources
*/
this.rawLink = rawLink;
}
}
module.exports = media;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,189 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/media/queuedMedia.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/media/queuedMedia.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//Local Imports
const media = require('./media');
/**
* Class extending media which represents a queued piece of media
* @extends media
*/
class queuedMedia extends media{
/**
* Creates a new queued media object
* @param {Number} startTime - JS Epoch representing start time
* @param {Number} startTimeStamp - Media start time stamp in seconds (relative to duration)
* @param {Number} earlyEnd - Media end timestamp in seconds (relative to duration)
* @param {String} uuid - Media object's unique identifier
*/
constructor(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp = 0, earlyEnd, uuid){
//Call derived constructor
super(title, fileName, url, id, type, duration, rawLink);
/**
* JS Epoch (millis) representing start time
*/
this.startTime = startTime;
/**
* Media start time stamp in seconds (relative to duration)
*/
this.startTimeStamp = startTimeStamp;
/**
* Media ent timestamp in seconds (relative to duration)
*/
this.earlyEnd = earlyEnd;
/**
* Media status type
*/
this.status = 'queued';
//If we have a null uuid (can't use default argument because of 'this')
if(uuid == null){
//Generate id unique to this specific entry of this specific file within this specific channel's queue
//That way even if we have six copies of the same video queued, we can still uniquely idenitify each instance
this.genUUID();
}else{
/**
* Media object's unique identifier
*/
this.uuid = uuid;
}
}
//statics
/**
* Creates a queuedMedia object from a media object
* @param {media} media - Media object to queue
* @param {Number} startTime - Start time formatted as a JS Epoch
* @param {Number} startTimeStamp - Start time stamp in seconds
* @returns {queuedMedia} queuedMedia object created from given media object
*/
static fromMedia(media, startTime, startTimeStamp){
//Create and return queuedMedia object from given media object and arguments
return new this(
media.title,
media.fileName,
media.url,
media.id,
media.type,
media.duration,
media.rawLink,
startTime,
startTimeStamp);
}
/**
* Converts array of media objects into array of queuedMedia objects
* @param {Array} mediaList - Array of media objects to queue
* @param {Number} start - Start time formatted as JS Epoch
* @returns Array of converted queued media objects
*/
static fromMediaArray(mediaList, start){
//Queued Media List
const queuedMediaList = [];
//Start Time Offset
let startOffset = 0;
for(let media of mediaList){
//Convert mediaObj to queuedMedia and push to the back of the list
queuedMediaList.push(this.fromMedia(media, start + startOffset, 0));
//Set start offset to end of the current item
startOffset += (media.duration * 1000) + 5;
}
return queuedMediaList;
}
//methods
/**
* Generates new unique identifier for queued media
*/
genUUID(){
this.uuid = crypto.randomUUID();
}
/**
* return the end time of a given queuedMedia object
* @param {boolean} fullTime - Overrides early ends
* @returns end time of given queuedMedia object
*/
getEndTime(fullTime = false){
//If we have an early ending
if(this.earlyEnd == null || fullTime){
//Calculate our ending
return this.startTime + ((this.duration - this.startTimeStamp) * 1000);
}else{
//Return our early end
return this.startTime + (this.earlyEnd * 1000);
}
}
}
module.exports = queuedMedia;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -1,308 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: app/channel/tokebot.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: app/channel/tokebot.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*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 &lt;https://www.gnu.org/licenses/>.*/
//Local Imports
const tokeCommandModel = require('../../schemas/tokebot/tokeCommandSchema');
const {userModel} = require('../../schemas/user/userSchema');
const statSchema = require('../../schemas/statSchema');
/**
* Class containing global server-side tokebot logic
*/
class tokebot{
/**
* Instantiates a tokebot object
* @param {channelManager} server - Parent Server Object
* @param {chatHandler} chatHandler - Parent Chat Handler Object
*/
constructor(server, chatHandler){
/**
* Parent Server Object
*/
this.server = server;
/**
* Parent Chat Handler
*/
this.chatHandler = chatHandler;
/**
* Toke Timer
*/
this.tokeTimer = null;
/**
* Cooldown Timer
*/
this.cooldownTimer = null;
/**
* Toke time
*/
this.tokeTime = 60;
/**
* Cooldown Time
*/
this.cooldownTime = 120;
/**
* Toke Counter
*/
this.tokeCounter = 0;
/**
* Cooldown Counter
*/
this.cooldownCounter = 0;
/**
* List of current tokers
*/
this.tokers = new Map();
//Load in toke commands from the DB
this.refreshCommands();
}
/**
* Reloads toke commands from DB into RAM-based toke command store
*/
async refreshCommands(){
//Pull Command Strings from DB
this.tokeCommands = await tokeCommandModel.getCommandStrings();
}
/**
* Processes toke commands from Command Pre-Processor
* @param {Object} commandObj - Object representing a single given command/chat request, passed down from the Command Pre-Processor
* @returns {Boolean} True if the toke is an invalid toke command (tells Command Pre-Processor to send command as chat)
*/
tokeProcessor(commandObj){
//Check for site-wide toke commands
if(this.tokeCommands.indexOf(commandObj.argumentArray[0].toLowerCase()) != -1){
//Seems lame to set a bool in an if statement but this would've made a really ugly turinary
var foundToke = true;
}else if(commandObj.argumentArray[0].toLowerCase() == 'r'){
//Find the users active channel
const activeChan = this.server.activeChannels.get(commandObj.socket.chan);
//Combile site-wide and channel tokes into one list
const tokeList = this.tokeCommands.concat(activeChan.tokeCommands);
//Pick a random number between 0 and one less than the number of tokes
const foundIndex = Math.round(Math.random() * (tokeList.length - 1));
//Set override command argument 0 w/ the found toke
commandObj.argumentArray[0] = tokeList[foundIndex];
//throw toke flag
var foundToke = true;
}else{
//Find the users active channel
const activeChan = this.server.activeChannels.get(commandObj.socket.chan);
//Check if they're using a channel-only toke
//This should be safe to do without a null check but someone prove me wrong lmao
var foundToke = (activeChan.tokeCommands.indexOf(commandObj.argumentArray[0].toLowerCase()) != -1);
}
//If we found a toke
if(foundToke){
//If there is no active toke or cooldown (new toke)
if(this.tokeTimer == null &amp;&amp; this.cooldownTimer == null){
//Call-out toke start
this.chatHandler.relayTokeCallout(`A group toke has been started by ${commandObj.socket.user.user} from #${commandObj.socket.chan}! We'll be taking a toke in 60 seconds - join in by posting !${commandObj.argumentArray[0]}`);
//Set a full minute on our toke timer
this.tokeCounter = this.tokeTime;
//Add the toking user to the tokers map
this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase());
//kick-off the count-down
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000)
//If the tokeTimer is popping but the cooldownTimer has fucked off (a toke is in progress)
}else if(this.cooldownTimer == null){
//look for user in tokers map
const foundToker = this.tokers.get(commandObj.socket.user.user);
//if the user has not yet joined the toke
if(foundToker == null){
//Call-out toke join
this.chatHandler.relayTokeCallout(`${commandObj.socket.user.user} has joined the toke from #${commandObj.socket.chan}! Post !${commandObj.argumentArray[0]} to take part!`);
//Add the toking user to the tokers map
this.tokers.set(commandObj.socket.user.user, commandObj.argumentArray[0].toLowerCase());
//If the user is already in the toke
}else{
//Tell them to fuck off
this.chatHandler.relayTokeWhisper(commandObj.socket, "You're already taking part in this toke!");
}
//Otherwise (there isn't a toke timer, but there is a cooldown timer. AKA: we're in cooldown)
}else{
//if the cooldownTimer exists (we're cooling down the toke)
this.chatHandler.relayTokeWhisper(commandObj.socket, `Please wait ${this.cooldownCounter} seconds before starting a new group toke.`);
}
//Toke command found, and there isn't any extra text, don't send as chat (re-create fore.st tokebot behaviour)
return (commandObj.command != `!${commandObj.argumentArray[0]}` &amp;&amp; commandObj.command != '!r');
}else{
//No toke found, send it down the line, because shaming the user is funny
return true;
}
}
/**
* Called each second during the toke. Handles decrementing the timer variable, and countdown end logic.
*/
countdown(){
//If we're in the last three seconds
if(this.tokeCounter &lt;= 3 &amp;&amp; this.tokeCounter > 0){
//Callout the last three seconds
this.chatHandler.relayTokeCallout(`${this.tokeCounter}...`);
//if the toke is over
}else if(this.tokeCounter &lt; 0){
//if we had multiple tokers
if(this.tokers.size > 1){
//call out the toke
this.chatHandler.relayTokeCallout(`Take a toke ${Array.from(this.tokers.keys()).join(', ')}! ${this.tokers.size} tokers!`);
//if we only had one toker
}else{
//call out the solo toke
this.chatHandler.relayTokeCallout(`Take a toke ${Array.from(this.tokers.keys())[0]}.`);
}
//Asynchronously tattoo the toke into the users documents within the database so that tokebot doesn't have to wait or worry about DB transactions
userModel.tattooToke(this.tokers);
//Do the same for the global stat schema
statSchema.tattooToke(this.tokers);
//Set the toke cooldown
this.cooldownCounter = this.cooldownTime;
this.cooldownTimer = setTimeout(this.cooldown.bind(this), 1000);
//Empty out the tokers array
this.tokers = new Map;
//Null out our timer
this.tokeTimer = null;
//return the function before it can continue
return;
}
//Decrement toke time
this.tokeCounter--;
//try again in another second
this.tokeTimer = setTimeout(this.countdown.bind(this), 1000)
}
/**
* This method seems to be a vestage from a bygone era. We should remove it after documenting shit.
* I would now, but I don't want to break shit in a comment-only commit.
*/
async asyncFinisher(){
//Grab a copy of the tokers map before it gets cleared out
const tokers = this.tokers;
//we need to wait for this so we don't send used tokes pre-maturely
await userModel.tattooToke(tokers);
}
/**
* Runs every second for 60 seconds after a toke
*/
cooldown(){
//If the cooldown timer isn't over
if(this.cooldownCounter > 0){
//Decrement toke time
this.cooldownCounter--;
//try again in another second
this.cooldownTimer = setTimeout(this.cooldown.bind(this), 1000);
//If the cooldown is over
}else{
//Null out the cooldown timer
this.cooldownTimer = null;
}
}
/**
* Resets toke cooldowns early upon authorized request
*/
resetToke(){
//Set cooldown to 0
this.cooldownCounter = 0;
//Null out the timer
this.cooldownTimer = null;
}
}
module.exports = tokebot;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,714 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: chat</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: chat</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>chat<span class="signature">(user, flair, highLevel, msg, type, links)</span><span class="type-signature"></span></h2>
<div class="class-description">Class representing a single chat message</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="chat"><span class="type-signature"></span>new chat<span class="signature">(user, flair, highLevel, msg, type, links)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a chat message object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>user</code></td>
<td class="type">
<span class="param-type"><a href="connectedUser.html">connectedUser</a></span>
</td>
<td class="description last">User who sent the message</td>
</tr>
<tr>
<td class="name"><code>flair</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Flair ID String for the flair used to send the message</td>
</tr>
<tr>
<td class="name"><code>highLevel</code></td>
<td class="type">
<span class="param-type">Number</span>
</td>
<td class="description last">Number representing current high level</td>
</tr>
<tr>
<td class="name"><code>msg</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Contents of the message, with links replaced with numbered file-seperator markers</td>
</tr>
<tr>
<td class="name"><code>type</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last">Message Type Identifier, used for client-side processing.</td>
</tr>
<tr>
<td class="name"><code>links</code></td>
<td class="type">
<span class="param-type">Array</span>
</td>
<td class="description last">Array of URLs/Links included in the message.</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line20">line 20</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="flair"><span class="type-signature"></span>flair<span class="type-signature"></span></h4>
<div class="description">
Flair ID String for the flair used to send the message
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line39">line 39</a>
</li></ul></dd>
</dl>
<h4 class="name" id="highLevel"><span class="type-signature"></span>highLevel<span class="type-signature"></span></h4>
<div class="description">
Number representing current high level
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line44">line 44</a>
</li></ul></dd>
</dl>
<h4 class="name" id="links"><span class="type-signature"></span>links<span class="type-signature"></span></h4>
<div class="description">
Array of URLs/Links included in the message.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line59">line 59</a>
</li></ul></dd>
</dl>
<h4 class="name" id="msg"><span class="type-signature"></span>msg<span class="type-signature"></span></h4>
<div class="description">
COntents of the message, with links replaced with numbered file-seperator marks
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line49">line 49</a>
</li></ul></dd>
</dl>
<h4 class="name" id="type"><span class="type-signature"></span>type<span class="type-signature"></span></h4>
<div class="description">
Message Type Identifier, used for client-side processing.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line54">line 54</a>
</li></ul></dd>
</dl>
<h4 class="name" id="user"><span class="type-signature"></span>user<span class="type-signature"></span></h4>
<div class="description">
User who sent the message
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="app_channel_chat.js.html">app/channel/chat.js</a>, <a href="app_channel_chat.js.html#line34">line 34</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="activeChannel.html">activeChannel</a></li><li><a href="channelManager.html">channelManager</a></li><li><a href="chat.html">chat</a></li><li><a href="chatBuffer.html">chatBuffer</a></li><li><a href="chatHandler.html">chatHandler</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="connectedUser.html">connectedUser</a></li><li><a href="media.html">media</a></li><li><a href="playlistHandler.html">playlistHandler</a></li><li><a href="queue.html">queue</a></li><li><a href="queuedMedia.html">queuedMedia</a></li><li><a href="tokebot.html">tokebot</a></li></ul><h3>Global</h3><ul><li><a href="global.html#authenticateSession">authenticateSession</a></li><li><a href="global.html#cache">cache</a></li><li><a href="global.html#channelBanSchema">channelBanSchema</a></li><li><a href="global.html#channelPermissionSchema">channelPermissionSchema</a></li><li><a href="global.html#channelSchema">channelSchema</a></li><li><a href="global.html#chatSchema">chatSchema</a></li><li><a href="global.html#comparePassword">comparePassword</a></li><li><a href="global.html#consoleWarn">consoleWarn</a></li><li><a href="global.html#daysToExpire">daysToExpire</a></li><li><a href="global.html#dumpError">dumpError</a></li><li><a href="global.html#emailChangeSchema">emailChangeSchema</a></li><li><a href="global.html#emoteSchema">emoteSchema</a></li><li><a href="global.html#errorHandler">errorHandler</a></li><li><a href="global.html#errorMiddleware">errorMiddleware</a></li><li><a href="global.html#escapeRegex">escapeRegex</a></li><li><a href="global.html#exceptionHandler">exceptionHandler</a></li><li><a href="global.html#exceptionSmith">exceptionSmith</a></li><li><a href="global.html#failedAttempts">failedAttempts</a></li><li><a href="global.html#fetchMetadata">fetchMetadata</a></li><li><a href="global.html#fetchVideoMetadata">fetchVideoMetadata</a></li><li><a href="global.html#fetchYoutubeMetadata">fetchYoutubeMetadata</a></li><li><a href="global.html#fetchYoutubePlaylistMetadata">fetchYoutubePlaylistMetadata</a></li><li><a href="global.html#flairSchema">flairSchema</a></li><li><a href="global.html#genCaptcha">genCaptcha</a></li><li><a href="global.html#getLoginAttempts">getLoginAttempts</a></li><li><a href="global.html#getMediaType">getMediaType</a></li><li><a href="global.html#hashIP">hashIP</a></li><li><a href="global.html#hashPassword">hashPassword</a></li><li><a href="global.html#kickoff">kickoff</a></li><li><a href="global.html#killSession">killSession</a></li><li><a href="global.html#lifetime">lifetime</a></li><li><a href="global.html#localExceptionHandler">localExceptionHandler</a></li><li><a href="global.html#mailem">mailem</a></li><li><a href="global.html#markLink">markLink</a></li><li><a href="global.html#maxAttempts">maxAttempts</a></li><li><a href="global.html#mediaSchema">mediaSchema</a></li><li><a href="global.html#passwordResetSchema">passwordResetSchema</a></li><li><a href="global.html#permissionSchema">permissionSchema</a></li><li><a href="global.html#playlistMediaProperties">playlistMediaProperties</a></li><li><a href="global.html#playlistSchema">playlistSchema</a></li><li><a href="global.html#processExpiredAttempts">processExpiredAttempts</a></li><li><a href="global.html#queuedProperties">queuedProperties</a></li><li><a href="global.html#rankEnum">rankEnum</a></li><li><a href="global.html#refreshRawLink">refreshRawLink</a></li><li><a href="global.html#schedule">schedule</a></li><li><a href="global.html#securityCheck">securityCheck</a></li><li><a href="global.html#sendAddressVerification">sendAddressVerification</a></li><li><a href="global.html#socketCriticalExceptionHandler">socketCriticalExceptionHandler</a></li><li><a href="global.html#socketErrorHandler">socketErrorHandler</a></li><li><a href="global.html#socketExceptionHandler">socketExceptionHandler</a></li><li><a href="global.html#spent">spent</a></li><li><a href="global.html#statSchema">statSchema</a></li><li><a href="global.html#throttleAttempts">throttleAttempts</a></li><li><a href="global.html#tokeCommandSchema">tokeCommandSchema</a></li><li><a href="global.html#transporter">transporter</a></li><li><a href="global.html#typeEnum">typeEnum</a></li><li><a href="global.html#userBanSchema">userBanSchema</a></li><li><a href="global.html#userSchema">userSchema</a></li><li><a href="global.html#verify">verify</a></li><li><a href="global.html#yankMedia">yankMedia</a></li><li><a href="global.html#ytdlpFetch">ytdlpFetch</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Sat Sep 06 2025 19:07:56 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 114 KiB

Some files were not shown because too many files have changed in this diff Show more