Merge pull request #685 from Xaekai/custom.path

Customize channel path
This commit is contained in:
Calvin Montgomery 2017-06-16 21:22:11 -07:00 committed by GitHub
commit 0f5193c700
18 changed files with 71 additions and 75 deletions

View File

@ -138,8 +138,6 @@ mail:
# 5. Click "Server key" # 5. Click "Server key"
# 6. Under "APIs & auth" click "YouTube Data API" and then click "Enable API" # 6. Under "APIs & auth" click "YouTube Data API" and then click "Enable API"
youtube-v3-key: '' youtube-v3-key: ''
# Minutes between saving channel state to disk
channel-save-interval: 5
# Limit for the number of channels a user can register # Limit for the number of channels a user can register
max-channels-per-user: 5 max-channels-per-user: 5
# Limit for the number of accounts an IP address can register # 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 # Minimum number of seconds between guest logins from the same IP
guest-login-delay: 60 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 # Configure statistics tracking
stats: stats:
# Interval (in milliseconds) between data points - default 1h # 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. # The server must be invoked with node --expose-gc index.js for this to have any effect.
aggressive-gc: false 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 # If you have ffmpeg installed, you can query metadata from raw files, allowing
# server-synched raw file playback. This requires the following: # server-synched raw file playback. This requires the following:
# * ffmpeg must be installed on the server # * ffmpeg must be installed on the server
@ -231,16 +243,6 @@ setuid:
# how long to wait in ms before changing uid/gid # how long to wait in ms before changing uid/gid
timeout: 15 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 # Allows for external services to access the system commandline
# Useful for setups where stdin isn't available such as when using PM2 # Useful for setups where stdin isn't available such as when using PM2
service-socket: service-socket:

View File

@ -21,4 +21,4 @@ example, for cytu.be I use:
npm run generate-userscript CyTube http://cytu.be/r/* https://cytu.be/r/* 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.

View File

@ -61,6 +61,7 @@ function initPasswordResetCleanup(Server) {
} }
function initChannelDumper(Server) { function initChannelDumper(Server) {
const chanPath = Config.get('channel-path');
var CHANNEL_SAVE_INTERVAL = parseInt(Config.get("channel-save-interval")) var CHANNEL_SAVE_INTERVAL = parseInt(Config.get("channel-save-interval"))
* 60000; * 60000;
setInterval(function () { setInterval(function () {
@ -70,9 +71,9 @@ function initChannelDumper(Server) {
return Promise.delay(wait).then(() => { return Promise.delay(wait).then(() => {
if (!chan.dead && chan.users && chan.users.length > 0) { if (!chan.dead && chan.users && chan.users.length > 0) {
return chan.saveState().tap(() => { return chan.saveState().tap(() => {
LOGGER.info(`Saved /r/${chan.name}`); LOGGER.info(`Saved /${chanPath}/${chan.name}`);
}).catch(err => { }).catch(err => {
LOGGER.error(`Failed to save /r/${chan.name}: ${err.stack}`); LOGGER.error(`Failed to save /${chanPath}/${chan.name}: ${err.stack}`);
}); });
} }
}).catch(error => { }).catch(error => {

View File

@ -114,6 +114,8 @@ function fixOldChandump(data) {
} }
function migrate(src, dest, opts) { function migrate(src, dest, opts) {
const chanPath = Config.get('channel-path');
return src.listChannels().then(names => { return src.listChannels().then(names => {
return Promise.reduce(names, (_, name) => { return Promise.reduce(names, (_, name) => {
// A long time ago there was a bug where CyTube would save a different // 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); return dest.save(name, data);
}).then(() => { }).then(() => {
console.log(`Migrated /r/${name}`); console.log(`Migrated /${chanPath}/${name}`);
}).catch(ChannelNotFoundError, err => { }).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 => { }).catch(err => {
console.error(`Failed to migrate /r/${name}: ${err.stack}`); console.error(`Failed to migrate /${chanPath}/${name}: ${err.stack}`);
}); });
}, 0); }, 0);
}); });

View File

@ -70,7 +70,12 @@ var defaults = {
"from-name": "CyTube Services" "from-name": "CyTube Services"
}, },
"youtube-v3-key": "", "youtube-v3-key": "",
"channel-blacklist": [],
"channel-path": "r",
"channel-save-interval": 5, "channel-save-interval": 5,
"channel-storage": {
type: "file"
},
"max-channels-per-user": 5, "max-channels-per-user": 5,
"max-accounts-per-ip": 5, "max-accounts-per-ip": 5,
"guest-login-delay": 60, "guest-login-delay": 60,
@ -102,7 +107,6 @@ var defaults = {
"max-items": 4000, "max-items": 4000,
"update-interval": 5 "update-interval": 5
}, },
"channel-blacklist": [],
ffmpeg: { ffmpeg: {
enabled: false, enabled: false,
"ffprobe-exec": "ffprobe" "ffprobe-exec": "ffprobe"
@ -114,9 +118,6 @@ var defaults = {
"user": "nobody", "user": "nobody",
"timeout": 15 "timeout": 15
}, },
"channel-storage": {
type: "file"
},
"service-socket": { "service-socket": {
enabled: false, enabled: false,
socket: "service.sock" socket: "service.sock"
@ -390,6 +391,12 @@ function preprocessConfig(cfg) {
}); });
cfg["channel-blacklist"] = tbl; 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) { if (cfg["link-domain-blacklist"].length > 0) {
cfg["link-domain-blacklist-regex"] = new RegExp( cfg["link-domain-blacklist-regex"] = new RegExp(
cfg["link-domain-blacklist"].join("|").replace(/\./g, "\\."), "gi"); cfg["link-domain-blacklist"].join("|").replace(/\./g, "\\."), "gi");

View File

@ -62,6 +62,7 @@ var Server = function () {
self.announcement = null; self.announcement = null;
self.infogetter = null; self.infogetter = null;
self.servers = {}; self.servers = {};
self.chanPath = Config.get('channel-path');
// backend init // backend init
var initModule; var initModule;
@ -264,7 +265,7 @@ Server.prototype.unloadChannel = function (chan, options) {
if (!options.skipSave) { if (!options.skipSave) {
chan.saveState().catch(error => { 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 => { Promise.map(this.channels, channel => {
try { try {
return channel.saveState().tap(() => { return channel.saveState().tap(() => {
LOGGER.info(`Saved /r/${channel.name}`); LOGGER.info(`Saved /${this.chanPath}/${channel.name}`);
}).catch(err => { }).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) { } catch (error) {
LOGGER.error(`Failed to save channel: ${error.stack}`); LOGGER.error(`Failed to save channel: ${error.stack}`);
@ -391,7 +392,7 @@ Server.prototype.handlePartitionMapChange = function () {
}); });
this.unloadChannel(channel, { skipSave: true }); this.unloadChannel(channel, { skipSave: true });
}).catch(error => { }).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}`); `partition map flip: ${error.stack}`);
}); });
} }

View File

@ -16,7 +16,8 @@ function merge(locals, res) {
loginDomain: Config.get("https.enabled") ? Config.get("https.full-address") loginDomain: Config.get("https.enabled") ? Config.get("https.full-address")
: Config.get("http.full-address"), : Config.get("http.full-address"),
csrfToken: typeof res.req.csrfToken === 'function' ? res.req.csrfToken() : '', csrfToken: typeof res.req.csrfToken === 'function' ? res.req.csrfToken() : '',
baseUrl: getBaseUrl(res) baseUrl: getBaseUrl(res),
channelPath: Config.get("channel-path"),
}; };
if (typeof locals !== "object") { if (typeof locals !== "object") {
return _locals; return _locals;

View File

@ -4,8 +4,8 @@ import { sendPug } from '../pug';
import * as HTTPStatus from '../httpstatus'; import * as HTTPStatus from '../httpstatus';
import { HTTPError } from '../../errors'; import { HTTPError } from '../../errors';
export default function initialize(app, ioConfig) { export default function initialize(app, ioConfig, chanPath) {
app.get('/r/:channel', (req, res) => { app.get(`/${chanPath}/:channel`, (req, res) => {
if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) { if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) {
throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` + throw new HTTPError(`"${sanitizeText(req.params.channel)}" is not a valid ` +
'channel name.', { status: HTTPStatus.NOT_FOUND }); 'channel name.', { status: HTTPStatus.NOT_FOUND });

View File

@ -132,6 +132,8 @@ module.exports = {
* Initializes webserver callbacks * Initializes webserver callbacks
*/ */
init: function (app, webConfig, ioConfig, clusterClient, channelIndex, session) { init: function (app, webConfig, ioConfig, clusterClient, channelIndex, session) {
const chanPath = Config.get('channel-path');
app.use((req, res, next) => { app.use((req, res, next) => {
counters.add("http:request", 1); counters.add("http:request", 1);
next(); next();
@ -147,7 +149,7 @@ module.exports = {
} }
app.use(cookieParser(webConfig.getCookieSecret())); app.use(cookieParser(webConfig.getCookieSecret()));
app.use(csrf.init(webConfig.getCookieDomain())); app.use(csrf.init(webConfig.getCookieDomain()));
app.use('/r/:channel', require('./middleware/ipsessioncookie').ipSessionCookieMiddleware); app.use(`/${chanPath}/:channel`, require('./middleware/ipsessioncookie').ipSessionCookieMiddleware);
initializeLog(app); initializeLog(app);
require('./middleware/authorize')(app, session); require('./middleware/authorize')(app, session);
@ -176,7 +178,7 @@ module.exports = {
LOGGER.info('Enabled express-minify for CSS and JS'); 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()); require('./routes/index')(app, channelIndex, webConfig.getMaxIndexEntries());
app.get('/sioconfig(.json)?', handleLegacySocketConfig); app.get('/sioconfig(.json)?', handleLegacySocketConfig);
require('./routes/socketconfig')(app, clusterClient); require('./routes/socketconfig')(app, clusterClient);

View File

@ -44,7 +44,7 @@ html(lang="en")
input(type="hidden", name="name", value=c.name) input(type="hidden", name="name", value=c.name)
button.btn.btn-xs.btn-danger(type="submit") Delete button.btn.btn-xs.btn-danger(type="submit") Delete
span.glyphicon.glyphicon-trash 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 .col-lg-6.col-md-6
h3 Register a new channel h3 Register a new channel
if newChannelError if newChannelError
@ -57,7 +57,7 @@ html(lang="en")
.form-group .form-group
label.control-label(for="channelname") Channel URL label.control-label(for="channelname") Channel URL
.input-group .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()") input#channelname.form-control(type="text", name="name", maxlength="30", onkeyup="checkChannel()")
p#validate_channel.text-danger.pull-right p#validate_channel.text-danger.pull-right
button#register.btn.btn-primary.btn-block(type="submit") Register button#register.btn.btn-primary.btn-block(type="submit") Register

View File

@ -11,7 +11,7 @@ html(lang="en")
include nav include nav
+navheader() +navheader()
#nav-collapsible.collapse.navbar-collapse #nav-collapsible.collapse.navbar-collapse
- var cname = "/r/" + channelName - var cname = `/${channelPath}/${channelName}`
ul.nav.navbar-nav ul.nav.navbar-nav
+navdefaultlinks(cname) +navdefaultlinks(cname)
li: a(href="javascript:void(0)", onclick="javascript:showUserOptions()") Options li: a(href="javascript:void(0)", onclick="javascript:showUserOptions()") Options

View File

@ -86,7 +86,7 @@ mixin adminoptions
#cs-adminoptions.tab-pane #cs-adminoptions.tab-pane
h4 Admin-Only Settings h4 Admin-Only Settings
form.form-horizontal(action="javascript:void(0)") 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-pagetitle", "Page title", defname)
+textbox-auto("cs-password", "Password", "leave blank to disable") +textbox-auto("cs-password", "Password", "leave blank to disable")
+textbox-auto("cs-externalcss", "External CSS", "Stylesheet URL") +textbox-auto("cs-externalcss", "External CSS", "Stylesheet URL")

View File

@ -11,8 +11,16 @@ mixin head()
link(href="/css/cytube.css", rel="stylesheet") link(href="/css/cytube.css", rel="stylesheet")
link(id="usertheme", href=DEFAULT_THEME, rel="stylesheet") link(id="usertheme", href=DEFAULT_THEME, rel="stylesheet")
script(type="text/javascript"). if channelName
var DEFAULT_THEME = '#{DEFAULT_THEME}'; 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") script(src="/js/theme.js")
//[if lt IE 9] //[if lt IE 9]
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>

View File

@ -25,7 +25,7 @@ html(lang="en")
tbody tbody
each chan in channels each chan in channels
tr 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.usercount
td= chan.mediatitle td= chan.mediatitle
.col-lg-3.col-md-3 .col-lg-3.col-md-3
@ -37,6 +37,6 @@ html(lang="en")
script(type="text/javascript"). script(type="text/javascript").
$("#channelname").keydown(function (ev) { $("#channelname").keydown(function (ev) {
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
location.href = "/r/" + $("#channelname").val(); location.href = "/#{channelPath}/" + $("#channelname").val();
} }
}); });

View File

@ -453,8 +453,8 @@ console.log(channels[0]);
channels.forEach(function (c) { channels.forEach(function (c) {
var tr = $("<tr/>").appendTo(tbl); var tr = $("<tr/>").appendTo(tbl);
var name = $("<td/>").appendTo(tr); var name = $("<td/>").appendTo(tr);
$("<a/>").attr("href", "/r/" + c.name) $("<a/>").attr("href", `/${CHANNELPATH}/${c.name}`)
.text(c.pagetitle + " (/r/" + c.name + ")") .text(c.pagetitle + ` (/${CHANNELPATH}/${c.name})`)
.appendTo(name); .appendTo(name);
var usercount = $("<td/>").text(c.usercount).appendTo(tr); var usercount = $("<td/>").text(c.usercount).appendTo(tr);
count += c.usercount; count += c.usercount;
@ -475,7 +475,7 @@ console.log(channels[0]);
.attr("title", "Unload") .attr("title", "Unload")
.appendTo(controlInner) .appendTo(controlInner)
.click(function () { .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", { socket.emit("acp-force-unload", {
name: c.name name: c.name
}); });

View File

@ -425,23 +425,6 @@ Callbacks = {
if (!CLIENT.guest) { if (!CLIENT.guest) {
socket.emit("initUserPLCallbacks"); socket.emit("initUserPLCallbacks");
if ($("#loginform").length === 0) {
return;
}
var logoutform = $("<p/>").attr("id", "logoutform")
.addClass("navbar-text pull-right")
.insertAfter($("#loginform"));
$("<span/>").attr("id", "welcome").text("Welcome, " + CLIENT.name)
.appendTo(logoutform);
$("<span/>").html("&nbsp;&middot;&nbsp;").appendTo(logoutform);
var domain = $("#loginform").attr("action").replace("/login", "");
$("<a/>").attr("id", "logout")
.attr("href", domain + "/logout?redirect=/r/" + CHANNEL.name)
.text("Logout")
.appendTo(logoutform);
$("#loginform").remove();
} }
} }
}, },

View File

@ -19,7 +19,7 @@ var CHANNEL = {
css: "", css: "",
js: "", js: "",
motd: "", motd: "",
name: false, name: CHANNELNAME,
usercount: 0, usercount: 0,
emotes: [] emotes: []
}; };

View File

@ -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 */ /* channel ranks stuff */
function chanrankSubmit(rank) { function chanrankSubmit(rank) {
var name = $("#cs-chanranks-name").val(); var name = $("#cs-chanranks-name").val();