From 85fbe6bb5a94fda803427442a4067b4a04082c5f Mon Sep 17 00:00:00 2001 From: rainbownapkin Date: Sun, 21 Aug 2022 21:26:53 +0000 Subject: [PATCH] Autobump system and UI complete. The bulk of this update is now over. Just a couple of bug fixes and tweaks before release. --- README.md | 147 +++++++++++--- src/channel/autobump.js | 273 ++++++++++++++++++++++--- src/media.js | 1 + www/css/cytube.css | 54 ++++- www/css/themes/fore.st.css | 31 ++- www/img/bumps.png | Bin 0 -> 29840 bytes www/js/callbacks.js | 91 ++++++++- www/js/data.js | 3 +- www/js/fpanel.js | 397 ++++++++++++++++++++++++++++++++++++- 9 files changed, 925 insertions(+), 72 deletions(-) create mode 100644 www/img/bumps.png diff --git a/README.md b/README.md index 101eaed2..94eaf9d6 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ dev goals for 1.1 pineapple express: - reset toke cooldown every hour at *:19:30 for 4:20 !tokes ✓ - extend toke until *:20:00 if it ends after *:19:30 but before *:20:00 ✓ -- autobump +- autobump ✓ - Serverside ✓ - Bump Management System ✓ - bump object: name, user(person who made bump, optional), lowername, resettoke bool, id, listname, media item ✓ @@ -262,7 +262,7 @@ dev goals for 1.1 pineapple express: - list selection method ✓ - smashList ✓ - randomList ✓ - - bump selection method: random from last-half, round-robin, full random + - bump selection method: random from last-half, round-robin, full random ✓ - round-robin ✓ - full random ✓ - last-half random ✓ @@ -296,52 +296,135 @@ dev goals for 1.1 pineapple express: - bump selmed ✓ - list selmed ✓ - min length to bump ✓ - - Add manual only property to bump object(default false). + - 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 + - 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) ✓ - reset tokebot when tokebump is played ✓ - - Clientside + - Additions/Changes for Clientside ✓ + - send list function ✓ + - send deleted list name function ✓ + - send hist function ✓ + - send config function ✓ + - send list to all mods on list save ✓ + - send deleted list names to all mods on list deletion ✓ + - send config to all mods on config save ✓ + - send history to all mods when item added to history ✓ + - Save list filename as lowername ✓ + - Only load lists with filenames that end in .bump ✓ + - renameBump socket binding ✓ + - changeCreator socket binding ✓ + - renameList socket binding ✓ + - Store config/lists on sendBumplists callback ✓ + - Clientside ✓ + - Update Auto-Bump panel on data receive callback (when open) ✓ + - sendBumplists ✓ + - sendBumplist ✓ + - remove list upon deletion ✓ + - sendBumpHist ✓ + - sendBumpConf ✓ + - decorate bumps in playlist with translucent-green diagnal "BUMP" watermark (similiar to temporary lines) ✓ + - General Config ✓ + - Agro Level ✓ + - Bump Frequency ✓ + - Minimum Duration ✓ + - Bump Selection Method ✓ + - List Selection Method ✓ + - Bump/List Management ✓ + - Add Bump ✓ + - Add List ✓ + - Lists ✓ + - Create function to display/edit bumps inside of array ✓ + - bump title ✓ + - rename ✓ + - media title ✓ + - manual only ✓ + - toggle ✓ + - rtoke ✓ + - toggle ✓ + - duration ✓ + - user ✓ + - rename ✓ + - CSS ✓ + - Show Bump Controls Boolean ✓ + - Queue Next ✓ + - Delete Bump ✓ + - Create function to display array of bumplists ✓ + - Toggle Active ✓ + - Move Add Bump ✓ + - Delete Bumplist ✓ + - Rename Bumplist ✓ + - Display Bumplist ✓ + - CSS ✓ + - Active Lists (expanded) ✓ + - All Lists (collapsed) ✓ + - Play History (collapsed) ✓ - 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: 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: 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. 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: 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: 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 - - save temporary vids to channel library - - getplaylistlinks outputs in fpanel - - display links - - pop mod nmenu - - 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 - - merge fore.st theme changes to fore.st dusk, consider moving some of them over to cytube.css for easier management + - Critical Bug Fixes + - video sometimes unlatches if sync delayed on video start.(Fix pre-latch, if not duration check until sync is past 2s?) + - userlist profile & current connected users tooltips are currently broken ✓ + - chat does not fill screen in portrait mode (video height being subtracted while video collapsed) + - make serverside commands case insensitive(May have been intentional with cytube, don't give a shit, it's a bug.) + - serverside commands trigger other commands with same letters (!announce triggers !a. may only be an issue with single letter commands, double check this though.) + - fix playlist errors in fpanel + - Unescaped characters when preforming youtube search + - 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 Fixes + - hide "close playlist" button when playlist is in fpanel + - fix fpanel not resizing on video expand/collapse + - fix playlist resizing (both window resize, and video collapse/expand) in fpanel + - Execute serverside commands with whitespace before them while also sending them as normal chat to comform to tokebot behavior in (v1)Panama Red + - disable pm send to tokebot + - dont open playlist if legacy playlist disabled(on perm change) + - add item to end of block when queueing round robin + - remove tokebot from !clear dropdown in mod panel + - don't collapse expanded bumplists when reloading bump panel + - getBumplists when values are undefined at bump panel launch (should only happen on server startup anywho) + - Optimization/Refactor + - Change join/leave message prefix/postfix to single string with replacable user token instead of two vars + - Keep start time as proprety of media object while in playlist instead of in an array in metadata. + - Check and Sanatize input for Tokebot commands + - Check and Sanatize input from autobump panel + - Cytube Feature Changes + - save temporary vids to channel library + - enable/disable scroll on video change(if possible) + - Extras + - call scrollTo() on fpplaylist open + - call scrollTo() on move/queue next + - enable/disable either from mod prefs/qs (both enabled by default) + - getplaylistlinks outputs in fpanel + - display links + - pop mod nmenu + - accept invidious links as youtube links + - 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...) + - keep user on same page after failed login + - css variables in theme for ez customizablity + - Preformance test on older machines (not everyone has a high end machine) + - 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 - extra shit(probs wait til next update, or hotfix) + - check fi urls for "expire/expires/expiring" + - mark media object as expiring + - do not save + - set raw file color back to normal + - add icon to expiring items - shared tokes across channels - short chats (acronyms, emoji, single letters/numbers/symbols) pop in over video from left starting at top left, overflow pops in below, instead of in chat box. Chats slide back up into top of vid after 2s. (optional, default on) - pop out btn + - toggle 4:20 assist from mod panel/chat command + - close nested menu when parent button is thrown in panelbtn + - close fpanel when messagebuffer or userlist is clicked + - enable/disable in userprefs (default enabled) - basic profile page (in side panel) - improved mod chat (dedicated pop out to access mod channel chat from any channel) - user themes diff --git a/src/channel/autobump.js b/src/channel/autobump.js index 59bb3264..193cd700 100644 --- a/src/channel/autobump.js +++ b/src/channel/autobump.js @@ -19,6 +19,7 @@ var InfoGetter = require ("../get-info"); var ChannelModule = require("./module"); var Media = require("../media"); var util = require("../utilities"); +var Server = require("../server"); //type declerations const TYPE_NEWBUMP = { @@ -46,6 +47,17 @@ const TYPE_BFREQ = { max: "number" } +const TYPE_RENAME = { + bl: "string", + id: "number", + name: "string" +} + +const TYPE_LISTRENAME = { + oldname: "string", + newname: "string" +} + //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 @@ -72,8 +84,17 @@ function loadList(bfile){ } var data = JSON.parse(rdata); + var blist = []; + data.bumps.forEach(function(b){ + if(b != null){ + var nbump = new bump(b.name, b.user, b.rtoke, b.media, null, b.noauto); + nbump.listname = data.lowername; + nbump.id = b.id; + blist[b.id] = nbump; + } + }); - bumplists.set(data.lowername, new bumplist(data.name,data.bumps)); + bumplists.set(data.lowername, new bumplist(data.name,blist)); }); } @@ -86,10 +107,7 @@ function loadLists(cb, callp){ 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){ - - } + if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){ loadList("bumps/" + list); } }); @@ -115,7 +133,7 @@ function bump(name, user, rtoke, media, bumplist, noauto){//bump object construc this.id = null;//this is assigned by the bumplist :P this.media = media; this.noauto = noauto - if(bumplists != null){ + if(bumplist != null){ if(bumplists.get(bumplist.toLowerCase().replace(lowerReg, ""))){//if bumplist exists bumplists.get(bumplist.toLowerCase().replace(lowerReg, "")).addBump(this);//add this to the bumplist } @@ -129,12 +147,27 @@ function bumplist(name, bumps){//bumplist object constructor } //prototypes -bumplist.prototype.saveList = function(){ +//bump +bump.prototype.rename = function(name){ + this.name = name; + this.lowername = this.name.toLowerCase().replace(lowerReg, ""); +} + +//bumplist +bumplist.prototype.saveList = function(oldname){ var _this = this; - fs.writeFile("bumps/" + this.name + ".bump", JSON.stringify(this), function(err,data){ //RIPPED FROM TOKEBOT, NOT CHANGED YET + fs.writeFile("bumps/" + this.lowername + ".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); } + + Server.getServer().channels.forEach(function(channel){ + channel.users.forEach(function(user){ + if(user != null && user.account.effectiveRank >= 2){ + channel.modules.autobump.sendList(user, _this.lowername, oldname); + } + }); + }); }); }; @@ -196,6 +229,54 @@ bumplist.prototype.packList = function(){ return pbl; } +bumplist.prototype.rename = function(oldname,nname,cb){ + var _this = this; + var lnname = nname.toLowerCase().replace(lowerReg, ""); + + fs.rename("bumps/" + this.lowername + ".bump", "bumps/" + lnname + ".bump", function(err){ + if(err){ + console.log("[bump] BUMP LIST " + _this.name + " FILE RENAME ERROR: " + err); + } + + bumplists.delete(_this.lowername); + + _this.name = nname; + _this.lowername = lnname; + + _this.bumps.forEach(function(bump){ + if(bump != null){ + bump.listname = _this.lowername; + } + }); + + bumplists.set(_this.lowername, _this);//create new bumplist + + _this.saveList(oldname); + if(typeof cb === "function"){ + cb(); + } + }); +} + +bumplist.prototype.deleteList = function(){ + var _this = this; + fs.unlink("bumps/" + this.lowername + ".bump", function (err){ + if(err){ + console.log("[bump] BUMP LIST " + _this.name + " FILE DELETE ERROR: " + err); + } + + Server.getServer().channels.forEach(function(channel){ + channel.users.forEach(function(user){ + if(user != null && user.account.effectiveRank >= 2){ + user.socket.emit("rmBumplist",_this.lowername); + } + }); + }); + + bumplists.delete(_this.lowername); + }); +}; + //constructor function AutobumpModule(_channel){ ChannelModule.apply(this, arguments); @@ -218,7 +299,13 @@ 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("renameBump", TYPE_RENAME, this.handleRenameBump.bind(this, user));//handle newBumplist + user.socket.typecheckedOn("changeCreator", TYPE_RENAME, this.handleChangeCreator.bind(this, user));//handle newBumplist + user.socket.typecheckedOn("toggleRtoke", TYPE_DELBUMP, this.handleToggleRtoke.bind(this, user));//toggle rtoke + user.socket.typecheckedOn("toggleNoauto", TYPE_DELBUMP, this.handleToggleNoauto.bind(this, user));//toggle rtoke user.socket.typecheckedOn("newBumplist", '', this.handleNewBumplist.bind(this, user));//handle newBumplist + user.socket.typecheckedOn("delBumplist", '', this.handleDelBumplist.bind(this, user));//handle newBumplist + user.socket.typecheckedOn("renameBumplist", TYPE_LISTRENAME, this.handleRenameBumplist.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 @@ -227,6 +314,7 @@ AutobumpModule.prototype.onUserPostJoin = function (user){//on user join 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); } }; @@ -266,16 +354,18 @@ 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: [[],[]] - }; + if(this.channel.modules.playlist.current.next != null){ + 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); + this.channel.modules.playlist.handleMoveMedia("autobump",moved,true); + } } } } @@ -433,12 +523,13 @@ AutobumpModule.prototype.queueBump = function(listn, bid){//listname, bump ID var list = bumplists.get(listn.toLowerCase().replace(lowerReg, "")) var bump = null; var data = null; + var _this = this; 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); + return (cbump != null && bump != null && cbump.id == bump.id && cbump.listname === bump.listname); }); if(this.lastPlayed[lastin] != null){ @@ -447,6 +538,10 @@ AutobumpModule.prototype.queueBump = function(listn, bid){//listname, bump ID this.lastPlayed.push(bump) + this.channel.users.forEach(function(user){ + _this.sendHist(user); + }); + if(bump != null){ data = { //create faux data object for _addItem function @@ -524,8 +619,6 @@ AutobumpModule.prototype.loadConfig = function(_this){ var data = JSON.parse(rdata); - //console.log(data); - data.active == null ? [] : data.active; if(data.active != null){ data.active.forEach(function(al){ @@ -545,6 +638,7 @@ AutobumpModule.prototype.loadConfig = function(_this){ }; AutobumpModule.prototype.saveConfig = function(){ + var _this = this; var confObj = { active: [], agro: this.agro, @@ -564,6 +658,10 @@ AutobumpModule.prototype.saveConfig = function(){ console.log("[Autobump Config] " + err); return; } + + _this.channel.users.forEach(function(user){ + _this.sendConf(user) + }); }); } @@ -644,7 +742,7 @@ AutobumpModule.prototype.handleRemoveActive = function(user, data){ }else{ console.log("list not active!") } - + this.saveConfig(); } }; @@ -665,7 +763,7 @@ AutobumpModule.prototype.sendLists = function(user, data){ sendobj.lists.push(bumplist.packList()); }); - this.activeLists.forEach(function(bumplist){ + this.activeLists.forEach(function(bumplist, key){ sendobj.active.push(bumplist.lowername); }); @@ -678,10 +776,56 @@ AutobumpModule.prototype.sendLists = function(user, data){ } }; +AutobumpModule.prototype.sendList = function(user, data, oname){ + if(user != null && user.account.effectiveRank >= 2){ + var list = bumplists.get(data.toLowerCase().replace(lowerReg, "")) + if(list != null){ + var pack = list.packList(); + if(oname != null){ + pack.oname = oname; + } + user.socket.emit("sendBumplist", pack); + } + } +}; + +AutobumpModule.prototype.sendHist = function(user){ + if(user != null && user.account.effectiveRank >= 2){ + var sendobj = []; + this.lastPlayed.forEach(function(bump){ + sendobj.push([bump.listname, bump.id]); + + }); + user.socket.emit("sendBumphist", sendobj); + } +} + +AutobumpModule.prototype.sendConf = function(user){ + if(user != null && user.account.effectiveRank >= 2){ + var sendobj = { + active: [], + freq: this.bumpFreq, + agro: this.agro, + minBump: this.minBump, + bsort: this.selmed.name, + lsort: this.listsel.name + } + + this.activeLists.forEach(function(bumplist, key){ + sendobj.active.push(bumplist.lowername); + }); + + user.socket.emit("sendBumpconf", 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 + + this.sendList(user,data.toLowerCase().replace(lowerReg, "")); + }else{ user.socket.emit("errorMsg", { msg: "Bumplist name taken: " + data.toLowerCase().replace(lowerReg, ""), @@ -692,6 +836,51 @@ AutobumpModule.prototype.handleNewBumplist = function(user, data){//handle newBu } }; + +AutobumpModule.prototype.handleRenameBumplist = function(user, data){//handle newBumplist + if(user.account.effectiveRank >= 2){ + if(bumplists.get(data.oldname.toLowerCase().replace(lowerReg, "")) != null){ + var active = false; + var _this = this; + var list = bumplists.get(data.oldname.toLowerCase().replace(lowerReg, "")); + if(active = this.activeLists.get(data.oldname.toLowerCase().replace(lowerReg, "")) != null) + this.activeLists.delete(data.oldname.toLowerCase().replace(lowerReg, "")); + + list.rename(data.oldname, data.newname, function(){ + if(active){ + _this.activeLists.set(data.newname.toLowerCase().replace(lowerReg, ""), list); + _this.saveConfig(); + } + }); + + + }else{ + user.socket.emit("errorMsg", { + msg: "Non-Existant Bumplist: " + data.oldname.toLowerCase().replace(lowerReg, ""), + alert: true + }); + } + } +} + +AutobumpModule.prototype.handleDelBumplist = function(user, data){//handle newBumplist + if(user.account.effectiveRank >= 2){ + var active = false; + if(bumplists.get(data.toLowerCase().replace(lowerReg, "")) != null){ + if(active = this.activeLists.get(data.toLowerCase().replace(lowerReg, "")) != null) + this.activeLists.delete(data.toLowerCase().replace(lowerReg, "")); + + bumplists.get(data.toLowerCase().replace(lowerReg, "")).deleteList(); + }else{ + user.socket.emit("errorMsg", { + msg: "Non-Existant Bumplist: " + data.toLowerCase().replace(lowerReg, ""), + alert: true + }); + return; + } + } +}; + //bump management/commands AutobumpModule.prototype.handleNewBump = function(user, data){//handle newBump if(user.account.effectiveRank >= 2){ @@ -712,6 +901,43 @@ AutobumpModule.prototype.handleNewBump = function(user, data){//handle newBump } }; +AutobumpModule.prototype.handleRenameBump = function(user, data){ + if(user.account.effectiveRank >= 2){ + if(bumplists.get(data.bl) != null && bumplists.get(data.bl).bumps[data.id] != null){ + bumplists.get(data.bl).bumps[data.id].rename(data.name); + bumplists.get(data.bl).saveList(); + + } + } +} + +AutobumpModule.prototype.handleChangeCreator = function(user, data){ + if(user.account.effectiveRank >= 2){ + if(bumplists.get(data.bl) != null && bumplists.get(data.bl).bumps[data.id] != null){ + bumplists.get(data.bl).bumps[data.id].user = (data.name); + bumplists.get(data.bl).saveList(); + } + } +} + +AutobumpModule.prototype.handleToggleNoauto = function(user, data){ + if(user.account.effectiveRank >= 2){ + if(bumplists.get(data.bl) != null && bumplists.get(data.bl).bumps[data.id] != null){ + bumplists.get(data.bl).bumps[data.id].noauto = !bumplists.get(data.bl).bumps[data.id].noauto; + bumplists.get(data.bl).saveList(); + } + } +} + +AutobumpModule.prototype.handleToggleRtoke = function(user, data){ + if(user.account.effectiveRank >= 2){ + if(bumplists.get(data.bl) != null && bumplists.get(data.bl).bumps[data.id] != null){ + bumplists.get(data.bl).bumps[data.id].rtoke = !bumplists.get(data.bl).bumps[data.id].rtoke; + bumplists.get(data.bl).saveList(); + } + } +} + AutobumpModule.prototype.handleDeleteBump = function(user, data){ if(user.account.effectiveRank >= 2){ var bl = bumplists.get(data.bl); @@ -726,7 +952,4 @@ AutobumpModule.prototype.handleDeleteBump = function(user, data){ } }; - - - module.exports = AutobumpModule; diff --git a/src/media.js b/src/media.js index 5da9e21a..08b0494d 100644 --- a/src/media.js +++ b/src/media.js @@ -35,6 +35,7 @@ Media.prototype = { duration: this.duration, startTime: this.startTime, type: this.type, + isBump: this.isBump, meta: { restricted: this.meta.restricted, codec: this.meta.codec, diff --git a/www/css/cytube.css b/www/css/cytube.css index 78149642..c14d32bf 100644 --- a/www/css/cytube.css +++ b/www/css/cytube.css @@ -822,7 +822,7 @@ table td { padding-bottom: 2px; } -.queue_entry { +.queue_entry, .ab-bumparray-bump{ line-height: 22px; padding: 2px; font-size: 8pt; @@ -830,10 +830,53 @@ table td { border-top-width: 0; } +.ab-bumparray-span{ + display: grid; + grid-template: 2em 2em / 33% 33% auto; + grid-auto-flow: column; + justify-content: space-between; +} +.ab-bumparray-creator{ + display: inline; +} +.ab-bumparray-cspan{ + float: right; +} +#ab-bumphist-div{ + overflow: scroll; + max-height: 25em; +} +.ab-bumplist-lbl, .ab-newlist-form{ + display: inline; +} +.ab-newlist-form{ + margin-left: 0.5em; +} +.ab-newbump-form{ + display: inline; +} +.ab-newbump-cancel{ + display: inline; +} .emotelist-table { margin: auto; } - +#ab-freq-min, #ab-freq-max{ + width: 2em; +} +#ab-dur-min{ + width: 4em; +} +.ab-bumplists-ltype, .ab-bumplist-listname{ + display: inline; +} +.ab-bumplist-bumps{ + max-height: 30em; + overflow: scroll; +} +.ab-bumplist-delete{ + float: right; +} .emote-preview-container { width: 100px; height: 100px; @@ -937,6 +980,9 @@ input#logout[type="submit"]:hover { font-weight: 700; display: inline; } +#ab-bumparray-reuserfl{ + margin: 0px; +} body.hd #resize-video-larger, body.hd #resize-video-smaller { display: none; } @@ -944,3 +990,7 @@ body.hd #resize-video-larger, body.hd #resize-video-smaller { .userlist-ignored { text-decoration: line-through; } +#bumpi{ + background-image: url("../img/bumps.png"); + background-size: 2.2em; +} diff --git a/www/css/themes/fore.st.css b/www/css/themes/fore.st.css index 2b747784..622a406c 100644 --- a/www/css/themes/fore.st.css +++ b/www/css/themes/fore.st.css @@ -988,7 +988,7 @@ hr{ font-size:16px } p{ - margin:0 0 10px + margin:0; } .lead{ margin-bottom:20px; @@ -5396,7 +5396,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{ } /* CYTUBE EDIT */ body { - background-image: url(https://ourfore.st/img/frst.jpg); + background-image: url(../../img/frst.jpg); color: #c8c8c8; } @@ -5471,11 +5471,34 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{ border-bottom-left-radius: 0; border-bottom-right-radius: 0; } - .queue_entry { +.ab-bumparray-bump, .queue_entry { border-color: #949494; background-color: #060606; } - .navbar-inverse .navbar-text-nofloat { +.ab-bumparray-edit{ + margin: 0 0.5em 0 0.5em; +} +.ab-bumplist-list{ + background-color: #060606; + border-bottom: 1px solid #949494; + margin: 0; + padding-top: 0.5em; +} +.ab-bumplist-bumps{ + border-top: 1px solid #949494; +} +.ab-bumplist-setactive-active{ + color: #339933; + text-shadow: #339933 0 0 10px; +} +.ab-bumplist-delete{ + color: #c00; +} +.ab-bumplist-setactive:hover, .ab-bumplist-setactive-active:hover{ + color: #ded; + text-shadow: #339933 0 0 10px; +} +.navbar-inverse .navbar-text-nofloat { color: #888; } .queue_entry.queue_active { diff --git a/www/img/bumps.png b/www/img/bumps.png new file mode 100644 index 0000000000000000000000000000000000000000..2204b5c8d039349aa436dcee46864fc32c6e9a9d GIT binary patch literal 29840 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 eSN97*c~T").append( $("