From 6d4558c9787bcc86f3f99b697331233d85a77f50 Mon Sep 17 00:00:00 2001 From: Xaekai Date: Fri, 16 Jun 2017 00:16:59 -0700 Subject: [PATCH] Allow channel path to be customizable We now allow server operators to customize the /r/ part of the channel links The new config option in the template is commented and the config module validates and will terminate with status 78 if an improper value is used. We've also dropped some old cruft and uses a more elegant method to assign CHANNEL.name Resolves #668 --- config.template.yaml | 34 ++++++++++++++------------ docs/gdrive-userscript-serveradmins.md | 2 +- src/bgtask.js | 5 ++-- src/channel-storage/migrator.js | 8 +++--- src/config.js | 15 +++++++++--- src/server.js | 9 ++++--- src/web/pug.js | 3 ++- src/web/routes/channel.js | 4 +-- src/web/webserver.js | 6 +++-- templates/account-channels.pug | 4 +-- templates/channel.pug | 2 +- templates/channeloptions.pug | 2 +- templates/head.pug | 12 +++++++-- templates/index.pug | 4 +-- www/js/acp.js | 6 ++--- www/js/callbacks.js | 17 ------------- www/js/data.js | 2 +- www/js/ui.js | 11 --------- 18 files changed, 71 insertions(+), 75 deletions(-) diff --git a/config.template.yaml b/config.template.yaml index 28fdd7c4..06499369 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -138,8 +138,6 @@ mail: # 5. Click "Server key" # 6. Under "APIs & auth" click "YouTube Data API" and then click "Enable API" youtube-v3-key: '' -# Minutes between saving channel state to disk -channel-save-interval: 5 # Limit for the number of channels a user can register max-channels-per-user: 5 # Limit for the number of accounts an IP address can register @@ -147,6 +145,24 @@ max-accounts-per-ip: 5 # Minimum number of seconds between guest logins from the same IP guest-login-delay: 60 +# Allows you to customize the path divider. The /r/ in http://localhost/r/yourchannel +# Acceptable characters are a-z A-Z 0-9 _ and - +channel-path: 'r' +# Allows you to blacklist certain channels. Users will be automatically kicked +# upon trying to join one. +channel-blacklist: [] +# Minutes between saving channel state to disk +channel-save-interval: 5 +# Determines channel data storage mechanism. +# Defaults to 'file', in which channel data is JSON stringified and saved to a file +# in the `chandump/` folder. This is the legacy behavior of CyTube. +# The other possible option is 'database', in which case each key-value pair of +# channel data is stored as a row in the `channel_data` database table. +# To migrate legacy chandump files to the database, shut down CyTube (to prevent +# concurrent updates), then run `node lib/channel-storage/migrate.js`. +channel-storage: + type: 'file' + # Configure statistics tracking stats: # Interval (in milliseconds) between data points - default 1h @@ -208,10 +224,6 @@ playlist: # The server must be invoked with node --expose-gc index.js for this to have any effect. aggressive-gc: false -# Allows you to blacklist certain channels. Users will be automatically kicked -# upon trying to join one. -channel-blacklist: [] - # If you have ffmpeg installed, you can query metadata from raw files, allowing # server-synched raw file playback. This requires the following: # * ffmpeg must be installed on the server @@ -231,16 +243,6 @@ setuid: # how long to wait in ms before changing uid/gid timeout: 15 -# Determines channel data storage mechanism. -# Defaults to 'file', in which channel data is JSON stringified and saved to a file -# in the `chandump/` folder. This is the legacy behavior of CyTube. -# The other possible option is 'database', in which case each key-value pair of -# channel data is stored as a row in the `channel_data` database table. -# To migrate legacy chandump files to the database, shut down CyTube (to prevent -# concurrent updates), then run `node lib/channel-storage/migrate.js`. -channel-storage: - type: 'file' - # Allows for external services to access the system commandline # Useful for setups where stdin isn't available such as when using PM2 service-socket: diff --git a/docs/gdrive-userscript-serveradmins.md b/docs/gdrive-userscript-serveradmins.md index 3f4b19a6..5f2c6ea7 100644 --- a/docs/gdrive-userscript-serveradmins.md +++ b/docs/gdrive-userscript-serveradmins.md @@ -21,4 +21,4 @@ example, for cytu.be I use: npm run generate-userscript CyTube http://cytu.be/r/* https://cytu.be/r/* ``` -This will generate `www/js/cytube-google-drive.user.js`. +This will generate `www/js/cytube-google-drive.user.js`. If you've changed the channel path, be sure to take that into account. diff --git a/src/bgtask.js b/src/bgtask.js index f1c63c9e..c54bbe19 100644 --- a/src/bgtask.js +++ b/src/bgtask.js @@ -61,6 +61,7 @@ function initPasswordResetCleanup(Server) { } function initChannelDumper(Server) { + const chanPath = Config.get('channel-path'); var CHANNEL_SAVE_INTERVAL = parseInt(Config.get("channel-save-interval")) * 60000; setInterval(function () { @@ -70,9 +71,9 @@ function initChannelDumper(Server) { return Promise.delay(wait).then(() => { if (!chan.dead && chan.users && chan.users.length > 0) { return chan.saveState().tap(() => { - LOGGER.info(`Saved /r/${chan.name}`); + LOGGER.info(`Saved /${chanPath}/${chan.name}`); }).catch(err => { - LOGGER.error(`Failed to save /r/${chan.name}: ${err.stack}`); + LOGGER.error(`Failed to save /${chanPath}/${chan.name}: ${err.stack}`); }); } }).catch(error => { diff --git a/src/channel-storage/migrator.js b/src/channel-storage/migrator.js index 7ddceba2..a182673e 100644 --- a/src/channel-storage/migrator.js +++ b/src/channel-storage/migrator.js @@ -114,6 +114,8 @@ function fixOldChandump(data) { } function migrate(src, dest, opts) { + const chanPath = Config.get('channel-path'); + return src.listChannels().then(names => { return Promise.reduce(names, (_, name) => { // A long time ago there was a bug where CyTube would save a different @@ -143,11 +145,11 @@ function migrate(src, dest, opts) { }); return dest.save(name, data); }).then(() => { - console.log(`Migrated /r/${name}`); + console.log(`Migrated /${chanPath}/${name}`); }).catch(ChannelNotFoundError, err => { - console.log(`Skipping /r/${name} (not present in the database)`); + console.log(`Skipping /${chanPath}/${name} (not present in the database)`); }).catch(err => { - console.error(`Failed to migrate /r/${name}: ${err.stack}`); + console.error(`Failed to migrate /${chanPath}/${name}: ${err.stack}`); }); }, 0); }); diff --git a/src/config.js b/src/config.js index 74c3d550..1d9839fe 100644 --- a/src/config.js +++ b/src/config.js @@ -70,7 +70,12 @@ var defaults = { "from-name": "CyTube Services" }, "youtube-v3-key": "", + "channel-blacklist": [], + "channel-path": "r", "channel-save-interval": 5, + "channel-storage": { + type: "file" + }, "max-channels-per-user": 5, "max-accounts-per-ip": 5, "guest-login-delay": 60, @@ -102,7 +107,6 @@ var defaults = { "max-items": 4000, "update-interval": 5 }, - "channel-blacklist": [], ffmpeg: { enabled: false, "ffprobe-exec": "ffprobe" @@ -114,9 +118,6 @@ var defaults = { "user": "nobody", "timeout": 15 }, - "channel-storage": { - type: "file" - }, "service-socket": { enabled: false, socket: "service.sock" @@ -390,6 +391,12 @@ function preprocessConfig(cfg) { }); cfg["channel-blacklist"] = tbl; + /* Check channel path */ + if(!/^[-\w]+$/.test(cfg["channel-blacklist"])){ + LOGGER.error("Channel paths may only use the same characters as usernames and channel names."); + process.exit(78); // sysexits.h for bad config + } + if (cfg["link-domain-blacklist"].length > 0) { cfg["link-domain-blacklist-regex"] = new RegExp( cfg["link-domain-blacklist"].join("|").replace(/\./g, "\\."), "gi"); diff --git a/src/server.js b/src/server.js index a9030d9d..57578a97 100644 --- a/src/server.js +++ b/src/server.js @@ -62,6 +62,7 @@ var Server = function () { self.announcement = null; self.infogetter = null; self.servers = {}; + self.chanPath = Config.get('channel-path'); // backend init var initModule; @@ -264,7 +265,7 @@ Server.prototype.unloadChannel = function (chan, options) { if (!options.skipSave) { chan.saveState().catch(error => { - LOGGER.error(`Failed to save /r/${chan.name} for unload: ${error.stack}`); + LOGGER.error(`Failed to save /${this.chanPath}/${chan.name} for unload: ${error.stack}`); }); } @@ -354,9 +355,9 @@ Server.prototype.shutdown = function () { Promise.map(this.channels, channel => { try { return channel.saveState().tap(() => { - LOGGER.info(`Saved /r/${channel.name}`); + LOGGER.info(`Saved /${this.chanPath}/${channel.name}`); }).catch(err => { - LOGGER.error(`Failed to save /r/${channel.name}: ${err.stack}`); + LOGGER.error(`Failed to save /${this.chanPath}/${channel.name}: ${err.stack}`); }); } catch (error) { LOGGER.error(`Failed to save channel: ${error.stack}`); @@ -391,7 +392,7 @@ Server.prototype.handlePartitionMapChange = function () { }); this.unloadChannel(channel, { skipSave: true }); }).catch(error => { - LOGGER.error(`Failed to unload /r/${channel.name} for ` + + LOGGER.error(`Failed to unload /${this.chanPath}/${channel.name} for ` + `partition map flip: ${error.stack}`); }); } diff --git a/src/web/pug.js b/src/web/pug.js index b944ed0b..204513e5 100644 --- a/src/web/pug.js +++ b/src/web/pug.js @@ -16,7 +16,8 @@ function merge(locals, res) { loginDomain: Config.get("https.enabled") ? Config.get("https.full-address") : Config.get("http.full-address"), csrfToken: typeof res.req.csrfToken === 'function' ? res.req.csrfToken() : '', - baseUrl: getBaseUrl(res) + baseUrl: getBaseUrl(res), + channelPath: Config.get("channel-path"), }; if (typeof locals !== "object") { return _locals; diff --git a/src/web/routes/channel.js b/src/web/routes/channel.js index 6be33232..e7113d84 100644 --- a/src/web/routes/channel.js +++ b/src/web/routes/channel.js @@ -4,8 +4,8 @@ import { sendPug } from '../pug'; import * as HTTPStatus from '../httpstatus'; import { HTTPError } from '../../errors'; -export default function initialize(app, ioConfig) { - app.get('/r/:channel', (req, res) => { +export default function initialize(app, ioConfig, chanPath) { + app.get(`/${chanPath}/:channel`, (req, res) => { if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) { throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` + 'channel name.', { status: HTTPStatus.NOT_FOUND }); diff --git a/src/web/webserver.js b/src/web/webserver.js index 4f3077a6..379d375c 100644 --- a/src/web/webserver.js +++ b/src/web/webserver.js @@ -132,6 +132,8 @@ module.exports = { * Initializes webserver callbacks */ init: function (app, webConfig, ioConfig, clusterClient, channelIndex, session) { + const chanPath = Config.get('channel-path'); + app.use((req, res, next) => { counters.add("http:request", 1); next(); @@ -147,7 +149,7 @@ module.exports = { } app.use(cookieParser(webConfig.getCookieSecret())); app.use(csrf.init(webConfig.getCookieDomain())); - app.use('/r/:channel', require('./middleware/ipsessioncookie').ipSessionCookieMiddleware); + app.use(`/${chanPath}/:channel`, require('./middleware/ipsessioncookie').ipSessionCookieMiddleware); initializeLog(app); require('./middleware/authorize')(app, session); @@ -176,7 +178,7 @@ module.exports = { LOGGER.info('Enabled express-minify for CSS and JS'); } - require('./routes/channel')(app, ioConfig); + require('./routes/channel')(app, ioConfig, chanPath); require('./routes/index')(app, channelIndex, webConfig.getMaxIndexEntries()); app.get('/sioconfig(.json)?', handleLegacySocketConfig); require('./routes/socketconfig')(app, clusterClient); diff --git a/templates/account-channels.pug b/templates/account-channels.pug index f756b483..286090f5 100644 --- a/templates/account-channels.pug +++ b/templates/account-channels.pug @@ -44,7 +44,7 @@ html(lang="en") input(type="hidden", name="name", value=c.name) button.btn.btn-xs.btn-danger(type="submit") Delete span.glyphicon.glyphicon-trash - a(href="/r/"+c.name, style="margin-left: 5px")= c.name + a(href=`/${channelPath}/${c.name}`, style="margin-left: 5px")= c.name .col-lg-6.col-md-6 h3 Register a new channel if newChannelError @@ -57,7 +57,7 @@ html(lang="en") .form-group label.control-label(for="channelname") Channel URL .input-group - span.input-group-addon #{baseUrl}/r/ + span.input-group-addon #{baseUrl}/#{channelPath}/ input#channelname.form-control(type="text", name="name", maxlength="30", onkeyup="checkChannel()") p#validate_channel.text-danger.pull-right button#register.btn.btn-primary.btn-block(type="submit") Register diff --git a/templates/channel.pug b/templates/channel.pug index 9fb6e24e..1452fc2a 100644 --- a/templates/channel.pug +++ b/templates/channel.pug @@ -11,7 +11,7 @@ html(lang="en") include nav +navheader() #nav-collapsible.collapse.navbar-collapse - - var cname = "/r/" + channelName + - var cname = `/${channelPath}/${channelName}` ul.nav.navbar-nav +navdefaultlinks(cname) li: a(href="javascript:void(0)", onclick="javascript:showUserOptions()") Options diff --git a/templates/channeloptions.pug b/templates/channeloptions.pug index e2c6f182..047f442d 100644 --- a/templates/channeloptions.pug +++ b/templates/channeloptions.pug @@ -86,7 +86,7 @@ mixin adminoptions #cs-adminoptions.tab-pane h4 Admin-Only Settings form.form-horizontal(action="javascript:void(0)") - - var defname = "CyTube - /r/" + channelName + - var defname = `CyTube - /${channelPath}/${channelName}` +textbox-auto("cs-pagetitle", "Page title", defname) +textbox-auto("cs-password", "Password", "leave blank to disable") +textbox-auto("cs-externalcss", "External CSS", "Stylesheet URL") diff --git a/templates/head.pug b/templates/head.pug index 57d047aa..ac944a24 100644 --- a/templates/head.pug +++ b/templates/head.pug @@ -11,8 +11,16 @@ mixin head() link(href="/css/cytube.css", rel="stylesheet") link(id="usertheme", href=DEFAULT_THEME, rel="stylesheet") - script(type="text/javascript"). - var DEFAULT_THEME = '#{DEFAULT_THEME}'; + if channelName + script(type="text/javascript"). + var DEFAULT_THEME = '#{DEFAULT_THEME}'; + var CHANNELPATH = '#{channelPath}'; + var CHANNELNAME = '#{channelName}'; + else + script(type="text/javascript"). + var DEFAULT_THEME = '#{DEFAULT_THEME}'; + var CHANNELPATH = '#{channelPath}'; + script(src="/js/theme.js") //[if lt IE 9] diff --git a/templates/index.pug b/templates/index.pug index 4ee05954..2b93f641 100644 --- a/templates/index.pug +++ b/templates/index.pug @@ -25,7 +25,7 @@ html(lang="en") tbody each chan in channels tr - td: a(href="/r/"+chan.name) #{chan.pagetitle} (#{chan.name}) + td: a(href=`/${channelPath}/${chan.name}`) #{chan.pagetitle} (#{chan.name}) td= chan.usercount td= chan.mediatitle .col-lg-3.col-md-3 @@ -37,6 +37,6 @@ html(lang="en") script(type="text/javascript"). $("#channelname").keydown(function (ev) { if (ev.keyCode === 13) { - location.href = "/r/" + $("#channelname").val(); + location.href = "/#{channelPath}/" + $("#channelname").val(); } }); diff --git a/www/js/acp.js b/www/js/acp.js index e9eef7f3..318ca849 100644 --- a/www/js/acp.js +++ b/www/js/acp.js @@ -453,8 +453,8 @@ console.log(channels[0]); channels.forEach(function (c) { var tr = $("").appendTo(tbl); var name = $("").appendTo(tr); - $("").attr("href", "/r/" + c.name) - .text(c.pagetitle + " (/r/" + c.name + ")") + $("").attr("href", `/${CHANNELPATH}/${c.name}`) + .text(c.pagetitle + ` (/${CHANNELPATH}/${c.name})`) .appendTo(name); var usercount = $("").text(c.usercount).appendTo(tr); count += c.usercount; @@ -475,7 +475,7 @@ console.log(channels[0]); .attr("title", "Unload") .appendTo(controlInner) .click(function () { - if (confirm("Are you sure you want to unload /r/" + c.name + "?")) { + if (confirm(`Are you sure you want to unload /${CHANNELPATH}/${c.name}?`)) { socket.emit("acp-force-unload", { name: c.name }); diff --git a/www/js/callbacks.js b/www/js/callbacks.js index d2d1438e..5b981f02 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -425,23 +425,6 @@ Callbacks = { if (!CLIENT.guest) { socket.emit("initUserPLCallbacks"); - if ($("#loginform").length === 0) { - return; - } - var logoutform = $("

").attr("id", "logoutform") - .addClass("navbar-text pull-right") - .insertAfter($("#loginform")); - - $("").attr("id", "welcome").text("Welcome, " + CLIENT.name) - .appendTo(logoutform); - $("").html(" · ").appendTo(logoutform); - var domain = $("#loginform").attr("action").replace("/login", ""); - $("").attr("id", "logout") - .attr("href", domain + "/logout?redirect=/r/" + CHANNEL.name) - .text("Logout") - .appendTo(logoutform); - - $("#loginform").remove(); } } }, diff --git a/www/js/data.js b/www/js/data.js index 8bf77dc8..996687c2 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -19,7 +19,7 @@ var CHANNEL = { css: "", js: "", motd: "", - name: false, + name: CHANNELNAME, usercount: 0, emotes: [] }; diff --git a/www/js/ui.js b/www/js/ui.js index d315faa3..b949a274 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -615,17 +615,6 @@ $("#shuffleplaylist").click(function() { } }); -/* load channel */ - -var loc = document.location+""; -var m = loc.match(/\/r\/([a-zA-Z0-9-_]+)/); -if(m) { - CHANNEL.name = m[1]; - if (CHANNEL.name.indexOf("#") !== -1) { - CHANNEL.name = CHANNEL.name.substring(0, CHANNEL.name.indexOf("#")); - } -} - /* channel ranks stuff */ function chanrankSubmit(rank) { var name = $("#cs-chanranks-name").val();