From 1453e262d1f851130e2eb356847ada3996610297 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 19 Feb 2018 13:08:03 +0200 Subject: [PATCH 1/2] Enforce padding-line-between-statements in eslint --- .eslintrc.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.eslintrc.yml b/.eslintrc.yml index 181bc3a2..bc4d24d6 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -48,6 +48,18 @@ rules: no-var: error object-curly-spacing: [error, never] padded-blocks: [error, never] + padding-line-between-statements: + - error + - blankLine: always + prev: + - block + - block-like + next: "*" + - blankLine: always + prev: "*" + next: + - block + - block-like prefer-const: error prefer-rest-params: error prefer-spread: error From c733e72e7a4e44455fc2415247a962bb9b9b68fb Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 20 Feb 2018 09:28:04 +0200 Subject: [PATCH 2/2] Auto-fix code for padding-line-between-statements rule --- client/js/autocompletion.js | 6 ++++++ client/js/condensed.js | 1 + client/js/join-channel.js | 2 ++ client/js/libs/handlebars/colorClass.js | 1 + .../libs/handlebars/ircmessageparser/fill.js | 1 + .../handlebars/ircmessageparser/findNames.js | 1 + .../handlebars/ircmessageparser/parseStyle.js | 9 +++++++++ client/js/libs/handlebars/parse.js | 8 ++++++++ client/js/loading-error-handlers.js | 1 + client/js/lounge.js | 18 ++++++++++++++++++ client/js/options.js | 2 ++ client/js/renderPreview.js | 6 ++++++ client/js/socket-events/change_password.js | 1 + client/js/socket-events/more.js | 1 + client/js/socket-events/msg.js | 8 ++++++++ client/js/socket-events/nick.js | 1 + client/js/utils.js | 4 ++++ client/views/index.js | 1 + index.js | 1 + scripts/changelog.js | 7 +++++++ src/client.js | 8 ++++++++ src/clientManager.js | 6 ++++++ src/command-line/index.js | 2 ++ src/command-line/uninstall.js | 1 + src/command-line/users/add.js | 1 + src/command-line/users/edit.js | 1 + src/command-line/users/reset.js | 2 ++ src/command-line/utils.js | 3 +++ src/helper.js | 2 ++ src/models/network.js | 2 ++ src/plugins/auth/ldap.js | 4 ++++ src/plugins/changelog.js | 1 + src/plugins/inputs/msg.js | 1 + src/plugins/inputs/query.js | 3 +++ src/plugins/inputs/topic.js | 1 + src/plugins/irc-events/banlist.js | 2 ++ src/plugins/irc-events/link.js | 4 ++++ src/plugins/irc-events/message.js | 1 + src/plugins/irc-events/mode.js | 3 +++ src/plugins/irc-events/names.js | 1 + src/plugins/irc-events/whois.js | 1 + src/plugins/packages/themes.js | 3 +++ src/server.js | 11 +++++++++++ src/userLog.js | 1 + test/plugins/auth/ldap.js | 1 + test/plugins/packages/indexTest.js | 1 + test/server.js | 1 + test/util.js | 1 + 48 files changed, 149 insertions(+) diff --git a/client/js/autocompletion.js b/client/js/autocompletion.js index f6080a20..ec8e1d7f 100644 --- a/client/js/autocompletion.js +++ b/client/js/autocompletion.js @@ -50,6 +50,7 @@ const nicksStrategy = { match: /\B(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/, search(term, callback) { term = term.slice(1); + if (term[0] === "@") { callback(completeNicks(term.slice(1), true) .map((val) => ["@" + val[0], "@" + val[1]])); @@ -122,6 +123,7 @@ const foregroundColorStrategy = { post: "", }).rendered]; } + return i; }); @@ -150,6 +152,7 @@ const backgroundColorStrategy = { post: "", }).rendered]; } + return pair; }) .map((pair) => pair.concat(match[1])); // Needed to pass fg color to `template`... @@ -268,9 +271,11 @@ function completeNicks(word, isFuzzy) { } const words = users.data("nicks"); + if (isFuzzy) { return fuzzyGrep(word, words); } + return $.grep( words, (w) => !w.toLowerCase().indexOf(word) @@ -291,6 +296,7 @@ function completeChans(word) { .find(".chan") .each(function() { const self = $(this); + if (!self.hasClass("lobby")) { words.push(self.attr("aria-label")); } diff --git a/client/js/condensed.js b/client/js/condensed.js index ee9726f8..73e3c2ee 100644 --- a/client/js/condensed.js +++ b/client/js/condensed.js @@ -63,6 +63,7 @@ function updateText(condensed, addedTypes) { }); let text = strings.pop(); + if (strings.length) { text = strings.join(", ") + ", and " + text; } diff --git a/client/js/join-channel.js b/client/js/join-channel.js index 9275e4e6..b21a67eb 100644 --- a/client/js/join-channel.js +++ b/client/js/join-channel.js @@ -68,6 +68,7 @@ sidebar.on("submit", ".join-form", function() { const key = form.find("input[name='key']"); const keyString = key.val(); const chan = utils.findCurrentNetworkChan(channelString); + if (chan.length) { chan.trigger("click"); } else { @@ -76,6 +77,7 @@ sidebar.on("submit", ".join-form", function() { target: form.prev().data("id"), }); } + closeForm(form.closest(".network")); return false; }); diff --git a/client/js/libs/handlebars/colorClass.js b/client/js/libs/handlebars/colorClass.js index 2780f3f2..00333e14 100644 --- a/client/js/libs/handlebars/colorClass.js +++ b/client/js/libs/handlebars/colorClass.js @@ -3,6 +3,7 @@ // Generates a string from "color-1" to "color-32" based on an input string module.exports = function(str) { let hash = 0; + for (let i = 0; i < str.length; i++) { hash += str.charCodeAt(i); } diff --git a/client/js/libs/handlebars/ircmessageparser/fill.js b/client/js/libs/handlebars/ircmessageparser/fill.js index 152c4ee7..564cf478 100644 --- a/client/js/libs/handlebars/ircmessageparser/fill.js +++ b/client/js/libs/handlebars/ircmessageparser/fill.js @@ -16,6 +16,7 @@ function fill(existingEntries, text) { end: textSegment.start, }); } + position = textSegment.end; return acc; }, []); diff --git a/client/js/libs/handlebars/ircmessageparser/findNames.js b/client/js/libs/handlebars/ircmessageparser/findNames.js index b6dc5093..5ce136b5 100644 --- a/client/js/libs/handlebars/ircmessageparser/findNames.js +++ b/client/js/libs/handlebars/ircmessageparser/findNames.js @@ -11,6 +11,7 @@ function findNames(text, users) { } let match; + while ((match = nickRegExp.exec(text))) { if (users.indexOf(match[1]) > -1) { result.push({ diff --git a/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/client/js/libs/handlebars/ircmessageparser/parseStyle.js index 16deb790..df1b2456 100644 --- a/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -46,6 +46,7 @@ function parseStyle(text) { strikethrough = false; monospace = false; }; + resetStyle(); // When called, this "closes" the current fragment by adding an entry to the @@ -111,9 +112,11 @@ function parseStyle(text) { if (colorCodes) { textColor = Number(colorCodes[1]); + if (colorCodes[2]) { bgColor = Number(colorCodes[2]); } + // Color code length is > 1, so bump the current position cursor by as // much (and reset the start cursor for the current text block as well) position += colorCodes[0].length; @@ -123,6 +126,7 @@ function parseStyle(text) { textColor = undefined; bgColor = undefined; } + break; case HEX_COLOR: @@ -132,9 +136,11 @@ function parseStyle(text) { if (colorCodes) { hexColor = colorCodes[1].toUpperCase(); + if (colorCodes[2]) { hexBgColor = colorCodes[2].toUpperCase(); } + // Color code length is > 1, so bump the current position cursor by as // much (and reset the start cursor for the current text block as well) position += colorCodes[0].length; @@ -154,6 +160,7 @@ function parseStyle(text) { textColor = tmp; break; } + case ITALIC: emitFragment(); italic = !italic; @@ -194,12 +201,14 @@ function prepare(text) { .reduce((prev, curr) => { if (prev.length) { const lastEntry = prev[prev.length - 1]; + if (properties.every((key) => curr[key] === lastEntry[key])) { lastEntry.text += curr.text; lastEntry.end += curr.text.length; return prev; } } + return prev.concat([curr]); }, []); } diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index 71bb3017..52d6f5be 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -13,24 +13,31 @@ const colorClass = require("./colorClass"); // Create an HTML `span` with styling information for a given fragment function createFragment(fragment) { const classes = []; + if (fragment.bold) { classes.push("irc-bold"); } + if (fragment.textColor !== undefined) { classes.push("irc-fg" + fragment.textColor); } + if (fragment.bgColor !== undefined) { classes.push("irc-bg" + fragment.bgColor); } + if (fragment.italic) { classes.push("irc-italic"); } + if (fragment.underline) { classes.push("irc-underline"); } + if (fragment.strikethrough) { classes.push("irc-strikethrough"); } + if (fragment.monospace) { classes.push("irc-monospace"); } @@ -89,6 +96,7 @@ module.exports = function parse(text, users) { if (intersection) { return prev; } + return prev.concat([curr]); }, []); diff --git a/client/js/loading-error-handlers.js b/client/js/loading-error-handlers.js index 18e69182..66b9f2b4 100644 --- a/client/js/loading-error-handlers.js +++ b/client/js/loading-error-handlers.js @@ -11,6 +11,7 @@ (function() { var displayReload = function displayReload() { var loadingReload = document.getElementById("loading-reload"); + if (loadingReload) { loadingReload.style.display = "block"; } diff --git a/client/js/lounge.js b/client/js/lounge.js index d3af1878..4264c767 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -100,6 +100,7 @@ $(function() { }); const channel = target.closest(".chan"); + if (utils.isOpInChannel(channel) && channel.data("type") === "channel") { output += templates.contextmenu_divider(); output += templates.contextmenu_item({ @@ -127,6 +128,7 @@ $(function() { data: target.data("target"), }); output += templates.contextmenu_divider(); + if (target.hasClass("lobby")) { output += templates.contextmenu_item({ class: "list", @@ -141,6 +143,7 @@ $(function() { data: target.data("id"), }); } + if (target.hasClass("channel")) { output += templates.contextmenu_item({ class: "list", @@ -149,6 +152,7 @@ $(function() { data: target.data("id"), }); } + output += templates.contextmenu_item({ class: "close", action: "close", @@ -213,6 +217,7 @@ $(function() { }); let focus = $.noop; + if (!("ontouchstart" in window || navigator.maxTouchPoints > 0)) { focus = function() { if (chat.find(".active").hasClass("chan")) { @@ -250,6 +255,7 @@ $(function() { if (text.charAt(0) === "/") { const args = text.substr(1).split(" "); const cmd = args.shift().toLowerCase(); + if (typeof utils.inputCommands[cmd] === "function" && utils.inputCommands[cmd](args)) { return; } @@ -336,6 +342,7 @@ $(function() { const openWindow = function openWindow(e, data) { const self = $(this); const target = self.data("target"); + if (!target) { return; } @@ -397,13 +404,16 @@ $(function() { let title = $(document.body).data("app-name"); const chanTitle = chan.attr("aria-label"); + if (chanTitle.length > 0) { title = `${chanTitle} — ${title}`; } + document.title = title; const type = chan.data("type"); let placeholder = ""; + if (type === "channel" || type === "query") { placeholder = `Write to ${chanTitle}`; } @@ -418,6 +428,7 @@ $(function() { } const chanChat = chan.find(".chat"); + if (chanChat.length > 0 && type !== "special") { chanChat.sticky(); } @@ -446,6 +457,7 @@ $(function() { if (data && data.pushState === false) { return; } + const state = {}; if (self.prop("id")) { @@ -486,10 +498,12 @@ $(function() { if (chan.hasClass("lobby")) { cmd = "/quit"; const server = chan.find(".name").html(); + if (!confirm("Disconnect from " + server + "?")) { // eslint-disable-line no-alert return false; } } + socket.emit("input", { target: chan.data("id"), text: cmd, @@ -602,6 +616,7 @@ $(function() { if ($("body").hasClass("public") && (window.location.hash === "#connect" || window.location.hash === "")) { $("#connect").one("show", function() { const params = URI(document.location.search).search(true); + // Possible parameters: name, host, port, password, tls, nick, username, realname, join // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#Iterating_over_own_properties_only for (let key in params) { @@ -611,6 +626,7 @@ $(function() { key = key.replace(/\W/g, ""); const element = $("#connect input[name='" + key + "']"); + // if the element exists, it isn't disabled, and it isn't hidden if (element.length > 0 && !element.is(":disabled") && !element.is(":hidden")) { if (element.is(":checkbox")) { @@ -647,10 +663,12 @@ $(function() { // This should always be 24h later but re-computing exact value just in case setTimeout(updateDateMarkers, msUntilNextDay()); } + setTimeout(updateDateMarkers, msUntilNextDay()); window.addEventListener("popstate", (e) => { const {state} = e; + if (!state) { return; } diff --git a/client/js/options.js b/client/js/options.js index bb2fd440..39aaa2a8 100644 --- a/client/js/options.js +++ b/client/js/options.js @@ -90,6 +90,7 @@ module.exports.initialize = () => { if (Notification.permission === "default" && desktopNotificationsCheckbox.prop("checked")) { desktopNotificationsCheckbox.prop("checked", false); } + desktopNotificationsCheckbox.prop("disabled", false); warningBlocked.hide(); } @@ -148,6 +149,7 @@ module.exports.initialize = () => { const highlightsTokens = options.highlights.map(function(h) { return escapeRegExp(h); }); + if (highlightsTokens && highlightsTokens.length) { module.exports.highlightsRE = new RegExp("\\b(?:" + highlightsTokens.join("|") + ")\\b", "i"); } else { diff --git a/client/js/renderPreview.js b/client/js/renderPreview.js index 0e72f050..9fb52934 100644 --- a/client/js/renderPreview.js +++ b/client/js/renderPreview.js @@ -141,19 +141,23 @@ function openImageViewer(link, {pushState = true} = {}) { // Previous image let previousImage = link.closest(".preview").prev(".preview") .find(".toggle-content.show .toggle-thumbnail").last(); + if (!previousImage.length) { previousImage = link.closest(".msg").prevAll() .find(".toggle-content.show .toggle-thumbnail").last(); } + previousImage.addClass("previous-image"); // Next image let nextImage = link.closest(".preview").next(".preview") .find(".toggle-content.show .toggle-thumbnail").first(); + if (!nextImage.length) { nextImage = link.closest(".msg").nextAll() .find(".toggle-content.show .toggle-thumbnail").first(); } + nextImage.addClass("next-image"); imageViewer.html(templates.image_viewer({ @@ -173,12 +177,14 @@ function openImageViewer(link, {pushState = true} = {}) { // History management if (pushState) { let clickTarget = ""; + // Images can be in a message (channel URL previews) or not (window URL // preview, e.g. changelog). This is sub-optimal and needs improvement to // make image preview more generic and not specific for channel previews. if (link.closest(".msg").length > 0) { clickTarget = `#${link.closest(".msg").prop("id")} `; } + clickTarget += `a.toggle-thumbnail[href="${link.prop("href")}"] img`; history.pushState({clickTarget}, null, null); } diff --git a/client/js/socket-events/change_password.js b/client/js/socket-events/change_password.js index 732aecd4..09bb144e 100644 --- a/client/js/socket-events/change_password.js +++ b/client/js/socket-events/change_password.js @@ -5,6 +5,7 @@ const socket = require("../socket"); socket.on("change-password", function(data) { const passwordForm = $("#change-password"); + if (data.error || data.success) { const message = data.success ? data.success : data.error; const feedback = passwordForm.find(".feedback"); diff --git a/client/js/socket-events/more.js b/client/js/socket-events/more.js index 20460ca4..ffde8419 100644 --- a/client/js/socket-events/more.js +++ b/client/js/socket-events/more.js @@ -24,6 +24,7 @@ socket.on("more", function(data) { // Remove the date-change marker we put at the top, because it may // not actually be a date change now const children = $(chan).children(); + if (children.eq(0).hasClass("date-marker-container")) { // Check top most child children.eq(0).remove(); } else if (children.eq(1).hasClass("date-marker-container")) { diff --git a/client/js/socket-events/msg.js b/client/js/socket-events/msg.js index 98aa8800..71967b33 100644 --- a/client/js/socket-events/msg.js +++ b/client/js/socket-events/msg.js @@ -12,6 +12,7 @@ const chat = $("#chat"); const sidebar = $("#sidebar"); let pop; + try { pop = new Audio(); pop.src = "audio/pop.ogg"; @@ -69,6 +70,7 @@ function processReceivedMessage(data) { notifyMessage(targetId, channel, data); const lastVisible = container.find("div:visible").last(); + if (data.msg.self || lastVisible.hasClass("unread-marker") || (lastVisible.hasClass("date-marker") @@ -100,8 +102,10 @@ function processReceivedMessage(data) { if ((data.msg.type === "message" || data.msg.type === "action") && channel.hasClass("channel")) { const nicks = channel.find(".users").data("nicks"); + if (nicks) { const find = nicks.indexOf(data.msg.from.nick); + if (find !== -1) { nicks.splice(find, 1); nicks.unshift(data.msg.from.nick); @@ -119,6 +123,7 @@ function notifyMessage(targetId, channel, msg) { } const button = sidebar.find(".chan[data-id='" + targetId + "']"); + if (msg.highlight || (options.notifyAllMessages && msg.type === "message")) { if (!document.hasFocus() || !channel.hasClass("active")) { if (options.notification) { @@ -140,12 +145,15 @@ function notifyMessage(targetId, channel, msg) { body = msg.from.nick + " invited you to " + msg.channel; } else { title = msg.from.nick; + if (!button.hasClass("query")) { title += " (" + button.attr("aria-label").trim() + ")"; } + if (msg.type === "message") { title += " says:"; } + body = cleanIrcMessage(msg.text); } diff --git a/client/js/socket-events/nick.js b/client/js/socket-events/nick.js index 75aa0411..e4889d53 100644 --- a/client/js/socket-events/nick.js +++ b/client/js/socket-events/nick.js @@ -9,6 +9,7 @@ socket.on("nick", function(data) { const id = data.network; const nick = data.nick; const network = sidebar.find("#network-" + id).data("nick", nick); + if (network.find(".active").length) { utils.setNick(nick); } diff --git a/client/js/utils.js b/client/js/utils.js index 9fdaaede..47e0568a 100644 --- a/client/js/utils.js +++ b/client/js/utils.js @@ -66,8 +66,10 @@ function expand() { function join(args) { const channel = args[0]; + if (channel) { const chan = findCurrentNetworkChan(channel); + if (chan.length) { chan.trigger("click"); } @@ -113,10 +115,12 @@ function confirmExit() { function move(array, old_index, new_index) { if (new_index >= array.length) { let k = new_index - array.length; + while ((k--) + 1) { this.push(undefined); } } + array.splice(new_index, 0, array.splice(old_index, 1)[0]); return array; } diff --git a/client/views/index.js b/client/views/index.js index 9cde732d..be9f04f9 100644 --- a/client/views/index.js +++ b/client/views/index.js @@ -19,6 +19,7 @@ module.exports = requireViews.keys().reduce((acc, path) => { } else { tmp[key] = tmp[key] || {}; } + tmp = tmp[key]; }); diff --git a/index.js b/index.js index da973d69..2f632984 100755 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ process.chdir(__dirname); // other issues // Try to display messages nicely, but gracefully degrade if anything goes wrong const pkg = require("./package.json"); + if (!require("semver").satisfies(process.version, pkg.engines.node)) { let colors; let log; diff --git a/scripts/changelog.js b/scripts/changelog.js index 75361787..21f6c2f5 100644 --- a/scripts/changelog.js +++ b/scripts/changelog.js @@ -397,6 +397,7 @@ function pullRequestNumbersInCommits(commits) { if (pullRequestId) { array.push(pullRequestId); } + return array; }, []); } @@ -439,6 +440,7 @@ function printLine(entry) { if (entry.title) { return printPullRequest(entry); } + return printCommit(entry); } @@ -569,6 +571,7 @@ function parse(entries) { if (!result[dependencyType][packageName]) { result[dependencyType][packageName] = []; } + result[dependencyType][packageName].push(entry); } else { log.info(`${colors.bold(packageName)} was updated in ${colors.green("#" + entry.number)} then removed since last release. Skipping.`); @@ -591,6 +594,7 @@ function parse(entries) { result.uncategorized.other.push(entry); } } + return result; }, { skipped: [], @@ -616,6 +620,7 @@ function extractContributors(entries) { if (pullRequest.author.login !== "greenkeeper") { memo.add("@" + pullRequest.author.login); } + return memo; }, new Set()); @@ -699,6 +704,7 @@ async function addToChangelog(newEntry) { } else { log.error(error); } + process.exit(1); } @@ -724,6 +730,7 @@ async function addToChangelog(newEntry) { // Step 4: Print out some information about what just happened to the console const commitCommand = `git commit -m 'Add changelog entry for v${version}' CHANGELOG.md`; + if (isPrerelease(version)) { log.info(`You can now run: ${colors.bold(commitCommand)}`); } else { diff --git a/src/client.js b/src/client.js index eff05433..a4d60f61 100644 --- a/src/client.js +++ b/src/client.js @@ -115,14 +115,17 @@ Client.prototype.emit = function(event, data) { Client.prototype.find = function(channelId) { let network = null; let chan = null; + for (const i in this.networks) { const n = this.networks[i]; chan = _.find(n.channels, {id: channelId}); + if (chan) { network = n; break; } } + if (network && chan) { return { network: network, @@ -340,6 +343,7 @@ Client.prototype.input = function(data) { Client.prototype.inputLine = function(data) { const client = this; const target = client.find(data.target); + if (!target) { return; } @@ -373,6 +377,7 @@ Client.prototype.inputLine = function(data) { if (cmd in inputs) { const plugin = inputs[cmd]; + if (connected || plugin.allowDisconnected) { connected = true; plugin.input.apply(client, [target.network, target.chan, cmd, args]); @@ -427,6 +432,7 @@ Client.prototype.open = function(socketId, target) { } target = this.find(target); + if (!target) { return; } @@ -459,6 +465,7 @@ Client.prototype.sort = function(data) { case "channels": { const network = _.find(this.networks, {id: data.target}); + if (!network) { return; } @@ -478,6 +485,7 @@ Client.prototype.sort = function(data) { Client.prototype.names = function(data) { const client = this; const target = client.find(data.target); + if (!target) { return; } diff --git a/src/clientManager.js b/src/clientManager.js index 37fdd981..44b846cd 100644 --- a/src/clientManager.js +++ b/src/clientManager.js @@ -52,6 +52,7 @@ ClientManager.prototype.autoloadUsers = function() { // Existing users removed since last time users were loaded _.difference(loaded, updatedUsers).forEach((name) => { const client = _.find(this.clients, {name: name}); + if (client) { client.quit(true); this.clients = _.without(this.clients, client); @@ -86,6 +87,7 @@ ClientManager.prototype.loadUser = function(name) { client = new Client(this, name, userConfig); this.clients.push(client); } + return client; }; @@ -131,9 +133,11 @@ ClientManager.prototype.updateUser = function(name, opts, callback) { if (!user) { log.error(`Tried to update invalid user ${colors.green(name)}. This is most likely a bug.`); + if (callback) { callback(true); } + return false; } @@ -151,9 +155,11 @@ ClientManager.prototype.updateUser = function(name, opts, callback) { return callback ? callback() : true; } catch (e) { log.error(`Failed to update user ${colors.green(name)} (${e})`); + if (callback) { callback(e); } + throw e; } }; diff --git a/src/command-line/index.js b/src/command-line/index.js index 8a4a2dc7..d83ecd7e 100644 --- a/src/command-line/index.js +++ b/src/command-line/index.js @@ -40,9 +40,11 @@ _.merge(Helper.config, program.config); require("./start"); require("./config"); + if (!Helper.config.public && !Helper.config.ldap.enable) { require("./users"); } + require("./install"); require("./uninstall"); diff --git a/src/command-line/uninstall.js b/src/command-line/uninstall.js index e29f9b8b..33bf1585 100644 --- a/src/command-line/uninstall.js +++ b/src/command-line/uninstall.js @@ -31,6 +31,7 @@ program } const npm = process.platform === "win32" ? "npm.cmd" : "npm"; + const errorHandler = (error) => { log.error( `Failed to uninstall ${colors.green(packageName)}. ` + diff --git a/src/command-line/users/add.js b/src/command-line/users/add.js index f22e2285..c3d0eb5d 100644 --- a/src/command-line/users/add.js +++ b/src/command-line/users/add.js @@ -37,6 +37,7 @@ program log.error("Password cannot be empty."); return; } + if (!err) { log.prompt({ text: "Save logs to disk?", diff --git a/src/command-line/users/edit.js b/src/command-line/users/edit.js index c3a89819..8dabc785 100644 --- a/src/command-line/users/edit.js +++ b/src/command-line/users/edit.js @@ -28,6 +28,7 @@ program log.error(`User ${colors.bold(name)} does not exist.`); return; } + const child_spawn = child.spawn( process.env.EDITOR || "vi", [Helper.getUserConfigPath(name)], diff --git a/src/command-line/users/reset.js b/src/command-line/users/reset.js index 605f04db..b8d5412a 100644 --- a/src/command-line/users/reset.js +++ b/src/command-line/users/reset.js @@ -27,6 +27,7 @@ program log.error(`User ${colors.bold(name)} does not exist.`); return; } + const file = Helper.getUserConfigPath(name); const user = require(file); log.prompt({ @@ -36,6 +37,7 @@ program if (err) { return; } + user.password = Helper.password.hash(password); user.sessions = {}; fs.writeFileSync( diff --git a/src/command-line/utils.js b/src/command-line/utils.js index 608c59b5..e598ecdf 100644 --- a/src/command-line/utils.js +++ b/src/command-line/utils.js @@ -56,12 +56,15 @@ class Utils { } else if (/^\[.*\]$/.test(value)) { // Arrays // Supporting arrays `[a,b]` and `[a, b]` const array = value.slice(1, -1).split(/,\s*/); + // If [] is given, it will be parsed as `[ "" ]`, so treat this as empty if (array.length === 1 && array[0] === "") { return []; } + return array.map(parseValue); // Re-parses all values of the array } + return value; }; diff --git a/src/helper.js b/src/helper.js index f8c7a6ee..710b4b4a 100644 --- a/src/helper.js +++ b/src/helper.js @@ -53,10 +53,12 @@ function getVersion() { } let _gitCommit; + function getGitCommit() { if (_gitCommit !== undefined) { return _gitCommit; } + try { _gitCommit = require("child_process") .execSync("git rev-parse --short HEAD 2> /dev/null") // Returns hash of current commit diff --git a/src/models/network.js b/src/models/network.js index f2f0c0af..1348a8b1 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -141,11 +141,13 @@ Network.prototype.export = function() { }) .map(function(chan) { const keys = ["name"]; + if (chan.type === Chan.Type.CHANNEL) { keys.push("key"); } else if (chan.type === Chan.Type.QUERY) { keys.push("type"); } + return _.pick(chan, keys); }); diff --git a/src/plugins/auth/ldap.js b/src/plugins/auth/ldap.js index 450eb937..ab71a549 100644 --- a/src/plugins/auth/ldap.js +++ b/src/plugins/auth/ldap.js @@ -96,6 +96,7 @@ function advancedLdapAuth(user, password, callback) { }); res.on("end", function() { ldapclient.unbind(); + if (!found) { callback(false); } @@ -115,15 +116,18 @@ function ldapAuth(manager, client, user, password, callback) { if (valid && !client) { manager.addUser(user, null); } + callback(valid); } let auth; + if ("baseDN" in Helper.config.ldap) { auth = simpleLdapAuth; } else { auth = advancedLdapAuth; } + return auth(user, password, callbackWrapper); } diff --git a/src/plugins/changelog.js b/src/plugins/changelog.js index 2103ded4..e6fc6a11 100644 --- a/src/plugins/changelog.js +++ b/src/plugins/changelog.js @@ -43,6 +43,7 @@ function fetch(callback) { // Find the current release among releases on GitHub for (i = 0; i < body.length; i++) { release = body[i]; + if (release.tag_name === versions.current.version) { versions.current.changelog = release.body_html; prerelease = release.prerelease; diff --git a/src/plugins/inputs/msg.js b/src/plugins/inputs/msg.js index 83513041..57a7d1cf 100644 --- a/src/plugins/inputs/msg.js +++ b/src/plugins/inputs/msg.js @@ -19,6 +19,7 @@ exports.input = function(network, chan, cmd, args) { if (!network.irc.network.cap.isEnabled("echo-message")) { const channel = network.getChannel(target); + if (typeof channel !== "undefined") { network.irc.emit("privmsg", { nick: network.irc.user.nick, diff --git a/src/plugins/inputs/query.js b/src/plugins/inputs/query.js index ee3e40d2..8a4fb910 100644 --- a/src/plugins/inputs/query.js +++ b/src/plugins/inputs/query.js @@ -8,6 +8,7 @@ exports.commands = ["query"]; exports.input = function(network, chan, cmd, args) { const target = args[0]; + if (args.length === 0 || target.length === 0) { chan.pushMessage(this, new Msg({ type: Msg.Type.ERROR, @@ -17,11 +18,13 @@ exports.input = function(network, chan, cmd, args) { } const query = _.find(network.channels, {name: target}); + if (typeof query !== "undefined") { return; } const char = target[0]; + if (network.irc.network.options.CHANTYPES && network.irc.network.options.CHANTYPES.includes(char)) { chan.pushMessage(this, new Msg({ type: Msg.Type.ERROR, diff --git a/src/plugins/inputs/topic.js b/src/plugins/inputs/topic.js index 14fff3ae..1fbb96dd 100644 --- a/src/plugins/inputs/topic.js +++ b/src/plugins/inputs/topic.js @@ -14,6 +14,7 @@ exports.input = function({irc}, chan, cmd, args) { return; } + irc.setTopic(chan.name, args.join(" ")); return true; }; diff --git a/src/plugins/irc-events/banlist.js b/src/plugins/irc-events/banlist.js index adac4cc0..9ffe014f 100644 --- a/src/plugins/irc-events/banlist.js +++ b/src/plugins/irc-events/banlist.js @@ -9,6 +9,7 @@ module.exports = function(irc, network) { irc.on("banlist", function(banlist) { const channel = banlist.channel; const bans = banlist.bans; + if (!bans || bans.length === 0) { const msg = new Msg({ time: Date.now(), @@ -21,6 +22,7 @@ module.exports = function(irc, network) { const chanName = `Banlist for ${channel}`; let chan = network.getChannel(chanName); + if (typeof chan === "undefined") { chan = new Chan({ type: Chan.Type.SPECIAL, diff --git a/src/plugins/irc-events/link.js b/src/plugins/irc-events/link.js index 4c0cff3a..e0c738ad 100644 --- a/src/plugins/irc-events/link.js +++ b/src/plugins/irc-events/link.js @@ -122,6 +122,7 @@ function parse(msg, preview, res, client) { if (!preview.link.startsWith("https://")) { break; } + preview.type = "audio"; preview.res = res.type; @@ -133,6 +134,7 @@ function parse(msg, preview, res, client) { if (!preview.link.startsWith("https://")) { break; } + preview.res = res.type; preview.type = "video"; @@ -191,6 +193,7 @@ function emitPreview(client, msg, preview) { function fetch(uri, cb) { let req; + try { req = request.get({ url: uri, @@ -214,6 +217,7 @@ function fetch(uri, cb) { // response is an image // if Content-Length header reports a size exceeding the prefetch limit, abort fetch const contentLength = parseInt(res.headers["content-length"], 10) || 0; + if (contentLength > limit) { req.abort(); } diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js index 9b24974e..06b063fc 100644 --- a/src/plugins/irc-events/message.js +++ b/src/plugins/irc-events/message.js @@ -107,6 +107,7 @@ module.exports = function(irc, network) { } let match; + while ((match = nickRegExp.exec(data.message))) { if (chan.findUser(match[1])) { msg.users.push(match[1]); diff --git a/src/plugins/irc-events/mode.js b/src/plugins/irc-events/mode.js index 5b416666..534a9fa9 100644 --- a/src/plugins/irc-events/mode.js +++ b/src/plugins/irc-events/mode.js @@ -16,6 +16,7 @@ module.exports = function(irc, network) { } const targetChan = network.getChannel(data.channel); + if (typeof targetChan === "undefined") { return; } @@ -39,6 +40,7 @@ module.exports = function(irc, network) { targetChan = network.channels[0]; } else { targetChan = network.getChannel(data.target); + if (typeof targetChan === "undefined") { return; } @@ -80,6 +82,7 @@ module.exports = function(irc, network) { } const user = targetChan.findUser(mode.param); + if (!user) { return; } diff --git a/src/plugins/irc-events/names.js b/src/plugins/irc-events/names.js index 77c72869..2c6097f2 100644 --- a/src/plugins/irc-events/names.js +++ b/src/plugins/irc-events/names.js @@ -5,6 +5,7 @@ module.exports = function(irc, network) { irc.on("userlist", function(data) { const chan = network.getChannel(data.channel); + if (typeof chan === "undefined") { return; } diff --git a/src/plugins/irc-events/whois.js b/src/plugins/irc-events/whois.js index d07836c0..d0b8a93a 100644 --- a/src/plugins/irc-events/whois.js +++ b/src/plugins/irc-events/whois.js @@ -22,6 +22,7 @@ module.exports = function(irc, network) { } let msg; + if (data.error) { msg = new Msg({ type: Msg.Type.ERROR, diff --git a/src/plugins/packages/themes.js b/src/plugins/packages/themes.js index a32950b4..aba7269d 100644 --- a/src/plugins/packages/themes.js +++ b/src/plugins/packages/themes.js @@ -18,6 +18,7 @@ function loadLocalThemes() { if (err) { return; } + builtInThemes .filter((theme) => theme.endsWith(".css")) .map(makeLocalThemeObject) @@ -27,6 +28,7 @@ function loadLocalThemes() { function addTheme(packageName, packageObject) { const theme = makePackageThemeObject(packageName, packageObject); + if (theme) { themes.set(theme.name, theme); } @@ -54,6 +56,7 @@ function makePackageThemeObject(moduleName, module) { if (!module || module.type !== "theme") { return; } + const modulePath = Helper.getPackageModulePath(moduleName); const displayName = module.name || moduleName; const filename = path.join(modulePath, module.css); diff --git a/src/server.js b/src/server.js index 9bf6ec00..22163cab 100644 --- a/src/server.js +++ b/src/server.js @@ -55,9 +55,11 @@ module.exports = function() { app.get("/themes/:theme.css", (req, res) => { const themeName = req.params.theme; const theme = themes.getFilename(themeName); + if (theme === undefined) { return res.status(404).send("Not found"); } + return res.sendFile(theme); }); @@ -65,9 +67,11 @@ module.exports = function() { const packageName = req.params.package; const fileName = req.params.filename; const packageFile = packages.getPackage(packageName); + if (!packageFile || !packages.getStylesheets().includes(`${packageName}/${fileName}`)) { return res.status(404).send("Not found"); } + const packagePath = Helper.getPackageModulePath(packageName); return res.sendFile(path.join(packagePath, fileName)); }); @@ -161,6 +165,7 @@ module.exports = function() { // Handle ctrl+c and kill gracefully let suicideTimeout = null; + const exitGracefully = function() { if (suicideTimeout !== null) { return; @@ -304,12 +309,14 @@ function initializeClient(socket, client, token, lastMessage) { const old = data.old_password; const p1 = data.new_password; const p2 = data.verify_password; + if (typeof p1 === "undefined" || p1 === "") { socket.emit("change-password", { error: "Please enter a new password", }); return; } + if (p1 !== p2) { socket.emit("change-password", { error: "Both new password fields must match", @@ -326,6 +333,7 @@ function initializeClient(socket, client, token, lastMessage) { }); return; } + const hash = Helper.password.hash(p1); client.setPassword(hash, (success) => { @@ -375,6 +383,7 @@ function initializeClient(socket, client, token, lastMessage) { socket.on("msg:preview:toggle", function(data) { const networkAndChan = client.find(data.target); + if (!networkAndChan) { return; } @@ -602,12 +611,14 @@ function performAuthentication(data) { let auth = () => { log.error("None of the auth plugins is enabled"); }; + for (let i = 0; i < authPlugins.length; ++i) { if (authPlugins[i].isEnabled()) { auth = authPlugins[i].auth; break; } } + auth(manager, client, data.user, data.password, authCallback); } diff --git a/src/userLog.js b/src/userLog.js index 75219c67..863c6f62 100644 --- a/src/userLog.js +++ b/src/userLog.js @@ -22,6 +22,7 @@ module.exports.write = function(user, network, chan, msg) { let line = `[${time}] `; const type = msg.type.trim(); + if (type === "message" || type === "highlight") { // Format: // [2014-01-01 00:00:00] Put that cookie down.. Now!! diff --git a/test/plugins/auth/ldap.js b/test/plugins/auth/ldap.js index 77930a92..cf58b2d2 100644 --- a/test/plugins/auth/ldap.js +++ b/test/plugins/auth/ldap.js @@ -116,6 +116,7 @@ describe("LDAP authentication plugin", function() { before(function(done) { originalLogInfo = log.info; + log.info = () => {}; server = startLdapServer(done); diff --git a/test/plugins/packages/indexTest.js b/test/plugins/packages/indexTest.js index e5be96fa..417d6fa6 100644 --- a/test/plugins/packages/indexTest.js +++ b/test/plugins/packages/indexTest.js @@ -10,6 +10,7 @@ describe("packages", function() { beforeEach(function() { originalLogInfo = log.info; + log.info = () => {}; delete require.cache[require.resolve("../../../src/plugins/packages")]; diff --git a/test/server.js b/test/server.js index 3ed0ec40..ef0aa611 100644 --- a/test/server.js +++ b/test/server.js @@ -12,6 +12,7 @@ describe("Server", function() { before(function() { originalLogInfo = log.info; + log.info = () => {}; server = require("../src/server")(); diff --git a/test/util.js b/test/util.js index 68ca2903..6f820736 100644 --- a/test/util.js +++ b/test/util.js @@ -10,6 +10,7 @@ const Chan = require("../src/models/chan"); function MockClient() { this.user = {nick: "test-user"}; } + util.inherits(MockClient, EventEmitter); MockClient.prototype.createMessage = function(opts) {