Merge pull request #2229 from thelounge/xpaw/edit-networks
Allow editing networks via UI
This commit is contained in:
commit
efae5fd28d
@ -419,6 +419,7 @@ kbd {
|
|||||||
margin-right: 9px;
|
margin-right: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu-edit::before,
|
||||||
#set-nick::before {
|
#set-nick::before {
|
||||||
content: "\f303"; /* https://fontawesome.com/icons/pencil-alt?style=solid */
|
content: "\f303"; /* https://fontawesome.com/icons/pencil-alt?style=solid */
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,21 @@ function addFocusItem() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addEditNetworkItem() {
|
||||||
|
function edit(itemData) {
|
||||||
|
socket.emit("network:get", itemData);
|
||||||
|
$('button[data-target="#connect"]').trigger("click");
|
||||||
|
}
|
||||||
|
|
||||||
|
addContextMenuItem({
|
||||||
|
check: (target) => target.hasClass("lobby"),
|
||||||
|
className: "edit",
|
||||||
|
displayName: "Edit this network…",
|
||||||
|
data: (target) => target.closest(".network").data("uuid"),
|
||||||
|
callback: edit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function addChannelListItem() {
|
function addChannelListItem() {
|
||||||
function list(itemData) {
|
function list(itemData) {
|
||||||
socket.emit("input", {
|
socket.emit("input", {
|
||||||
@ -207,6 +222,7 @@ function addDefaultItems() {
|
|||||||
addQueryItem();
|
addQueryItem();
|
||||||
addKickItem();
|
addKickItem();
|
||||||
addFocusItem();
|
addFocusItem();
|
||||||
|
addEditNetworkItem();
|
||||||
addChannelListItem();
|
addChannelListItem();
|
||||||
addBanListItem();
|
addBanListItem();
|
||||||
addJoinItem();
|
addJoinItem();
|
||||||
|
@ -18,7 +18,6 @@ const utils = require("./utils");
|
|||||||
require("./webpush");
|
require("./webpush");
|
||||||
require("./keybinds");
|
require("./keybinds");
|
||||||
require("./clipboard");
|
require("./clipboard");
|
||||||
const Changelog = require("./socket-events/changelog");
|
|
||||||
const contextMenuFactory = require("./contextMenuFactory");
|
const contextMenuFactory = require("./contextMenuFactory");
|
||||||
const contextMenuContainer = $("#context-menu-container");
|
const contextMenuContainer = $("#context-menu-container");
|
||||||
|
|
||||||
@ -322,15 +321,6 @@ $(function() {
|
|||||||
socket.emit("names", {target: self.data("id")});
|
socket.emit("names", {target: self.data("id")});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target === "#settings") {
|
|
||||||
$("#session-list").html("<p>Loading…</p>");
|
|
||||||
socket.emit("sessions:get");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target === "#help" || target === "#changelog") {
|
|
||||||
Changelog.requestIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pushes states to history web API when clicking elements with a data-target attribute.
|
// Pushes states to history web API when clicking elements with a data-target attribute.
|
||||||
// States are very trivial and only contain a single `clickTarget` property which
|
// States are very trivial and only contain a single `clickTarget` property which
|
||||||
// contains a CSS selector that targets elements which takes the user to a different view
|
// contains a CSS selector that targets elements which takes the user to a different view
|
||||||
|
@ -4,10 +4,6 @@ const $ = require("jquery");
|
|||||||
const socket = require("../socket");
|
const socket = require("../socket");
|
||||||
const templates = require("../../views");
|
const templates = require("../../views");
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
requestIfNeeded,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Requests version information if it hasn't been retrieved before (or if it has
|
// Requests version information if it hasn't been retrieved before (or if it has
|
||||||
// been removed from the page, i.e. when clicking on "Check now". Displays a
|
// been removed from the page, i.e. when clicking on "Check now". Displays a
|
||||||
// loading state until received.
|
// loading state until received.
|
||||||
@ -54,6 +50,8 @@ socket.on("changelog", function(data) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#help, #changelog").on("show", requestIfNeeded);
|
||||||
|
|
||||||
// When clicking the "Check now" button, remove current checker information and
|
// When clicking the "Check now" button, remove current checker information and
|
||||||
// request a new one. Loading will be displayed in the meantime.
|
// request a new one. Loading will be displayed in the meantime.
|
||||||
$("#help").on("click", "#check-now", () => {
|
$("#help").on("click", "#check-now", () => {
|
||||||
|
@ -5,6 +5,7 @@ const socket = require("../socket");
|
|||||||
const templates = require("../../views");
|
const templates = require("../../views");
|
||||||
const options = require("../options");
|
const options = require("../options");
|
||||||
const webpush = require("../webpush");
|
const webpush = require("../webpush");
|
||||||
|
const connect = $("#connect");
|
||||||
|
|
||||||
socket.on("configuration", function(data) {
|
socket.on("configuration", function(data) {
|
||||||
if (options.initialized) {
|
if (options.initialized) {
|
||||||
@ -14,10 +15,14 @@ socket.on("configuration", function(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$("#settings").html(templates.windows.settings(data));
|
$("#settings").html(templates.windows.settings(data));
|
||||||
$("#connect").html(templates.windows.connect(data));
|
|
||||||
$("#help").html(templates.windows.help(data));
|
$("#help").html(templates.windows.help(data));
|
||||||
$("#changelog").html(templates.windows.changelog());
|
$("#changelog").html(templates.windows.changelog());
|
||||||
|
|
||||||
|
$("#settings").on("show", () => {
|
||||||
|
$("#session-list").html("<p>Loading…</p>");
|
||||||
|
socket.emit("sessions:get");
|
||||||
|
});
|
||||||
|
|
||||||
$("#play").on("click", () => {
|
$("#play").on("click", () => {
|
||||||
const pop = new Audio();
|
const pop = new Audio();
|
||||||
pop.src = "audio/pop.ogg";
|
pop.src = "audio/pop.ogg";
|
||||||
@ -33,9 +38,7 @@ socket.on("configuration", function(data) {
|
|||||||
options.processSetting("theme", data.defaultTheme, true);
|
options.processSetting("theme", data.defaultTheme, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const forms = $("#connect form, #change-password form");
|
function handleFormSubmit() {
|
||||||
|
|
||||||
forms.on("submit", function() {
|
|
||||||
const form = $(this);
|
const form = $(this);
|
||||||
const event = form.data("event");
|
const event = form.data("event");
|
||||||
|
|
||||||
@ -51,9 +54,15 @@ socket.on("configuration", function(data) {
|
|||||||
socket.emit(event, values);
|
socket.emit(event, values);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
}
|
||||||
|
|
||||||
$(".nick")
|
$("#change-password form").on("submit", handleFormSubmit);
|
||||||
|
connect.on("submit", "form", handleFormSubmit);
|
||||||
|
|
||||||
|
connect.on("show", function() {
|
||||||
|
connect
|
||||||
|
.html(templates.windows.connect(data))
|
||||||
|
.find("#connect\\:nick")
|
||||||
.on("focusin", function() {
|
.on("focusin", function() {
|
||||||
// Need to set the first "lastvalue", so it can be used in the below function
|
// Need to set the first "lastvalue", so it can be used in the below function
|
||||||
const nick = $(this);
|
const nick = $(this);
|
||||||
@ -61,7 +70,7 @@ socket.on("configuration", function(data) {
|
|||||||
})
|
})
|
||||||
.on("input", function() {
|
.on("input", function() {
|
||||||
const nick = $(this).val();
|
const nick = $(this).val();
|
||||||
const usernameInput = forms.find(".username");
|
const usernameInput = connect.find(".username");
|
||||||
|
|
||||||
// Because this gets called /after/ it has already changed, we need use the previous value
|
// Because this gets called /after/ it has already changed, we need use the previous value
|
||||||
const lastValue = $(this).data("lastvalue");
|
const lastValue = $(this).data("lastvalue");
|
||||||
@ -75,3 +84,4 @@ socket.on("configuration", function(data) {
|
|||||||
$(this).data("lastvalue", nick);
|
$(this).data("lastvalue", nick);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
const $ = require("jquery");
|
const $ = require("jquery");
|
||||||
const socket = require("../socket");
|
const socket = require("../socket");
|
||||||
const render = require("../render");
|
const render = require("../render");
|
||||||
|
const templates = require("../../views");
|
||||||
const sidebar = $("#sidebar");
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
socket.on("network", function(data) {
|
socket.on("network", function(data) {
|
||||||
@ -27,3 +28,17 @@ socket.on("network:status", function(data) {
|
|||||||
.toggleClass("not-connected", !data.connected)
|
.toggleClass("not-connected", !data.connected)
|
||||||
.toggleClass("not-secure", !data.secure);
|
.toggleClass("not-secure", !data.secure);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("network:info", function(data) {
|
||||||
|
$("#connect")
|
||||||
|
.html(templates.windows.connect(data))
|
||||||
|
.find("form").on("submit", function() {
|
||||||
|
const uuid = $(this).find("input[name=uuid]").val();
|
||||||
|
const newName = $(this).find("#connect\\:name").val();
|
||||||
|
|
||||||
|
sidebar.find(`.network[data-uuid="${uuid}"] .chan.lobby .name`)
|
||||||
|
.attr("title", newName)
|
||||||
|
.text(newName)
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<button class="lt" aria-label="Toggle channel list"></button>
|
<button class="lt" aria-label="Toggle channel list"></button>
|
||||||
</div>
|
</div>
|
||||||
<form class="container" method="post" action="" data-event="conn">
|
<form class="container" method="post" action="" data-event="{{#if defaults.uuid}}network:edit{{else}}network:new{{/if}}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
|
{{#if defaults.uuid}}
|
||||||
|
<input type="hidden" name="uuid" value="{{defaults.uuid}}">
|
||||||
|
|
||||||
|
Edit {{defaults.name}}
|
||||||
|
{{else}}
|
||||||
{{#if public}}The Lounge - {{/if}}
|
{{#if public}}The Lounge - {{/if}}
|
||||||
Connect
|
Connect
|
||||||
{{#unless displayNetwork}}
|
{{#unless displayNetwork}}
|
||||||
@ -12,6 +17,7 @@
|
|||||||
to {{defaults.name}}
|
to {{defaults.name}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
{{#if displayNetwork}}
|
{{#if displayNetwork}}
|
||||||
@ -29,7 +35,7 @@
|
|||||||
<label for="connect:host">Server</label>
|
<label for="connect:host">Server</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-xs-8">
|
<div class="col-sm-6 col-xs-8">
|
||||||
<input class="input" id="connect:host" name="host" value="{{defaults.host}}" aria-label="Server address" {{#if lockNetwork}}disabled{{/if}}>
|
<input class="input" id="connect:host" name="host" value="{{defaults.host}}" aria-label="Server address" required {{#if lockNetwork}}disabled{{/if}}>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3 col-xs-4">
|
<div class="col-sm-3 col-xs-4">
|
||||||
<div class="port">
|
<div class="port">
|
||||||
@ -59,7 +65,7 @@
|
|||||||
<label for="connect:nick">Nick</label>
|
<label for="connect:nick">Nick</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input class="input nick" id="connect:nick" name="nick" value="{{defaults.nick}}">
|
<input class="input nick" id="connect:nick" name="nick" value="{{defaults.nick}}" required>
|
||||||
</div>
|
</div>
|
||||||
{{#unless useHexIp}}
|
{{#unless useHexIp}}
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
@ -81,6 +87,18 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input class="input" id="connect:realname" name="realname" value="{{defaults.realname}}">
|
<input class="input" id="connect:realname" name="realname" value="{{defaults.realname}}">
|
||||||
</div>
|
</div>
|
||||||
|
{{#if defaults.uuid}}
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<label for="connect:commands">Commands</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea class="input" id="connect:commands" name="commands" placeholder="One raw command per line, each command will be executed on new connection">{{~#each defaults.commands~}}{{~this}}
|
||||||
|
{{/each~}}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9 col-sm-offset-3">
|
||||||
|
<button type="submit" class="btn">Save</button>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<label for="connect:channels">Channels</label>
|
<label for="connect:channels">Channels</label>
|
||||||
</div>
|
</div>
|
||||||
@ -90,5 +108,6 @@
|
|||||||
<div class="col-sm-9 col-sm-offset-3">
|
<div class="col-sm-9 col-sm-offset-3">
|
||||||
<button type="submit" class="btn">Connect</button>
|
<button type="submit" class="btn">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
100
src/client.js
100
src/client.js
@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const colors = require("chalk");
|
const colors = require("chalk");
|
||||||
const pkg = require("../package.json");
|
|
||||||
const Chan = require("./models/chan");
|
const Chan = require("./models/chan");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
const Msg = require("./models/msg");
|
const Msg = require("./models/msg");
|
||||||
const Network = require("./models/network");
|
const Network = require("./models/network");
|
||||||
const ircFramework = require("irc-framework");
|
|
||||||
const Helper = require("./helper");
|
const Helper = require("./helper");
|
||||||
const UAParser = require("ua-parser-js");
|
const UAParser = require("ua-parser-js");
|
||||||
const MessageStorage = require("./plugins/sqlite");
|
const MessageStorage = require("./plugins/sqlite");
|
||||||
@ -140,8 +138,6 @@ Client.prototype.find = function(channelId) {
|
|||||||
|
|
||||||
Client.prototype.connect = function(args) {
|
Client.prototype.connect = function(args) {
|
||||||
const client = this;
|
const client = this;
|
||||||
const nick = args.nick || "thelounge";
|
|
||||||
let webirc = null;
|
|
||||||
let channels = [];
|
let channels = [];
|
||||||
|
|
||||||
if (args.channels) {
|
if (args.channels) {
|
||||||
@ -176,105 +172,33 @@ Client.prototype.connect = function(args) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
args.ip = args.ip || (client.config && client.config.ip) || client.ip;
|
|
||||||
args.hostname = args.hostname || (client.config && client.config.hostname) || client.hostname;
|
|
||||||
|
|
||||||
const network = new Network({
|
const network = new Network({
|
||||||
uuid: args.uuid,
|
uuid: args.uuid,
|
||||||
name: args.name || (Helper.config.displayNetwork ? "" : Helper.config.defaults.name) || "",
|
name: String(args.name || (Helper.config.displayNetwork ? "" : Helper.config.defaults.name) || ""),
|
||||||
host: args.host || "",
|
host: String(args.host || ""),
|
||||||
port: parseInt(args.port, 10) || (args.tls ? 6697 : 6667),
|
port: parseInt(args.port, 10),
|
||||||
tls: !!args.tls,
|
tls: !!args.tls,
|
||||||
rejectUnauthorized: !!args.rejectUnauthorized,
|
rejectUnauthorized: !!args.rejectUnauthorized,
|
||||||
password: args.password,
|
password: String(args.password || ""),
|
||||||
username: args.username || nick.replace(/[^a-zA-Z0-9]/g, ""),
|
nick: String(args.nick || ""),
|
||||||
realname: args.realname || "The Lounge User",
|
username: String(args.username || ""),
|
||||||
commands: args.commands,
|
realname: String(args.realname || ""),
|
||||||
ip: args.ip,
|
commands: args.commands || [],
|
||||||
hostname: args.hostname,
|
ip: args.ip || (client.config && client.config.ip) || client.ip,
|
||||||
|
hostname: args.hostname || (client.config && client.config.hostname) || client.hostname,
|
||||||
channels: channels,
|
channels: channels,
|
||||||
});
|
});
|
||||||
network.setNick(nick);
|
|
||||||
|
|
||||||
client.networks.push(network);
|
client.networks.push(network);
|
||||||
client.emit("network", {
|
client.emit("network", {
|
||||||
networks: [network.getFilteredClone(this.lastActiveChannel, -1)],
|
networks: [network.getFilteredClone(this.lastActiveChannel, -1)],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Helper.config.lockNetwork) {
|
if (!network.validate(client)) {
|
||||||
// This check is needed to prevent invalid user configurations
|
|
||||||
if (!Helper.config.public && args.host && args.host.length > 0 && args.host !== Helper.config.defaults.host) {
|
|
||||||
network.channels[0].pushMessage(client, new Msg({
|
|
||||||
type: Msg.Type.ERROR,
|
|
||||||
text: "Hostname you specified is not allowed.",
|
|
||||||
}), true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
network.host = Helper.config.defaults.host;
|
network.createIrcFramework(client);
|
||||||
network.port = Helper.config.defaults.port;
|
|
||||||
network.tls = Helper.config.defaults.tls;
|
|
||||||
network.rejectUnauthorized = Helper.config.defaults.rejectUnauthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (network.host.length === 0) {
|
|
||||||
network.channels[0].pushMessage(client, new Msg({
|
|
||||||
type: Msg.Type.ERROR,
|
|
||||||
text: "You must specify a hostname to connect.",
|
|
||||||
}), true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Helper.config.webirc && network.host in Helper.config.webirc) {
|
|
||||||
if (!args.hostname) {
|
|
||||||
args.hostname = args.ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.ip) {
|
|
||||||
if (Helper.config.webirc[network.host] instanceof Function) {
|
|
||||||
webirc = Helper.config.webirc[network.host](client, args);
|
|
||||||
} else {
|
|
||||||
webirc = {
|
|
||||||
password: Helper.config.webirc[network.host],
|
|
||||||
username: pkg.name,
|
|
||||||
address: args.ip,
|
|
||||||
hostname: args.hostname,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn("Cannot find a valid WEBIRC configuration for " + nick
|
|
||||||
+ "!" + network.username + "@" + network.host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
network.irc = new ircFramework.Client({
|
|
||||||
version: false, // We handle it ourselves
|
|
||||||
host: network.host,
|
|
||||||
port: network.port,
|
|
||||||
nick: nick,
|
|
||||||
username: Helper.config.useHexIp ? Helper.ip2hex(args.ip) : network.username,
|
|
||||||
gecos: network.realname,
|
|
||||||
password: network.password,
|
|
||||||
tls: network.tls,
|
|
||||||
outgoing_addr: Helper.config.bind,
|
|
||||||
rejectUnauthorized: network.rejectUnauthorized,
|
|
||||||
enable_chghost: true,
|
|
||||||
enable_echomessage: true,
|
|
||||||
auto_reconnect: true,
|
|
||||||
auto_reconnect_wait: 10000 + Math.floor(Math.random() * 1000), // If multiple users are connected to the same network, randomize their reconnections a little
|
|
||||||
auto_reconnect_max_retries: 360, // At least one hour (plus timeouts) worth of reconnections
|
|
||||||
webirc: webirc,
|
|
||||||
});
|
|
||||||
|
|
||||||
network.irc.requestCap([
|
|
||||||
"znc.in/self-message", // Legacy echo-message for ZNC
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Request only new messages from ZNC if we have sqlite logging enabled
|
|
||||||
// See http://wiki.znc.in/Playback
|
|
||||||
if (client.config.log && Helper.config.messageStorage.includes("sqlite")) {
|
|
||||||
network.irc.requestCap("znc.in/playback");
|
|
||||||
}
|
|
||||||
|
|
||||||
events.forEach((plugin) => {
|
events.forEach((plugin) => {
|
||||||
require(`./plugins/irc-events/${plugin}`).apply(client, [
|
require(`./plugins/irc-events/${plugin}`).apply(client, [
|
||||||
|
@ -31,6 +31,7 @@ const Helper = {
|
|||||||
getGitCommit,
|
getGitCommit,
|
||||||
ip2hex,
|
ip2hex,
|
||||||
mergeConfig,
|
mergeConfig,
|
||||||
|
getDefaultNick,
|
||||||
|
|
||||||
password: {
|
password: {
|
||||||
hash: passwordHash,
|
hash: passwordHash,
|
||||||
@ -191,6 +192,14 @@ function passwordCompare(password, expected) {
|
|||||||
return bcrypt.compare(password, expected);
|
return bcrypt.compare(password, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDefaultNick() {
|
||||||
|
if (!this.config.defaults.nick) {
|
||||||
|
return "thelounge";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.config.defaults.nick.replace(/%/g, () => Math.floor(Math.random() * 10));
|
||||||
|
}
|
||||||
|
|
||||||
function mergeConfig(oldConfig, newConfig) {
|
function mergeConfig(oldConfig, newConfig) {
|
||||||
return _.mergeWith(oldConfig, newConfig, (objValue, srcValue, key) => {
|
return _.mergeWith(oldConfig, newConfig, (objValue, srcValue, key) => {
|
||||||
// Do not override config variables if the type is incorrect (e.g. object changed into a string)
|
// Do not override config variables if the type is incorrect (e.g. object changed into a string)
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const uuidv4 = require("uuid/v4");
|
const uuidv4 = require("uuid/v4");
|
||||||
|
const IrcFramework = require("irc-framework");
|
||||||
const Chan = require("./chan");
|
const Chan = require("./chan");
|
||||||
|
const Msg = require("./msg");
|
||||||
|
const Helper = require("../helper");
|
||||||
|
|
||||||
module.exports = Network;
|
module.exports = Network;
|
||||||
|
|
||||||
@ -59,6 +62,165 @@ function Network(attr) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Network.prototype.validate = function(client) {
|
||||||
|
this.setNick(String(this.nick || Helper.getDefaultNick()).replace(" ", "_"));
|
||||||
|
|
||||||
|
if (!this.username) {
|
||||||
|
this.username = this.nick.replace(/[^a-zA-Z0-9]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.realname) {
|
||||||
|
this.realname = "The Lounge User";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.port) {
|
||||||
|
this.port = this.tls ? 6697 : 6667;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Helper.config.lockNetwork) {
|
||||||
|
// This check is needed to prevent invalid user configurations
|
||||||
|
if (!Helper.config.public && this.host && this.host.length > 0 && this.host !== Helper.config.defaults.host) {
|
||||||
|
this.channels[0].pushMessage(client, new Msg({
|
||||||
|
type: Msg.Type.ERROR,
|
||||||
|
text: "Hostname you specified is not allowed.",
|
||||||
|
}), true);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.host = Helper.config.defaults.host;
|
||||||
|
this.port = Helper.config.defaults.port;
|
||||||
|
this.tls = Helper.config.defaults.tls;
|
||||||
|
this.rejectUnauthorized = Helper.config.defaults.rejectUnauthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.host.length === 0) {
|
||||||
|
this.channels[0].pushMessage(client, new Msg({
|
||||||
|
type: Msg.Type.ERROR,
|
||||||
|
text: "You must specify a hostname to connect.",
|
||||||
|
}), true);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Network.prototype.createIrcFramework = function(client) {
|
||||||
|
this.irc = new IrcFramework.Client({
|
||||||
|
version: false, // We handle it ourselves
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
nick: this.nick,
|
||||||
|
username: Helper.config.useHexIp ? Helper.ip2hex(this.ip) : this.username,
|
||||||
|
gecos: this.realname,
|
||||||
|
password: this.password,
|
||||||
|
tls: this.tls,
|
||||||
|
outgoing_addr: Helper.config.bind,
|
||||||
|
rejectUnauthorized: this.rejectUnauthorized,
|
||||||
|
enable_chghost: true,
|
||||||
|
enable_echomessage: true,
|
||||||
|
auto_reconnect: true,
|
||||||
|
auto_reconnect_wait: 10000 + Math.floor(Math.random() * 1000), // If multiple users are connected to the same network, randomize their reconnections a little
|
||||||
|
auto_reconnect_max_retries: 360, // At least one hour (plus timeouts) worth of reconnections
|
||||||
|
webirc: this.createWebIrc(client),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.irc.requestCap([
|
||||||
|
"znc.in/self-message", // Legacy echo-message for ZNC
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Request only new messages from ZNC if we have sqlite logging enabled
|
||||||
|
// See http://wiki.znc.in/Playback
|
||||||
|
if (client.config.log && Helper.config.messageStorage.includes("sqlite")) {
|
||||||
|
this.irc.requestCap("znc.in/playback");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Network.prototype.createWebIrc = function(client) {
|
||||||
|
if (!Helper.config.webirc || !(this.host in Helper.config.webirc)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.ip) {
|
||||||
|
log.warn(`Cannot find a valid WEBIRC configuration for ${this.nick}!${this.username}@${this.host}`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hostname) {
|
||||||
|
this.hostname = this.ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Helper.config.webirc[this.host] instanceof Function) {
|
||||||
|
return Helper.config.webirc[this.host](client, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
password: Helper.config.webirc[this.host],
|
||||||
|
username: "thelounge",
|
||||||
|
address: this.ip,
|
||||||
|
hostname: this.hostname,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Network.prototype.edit = function(client, args) {
|
||||||
|
const oldNick = this.nick;
|
||||||
|
|
||||||
|
this.nick = args.nick;
|
||||||
|
this.host = String(args.host || "");
|
||||||
|
this.name = String(args.name || "") || this.host;
|
||||||
|
this.port = parseInt(args.port, 10);
|
||||||
|
this.tls = !!args.tls;
|
||||||
|
this.rejectUnauthorized = !!args.rejectUnauthorized;
|
||||||
|
this.password = String(args.password || "");
|
||||||
|
this.username = String(args.username || "");
|
||||||
|
this.realname = String(args.realname || "");
|
||||||
|
|
||||||
|
// Split commands into an array
|
||||||
|
this.commands = String(args.commands || "")
|
||||||
|
.replace(/\r\n|\r|\n/g, "\n")
|
||||||
|
.split("\n")
|
||||||
|
.filter((command) => command.length > 0);
|
||||||
|
|
||||||
|
// Sync lobby channel name
|
||||||
|
this.channels[0].name = this.name;
|
||||||
|
|
||||||
|
if (!this.validate(client)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.irc) {
|
||||||
|
if (this.nick !== oldNick) {
|
||||||
|
if (this.irc.connection && this.irc.connection.connected) {
|
||||||
|
// Send new nick straight away
|
||||||
|
this.irc.raw("NICK", this.nick);
|
||||||
|
} else {
|
||||||
|
this.irc.options.nick = this.irc.user.nick = this.nick;
|
||||||
|
|
||||||
|
// Update UI nick straight away if IRC is not connected
|
||||||
|
client.emit("nick", {
|
||||||
|
network: this.id,
|
||||||
|
nick: this.nick,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.irc.options.host = this.host;
|
||||||
|
this.irc.options.port = this.port;
|
||||||
|
this.irc.options.password = this.password;
|
||||||
|
this.irc.options.gecos = this.irc.user.gecos = this.realname;
|
||||||
|
this.irc.options.tls = this.tls;
|
||||||
|
this.irc.options.rejectUnauthorized = this.rejectUnauthorized;
|
||||||
|
|
||||||
|
if (!Helper.config.useHexIp) {
|
||||||
|
this.irc.options.username = this.irc.user.username = this.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.save();
|
||||||
|
};
|
||||||
|
|
||||||
Network.prototype.destroy = function() {
|
Network.prototype.destroy = function() {
|
||||||
this.channels.forEach((channel) => channel.destroy());
|
this.channels.forEach((channel) => channel.destroy());
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Msg = require("../../models/msg");
|
const Msg = require("../../models/msg");
|
||||||
|
const Helper = require("../../helper");
|
||||||
|
|
||||||
module.exports = function(irc, network) {
|
module.exports = function(irc, network) {
|
||||||
const client = this;
|
const client = this;
|
||||||
@ -59,7 +60,7 @@ module.exports = function(irc, network) {
|
|||||||
lobby.pushMessage(client, msg, true);
|
lobby.pushMessage(client, msg, true);
|
||||||
|
|
||||||
if (irc.connection.registered === false) {
|
if (irc.connection.registered === false) {
|
||||||
irc.changeNick("thelounge" + Math.floor(Math.random() * 100));
|
irc.changeNick(Helper.getDefaultNick());
|
||||||
}
|
}
|
||||||
|
|
||||||
client.emit("nick", {
|
client.emit("nick", {
|
||||||
|
@ -301,17 +301,46 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("conn", (data) => {
|
socket.on("network:new", (data) => {
|
||||||
if (typeof data === "object") {
|
if (typeof data === "object") {
|
||||||
// prevent people from overriding webirc settings
|
// prevent people from overriding webirc settings
|
||||||
data.ip = null;
|
data.ip = null;
|
||||||
data.hostname = null;
|
data.hostname = null;
|
||||||
data.uuid = null;
|
data.uuid = null;
|
||||||
|
data.commands = null;
|
||||||
|
|
||||||
client.connect(data);
|
client.connect(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("network:get", (data) => {
|
||||||
|
if (typeof data !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const network = _.find(client.networks, {uuid: data});
|
||||||
|
|
||||||
|
if (!network) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit("network:info", getClientConfiguration(network.export()));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("network:edit", (data) => {
|
||||||
|
if (typeof data !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const network = _.find(client.networks, {uuid: data.uuid});
|
||||||
|
|
||||||
|
if (!network) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
network.edit(client, data);
|
||||||
|
});
|
||||||
|
|
||||||
if (!Helper.config.public && !Helper.config.ldap.enable) {
|
if (!Helper.config.public && !Helper.config.ldap.enable) {
|
||||||
socket.on("change-password", (data) => {
|
socket.on("change-password", (data) => {
|
||||||
if (typeof data === "object") {
|
if (typeof data === "object") {
|
||||||
@ -541,27 +570,22 @@ function initializeClient(socket, client, token, lastMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClientConfiguration() {
|
function getClientConfiguration(network) {
|
||||||
const config = _.pick(Helper.config, [
|
const config = _.pick(Helper.config, [
|
||||||
"public",
|
"public",
|
||||||
"lockNetwork",
|
"lockNetwork",
|
||||||
"displayNetwork",
|
"displayNetwork",
|
||||||
"useHexIp",
|
"useHexIp",
|
||||||
"themes",
|
|
||||||
"prefetch",
|
"prefetch",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
config.ldapEnabled = Helper.config.ldap.enable;
|
config.ldapEnabled = Helper.config.ldap.enable;
|
||||||
config.version = pkg.version;
|
|
||||||
config.gitCommit = Helper.getGitCommit();
|
|
||||||
config.themes = themes.getAll();
|
|
||||||
config.defaultTheme = Helper.config.theme;
|
|
||||||
|
|
||||||
if (config.displayNetwork) {
|
if (config.displayNetwork) {
|
||||||
config.defaults = _.clone(Helper.config.defaults);
|
config.defaults = _.clone(network || Helper.config.defaults);
|
||||||
} else {
|
} else {
|
||||||
// Only send defaults that are visible on the client
|
// Only send defaults that are visible on the client
|
||||||
config.defaults = _.pick(Helper.config.defaults, [
|
config.defaults = _.pick(network || Helper.config.defaults, [
|
||||||
"nick",
|
"nick",
|
||||||
"username",
|
"username",
|
||||||
"password",
|
"password",
|
||||||
@ -570,7 +594,13 @@ function getClientConfiguration() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
config.defaults.nick = config.defaults.nick.replace(/%/g, () => Math.floor(Math.random() * 10));
|
if (!network) {
|
||||||
|
config.version = pkg.version;
|
||||||
|
config.gitCommit = Helper.getGitCommit();
|
||||||
|
config.themes = themes.getAll();
|
||||||
|
config.defaultTheme = Helper.config.theme;
|
||||||
|
config.defaults.nick = Helper.getDefaultNick();
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ const Chan = require("../../src/models/chan");
|
|||||||
const Msg = require("../../src/models/msg");
|
const Msg = require("../../src/models/msg");
|
||||||
const User = require("../../src/models/user");
|
const User = require("../../src/models/user");
|
||||||
const Network = require("../../src/models/network");
|
const Network = require("../../src/models/network");
|
||||||
|
const Helper = require("../../src/helper");
|
||||||
|
|
||||||
describe("Network", function() {
|
describe("Network", function() {
|
||||||
describe("#export()", function() {
|
describe("#export()", function() {
|
||||||
@ -49,6 +50,104 @@ describe("Network", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("validate should set correct defaults", function() {
|
||||||
|
Helper.config.defaults.nick = "";
|
||||||
|
|
||||||
|
const network = new Network({
|
||||||
|
host: "localhost",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(network.validate()).to.be.true;
|
||||||
|
expect(network.nick).to.equal("thelounge");
|
||||||
|
expect(network.username).to.equal("thelounge");
|
||||||
|
expect(network.realname).to.equal("The Lounge User");
|
||||||
|
expect(network.port).to.equal(6667);
|
||||||
|
|
||||||
|
const network2 = new Network({
|
||||||
|
host: "localhost",
|
||||||
|
nick: "@Invalid Nick?",
|
||||||
|
});
|
||||||
|
expect(network2.validate()).to.be.true;
|
||||||
|
expect(network2.username).to.equal("InvalidNick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("lockNetwork should be enforced when validating", function() {
|
||||||
|
Helper.config.lockNetwork = true;
|
||||||
|
Helper.config.public = false;
|
||||||
|
|
||||||
|
const network = new Network({
|
||||||
|
host: "",
|
||||||
|
port: 1337,
|
||||||
|
tls: false,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
});
|
||||||
|
expect(network.validate()).to.be.true;
|
||||||
|
expect(network.host).to.equal("chat.freenode.net");
|
||||||
|
expect(network.port).to.equal(6697);
|
||||||
|
expect(network.tls).to.be.true;
|
||||||
|
expect(network.rejectUnauthorized).to.be.true;
|
||||||
|
|
||||||
|
Helper.config.public = true;
|
||||||
|
|
||||||
|
const network2 = new Network({
|
||||||
|
host: "some.fake.tld",
|
||||||
|
});
|
||||||
|
expect(network2.validate()).to.be.true;
|
||||||
|
expect(network2.host).to.equal("chat.freenode.net");
|
||||||
|
|
||||||
|
Helper.config.lockNetwork = false;
|
||||||
|
Helper.config.public = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("editing a network should enforce correct types", function() {
|
||||||
|
let saveCalled = false;
|
||||||
|
|
||||||
|
const network = new Network();
|
||||||
|
network.edit({
|
||||||
|
save() {
|
||||||
|
saveCalled = true;
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
nick: "newNick",
|
||||||
|
host: "new.tld",
|
||||||
|
name: "Lounge Test Network",
|
||||||
|
port: "1337",
|
||||||
|
tls: undefined,
|
||||||
|
rejectUnauthorized: undefined,
|
||||||
|
username: 1234,
|
||||||
|
password: 4567,
|
||||||
|
realname: 8901,
|
||||||
|
commands: "/command 1 2 3\r\n/ping HELLO\r\r\r\r/whois test\r\n\r\n",
|
||||||
|
ip: "newIp",
|
||||||
|
hostname: "newHostname",
|
||||||
|
id: 1000000,
|
||||||
|
guid: "newGuid",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(saveCalled).to.be.true;
|
||||||
|
expect(network.id).to.not.equal(1000000);
|
||||||
|
expect(network.guid).to.not.equal("newGuid");
|
||||||
|
expect(network.ip).to.be.null;
|
||||||
|
expect(network.hostname).to.be.null;
|
||||||
|
|
||||||
|
expect(network.name).to.equal("Lounge Test Network");
|
||||||
|
expect(network.channels[0].name).to.equal("Lounge Test Network");
|
||||||
|
|
||||||
|
expect(network.nick).to.equal("newNick");
|
||||||
|
expect(network.host).to.equal("new.tld");
|
||||||
|
expect(network.port).to.equal(1337);
|
||||||
|
expect(network.tls).to.be.false;
|
||||||
|
expect(network.rejectUnauthorized).to.be.false;
|
||||||
|
expect(network.username).to.equal("1234");
|
||||||
|
expect(network.password).to.equal("4567");
|
||||||
|
expect(network.realname).to.equal("8901");
|
||||||
|
expect(network.commands).to.deep.equal([
|
||||||
|
"/command 1 2 3",
|
||||||
|
"/ping HELLO",
|
||||||
|
"/whois test",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should generate uuid (v4) for each network", function() {
|
it("should generate uuid (v4) for each network", function() {
|
||||||
const network1 = new Network();
|
const network1 = new Network();
|
||||||
const network2 = new Network();
|
const network2 = new Network();
|
||||||
|
@ -88,7 +88,7 @@ describe("Server", function() {
|
|||||||
|
|
||||||
it("should create network", (done) => {
|
it("should create network", (done) => {
|
||||||
client.on("init", () => {
|
client.on("init", () => {
|
||||||
client.emit("conn", {
|
client.emit("network:new", {
|
||||||
username: "test-user",
|
username: "test-user",
|
||||||
realname: "The Lounge Test",
|
realname: "The Lounge Test",
|
||||||
nick: "test-user",
|
nick: "test-user",
|
||||||
|
Loading…
Reference in New Issue
Block a user