@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 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/thelounge/thelounge-docker">Docker</a>
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/thelounge/thelounge/actions"><img
30
+ <a href="https://github.com/LordBex/thelounge/actions"><img
31
31
  alt="Build Status"
32
- src="https://github.com/thelounge/thelounge/workflows/Build/badge.svg"></a>
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/thelounge/thelounge.git
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/thelounge/thelounge/blob/master/.github/CONTRIBUTING.md#contributing)
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.3-blowfish",
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(), lodash_1.default.debounce(() => {
68
- const loaded = this.clients.map((c) => c.name);
69
- const updatedUsers = this.getUsers();
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
- // Reload all users. Existing users will only have their passwords reloaded.
74
- updatedUsers.forEach((name) => this.loadUser(name));
75
- // Existing users removed since last time users were loaded
76
- lodash_1.default.difference(loaded, updatedUsers).forEach((name) => {
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
- });
84
- }, 1000, { maxWait: 10000 }));
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
- fs_1.default.writeFileSync(userPath, JSON.stringify(user, null, "\t"), {
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/thelounge/thelounge/pull/4695.`);
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
- delete this.irc.options.account;
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
- if (Object.prototype.hasOwnProperty.call(args, "fishKeysText")) {
313
- const fishKeysText = String(args.fishKeysText || "").replace(/\r\n|\r|\n/g, "\n");
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
- fishKeysText.split("\n").forEach((line) => {
316
- const trimmed = line.trim();
317
- if (!trimmed) {
318
- return;
319
- }
320
- const spaceIdx = trimmed.indexOf(" ");
321
- if (spaceIdx === -1) {
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
- map[name] = key;
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
- const lines = Object.entries(this.fishKeys || {}).map(([name, key]) => `${name} ${key}`);
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/thelounge/thelounge/releases", {
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 = "+OK " + (0, fish_1.fishEncryptPayload)(ctcp, chan.blowfishKey);
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 ? "+OK " + (0, fish_1.fishEncryptPayload)(msg, key) : msg;
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 ? "+OK " + (0, fish_1.fishEncryptPayload)(message, key) : message;
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
- fetch(url, {
69
- accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
70
- language: client.config.browser?.language || "",
71
- })
72
- .then((res) => {
73
- parse(msg, chan, preview, res, client);
74
- })
75
- .catch((err) => {
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/thelounge/thelounge)" +
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/thelounge/thelounge", this.vapidKeys.publicKey, this.vapidKeys.privateKey);
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) => {