Autobump backend finished. Tweaks may be made during creation of
configuration frontend, but for the most part should be set in stone.
This commit is contained in:
parent
fcb562397a
commit
cb4c99727f
127
README.md
127
README.md
|
|
@ -55,7 +55,6 @@ dev goals for 1.1 pineapple express:
|
||||||
- videojs.coffee(hls,videojs,raw-file,gdrive,rtmp) ✓
|
- videojs.coffee(hls,videojs,raw-file,gdrive,rtmp) ✓
|
||||||
- dailymotion ~(this is kinda broken, likely not possible client-side without breaking CORS policy. This can wait until next version when an installation script including nginx and cors-proxy config gets added to support the catbox.moe image upload button which faces the same issue.
|
- dailymotion ~(this is kinda broken, likely not possible client-side without breaking CORS policy. This can wait until next version when an installation script including nginx and cors-proxy config gets added to support the catbox.moe image upload button which faces the same issue.
|
||||||
- vimeo ✓
|
- vimeo ✓
|
||||||
|
|
||||||
- slide out panel (not an end user feature in and of itself, however a common UI element used for most menus, made to be quick and ezpz ✓
|
- slide out panel (not an end user feature in and of itself, however a common UI element used for most menus, made to be quick and ezpz ✓
|
||||||
- function for opening, closing ✓
|
- function for opening, closing ✓
|
||||||
- allow switching menus on panel without having to open/close it ✓
|
- allow switching menus on panel without having to open/close it ✓
|
||||||
|
|
@ -116,7 +115,6 @@ dev goals for 1.1 pineapple express:
|
||||||
- add user specific function to clear ('!clear <username>' to clear chats by said user) ✓
|
- add user specific function to clear ('!clear <username>' to clear chats by said user) ✓
|
||||||
|
|
||||||
- Merge Upstream to newest cytube commit ✓
|
- Merge Upstream to newest cytube commit ✓
|
||||||
|
|
||||||
- improved server-whisper system ✓
|
- improved server-whisper system ✓
|
||||||
- public leave/join messages ✓
|
- public leave/join messages ✓
|
||||||
- server-whisper target parameter for user specific whispers ✓
|
- server-whisper target parameter for user specific whispers ✓
|
||||||
|
|
@ -196,9 +194,16 @@ dev goals for 1.1 pineapple express:
|
||||||
- fix scroll to item ✓
|
- fix scroll to item ✓
|
||||||
- fix scrolling while dragging pl item ✓
|
- fix scrolling while dragging pl item ✓
|
||||||
- autobump control menu
|
- autobump control menu
|
||||||
- tokebot control menu
|
- tokebot control menu ✓
|
||||||
|
- mod ✓
|
||||||
|
- reset cooldown button ✓
|
||||||
|
- admin ✓
|
||||||
|
- reload tokes btn ✓
|
||||||
|
- tokewhisper filed+btn ✓
|
||||||
|
- tokesay field+btn ✓
|
||||||
|
- tokeannounce field+btn ✓
|
||||||
|
|
||||||
- merge tokebot into ourfore.st codebase, one server instead of two.
|
- merge tokebot into ourfore.st codebase, one server instead of two. ✓
|
||||||
- port chozobot code to cytube module ✓
|
- port chozobot code to cytube module ✓
|
||||||
- tokewhisper (server whisper, can optionally be displayed as PM client side) ✓
|
- tokewhisper (server whisper, can optionally be displayed as PM client side) ✓
|
||||||
- load toke commands from tokes file ✓
|
- load toke commands from tokes file ✓
|
||||||
|
|
@ -212,9 +217,9 @@ dev goals for 1.1 pineapple express:
|
||||||
- tokeannounce command ✓
|
- tokeannounce command ✓
|
||||||
- tokewhisper command ✓
|
- tokewhisper command ✓
|
||||||
- !r to rando-toke ✓
|
- !r to rando-toke ✓
|
||||||
- log tokes w/ date to file. This will be consolidated to a better toke history in mariadb in a future update
|
- log tokes w/ date to file. This will be consolidated to a better toke history in mariadb in a future update ✓
|
||||||
- append [tokers],# of tokers,timestamp(epoch) on toke.
|
- append [tokers],# of tokers,timestamp(epoch) on toke. ✓
|
||||||
- tokefile, list of usernames with toke count. This should eventually be moved to a property of account or user
|
- tokefile, list of usernames with toke count. This should eventually be moved to a property of account or user ✓
|
||||||
- json of map ["username", # of tokes] ✓
|
- json of map ["username", # of tokes] ✓
|
||||||
- load file on startup ✓
|
- load file on startup ✓
|
||||||
- update file every toke ✓
|
- update file every toke ✓
|
||||||
|
|
@ -225,27 +230,119 @@ dev goals for 1.1 pineapple express:
|
||||||
- include modflair on tokewhisper ✓
|
- include modflair on tokewhisper ✓
|
||||||
|
|
||||||
- autobump
|
- autobump
|
||||||
- sepearate bump lists, based on js/txt files at first, will be stored in db next update (may use multiple at once)
|
- Serverside
|
||||||
- skip next bump/disable bumping
|
- Bump Management System ✓
|
||||||
- bump frequency (default: 1)
|
- bump object: name, user(person who made bump, optional), lowername, resettoke bool, id, listname, media item ✓
|
||||||
- queue method: random from last-half, round-robin, full random
|
- bumplist object: name, lowername, bump array ✓
|
||||||
- override next bump
|
- addBump function for adding bumps to list ✓
|
||||||
- require video be at least 4 minutes to add bump (mods can override from bump menu)
|
- deleteBump function for removing bumps from list ✓
|
||||||
|
- saveList function for saving list to file ✓
|
||||||
|
- pack/send lists to mods on start and request ✓
|
||||||
|
- test callback(dumps bumplist to dev console) ✓
|
||||||
|
- loadList for re-creating object from file ✓
|
||||||
|
- loadLists for re-creating objects based on all files in bumps/ folder ✓
|
||||||
|
- double check perms(look into using rank enums instead of numbers) ✓
|
||||||
|
- newBump ✓
|
||||||
|
- deleteBump ✓
|
||||||
|
- newBumpList ✓
|
||||||
|
- sendLists ✓
|
||||||
|
- remove whitespace and non - or _ specials from lowernames ✓
|
||||||
|
- bump ✓
|
||||||
|
- bumplist ✓
|
||||||
|
- Automated Bump Queueing
|
||||||
|
- base queueing function ✓
|
||||||
|
- queue bump next ✓
|
||||||
|
- call from socket ✓
|
||||||
|
- Active lists ✓
|
||||||
|
- map of active lists ✓
|
||||||
|
- add/remove from active lists callback ✓
|
||||||
|
- list selection method ✓
|
||||||
|
- smashList ✓
|
||||||
|
- randomList ✓
|
||||||
|
- bump selection method: random from last-half, round-robin, full random
|
||||||
|
- round-robin ✓
|
||||||
|
- full random ✓
|
||||||
|
- last-half random ✓
|
||||||
|
- Set bump/list sel method from packet ✓
|
||||||
|
-Auto Queue Bump(s) ✓
|
||||||
|
- agro: passive ✓
|
||||||
|
- all auto-bumping features disabled ✓
|
||||||
|
- agro: min ✓
|
||||||
|
- Auto-clear old/skipped bumps ✓
|
||||||
|
- after non-bump vid starts if next item is not bump ✓
|
||||||
|
- if there is nothing queued before/after current item (bump or not) ✓
|
||||||
|
- agro: mid ✓
|
||||||
|
- when non-bump vid is added and there are bumps queued next, move new item after the bump block ✓
|
||||||
|
- notify modules on video add ✓
|
||||||
|
- agro: max ✓
|
||||||
|
- when a non-bump vid is moved before or inbetween next bump block, move it after the bump block ✓
|
||||||
|
- notify modules on video move ✓
|
||||||
|
- set agro from packet ✓
|
||||||
|
- dedicated method for automated queueing based on current settings ✓
|
||||||
|
- bump frequency (default: 1,3) ✓
|
||||||
|
- base setting ✓
|
||||||
|
- set from packet ✓
|
||||||
|
- minimum length to bump (default: 240 sec) ✓
|
||||||
|
- base setting ✓
|
||||||
|
- set from packet ✓
|
||||||
|
- per-channel config files in bumps/config/<channame>.conf, loaded on channel startup for persistent settings(move to db by next major update)
|
||||||
|
- json object with following values
|
||||||
|
- active lists
|
||||||
|
- write ✓
|
||||||
|
- load ✓
|
||||||
|
- agro level
|
||||||
|
- write ✓
|
||||||
|
- load ✓
|
||||||
|
- bump frequency
|
||||||
|
- write ✓
|
||||||
|
- load ✓
|
||||||
|
- bump selmed
|
||||||
|
- write ✓
|
||||||
|
- load ✓
|
||||||
|
- list selmed
|
||||||
|
- write ✓
|
||||||
|
- load ✓
|
||||||
|
- min length to bump
|
||||||
|
- write ✓
|
||||||
|
- load ✓
|
||||||
|
- Add manual only property to bump object(default false).
|
||||||
|
- add boolean to obj ✓
|
||||||
|
- add to handleAddBump & its type ✓
|
||||||
|
- add to packlist ✓
|
||||||
|
- do not autoQueue bumps with manualOnly set to true
|
||||||
|
- round-robin ✓
|
||||||
|
- full random ✓
|
||||||
|
- last-half random ✓
|
||||||
|
- Change packList to send history as array of arrays ([[lname,id],[lname,id]]) ✓
|
||||||
|
- double check perms(all actions should be at least mod+ only)
|
||||||
|
- Clientside
|
||||||
|
|
||||||
- finishing touches
|
- finishing touches
|
||||||
- Critical Bug Fix: video sometimes unlatches if sync delayed on video start.(Fix pre-latch, if not duration check until sync is past 2s?)
|
- Critical Bug Fix: video sometimes unlatches if sync delayed on video start.(Fix pre-latch, if not duration check until sync is past 2s?)
|
||||||
- Critical Bug Fix: userlist profile & current connected users tooltips are currently broken ✓
|
- Critical Bug Fix: userlist profile & current connected users tooltips are currently broken ✓
|
||||||
- Critical Bug Fix: chat does not fill screen in portrait mode (video height being subtracted while video collapsed)
|
- Critical Bug Fix: chat does not fill screen in portrait mode (video height being subtracted while video collapsed)
|
||||||
- Critical Bug Fix: make serverside commands case insensitive(May have been intentional with cytube, don't give a shit, it's a bug.)
|
- Critical Bug Fix: make serverside commands case insensitive(May have been intentional with cytube, don't give a shit, it's a bug.)
|
||||||
- Critical Bug Fix: serverside commands trigger other commands with same letters (!announce triggers !a)
|
- Critical Bug Fix: serverside commands trigger other commands with same letters (!announce triggers !a. may only be an issue with single letter commands, double check this though.)
|
||||||
|
- Critical Bug Fix: fix playlist errors in fpanel
|
||||||
|
- Critical Bug Fix: Unescaped characters when preforming youtube search
|
||||||
|
- Critical Bug Check/Fix: make sure we use internal flags instead of matching strings for function calls from tokebot/autobump to playlist/chat
|
||||||
|
- tokebot
|
||||||
|
- autobump
|
||||||
- Minor Bug Fix: hide "close playlist" button when playlist is in fpanel
|
- Minor Bug Fix: hide "close playlist" button when playlist is in fpanel
|
||||||
|
- Minor Bug Fix: fix playlist resizing (both window resize, and when controls expanded) in fpanel
|
||||||
- Minor Bug Fix: Execute serverside commands with whitespace before them while also sending them as normal chat to comform to tokebot behavior in (v1)Panama Red
|
- Minor Bug Fix: Execute serverside commands with whitespace before them while also sending them as normal chat to comform to tokebot behavior in (v1)Panama Red
|
||||||
|
- Minor Bug Fix: disable pm send to tokebot
|
||||||
|
- Minor Bug Fix: dont open playlist if legacy playlist disabled(on perm change)
|
||||||
|
- Minor Bug Fix: add item to end of block when queueing round robin
|
||||||
|
- Optimization/Refactor: Change join/leave message prefix/postfix to single string with replacable user token instead of two vars
|
||||||
|
- Optimization/Refactor: Keep start time as proprety of media object while in playlist instead of in an array in metadata.
|
||||||
- add scrollTo() on fpplaylist open
|
- add scrollTo() on fpplaylist open
|
||||||
- save temporary vids to channel library
|
- save temporary vids to channel library
|
||||||
- getplaylistlinks outputs in fpanel
|
- getplaylistlinks outputs in fpanel
|
||||||
- display links
|
- display links
|
||||||
- pop mod nmenu
|
- pop mod nmenu
|
||||||
- css variables in theme for ez customizablity
|
- css variables in theme for ez customizablity
|
||||||
|
- start public channels on server start(this makes more sense for fore.st than cytube since we're focused on a handful of site-run channels instead of user-created ones...)
|
||||||
- import data from old tokelog
|
- import data from old tokelog
|
||||||
- merge fore.st theme changes to fore.st dusk, consider moving some of them over to cytube.css for easier management
|
- merge fore.st theme changes to fore.st dusk, consider moving some of them over to cytube.css for easier management
|
||||||
|
|
||||||
|
|
@ -270,7 +367,7 @@ dev goals for 1.1 pineapple express:
|
||||||
- update minicont buttons
|
- update minicont buttons
|
||||||
- treez.one Now Playing in MOTD (these will need coordination with treez.one)
|
- treez.one Now Playing in MOTD (these will need coordination with treez.one)
|
||||||
- treez.one tokebot syncronization (ESPECIALLY this one)
|
- treez.one tokebot syncronization (ESPECIALLY this one)
|
||||||
-
|
|
||||||
## License
|
## License
|
||||||
Original fore.st code is provided under the Affero General Public License v3 in order to prevent fore.st being used in proprietary software.
|
Original fore.st code is provided under the Affero General Public License v3 in order to prevent fore.st being used in proprietary software.
|
||||||
(see the LICENSE file for the full text.)
|
(see the LICENSE file for the full text.)
|
||||||
|
|
|
||||||
732
src/channel/autobump.js
Normal file
732
src/channel/autobump.js
Normal file
|
|
@ -0,0 +1,732 @@
|
||||||
|
/*
|
||||||
|
fore.st 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.
|
||||||
|
|
||||||
|
fore.st 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 fore.st. If not, see < http://www.gnu.org/licenses/ >.
|
||||||
|
(C) 2022- by rainbownapkin, <ourforest@420blaze.it>
|
||||||
|
*/
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
var InfoGetter = require ("../get-info");
|
||||||
|
var ChannelModule = require("./module");
|
||||||
|
var Media = require("../media");
|
||||||
|
var util = require("../utilities");
|
||||||
|
|
||||||
|
//type declerations
|
||||||
|
const TYPE_NEWBUMP = {
|
||||||
|
name: "string",
|
||||||
|
user: "string",
|
||||||
|
id: "string",
|
||||||
|
type: "string",
|
||||||
|
rtoke: "boolean",
|
||||||
|
bumplist: "string",
|
||||||
|
noauto: "boolean"
|
||||||
|
};
|
||||||
|
|
||||||
|
const TYPE_DELBUMP = {
|
||||||
|
bl: "string",
|
||||||
|
id: "number"
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_SELTYPE = {
|
||||||
|
bump: "number",
|
||||||
|
list: "number"
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_BFREQ = {
|
||||||
|
min: "number",
|
||||||
|
max: "number"
|
||||||
|
}
|
||||||
|
|
||||||
|
//global vars
|
||||||
|
var bumplists = null;//decalare variable but keep it null until lists are loaded
|
||||||
|
var lowerReg = /[\s!"#$%&'()*+,./:;<=>?@[\]^`{|}~]/g;//regex for ripping out specials and whitespace from lowernames
|
||||||
|
var bumpFolder = "bumps/"
|
||||||
|
var configFolder = bumpFolder + "config/"
|
||||||
|
|
||||||
|
//global functions
|
||||||
|
function randi(len) {//get random number from zero to len, meant for use to pull random items from an array
|
||||||
|
return Math.floor(Math.random() * len); //The maximum is exclusive and the minimum is inclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
function randrange(min,max){
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is exclusive and the minimum is inclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadList(bfile){
|
||||||
|
var _this = this;
|
||||||
|
fs.readFile(bfile, function(err,rdata){
|
||||||
|
if(err){
|
||||||
|
console.log("[loadlist] " + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = JSON.parse(rdata);
|
||||||
|
|
||||||
|
bumplists.set(data.lowername, new bumplist(data.name,data.bumps));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLists(cb, callp){
|
||||||
|
fs.readdir(bumpFolder, function(err, item){
|
||||||
|
if(err){
|
||||||
|
console.log("[loadlists] " + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
bumplists = new Map();//create new map to load lists into, this clears the variable as well as lets the channel know whether or not they have been loaded yet.
|
||||||
|
|
||||||
|
item.forEach(function(list, i){
|
||||||
|
if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1)){
|
||||||
|
if((item.length - 2) == i){
|
||||||
|
|
||||||
|
}
|
||||||
|
loadList("bumps/" + list);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(cb != null){//lil' nasty but it calls loadconfig after loading lists :P
|
||||||
|
if(callp != null){
|
||||||
|
cb(callp);
|
||||||
|
}else{
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//constructors
|
||||||
|
function bump(name, user, rtoke, media, bumplist, noauto){//bump object constructor
|
||||||
|
this.name = name;
|
||||||
|
this.user = user;
|
||||||
|
this.lowername = name.toLowerCase().replace(lowerReg, "");
|
||||||
|
this.rtoke = rtoke;
|
||||||
|
this.id = null;//this is assigned by the bumplist :P
|
||||||
|
this.media = media;
|
||||||
|
this.noauto = noauto
|
||||||
|
if(bumplists != null){
|
||||||
|
if(bumplists.get(bumplist.toLowerCase().replace(lowerReg, ""))){//if bumplist exists
|
||||||
|
bumplists.get(bumplist.toLowerCase().replace(lowerReg, "")).addBump(this);//add this to the bumplist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bumplist(name, bumps){//bumplist object constructor
|
||||||
|
this.name = name;
|
||||||
|
this.lowername = this.name.toLowerCase().replace(lowerReg, "");
|
||||||
|
this.bumps = (bumps == null ? [] : bumps);
|
||||||
|
}
|
||||||
|
|
||||||
|
//prototypes
|
||||||
|
bumplist.prototype.saveList = function(){
|
||||||
|
var _this = this;
|
||||||
|
fs.writeFile("bumps/" + this.name + ".bump", JSON.stringify(this), function(err,data){ //RIPPED FROM TOKEBOT, NOT CHANGED YET
|
||||||
|
if(err){
|
||||||
|
console.log("[bump] BUMP LIST " + _this.name + " FILE WRITE ERROR: " + err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
bumplist.prototype.addBump = function(bump){//add bump to bumplist
|
||||||
|
bump.id = (this.getLastId());//Set ID to the ID of the last item + 1 unless there is no item
|
||||||
|
bump.listname = this.lowername;
|
||||||
|
|
||||||
|
this.bumps[bump.id] = bump;//add bump at bump id
|
||||||
|
this.saveList();
|
||||||
|
};
|
||||||
|
|
||||||
|
bumplist.prototype.deleteBump = function(bump){//delete bumps (should probably adjust ids and index of bumps but meh)
|
||||||
|
if(bump != null){
|
||||||
|
this.bumps[bump.id] = null;//null out bump
|
||||||
|
this.saveList();//save the bitch
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
bumplist.prototype.getLastId = function(){
|
||||||
|
var lid = 0;
|
||||||
|
this.bumps.forEach(function(bump){
|
||||||
|
if(bump != null){
|
||||||
|
lid = bump.id + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return lid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bumplist.prototype.packList = function(){
|
||||||
|
var pbl = {
|
||||||
|
name: this.name,
|
||||||
|
lowername: this.lowername,
|
||||||
|
bumps: []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bumps.forEach(function(bump){
|
||||||
|
if(bump != null){
|
||||||
|
pbl.bumps[bump.id] = {
|
||||||
|
name: bump.name,
|
||||||
|
lowername: bump.lowername,
|
||||||
|
rtoke: bump.rtoke,
|
||||||
|
id: bump.id,
|
||||||
|
noauto: bump.noauto,
|
||||||
|
media: {
|
||||||
|
id: bump.media.id,
|
||||||
|
title: bump.media.title,
|
||||||
|
type: bump.media.type,
|
||||||
|
duration: bump.media.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bump.user != null){
|
||||||
|
pbl.bumps[bump.id].user = bump.user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pbl;
|
||||||
|
}
|
||||||
|
|
||||||
|
//constructor
|
||||||
|
function AutobumpModule(_channel){
|
||||||
|
ChannelModule.apply(this, arguments);
|
||||||
|
this.selmed = this.lastHalfRandom;
|
||||||
|
this.listsel = this.smashList;
|
||||||
|
if(bumplists == null){
|
||||||
|
loadLists(this.loadConfig, this);//on startup load lists
|
||||||
|
}else{
|
||||||
|
this.loadConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//module protoype definition
|
||||||
|
AutobumpModule.prototype = Object.create(ChannelModule.prototype);
|
||||||
|
AutobumpModule.prototype.lastPlayed = [];
|
||||||
|
AutobumpModule.prototype.activeLists = new Map();
|
||||||
|
|
||||||
|
//event handling
|
||||||
|
AutobumpModule.prototype.onUserPostJoin = function (user){//on user join
|
||||||
|
if(user.account.effectiveRank >= 2){
|
||||||
|
user.socket.typecheckedOn("newBump", TYPE_NEWBUMP, this.handleNewBump.bind(this, user));//handle newBump
|
||||||
|
user.socket.typecheckedOn("deleteBump", TYPE_DELBUMP, this.handleDeleteBump.bind(this, user));//handle newBumplist
|
||||||
|
user.socket.typecheckedOn("newBumplist", '', this.handleNewBumplist.bind(this, user));//handle newBumplist
|
||||||
|
user.socket.typecheckedOn("setActive", '', this.handleSetActive.bind(this, user));//handle newBumplist
|
||||||
|
user.socket.typecheckedOn("removeActive", '', this.handleRemoveActive.bind(this, user));//handle newBumplist
|
||||||
|
user.socket.typecheckedOn("queueBump", TYPE_DELBUMP, this.handleQueueBump.bind(this, user));//TODO:fix perms for this
|
||||||
|
user.socket.typecheckedOn("setSelect", TYPE_SELTYPE, this.handleSetSelect.bind(this, user));//TODO:fix perms for this
|
||||||
|
user.socket.typecheckedOn("setAgro", 0, this.handleSetAgro.bind(this, user));//TODO:fix perms for this
|
||||||
|
user.socket.typecheckedOn("setBumpFreq", TYPE_BFREQ, this.handleSetBumpFreq.bind(this, user));//TODO:fix perms for this
|
||||||
|
user.socket.typecheckedOn("setMinBump", 0, this.handleSetMinBump.bind(this, user));//TODO:fix perms for this
|
||||||
|
user.socket.on("getBumplists", this.sendLists.bind(this, user));//send lists
|
||||||
|
this.sendLists(user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.onMediaChange = function(data){
|
||||||
|
if(this.agro >= 1){//if agro is 1 or above
|
||||||
|
var curFound = false;
|
||||||
|
var _this = this;
|
||||||
|
this.channel.modules.playlist.items.forEach(function(vid){
|
||||||
|
if(!curFound){//if item is not found
|
||||||
|
if(_this.channel.modules.playlist.current.prev != null){//if last item isnt null
|
||||||
|
curFound = (_this.channel.modules.playlist.current.prev.uid == vid.uid);//check if we're looking at current item
|
||||||
|
if(vid.media.isBump){//if the bitch is a bump
|
||||||
|
_this.channel.modules.playlist._delete(vid.uid);//FUCKIN KILL IT, KILL IT WITH FIRE!
|
||||||
|
}
|
||||||
|
}else{//if this goes then its probably the first item in the playlist, no reason to check for old bumps
|
||||||
|
curFound = true;
|
||||||
|
}
|
||||||
|
}//leave new items alone
|
||||||
|
});
|
||||||
|
|
||||||
|
var nextp = this.channel.modules.playlist.current.next != null //presence of next item
|
||||||
|
var nextb = false;//if next item is bump
|
||||||
|
|
||||||
|
if(nextp){//if there is something next
|
||||||
|
nextb = this.channel.modules.playlist.current.next.media.isBump;//check if next item is bump
|
||||||
|
if(!nextb && !data.isBump){//if neither current nor next item is bump
|
||||||
|
this.autobump();//queue bump via selmed
|
||||||
|
}
|
||||||
|
}else{//otherwise
|
||||||
|
this.autobump();//queue bump via selmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.onMediaAdd = function(data, media){
|
||||||
|
if(this.agro >= 2){//if agro is 2 or above
|
||||||
|
if(data.pos === "next"){//if someone added something next
|
||||||
|
if(!media.isBump){//new item isn't bumps
|
||||||
|
var lastBump = this.findBlockEnd(this.channel.modules.playlist.current.next.next);//get the last bump of the block
|
||||||
|
if(lastBump != null){//if we got a bump in the block
|
||||||
|
if(lastBump.media != media && lastBump != this.channel.modules.playlist.current){//make sure we actually have a block and this isn't returning the currently playing item or the item we added
|
||||||
|
var moved = {//create move obj
|
||||||
|
from: this.channel.modules.playlist.current.next.uid,//set from as the item that was just added
|
||||||
|
after: lastBump.uid,//move item after block
|
||||||
|
sTimes: [[],[]]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.channel.modules.playlist.handleMoveMedia("autobump",moved,true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutobumpModule.prototype.onMediaMove = function(data){
|
||||||
|
if(this.agro >= 3){//if agro is 3 or above
|
||||||
|
if(data.after === "prepend"){
|
||||||
|
return;//can't skip bumps this way anyhow
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastBump = this.findBlockEnd();//get last item of (potentially cut) block of bumps
|
||||||
|
var nItem = this.channel.modules.playlist.items.find(data.after).next.next;//get item after the moved item
|
||||||
|
|
||||||
|
//if (data.after == lastbump.id) or (data.after === current.id) and next item is a bump and current item isn't a bump
|
||||||
|
if((data.after == lastBump.uid || data.after == this.channel.modules.playlist.current.uid) &&
|
||||||
|
(nItem != null) && (nItem.media.isBump) && !nItem.prev.media.isBump){
|
||||||
|
var eBlock = this.findBlockEnd(nItem);//get last bump from block of bumps queued after moved item
|
||||||
|
|
||||||
|
var moved = {//create move obj
|
||||||
|
from: nItem.prev.uid,//set from as the item that was just moved
|
||||||
|
after: eBlock.uid,//move item after block
|
||||||
|
sTimes: [[],[]]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.channel.modules.playlist.handleMoveMedia("autobump",moved,true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Utility
|
||||||
|
AutobumpModule.prototype.findBlockEnd = function(citem){
|
||||||
|
if(citem == null){//if citem is unset
|
||||||
|
citem = this.channel.modules.playlist.current.next;//set citem to current item by default
|
||||||
|
if(citem == null){//return current item if you're at end of list
|
||||||
|
return this.channel.modules.playlist.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var og = citem;
|
||||||
|
while(citem != null && citem.media.isBump){//while a bump is next, and we haven't hit the end of the line
|
||||||
|
if(citem.next == null){//if we hit end of the list
|
||||||
|
return citem;//stahp, and return what we got.
|
||||||
|
}else{
|
||||||
|
citem = citem.next;//move downt the playlist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return citem.prev;//return last bump of block, return first item if no bumps
|
||||||
|
};
|
||||||
|
|
||||||
|
//Bump Queueing/
|
||||||
|
AutobumpModule.prototype.queueRandom = function queueRandom(alist){
|
||||||
|
if(alist == null){
|
||||||
|
alist = [];
|
||||||
|
this.listsel().forEach(function(bump){//go through selected list
|
||||||
|
if(bump != null && !bump.noauto){//for every item that isnt null
|
||||||
|
alist.push(bump);//add it to alist
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var chosen = alist[randi(alist.length)];
|
||||||
|
|
||||||
|
if(chosen != null){
|
||||||
|
/*(if(this.channel.modules.playlist.items.findVideoId(chosen.media.id) != false){//dupe detector dupes are an accepted casualty :P
|
||||||
|
this.queueRandom(alist);
|
||||||
|
}*/
|
||||||
|
this.queueBump(chosen.listname, chosen.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AutobumpModule.prototype.roundRobin = function roundRobin(){
|
||||||
|
var alist = [];//create alist var
|
||||||
|
this.listsel().forEach(function(bump){//go through selected list
|
||||||
|
if(bump != null && !bump.noauto){//for every item that isnt null
|
||||||
|
alist.push(bump);//add it to alist
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var curbump = null;//define curbump
|
||||||
|
|
||||||
|
var rhist = this.lastPlayed.slice().reverse();//create reverse history array
|
||||||
|
|
||||||
|
var curin = null;
|
||||||
|
rhist.forEach(function(bump, i){//go through bump history, starting with the newest bumps
|
||||||
|
if(alist.includes(bump) && curin == null){//if we got a match
|
||||||
|
curin = alist.findIndex(function(cbump){//get its index
|
||||||
|
return (cbump.id == bump.id && cbump.listname === bump.listname);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(curin != null){//if we found an index
|
||||||
|
if(alist.length <= curin + 1){//if we hit the end of the list
|
||||||
|
this.queueBump(alist[0].listname, alist[0].id);//wrap around list and queue from beggining
|
||||||
|
}else{//otherwise
|
||||||
|
this.queueBump(alist[curin + 1].listname, alist[curin + 1].id);//queue next bump
|
||||||
|
}
|
||||||
|
}else if(alist[0] != null){//or if we didnt
|
||||||
|
this.queueBump(alist[0].listname, alist[0].id);//assume we haven't played anything and play the first bump in the list.
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.lastHalfRandom = function lastHalfRandom(){
|
||||||
|
var alist = this.listsel();
|
||||||
|
var inhist = [];//filtered bump history containing only relavent bumps
|
||||||
|
var selbumps = [];//bumps elligible for selection
|
||||||
|
var alength = 0;//length of alist without null values
|
||||||
|
|
||||||
|
this.lastPlayed.forEach(function(bump){//for every bump in history
|
||||||
|
if(alist.includes(bump) && bump != null && !bump.noauto){//if that bump is in the list of bumps created by the listsel method
|
||||||
|
inhist.push(bump);//add it to the inhist array
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alist.forEach(function(bump){
|
||||||
|
if(bump != null)
|
||||||
|
alength++;
|
||||||
|
});
|
||||||
|
if(inhist.length >= alength){//if all active bumps have been played
|
||||||
|
selbumps = inhist.slice(0,Math.round(inhist.length / 2));//set eligible bumps to the least recently played half of all active bumps
|
||||||
|
}else{//if there are unplayed active bumps
|
||||||
|
alist.forEach(function(bump){//for every active bump
|
||||||
|
if(!inhist.includes(bump) && bump != null && !bump.noauto){//if it hasn't been played
|
||||||
|
selbumps.push(bump);//add it to selbumps
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selbumps.length > 0){//if we got somethin
|
||||||
|
this.queueRandom(selbumps);//then throw it at the random queue method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleQueueBump = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
this.queueBump(data.bl, data.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.autobump = function(){
|
||||||
|
var bcount = randrange(this.bumpFreq[0], this.bumpFreq[1]);
|
||||||
|
if(this.channel.modules.playlist.current.media.seconds > this.minBump){//if current item is long enough to have a bump
|
||||||
|
for(var i = 0; i < bcount; i++){//for bcount
|
||||||
|
this.selmed();//queue bump via selmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutobumpModule.prototype.queueBump = function(listn, bid){//listname, bump ID
|
||||||
|
var list = bumplists.get(listn.toLowerCase().replace(lowerReg, ""))
|
||||||
|
var bump = null;
|
||||||
|
var data = null;
|
||||||
|
|
||||||
|
if(list != null){//if bumplist exists
|
||||||
|
bump = list.bumps[bid]
|
||||||
|
|
||||||
|
var lastin = this.lastPlayed.findIndex(function(cbump){
|
||||||
|
return (cbump.id == bump.id && cbump.listname === bump.listname);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this.lastPlayed[lastin] != null){
|
||||||
|
this.lastPlayed.splice(lastin,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.lastPlayed.push(bump)
|
||||||
|
|
||||||
|
if(bump != null){
|
||||||
|
data = { //create faux data object for _addItem function
|
||||||
|
id: bump.media.id,
|
||||||
|
type: bump.media.type,
|
||||||
|
pos: 'next',
|
||||||
|
title: false,
|
||||||
|
subtitle: false,
|
||||||
|
link: util.formatLink(bump.media.id, bump.media.type),
|
||||||
|
temp: true,
|
||||||
|
shouldAddToLibrary: false,
|
||||||
|
queueby: 'autobump',
|
||||||
|
maxlength: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var nmed = new Media(bump.media.id, bump.media.title, bump.media.seconds, bump.media.type, bump.media.meta);//revive media object, should probably do this when parsing JSON but fuck you this works :P
|
||||||
|
nmed.isBump = true;
|
||||||
|
|
||||||
|
this.channel.modules.playlist._addItem(nmed, data, "autobump");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//list Selection
|
||||||
|
AutobumpModule.prototype.smashList = function smashList(){
|
||||||
|
var alist = [];
|
||||||
|
|
||||||
|
|
||||||
|
this.activeLists.forEach(function (bl){
|
||||||
|
if(bl.bumps != null){
|
||||||
|
bl.bumps.forEach(function(bump){
|
||||||
|
alist.push(bump);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return alist;
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.randomList = function randomList(){
|
||||||
|
var llist = [];
|
||||||
|
this.activeLists.forEach(function (bl){
|
||||||
|
llist.push(bl);
|
||||||
|
});
|
||||||
|
|
||||||
|
var clist = llist[randi(llist.length)];
|
||||||
|
if(clist != null){
|
||||||
|
return clist.bumps;
|
||||||
|
}else{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//configuration methods
|
||||||
|
AutobumpModule.prototype.loadConfig = function(_this){
|
||||||
|
if(_this == null){
|
||||||
|
var _this = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(configFolder + _this.channel.name + ".conf", function(err,rdata){
|
||||||
|
var defAgro = 2;
|
||||||
|
var defFreq = [1,3];
|
||||||
|
var defBSort = "lastHalfRandom";
|
||||||
|
var defLSort = "smashList";
|
||||||
|
var defMin = 240;
|
||||||
|
|
||||||
|
|
||||||
|
if(err){
|
||||||
|
console.log("[Autobump Config] " + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = JSON.parse(rdata);
|
||||||
|
|
||||||
|
//console.log(data);
|
||||||
|
|
||||||
|
data.active == null ? [] : data.active;
|
||||||
|
if(data.active != null){
|
||||||
|
data.active.forEach(function(al){
|
||||||
|
if(bumplists.get(al) != null){
|
||||||
|
_this.activeLists.set(al, bumplists.get(al));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_this.agro = data.agro == null ? defAgro : data.agro;
|
||||||
|
_this.bumpFreq = data.freq == null ? defFreq : data.freq;
|
||||||
|
_this.minBump = data.minBump == null ? defMin : data.minBump;
|
||||||
|
_this.selmed = data.bsort == null ? _this.lastHalfRandom : _this[data.bsort];
|
||||||
|
_this.listsel = data.lsort == null ? _this.smashList : _this[data.lsort];
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.saveConfig = function(){
|
||||||
|
var confObj = {
|
||||||
|
active: [],
|
||||||
|
agro: this.agro,
|
||||||
|
freq: this.bumpFreq,
|
||||||
|
bsort: this.selmed.name,
|
||||||
|
lsort: this.listsel.name,
|
||||||
|
minBump: this.minBump
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeLists.forEach(function(bumplist){
|
||||||
|
confObj.active.push(bumplist.lowername);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
fs.writeFile(configFolder + this.channel.name + ".conf", JSON.stringify(confObj), function(err,data){
|
||||||
|
if(err){
|
||||||
|
console.log("[Autobump Config] " + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleSetSelect = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
switch(data.list){
|
||||||
|
case 1:
|
||||||
|
this.listsel = this.smashList;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.listsel = this.randomList;
|
||||||
|
}
|
||||||
|
switch(data.bump){
|
||||||
|
case 1:
|
||||||
|
this.selmed = this.queueRandom;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.selmed = this.roundRobin;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.selmed = this.lastHalfRandom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleSetAgro = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
data = data > 3 ? 3 : data;
|
||||||
|
this.agro = data < 0 ? 0 : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleSetBumpFreq = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
data.min = Math.abs(data.min);//set values to absolute
|
||||||
|
data.max = Math.abs(data.max);
|
||||||
|
|
||||||
|
this.bumpFreq[0] = data.min;
|
||||||
|
this.bumpFreq[1] = data.min < data.max ? data.max : data.min;//if min is more then max then max will be min
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleSetMinBump = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
this.minBump = Math.abs(data);
|
||||||
|
|
||||||
|
this.saveConfig();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//list management/commands
|
||||||
|
AutobumpModule.prototype.handleSetActive = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
var bl = bumplists.get(data.toLowerCase().replace(lowerReg, ""));
|
||||||
|
if(bl != null){//if bumplist exists
|
||||||
|
if(this.activeLists.get(data.toLowerCase().replace(lowerReg, "")) != null){
|
||||||
|
console.log("list already active");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.activeLists.set(data.toLowerCase().replace(lowerReg, ""), bl);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveConfig();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleRemoveActive = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
if(this.activeLists.has(data.toLowerCase().replace(lowerReg, ""))){
|
||||||
|
this.activeLists.delete(data.toLowerCase().replace(lowerReg, ""));
|
||||||
|
}else{
|
||||||
|
console.log("list not active!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.sendLists = function(user, data){
|
||||||
|
if(user != null && user.account.effectiveRank >= 2){
|
||||||
|
var sendobj = {
|
||||||
|
lists: [],
|
||||||
|
active: [],
|
||||||
|
freq: this.bumpFreq,
|
||||||
|
agro: this.agro,
|
||||||
|
minBump: this.minBump,
|
||||||
|
history: [],
|
||||||
|
bsort: this.selmed.name,
|
||||||
|
lsort: this.listsel.name
|
||||||
|
}
|
||||||
|
|
||||||
|
bumplists.forEach(function(bumplist){
|
||||||
|
sendobj.lists.push(bumplist.packList());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activeLists.forEach(function(bumplist){
|
||||||
|
sendobj.active.push(bumplist.lowername);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lastPlayed.forEach(function(bump){
|
||||||
|
sendobj.history.push([bump.listname, bump.id]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
user.socket.emit("sendBumplists", sendobj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleNewBumplist = function(user, data){//handle newBumplist
|
||||||
|
if(user.account.effectiveRank >= 2){
|
||||||
|
if(bumplists.get(data.toLowerCase().replace(lowerReg, "")) == null){
|
||||||
|
bumplists.set(data.toLowerCase().replace(lowerReg, ""), new bumplist(data));//create new bumplist
|
||||||
|
}else{
|
||||||
|
user.socket.emit("errorMsg", {
|
||||||
|
msg: "Bumplist name taken: " + data.toLowerCase().replace(lowerReg, ""),
|
||||||
|
alert: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//bump management/commands
|
||||||
|
AutobumpModule.prototype.handleNewBump = function(user, data){//handle newBump
|
||||||
|
if(user.account.effectiveRank >= 2){
|
||||||
|
console.log("Pulling bump info...");
|
||||||
|
InfoGetter.getMedia(data.id, data.type, function(err, media){//get media
|
||||||
|
if(err){//error handling
|
||||||
|
user.socket.emit("errorMsg", {
|
||||||
|
msg: "Error pulling bump: " + err,
|
||||||
|
alert: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
media.isBump = true;
|
||||||
|
if(data.user === ""){
|
||||||
|
data.user = null;
|
||||||
|
}
|
||||||
|
var newbump = new bump(data.name, data.user, data.rtoke, media, data.bumplist, data.noauto);//create new bump
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutobumpModule.prototype.handleDeleteBump = function(user, data){
|
||||||
|
if(user.account.effectiveRank >= 2){
|
||||||
|
var bl = bumplists.get(data.bl);
|
||||||
|
if(bl != null){
|
||||||
|
bl.deleteBump(bl.bumps[data.id]);
|
||||||
|
}else{
|
||||||
|
user.socket.emit("errorMsg", {
|
||||||
|
msg: "Bumplist: " + data.bl + " not found!",
|
||||||
|
alert: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = AutobumpModule;
|
||||||
|
|
@ -239,6 +239,7 @@ Channel.prototype.initModules = function () {
|
||||||
"./emotes" : "emotes",
|
"./emotes" : "emotes",
|
||||||
"./chat" : "chat",
|
"./chat" : "chat",
|
||||||
"./tokebot" : "tokebot",
|
"./tokebot" : "tokebot",
|
||||||
|
"./autobump" : "autobump",
|
||||||
"./filters" : "filters",
|
"./filters" : "filters",
|
||||||
"./customization" : "customization",
|
"./customization" : "customization",
|
||||||
"./opts" : "options",
|
"./opts" : "options",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Config = require("../config");
|
var Config = require("../config");
|
||||||
var XSS = require("../xss");
|
var XSS = require("../xss");
|
||||||
var ChannelModule = require("./module");
|
var ChannelModule = require("./module");
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,41 @@
|
||||||
|
/*
|
||||||
|
fore.st 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.
|
||||||
|
|
||||||
|
fore.st 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 fore.st. If not, see < http://www.gnu.org/licenses/ >.
|
||||||
|
(C) 2022- by rainbownapkin, <ourforest@420blaze.it>
|
||||||
|
|
||||||
|
Original cytube license:
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2013-2022 Calvin Montgomery
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
function ChannelModule(channel) {
|
function ChannelModule(channel) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
|
|
@ -73,6 +111,18 @@ ChannelModule.prototype = {
|
||||||
onMediaChange: function (_data) {
|
onMediaChange: function (_data) {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Called when media is moved
|
||||||
|
*/
|
||||||
|
onMediaMove: function (_data) {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Called when media is added to the playlist
|
||||||
|
*/
|
||||||
|
onMediaAdd: function (_data, _media) {
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Channel module callback return codes */
|
/* Channel module callback return codes */
|
||||||
|
|
|
||||||
|
|
@ -675,8 +675,8 @@ PlaylistModule.prototype.handleSetTemp = function (user, data) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PlaylistModule.prototype.handleMoveMedia = function (user, data) {
|
PlaylistModule.prototype.handleMoveMedia = function (user, data, inCall) {
|
||||||
if (!this.channel.modules.permissions.canMoveVideo(user)) {
|
if (!this.channel.modules.permissions.canMoveVideo(user) && !inCall) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -716,6 +716,9 @@ PlaylistModule.prototype.handleMoveMedia = function (user, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!inCall){//at the current moment, calling this for internal calls would cause more harm than good.
|
||||||
|
self.channel.notifyModules("onMediaMove", [data]);
|
||||||
|
}
|
||||||
|
|
||||||
self.items.forEach(function (item){//iterate items
|
self.items.forEach(function (item){//iterate items
|
||||||
self.items.find(item.uid).media.startTime = tempST;//current item start time = tempST
|
self.items.find(item.uid).media.startTime = tempST;//current item start time = tempST
|
||||||
|
|
@ -738,9 +741,15 @@ PlaylistModule.prototype.handleMoveMedia = function (user, data) {
|
||||||
|
|
||||||
self.channel.broadcastAll("moveVideo", data);
|
self.channel.broadcastAll("moveVideo", data);
|
||||||
|
|
||||||
|
if(user === "autobump"){
|
||||||
|
self.channel.logger.log("[playlist] autobump moved " +
|
||||||
|
from.media.title +
|
||||||
|
(after ? " after " + after.media.title : ""));
|
||||||
|
}else{
|
||||||
self.channel.logger.log("[playlist] " + user.getName() + " moved " +
|
self.channel.logger.log("[playlist] " + user.getName() + " moved " +
|
||||||
from.media.title +
|
from.media.title +
|
||||||
(after ? " after " + after.media.title : ""));
|
(after ? " after " + after.media.title : ""));
|
||||||
|
}
|
||||||
self._listDirty = true;
|
self._listDirty = true;
|
||||||
lock.release();
|
lock.release();
|
||||||
self.channel.refCounter.unref("PlaylistModule::handleMoveMedia");
|
self.channel.refCounter.unref("PlaylistModule::handleMoveMedia");
|
||||||
|
|
@ -1005,6 +1014,7 @@ PlaylistModule.prototype._delete = function (uid) {
|
||||||
};
|
};
|
||||||
|
|
||||||
PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
|
var abump = user === "autobump";
|
||||||
var self = this;
|
var self = this;
|
||||||
var allowDuplicates = false;
|
var allowDuplicates = false;
|
||||||
if (this.channel.modules.options && this.channel.modules.options.get("allow_dupes")) {
|
if (this.channel.modules.options && this.channel.modules.options.get("allow_dupes")) {
|
||||||
|
|
@ -1012,24 +1022,29 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var qfail = function (msg) {
|
var qfail = function (msg) {
|
||||||
|
if(!abump){
|
||||||
user.socket.emit("queueFail", {
|
user.socket.emit("queueFail", {
|
||||||
msg: msg,
|
msg: msg,
|
||||||
link: data.link,
|
link: data.link,
|
||||||
id: data.id
|
id: data.id
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (data.duration) {
|
if (data.duration) {
|
||||||
media.seconds = data.duration;
|
media.seconds = data.duration;
|
||||||
media.duration = util.formatTime(media.seconds);
|
media.duration = util.formatTime(media.seconds);
|
||||||
} else if (media.seconds === 0 && !this.channel.modules.permissions.canAddLive(user)) {
|
} else if(!abump){
|
||||||
|
if (media.seconds === 0 && !this.channel.modules.permissions.canAddLive(user)) {
|
||||||
// Issue #766
|
// Issue #766
|
||||||
qfail("You don't have permission to add livestreams");
|
qfail("You don't have permission to add livestreams");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isNaN(media.seconds)) {
|
if (isNaN(media.seconds)) {
|
||||||
LOGGER.warn("Detected NaN duration for %j", media);
|
LOGGER.warn("Detected NaN duration for %j", media);
|
||||||
|
|
@ -1051,10 +1066,15 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var usersItems = this.items.findAll(function (item) {
|
var usersItems = this.items.findAll(function (item) {
|
||||||
|
if(!abump){
|
||||||
return item.queueby.toLowerCase() === user.getLowerName();
|
return item.queueby.toLowerCase() === user.getLowerName();
|
||||||
|
}else{
|
||||||
|
return item.queueby.toLowerCase() === "autobump";
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.channel.modules.options &&
|
if (!abump && this.channel.modules.options &&
|
||||||
this.channel.modules.options.get("playlist_max_per_user") &&
|
this.channel.modules.options.get("playlist_max_per_user") &&
|
||||||
usersItems.length >= this.channel.modules.options.get("playlist_max_per_user")) {
|
usersItems.length >= this.channel.modules.options.get("playlist_max_per_user")) {
|
||||||
|
|
||||||
|
|
@ -1068,12 +1088,15 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
|
|
||||||
const limit = this.channel.modules.options.get("playlist_max_duration_per_user");
|
const limit = this.channel.modules.options.get("playlist_max_duration_per_user");
|
||||||
const totalDuration = usersItems.map(item => item.media.seconds).reduce((a, b) => a + b, 0) + media.seconds;
|
const totalDuration = usersItems.map(item => item.media.seconds).reduce((a, b) => a + b, 0) + media.seconds;
|
||||||
|
|
||||||
|
if(!abump){
|
||||||
if (isNaN(totalDuration)) {
|
if (isNaN(totalDuration)) {
|
||||||
LOGGER.error("playlist_max_duration_per_user check calculated NaN: " + require('util').inspect(usersItems));
|
LOGGER.error("playlist_max_duration_per_user check calculated NaN: " + require('util').inspect(usersItems));
|
||||||
} else if (totalDuration >= limit && !this.channel.modules.permissions.canExceedMaxDurationPerUser(user)) {
|
} else if (totalDuration >= limit && !this.channel.modules.permissions.canExceedMaxDurationPerUser(user)) {
|
||||||
return qfail("Channel limit exceeded: maximum total playlist time per user");
|
return qfail("Channel limit exceeded: maximum total playlist time per user");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (media.meta.ytRating === "ytAgeRestricted") {
|
if (media.meta.ytRating === "ytAgeRestricted") {
|
||||||
return qfail("Cannot add age restricted videos. See: https://github.com/calzoneman/sync/wiki/Frequently-Asked-Questions#why-dont-age-restricted-youtube-videos-work");
|
return qfail("Cannot add age restricted videos. See: https://github.com/calzoneman/sync/wiki/Frequently-Asked-Questions#why-dont-age-restricted-youtube-videos-work");
|
||||||
|
|
@ -1081,11 +1104,13 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
|
|
||||||
/* Warn about blocked countries */
|
/* Warn about blocked countries */
|
||||||
if (media.meta.restricted) {
|
if (media.meta.restricted) {
|
||||||
|
if(!abump){
|
||||||
user.socket.emit("queueWarn", {
|
user.socket.emit("queueWarn", {
|
||||||
msg: "Video is blocked in the following countries: " + media.meta.restricted,
|
msg: "Video is blocked in the following countries: " + media.meta.restricted,
|
||||||
link: data.link
|
link: data.link
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var item = new PlaylistItem(media, {
|
var item = new PlaylistItem(media, {
|
||||||
uid: self._nextuid++,
|
uid: self._nextuid++,
|
||||||
|
|
@ -1113,9 +1138,11 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
try {
|
try {
|
||||||
tvalidate(ttracks);
|
tvalidate(ttracks);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if(!abump){
|
||||||
user.socket.emit("errorMsg", {
|
user.socket.emit("errorMsg", {
|
||||||
msg: `Invalid text track error:` + error
|
msg: `Invalid text track error:` + error
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1187,6 +1214,9 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.channel.notifyModules("onMediaAdd", [data, media]);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.pos === "end" || this.current == null) {
|
if (data.pos === "end" || this.current == null) {
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ function TokebotModule(_channel){
|
||||||
|
|
||||||
//admin command registration
|
//admin command registration
|
||||||
this.channel.modules.chat.registerCommand("!updatetokes", this.updatetokesCmd.bind(this));
|
this.channel.modules.chat.registerCommand("!updatetokes", this.updatetokesCmd.bind(this));
|
||||||
|
this.channel.modules.chat.registerCommand("!reloadtokes", this.updatetokesCmd.bind(this));
|
||||||
this.channel.modules.chat.registerCommand("!tokesay", this.tokesayCmd.bind(this));
|
this.channel.modules.chat.registerCommand("!tokesay", this.tokesayCmd.bind(this));
|
||||||
this.channel.modules.chat.registerCommand("!tokeannounce", this.tokeyellCmd.bind(this));
|
this.channel.modules.chat.registerCommand("!tokeannounce", this.tokeyellCmd.bind(this));
|
||||||
this.channel.modules.chat.registerCommand("!tokeyell", this.tokeyellCmd.bind(this));
|
this.channel.modules.chat.registerCommand("!tokeyell", this.tokeyellCmd.bind(this));
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ function Media(id, title, seconds, type, meta) {
|
||||||
this.meta = meta;
|
this.meta = meta;
|
||||||
this.currentTime = 0;
|
this.currentTime = 0;
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
|
this.isBump = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Media.prototype = {
|
Media.prototype = {
|
||||||
|
|
|
||||||
|
|
@ -760,6 +760,7 @@ Callbacks = {
|
||||||
},
|
},
|
||||||
|
|
||||||
queue: function(data) {
|
queue: function(data) {
|
||||||
|
console.log(data);
|
||||||
PL_ACTION_QUEUE.queue(function (plq) {
|
PL_ACTION_QUEUE.queue(function (plq) {
|
||||||
stopQueueSpinner(data.item.media);
|
stopQueueSpinner(data.item.media);
|
||||||
var li = makeQueueEntry(data.item, true);
|
var li = makeQueueEntry(data.item, true);
|
||||||
|
|
@ -1199,6 +1200,10 @@ Callbacks = {
|
||||||
if (CHANNEL.opts.allow_voteskip && hasPermission("voteskip")) {
|
if (CHANNEL.opts.allow_voteskip && hasPermission("voteskip")) {
|
||||||
$("#voteskip").attr("disabled", false);
|
$("#voteskip").attr("disabled", false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendBumplists: function (data){
|
||||||
|
console.log(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -607,6 +607,58 @@ fptoke.elm = [//fpmod element array
|
||||||
]
|
]
|
||||||
|
|
||||||
fptoke.ocall = function(){
|
fptoke.ocall = function(){
|
||||||
|
$("#fpcontdiv").append(
|
||||||
|
$("<h4>").html("Tokebot Control"),
|
||||||
|
$("<form>").addClass("qt").append(
|
||||||
|
$("<button/>").addClass("btn btn-primary btn-ln").attr("id","tb-resetcd").text("Reset Toke Cooldown").prop("type","button").click(function(){
|
||||||
|
chatsmack("!resettoke");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if(window.CLIENT.rank <= 256){
|
||||||
|
$("#fpcontdiv").append(
|
||||||
|
$("<form>").addClass("qt").append(
|
||||||
|
$("<button/>").addClass("btn btn-primary btn-ln").attr("id","tb-resetcd").text("Reload Tokes File").prop("type","button").click(function(){
|
||||||
|
chatsmack("!reloadtokes");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
$("<form>").addClass("qt").append(
|
||||||
|
$("<button/>").addClass("btn btn-primary btn-ln").attr("id","tb-whisperbtn").text("Toke Whisper: ").prop("type","button").click(function(){
|
||||||
|
chatsmack("!tokewhisper " + $("#tb-whisper").val());
|
||||||
|
}),
|
||||||
|
$("<input>").prop("id","tb-whisper").prop("type","text").addClass("qs-form").attr("placeholder","Whisper text.").keydown(function(ev){
|
||||||
|
if(ev.keyCode==13){
|
||||||
|
chatsmack("!tokewhisper " + $("#tb-whisper").val());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
$("<form>").addClass("qt").append(
|
||||||
|
$("<button/>").addClass("btn btn-primary btn-ln").attr("id","tb-saybtn").text("Toke Say: ").prop("type","button").click(function(){
|
||||||
|
chatsmack("!tokesay " + $("#tb-say").val());
|
||||||
|
}),
|
||||||
|
$("<input>").prop("id","tb-say").prop("type","text").addClass("qs-form").attr("placeholder","Chat text.").keydown(function(ev){
|
||||||
|
if(ev.keyCode==13){
|
||||||
|
chatsmack("!tokesay " + $("#tb-say").val());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
$("<form>").addClass("qt").append(
|
||||||
|
$("<button/>").addClass("btn btn-primary btn-ln").attr("id","tb-yellbtn").text("Toke Announce: ").prop("type","button").click(function(){
|
||||||
|
chatsmack("!tokeyell " + $("#tb-yell").val());
|
||||||
|
}),
|
||||||
|
$("<input>").prop("id","tb-yell").prop("type","text").addClass("qs-form").attr("placeholder","Announce text.").keydown(function(ev){
|
||||||
|
if(ev.keyCode==13){
|
||||||
|
chatsmack("!tokeyell " + $("#tb-yell").val());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
modNested.popMenu();
|
modNested.popMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue