@lordbex/thelounge 4.4.3-blowfish → 4.4.4-blowfish
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -5
- package/dist/package.json +3 -2
- package/dist/server/clientManager.js +19 -18
- package/dist/server/identification.js +1 -1
- package/dist/server/models/network.js +16 -20
- package/dist/server/plugins/changelog.js +1 -1
- package/dist/server/plugins/inputs/action.js +1 -1
- package/dist/server/plugins/inputs/msg.js +1 -1
- package/dist/server/plugins/inputs/notice.js +1 -1
- package/dist/server/plugins/irc-events/link.js +48 -14
- package/dist/server/plugins/webpush.js +1 -1
- package/dist/server/utils/fish.js +354 -387
- package/package.json +3 -2
- package/public/css/style.css +2 -2
- package/public/css/style.css.map +1 -1
- package/public/js/bundle.js +1 -1
- package/public/js/bundle.js.map +1 -1
- package/public/js/bundle.vendor.js +1 -1
- package/public/js/bundle.vendor.js.map +1 -1
- package/public/js/loading-error-handlers.js +1 -1
- package/public/service-worker.js +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
•
|
|
18
18
|
<a href="https://demo.thelounge.chat/">Demo</a>
|
|
19
19
|
•
|
|
20
|
-
<a href="https://github.com/
|
|
20
|
+
<a href="https://github.com/LordBex/thelounge-docker">Docker</a>
|
|
21
21
|
</strong>
|
|
22
22
|
</p>
|
|
23
23
|
<p align="center">
|
|
@@ -27,15 +27,39 @@
|
|
|
27
27
|
<a href="https://yarn.pm/thelounge"><img
|
|
28
28
|
alt="npm version"
|
|
29
29
|
src="https://img.shields.io/npm/v/thelounge.svg?colorA=333a41&maxAge=3600"></a>
|
|
30
|
-
<a href="https://github.com/
|
|
30
|
+
<a href="https://github.com/LordBex/thelounge/actions"><img
|
|
31
31
|
alt="Build Status"
|
|
32
|
-
src="https://github.com/
|
|
32
|
+
src="https://github.com/LordBex/thelounge/workflows/Build/badge.svg"></a>
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
<p align="center">
|
|
36
36
|
<img src="https://raw.githubusercontent.com/thelounge/thelounge.github.io/master/img/thelounge-screenshot.png" width="550">
|
|
37
37
|
</p>
|
|
38
38
|
|
|
39
|
+
## 🔐 FiSH Blowfish Fork
|
|
40
|
+
|
|
41
|
+
**This is a fork of The Lounge with integrated FiSH Blowfish encryption support.**
|
|
42
|
+
|
|
43
|
+
This enhanced version adds IRC message encryption capabilities using the FiSH (Blowfish) encryption protocol, compatible with popular IRC clients like HexChat, mIRC, and others.
|
|
44
|
+
|
|
45
|
+
### FiSH Features
|
|
46
|
+
|
|
47
|
+
- **Per-channel encryption** - Set individual encryption keys for each channel or query
|
|
48
|
+
- **Global encryption key** - Apply a default key across all channels
|
|
49
|
+
- **Automatic encryption/decryption** - Seamlessly encrypts outgoing and decrypts incoming messages
|
|
50
|
+
- **mIRC compatibility** - Full compatibility with standard FiSH implementations
|
|
51
|
+
- **Easy key management** - Simple `/blow` commands for setting and clearing keys
|
|
52
|
+
|
|
53
|
+
### Usage
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
/blow <key> # Set encryption key for current channel
|
|
57
|
+
/blow off # Disable encryption for current channel
|
|
58
|
+
/blow # Show current encryption status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Keys can also be configured via the network settings in the web interface.
|
|
62
|
+
|
|
39
63
|
## Overview
|
|
40
64
|
|
|
41
65
|
- **Modern features brought to IRC.** Push notifications, link previews, new message markers, and more bring IRC to the 21st century.
|
|
@@ -63,7 +87,7 @@ Please refer to the [install and upgrade documentation on our website](https://t
|
|
|
63
87
|
The following commands install and run the development version of The Lounge:
|
|
64
88
|
|
|
65
89
|
```sh
|
|
66
|
-
git clone https://github.com/
|
|
90
|
+
git clone https://github.com/yourusername/thelounge.git
|
|
67
91
|
cd thelounge
|
|
68
92
|
yarn install
|
|
69
93
|
NODE_ENV=production yarn build
|
|
@@ -82,7 +106,7 @@ fork.
|
|
|
82
106
|
|
|
83
107
|
Before submitting any change, make sure to:
|
|
84
108
|
|
|
85
|
-
- Read the [Contributing instructions](https://github.com/
|
|
109
|
+
- Read the [Contributing instructions](https://github.com/LordBex/thelounge/blob/master/.github/CONTRIBUTING.md#contributing)
|
|
86
110
|
- Run `yarn test` to execute linters and the test suite
|
|
87
111
|
- Run `yarn format:prettier` if linting fails
|
|
88
112
|
- Run `yarn build:client` if you change or add anything in `client/js` or `client/components`
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lordbex/thelounge",
|
|
3
3
|
"description": "The self-hosted Web IRC client",
|
|
4
|
-
"version": "4.4.
|
|
4
|
+
"version": "4.4.4-blowfish",
|
|
5
5
|
"preferGlobal": true,
|
|
6
6
|
"bin": {
|
|
7
7
|
"thelounge": "index.js"
|
|
@@ -170,5 +170,6 @@
|
|
|
170
170
|
"webpack-cli": "4.9.2",
|
|
171
171
|
"webpack-dev-middleware": "5.3.4",
|
|
172
172
|
"webpack-hot-middleware": "2.25.4"
|
|
173
|
-
}
|
|
173
|
+
},
|
|
174
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
174
175
|
}
|
|
@@ -64,24 +64,23 @@ class ClientManager {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
autoloadUsers() {
|
|
67
|
-
fs_1.default.watch(config_1.default.getUsersPath(),
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (updatedUsers.length === 0) {
|
|
71
|
-
log_1.default.info(`There are currently no users. Create one with ${chalk_1.default.bold("thelounge add <name>")}.`);
|
|
67
|
+
fs_1.default.watch(config_1.default.getUsersPath(), (_eventType, file) => {
|
|
68
|
+
if (!file.endsWith(".json")) {
|
|
69
|
+
return;
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
const name = file.slice(0, -5);
|
|
72
|
+
const userPath = config_1.default.getUserConfigPath(name);
|
|
73
|
+
if (fs_1.default.existsSync(userPath)) {
|
|
74
|
+
this.loadUser(name);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const client = lodash_1.default.find(this.clients, { name });
|
|
78
|
+
if (client) {
|
|
79
|
+
client.quit(true);
|
|
80
|
+
this.clients = lodash_1.default.without(this.clients, client);
|
|
81
|
+
log_1.default.info(`User ${chalk_1.default.bold(name)} disconnected and removed.`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
85
84
|
}
|
|
86
85
|
loadUser(name) {
|
|
87
86
|
const userConfig = this.readUserConfig(name);
|
|
@@ -132,9 +131,11 @@ class ClientManager {
|
|
|
132
131
|
log: enableLog,
|
|
133
132
|
};
|
|
134
133
|
try {
|
|
135
|
-
|
|
134
|
+
const tmpPath = userPath + ".tmp";
|
|
135
|
+
fs_1.default.writeFileSync(tmpPath, JSON.stringify(user, null, "\t"), {
|
|
136
136
|
mode: 0o600,
|
|
137
137
|
});
|
|
138
|
+
fs_1.default.renameSync(tmpPath, userPath);
|
|
138
139
|
}
|
|
139
140
|
catch (e) {
|
|
140
141
|
log_1.default.error(`Failed to create user ${chalk_1.default.green(name)} (${e})`);
|
|
@@ -108,7 +108,7 @@ class Identification {
|
|
|
108
108
|
// Race condition: this can happen when more than one socket gets disconnected at
|
|
109
109
|
// once, since we `refresh()` for each one being added/removed. This results
|
|
110
110
|
// in there possibly being one or more disconnected sockets remaining when we get here.
|
|
111
|
-
log_1.default.warn(`oidentd: socket has no remote or local port (id=${id}). See https://github.com/
|
|
111
|
+
log_1.default.warn(`oidentd: socket has no remote or local port (id=${id}). See https://github.com/lordbex/thelounge/pull/4695.`);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
if (!connection.socket.remoteAddress) {
|
|
@@ -222,7 +222,10 @@ class Network {
|
|
|
222
222
|
}
|
|
223
223
|
if (!this.sasl) {
|
|
224
224
|
delete this.irc.options.sasl_mechanism;
|
|
225
|
-
|
|
225
|
+
// irc-framework has a funny fallback where it uses nick + server pw
|
|
226
|
+
// in the sasl handshake, if account is undefined, so we need an empty
|
|
227
|
+
// object here to really turn it off
|
|
228
|
+
this.irc.options.account = {};
|
|
226
229
|
}
|
|
227
230
|
else if (this.sasl === "external") {
|
|
228
231
|
this.irc.options.sasl_mechanism = "EXTERNAL";
|
|
@@ -309,25 +312,19 @@ class Network {
|
|
|
309
312
|
if (Object.prototype.hasOwnProperty.call(args, "fishGlobalKey")) {
|
|
310
313
|
this.fishGlobalKey = String(args.fishGlobalKey || "").trim();
|
|
311
314
|
}
|
|
312
|
-
|
|
313
|
-
|
|
315
|
+
// FiSH: read per-target keys (only update when provided)
|
|
316
|
+
if (Object.prototype.hasOwnProperty.call(args, "fishKeys")) {
|
|
317
|
+
const value = args.fishKeys;
|
|
314
318
|
const map = {};
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const name = trimmed.substring(0, spaceIdx).toLowerCase();
|
|
325
|
-
const key = trimmed.substring(spaceIdx + 1).trim();
|
|
326
|
-
if (!name || !key) {
|
|
327
|
-
return;
|
|
319
|
+
if (value && typeof value === "object") {
|
|
320
|
+
for (const [rawName, rawKey] of Object.entries(value)) {
|
|
321
|
+
const name = String(rawName).trim().toLowerCase();
|
|
322
|
+
const key = String(rawKey ?? "").trim();
|
|
323
|
+
if (name && key) {
|
|
324
|
+
map[name] = key;
|
|
325
|
+
}
|
|
328
326
|
}
|
|
329
|
-
|
|
330
|
-
});
|
|
327
|
+
}
|
|
331
328
|
this.fishKeys = map;
|
|
332
329
|
}
|
|
333
330
|
// Sync lobby channel name
|
|
@@ -482,8 +479,7 @@ class Network {
|
|
|
482
479
|
data.hasSTSPolicy = !!sts_1.default.get(this.host);
|
|
483
480
|
// Include FiSH fields for editing UI
|
|
484
481
|
data.fishGlobalKey = this.fishGlobalKey || "";
|
|
485
|
-
|
|
486
|
-
data.fishKeysText = lines.join("\n");
|
|
482
|
+
data.fishKeys = { ...(this.fishKeys || {}) };
|
|
487
483
|
return data;
|
|
488
484
|
}
|
|
489
485
|
export() {
|
|
@@ -32,7 +32,7 @@ async function fetch() {
|
|
|
32
32
|
return versions;
|
|
33
33
|
}
|
|
34
34
|
try {
|
|
35
|
-
const response = await (0, got_1.default)("https://api.github.com/repos/
|
|
35
|
+
const response = await (0, got_1.default)("https://api.github.com/repos/lordbex/thelounge/releases", {
|
|
36
36
|
headers: {
|
|
37
37
|
Accept: "application/vnd.github.v3.html", // Request rendered markdown
|
|
38
38
|
"User-Agent": package_json_1.default.name + "; +" + package_json_1.default.repository.url, // Identify the client
|
|
@@ -29,7 +29,7 @@ const input = function ({ irc }, chan, cmd, args) {
|
|
|
29
29
|
// If FiSH key is set, encrypt CTCP ACTION and send as normal PRIVMSG with +OK
|
|
30
30
|
if (chan.blowfishKey) {
|
|
31
31
|
const ctcp = "\x01ACTION " + text + "\x01";
|
|
32
|
-
const toSend =
|
|
32
|
+
const toSend = (0, fish_1.createFishMessage)(ctcp, chan.blowfishKey);
|
|
33
33
|
irc.say(chan.name, toSend);
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
@@ -74,7 +74,7 @@ const input = function (network, chan, cmd, args) {
|
|
|
74
74
|
// Determine if we should encrypt using FiSH for this target
|
|
75
75
|
const targetChan = network.getChannel(targetName) || (chan.name === targetName ? chan : undefined);
|
|
76
76
|
const key = targetChan?.blowfishKey;
|
|
77
|
-
const toSend = key ?
|
|
77
|
+
const toSend = key ? (0, fish_1.createFishMessage)(msg, key) : msg;
|
|
78
78
|
network.irc.say(targetName, toSend);
|
|
79
79
|
// If the IRCd does not support echo-message, simulate the message
|
|
80
80
|
// being sent back to us. Emit the same text we sent (encrypted or plain)
|
|
@@ -11,7 +11,7 @@ const input = function (network, chan, cmd, args) {
|
|
|
11
11
|
// Encrypt if a FiSH key is set for the target channel/query
|
|
12
12
|
const targetChan = network.getChannel(targetName) || (chan.name === targetName ? chan : undefined);
|
|
13
13
|
const key = targetChan?.blowfishKey;
|
|
14
|
-
const toSend = key ?
|
|
14
|
+
const toSend = key ? (0, fish_1.createFishMessage)(message, key) : message;
|
|
15
15
|
network.irc.notice(targetName, toSend);
|
|
16
16
|
// If the IRCd does not support echo-message, simulate the message
|
|
17
17
|
// being sent back to us.
|
|
@@ -65,23 +65,57 @@ function default_1(client, chan, msg, cleanText) {
|
|
|
65
65
|
shown: null,
|
|
66
66
|
};
|
|
67
67
|
cleanLinks.push(preview);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
preview.type = "error";
|
|
77
|
-
preview.error = "message";
|
|
78
|
-
preview.message = err.message;
|
|
79
|
-
emitPreview(client, chan, msg, preview);
|
|
80
|
-
});
|
|
68
|
+
const urlObj = new url_1.URL(url);
|
|
69
|
+
if ((urlObj.hostname.endsWith("youtube.com") && urlObj.pathname.includes("watch")) ||
|
|
70
|
+
urlObj.hostname.endsWith("youtu.be")) {
|
|
71
|
+
fetchYoutube(url, msg, chan, preview, client);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
fetchUrl(url, msg, chan, preview, client);
|
|
75
|
+
}
|
|
81
76
|
return cleanLinks;
|
|
82
77
|
}, []);
|
|
83
78
|
}
|
|
84
79
|
exports.default = default_1;
|
|
80
|
+
function fetchUrl(url, msg, chan, preview, client) {
|
|
81
|
+
fetch(url, {
|
|
82
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
83
|
+
language: client.config.browser?.language || "",
|
|
84
|
+
})
|
|
85
|
+
.then((res) => {
|
|
86
|
+
parse(msg, chan, preview, res, client);
|
|
87
|
+
})
|
|
88
|
+
.catch((err) => {
|
|
89
|
+
preview.type = "error";
|
|
90
|
+
preview.error = "message";
|
|
91
|
+
preview.message = err.message;
|
|
92
|
+
emitPreview(client, chan, msg, preview);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function fetchYoutube(url, msg, chan, preview, client) {
|
|
96
|
+
const api_url = `https://www.youtube.com/oembed?url=${encodeURIComponent(url)}&format=json`;
|
|
97
|
+
fetch(api_url, {
|
|
98
|
+
accept: "application/json",
|
|
99
|
+
language: client.config.browser?.language || "",
|
|
100
|
+
})
|
|
101
|
+
.then((res) => {
|
|
102
|
+
const data = JSON.parse(res.data.toString());
|
|
103
|
+
let author = data.author_name || "";
|
|
104
|
+
author = author ? ` ~ ${author}` : "";
|
|
105
|
+
preview.type = "link";
|
|
106
|
+
preview.link = url;
|
|
107
|
+
preview.thumbActualUrl = data.thumbnail_url || "";
|
|
108
|
+
preview.head = data.title || "";
|
|
109
|
+
preview.body = author;
|
|
110
|
+
handlePreview(client, chan, msg, preview, res);
|
|
111
|
+
})
|
|
112
|
+
.catch((err) => {
|
|
113
|
+
preview.type = "error";
|
|
114
|
+
preview.error = "message";
|
|
115
|
+
preview.message = err.message;
|
|
116
|
+
emitPreview(client, chan, msg, preview);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
85
119
|
function parseHtml(preview, res, client) {
|
|
86
120
|
// TODO:
|
|
87
121
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
@@ -330,7 +364,7 @@ function getRequestHeaders(headers) {
|
|
|
330
364
|
const formattedHeaders = {
|
|
331
365
|
// Certain websites like Amazon only add <meta> tags to known bots,
|
|
332
366
|
// lets pretend to be them to get the metadata
|
|
333
|
-
"User-Agent": "Mozilla/5.0 (compatible; The Lounge IRC Client; +https://github.com/
|
|
367
|
+
"User-Agent": "Mozilla/5.0 (compatible; The Lounge IRC Client; +https://github.com/lordbex/thelounge)" +
|
|
334
368
|
" facebookexternalhit/1.1 Twitterbot/1.0",
|
|
335
369
|
Accept: headers.accept || "*/*",
|
|
336
370
|
"X-Purpose": "preview",
|
|
@@ -69,7 +69,7 @@ class WebPush {
|
|
|
69
69
|
});
|
|
70
70
|
log_1.default.info("New VAPID key pair has been generated for use with push subscription.");
|
|
71
71
|
}
|
|
72
|
-
web_push_1.default.setVapidDetails("https://github.com/
|
|
72
|
+
web_push_1.default.setVapidDetails("https://github.com/lordbex/thelounge", this.vapidKeys.publicKey, this.vapidKeys.privateKey);
|
|
73
73
|
}
|
|
74
74
|
push(client, payload, onlyToOffline) {
|
|
75
75
|
lodash_1.default.forOwn(client.config.sessions, ({ pushSubscription }, token) => {
|