@lordbex/thelounge 4.4.3-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.
Files changed (148) hide show
  1. package/.thelounge_home +1 -0
  2. package/LICENSE +22 -0
  3. package/README.md +95 -0
  4. package/client/index.html.tpl +69 -0
  5. package/dist/defaults/config.js +465 -0
  6. package/dist/package.json +174 -0
  7. package/dist/server/client.js +678 -0
  8. package/dist/server/clientManager.js +220 -0
  9. package/dist/server/command-line/index.js +85 -0
  10. package/dist/server/command-line/install.js +123 -0
  11. package/dist/server/command-line/outdated.js +30 -0
  12. package/dist/server/command-line/start.js +34 -0
  13. package/dist/server/command-line/storage.js +103 -0
  14. package/dist/server/command-line/uninstall.js +40 -0
  15. package/dist/server/command-line/upgrade.js +64 -0
  16. package/dist/server/command-line/users/add.js +67 -0
  17. package/dist/server/command-line/users/edit.js +39 -0
  18. package/dist/server/command-line/users/index.js +17 -0
  19. package/dist/server/command-line/users/list.js +53 -0
  20. package/dist/server/command-line/users/remove.js +37 -0
  21. package/dist/server/command-line/users/reset.js +64 -0
  22. package/dist/server/command-line/utils.js +177 -0
  23. package/dist/server/config.js +138 -0
  24. package/dist/server/helper.js +161 -0
  25. package/dist/server/identification.js +139 -0
  26. package/dist/server/index.js +3 -0
  27. package/dist/server/log.js +35 -0
  28. package/dist/server/models/chan.js +275 -0
  29. package/dist/server/models/msg.js +92 -0
  30. package/dist/server/models/network.js +546 -0
  31. package/dist/server/models/prefix.js +31 -0
  32. package/dist/server/models/user.js +42 -0
  33. package/dist/server/plugins/auth/ldap.js +188 -0
  34. package/dist/server/plugins/auth/local.js +41 -0
  35. package/dist/server/plugins/auth.js +70 -0
  36. package/dist/server/plugins/changelog.js +103 -0
  37. package/dist/server/plugins/clientCertificate.js +115 -0
  38. package/dist/server/plugins/dev-server.js +33 -0
  39. package/dist/server/plugins/inputs/action.js +54 -0
  40. package/dist/server/plugins/inputs/away.js +20 -0
  41. package/dist/server/plugins/inputs/ban.js +45 -0
  42. package/dist/server/plugins/inputs/blow.js +44 -0
  43. package/dist/server/plugins/inputs/connect.js +41 -0
  44. package/dist/server/plugins/inputs/ctcp.js +29 -0
  45. package/dist/server/plugins/inputs/disconnect.js +15 -0
  46. package/dist/server/plugins/inputs/ignore.js +74 -0
  47. package/dist/server/plugins/inputs/ignorelist.js +50 -0
  48. package/dist/server/plugins/inputs/index.js +105 -0
  49. package/dist/server/plugins/inputs/invite.js +31 -0
  50. package/dist/server/plugins/inputs/kick.js +26 -0
  51. package/dist/server/plugins/inputs/kill.js +13 -0
  52. package/dist/server/plugins/inputs/list.js +12 -0
  53. package/dist/server/plugins/inputs/mode.js +55 -0
  54. package/dist/server/plugins/inputs/msg.js +106 -0
  55. package/dist/server/plugins/inputs/mute.js +56 -0
  56. package/dist/server/plugins/inputs/nick.js +55 -0
  57. package/dist/server/plugins/inputs/notice.js +42 -0
  58. package/dist/server/plugins/inputs/part.js +46 -0
  59. package/dist/server/plugins/inputs/quit.js +27 -0
  60. package/dist/server/plugins/inputs/raw.js +13 -0
  61. package/dist/server/plugins/inputs/rejoin.js +25 -0
  62. package/dist/server/plugins/inputs/topic.js +24 -0
  63. package/dist/server/plugins/inputs/whois.js +19 -0
  64. package/dist/server/plugins/irc-events/away.js +59 -0
  65. package/dist/server/plugins/irc-events/cap.js +62 -0
  66. package/dist/server/plugins/irc-events/chghost.js +29 -0
  67. package/dist/server/plugins/irc-events/connection.js +152 -0
  68. package/dist/server/plugins/irc-events/ctcp.js +72 -0
  69. package/dist/server/plugins/irc-events/error.js +80 -0
  70. package/dist/server/plugins/irc-events/help.js +21 -0
  71. package/dist/server/plugins/irc-events/info.js +21 -0
  72. package/dist/server/plugins/irc-events/invite.js +27 -0
  73. package/dist/server/plugins/irc-events/join.js +53 -0
  74. package/dist/server/plugins/irc-events/kick.js +39 -0
  75. package/dist/server/plugins/irc-events/link.js +442 -0
  76. package/dist/server/plugins/irc-events/list.js +47 -0
  77. package/dist/server/plugins/irc-events/message.js +187 -0
  78. package/dist/server/plugins/irc-events/mode.js +124 -0
  79. package/dist/server/plugins/irc-events/modelist.js +67 -0
  80. package/dist/server/plugins/irc-events/motd.js +29 -0
  81. package/dist/server/plugins/irc-events/names.js +21 -0
  82. package/dist/server/plugins/irc-events/nick.js +45 -0
  83. package/dist/server/plugins/irc-events/part.js +35 -0
  84. package/dist/server/plugins/irc-events/quit.js +32 -0
  85. package/dist/server/plugins/irc-events/sasl.js +26 -0
  86. package/dist/server/plugins/irc-events/topic.js +42 -0
  87. package/dist/server/plugins/irc-events/unhandled.js +31 -0
  88. package/dist/server/plugins/irc-events/welcome.js +22 -0
  89. package/dist/server/plugins/irc-events/whois.js +57 -0
  90. package/dist/server/plugins/messageStorage/sqlite.js +454 -0
  91. package/dist/server/plugins/messageStorage/text.js +124 -0
  92. package/dist/server/plugins/packages/index.js +200 -0
  93. package/dist/server/plugins/packages/publicClient.js +66 -0
  94. package/dist/server/plugins/packages/themes.js +61 -0
  95. package/dist/server/plugins/storage.js +88 -0
  96. package/dist/server/plugins/sts.js +85 -0
  97. package/dist/server/plugins/uploader.js +267 -0
  98. package/dist/server/plugins/webpush.js +99 -0
  99. package/dist/server/server.js +857 -0
  100. package/dist/server/storageCleaner.js +131 -0
  101. package/dist/server/utils/fish.js +432 -0
  102. package/dist/shared/irc.js +19 -0
  103. package/dist/shared/linkify.js +81 -0
  104. package/dist/shared/types/chan.js +22 -0
  105. package/dist/shared/types/changelog.js +2 -0
  106. package/dist/shared/types/config.js +2 -0
  107. package/dist/shared/types/mention.js +2 -0
  108. package/dist/shared/types/msg.js +34 -0
  109. package/dist/shared/types/network.js +2 -0
  110. package/dist/shared/types/storage.js +2 -0
  111. package/dist/shared/types/user.js +2 -0
  112. package/dist/webpack.config.js +224 -0
  113. package/index.js +38 -0
  114. package/package.json +174 -0
  115. package/public/audio/pop.wav +0 -0
  116. package/public/css/style.css +12 -0
  117. package/public/css/style.css.map +1 -0
  118. package/public/favicon.ico +0 -0
  119. package/public/fonts/fa-solid-900.woff +0 -0
  120. package/public/fonts/fa-solid-900.woff2 +0 -0
  121. package/public/img/favicon-alerted.ico +0 -0
  122. package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
  123. package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
  124. package/public/img/icon-black-transparent-bg.svg +1 -0
  125. package/public/img/logo-grey-bg-120x120px.png +0 -0
  126. package/public/img/logo-grey-bg-152x152px.png +0 -0
  127. package/public/img/logo-grey-bg-167x167px.png +0 -0
  128. package/public/img/logo-grey-bg-180x180px.png +0 -0
  129. package/public/img/logo-grey-bg-192x192px.png +0 -0
  130. package/public/img/logo-grey-bg-512x512px.png +0 -0
  131. package/public/img/logo-grey-bg.svg +1 -0
  132. package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
  133. package/public/img/logo-horizontal-transparent-bg.svg +1 -0
  134. package/public/img/logo-transparent-bg-inverted.svg +1 -0
  135. package/public/img/logo-transparent-bg.svg +1 -0
  136. package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
  137. package/public/img/logo-vertical-transparent-bg.svg +1 -0
  138. package/public/js/bundle.js +2 -0
  139. package/public/js/bundle.js.map +1 -0
  140. package/public/js/bundle.vendor.js +3 -0
  141. package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
  142. package/public/js/bundle.vendor.js.map +1 -0
  143. package/public/js/loading-error-handlers.js +1 -0
  144. package/public/robots.txt +2 -0
  145. package/public/service-worker.js +1 -0
  146. package/public/thelounge.webmanifest +53 -0
  147. package/public/themes/default.css +35 -0
  148. package/public/themes/morning.css +183 -0
@@ -0,0 +1,678 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const lodash_1 = __importDefault(require("lodash"));
30
+ const ua_parser_js_1 = __importDefault(require("ua-parser-js"));
31
+ const uuid_1 = require("uuid");
32
+ const escapeRegExp_1 = __importDefault(require("lodash/escapeRegExp"));
33
+ const crypto_1 = __importDefault(require("crypto"));
34
+ const chalk_1 = __importDefault(require("chalk"));
35
+ const log_1 = __importDefault(require("./log"));
36
+ const chan_1 = __importDefault(require("./models/chan"));
37
+ const msg_1 = __importDefault(require("./models/msg"));
38
+ const config_1 = __importDefault(require("./config"));
39
+ const irc_1 = require("../shared/irc");
40
+ const msg_2 = require("../shared/types/msg");
41
+ const inputs_1 = __importDefault(require("./plugins/inputs"));
42
+ const publicClient_1 = __importDefault(require("./plugins/packages/publicClient"));
43
+ const sqlite_1 = __importDefault(require("./plugins/messageStorage/sqlite"));
44
+ const text_1 = __importDefault(require("./plugins/messageStorage/text"));
45
+ const network_1 = __importDefault(require("./models/network"));
46
+ const storageCleaner_1 = require("./storageCleaner");
47
+ const chan_2 = require("../shared/types/chan");
48
+ const events = [
49
+ "away",
50
+ "cap",
51
+ "connection",
52
+ "unhandled",
53
+ "ctcp",
54
+ "chghost",
55
+ "error",
56
+ "help",
57
+ "info",
58
+ "invite",
59
+ "join",
60
+ "kick",
61
+ "list",
62
+ "mode",
63
+ "modelist",
64
+ "motd",
65
+ "message",
66
+ "names",
67
+ "nick",
68
+ "part",
69
+ "quit",
70
+ "sasl",
71
+ "topic",
72
+ "welcome",
73
+ "whois",
74
+ ];
75
+ class Client {
76
+ awayMessage;
77
+ lastActiveChannel;
78
+ attachedClients;
79
+ config;
80
+ id;
81
+ idMsg;
82
+ idChan;
83
+ name;
84
+ networks;
85
+ mentions;
86
+ manager;
87
+ messageStorage;
88
+ highlightRegex;
89
+ highlightExceptionRegex;
90
+ messageProvider;
91
+ fileHash;
92
+ constructor(manager, name, config = {}) {
93
+ this.id = (0, uuid_1.v4)();
94
+ lodash_1.default.merge(this, {
95
+ awayMessage: "",
96
+ lastActiveChannel: -1,
97
+ attachedClients: {},
98
+ config: config,
99
+ idChan: 1,
100
+ idMsg: 1,
101
+ name: name,
102
+ networks: [],
103
+ mentions: [],
104
+ manager: manager,
105
+ messageStorage: [],
106
+ highlightRegex: null,
107
+ highlightExceptionRegex: null,
108
+ messageProvider: undefined,
109
+ });
110
+ const client = this;
111
+ client.config.log = Boolean(client.config.log);
112
+ client.config.password = String(client.config.password);
113
+ if (!config_1.default.values.public && client.config.log) {
114
+ if (config_1.default.values.messageStorage.includes("sqlite")) {
115
+ client.messageProvider = new sqlite_1.default(client.name);
116
+ if (config_1.default.values.storagePolicy.enabled) {
117
+ log_1.default.info(`Activating storage cleaner. Policy: ${config_1.default.values.storagePolicy.deletionPolicy}. MaxAge: ${config_1.default.values.storagePolicy.maxAgeDays} days`);
118
+ const cleaner = new storageCleaner_1.StorageCleaner(client.messageProvider);
119
+ cleaner.start();
120
+ }
121
+ client.messageStorage.push(client.messageProvider);
122
+ }
123
+ if (config_1.default.values.messageStorage.includes("text")) {
124
+ client.messageStorage.push(new text_1.default(client.name));
125
+ }
126
+ for (const messageStorage of client.messageStorage) {
127
+ messageStorage.enable().catch((e) => log_1.default.error(e));
128
+ }
129
+ }
130
+ if (!lodash_1.default.isPlainObject(client.config.sessions)) {
131
+ client.config.sessions = {};
132
+ }
133
+ if (!lodash_1.default.isPlainObject(client.config.clientSettings)) {
134
+ client.config.clientSettings = {};
135
+ }
136
+ if (!lodash_1.default.isPlainObject(client.config.browser)) {
137
+ client.config.browser = {};
138
+ }
139
+ if (client.config.clientSettings.awayMessage) {
140
+ client.awayMessage = client.config.clientSettings.awayMessage;
141
+ }
142
+ client.config.clientSettings.searchEnabled = client.messageProvider !== undefined;
143
+ client.compileCustomHighlights();
144
+ lodash_1.default.forOwn(client.config.sessions, (session) => {
145
+ if (session.pushSubscription) {
146
+ this.registerPushSubscription(session, session.pushSubscription, true);
147
+ }
148
+ });
149
+ }
150
+ connect() {
151
+ const client = this;
152
+ if (client.networks.length !== 0) {
153
+ throw new Error(`${client.name} is already connected`);
154
+ }
155
+ (client.config.networks || []).forEach((network) => client.connectToNetwork(network, true));
156
+ // Networks are stored directly in the client object
157
+ // We don't need to keep it in the config object
158
+ delete client.config.networks;
159
+ if (client.name) {
160
+ log_1.default.info(`User ${chalk_1.default.bold(client.name)} loaded`);
161
+ // Networks are created instantly, but to reduce server load on startup
162
+ // We randomize the IRC connections and channel log loading
163
+ let delay = client.manager.clients.length * 500;
164
+ client.networks.forEach((network) => {
165
+ setTimeout(() => {
166
+ network.channels.forEach((channel) => channel.loadMessages(client, network));
167
+ if (!network.userDisconnected && network.irc) {
168
+ network.irc.connect();
169
+ }
170
+ }, delay);
171
+ delay += 1000 + Math.floor(Math.random() * 1000);
172
+ });
173
+ client.fileHash = client.manager.getDataToSave(client).newHash;
174
+ }
175
+ }
176
+ createChannel(attr) {
177
+ const chan = new chan_1.default(attr);
178
+ chan.id = this.idChan++;
179
+ return chan;
180
+ }
181
+ emit(event, ...args) {
182
+ if (this.manager !== null) {
183
+ this.manager.sockets.in(this.id).emit(event, ...args);
184
+ }
185
+ }
186
+ find(channelId) {
187
+ let network = null;
188
+ let chan = null;
189
+ for (const n of this.networks) {
190
+ chan = lodash_1.default.find(n.channels, { id: channelId });
191
+ if (chan) {
192
+ network = n;
193
+ break;
194
+ }
195
+ }
196
+ if (network && chan) {
197
+ return { network, chan };
198
+ }
199
+ return false;
200
+ }
201
+ networkFromConfig(args) {
202
+ const client = this;
203
+ let channels = [];
204
+ if (Array.isArray(args.channels)) {
205
+ let badChanConf = false;
206
+ args.channels.forEach((chan) => {
207
+ const type = chan_2.ChanType[(chan.type || "channel").toUpperCase()];
208
+ if (!chan.name || !type) {
209
+ badChanConf = true;
210
+ return;
211
+ }
212
+ channels.push(client.createChannel({
213
+ name: chan.name,
214
+ key: chan.key || "",
215
+ type: type,
216
+ muted: chan.muted,
217
+ }));
218
+ });
219
+ if (badChanConf && client.name) {
220
+ log_1.default.warn("User '" +
221
+ client.name +
222
+ "' on network '" +
223
+ String(args.name) +
224
+ "' has an invalid channel which has been ignored");
225
+ }
226
+ // `join` is kept for backwards compatibility when updating from versions <2.0
227
+ // also used by the "connect" window
228
+ }
229
+ else if (args.join) {
230
+ channels = args.join
231
+ .replace(/,/g, " ")
232
+ .split(/\s+/g)
233
+ .map((chan) => {
234
+ if (!chan.match(/^[#&!+]/)) {
235
+ chan = `#${chan}`;
236
+ }
237
+ return client.createChannel({
238
+ name: chan,
239
+ });
240
+ });
241
+ }
242
+ // TODO; better typing for args
243
+ return new network_1.default({
244
+ uuid: args.uuid,
245
+ name: String(args.name || (config_1.default.values.lockNetwork ? config_1.default.values.defaults.name : "") || ""),
246
+ host: String(args.host || ""),
247
+ port: parseInt(String(args.port), 10),
248
+ tls: !!args.tls,
249
+ userDisconnected: !!args.userDisconnected,
250
+ rejectUnauthorized: !!args.rejectUnauthorized,
251
+ password: String(args.password || ""),
252
+ nick: String(args.nick || ""),
253
+ username: String(args.username || ""),
254
+ realname: String(args.realname || ""),
255
+ leaveMessage: String(args.leaveMessage || ""),
256
+ sasl: String(args.sasl || ""),
257
+ saslAccount: String(args.saslAccount || ""),
258
+ saslPassword: String(args.saslPassword || ""),
259
+ commands: args.commands || [],
260
+ channels: channels,
261
+ ignoreList: args.ignoreList ? args.ignoreList : [],
262
+ proxyEnabled: !!args.proxyEnabled,
263
+ proxyHost: String(args.proxyHost || ""),
264
+ proxyPort: parseInt(args.proxyPort, 10),
265
+ proxyUsername: String(args.proxyUsername || ""),
266
+ proxyPassword: String(args.proxyPassword || ""),
267
+ fishGlobalKey: String(args.fishGlobalKey || ""),
268
+ fishKeys: args.fishKeys || {},
269
+ });
270
+ }
271
+ connectToNetwork(args, isStartup = false) {
272
+ const client = this;
273
+ // Get channel id for lobby before creating other channels for nicer ids
274
+ const lobbyChannelId = client.idChan++;
275
+ const network = this.networkFromConfig(args);
276
+ // Set network lobby channel id
277
+ network.getLobby().id = lobbyChannelId;
278
+ client.networks.push(network);
279
+ client.emit("network", {
280
+ network: network.getFilteredClone(this.lastActiveChannel, -1),
281
+ });
282
+ if (!network.validate(client)) {
283
+ return;
284
+ }
285
+ network.createIrcFramework(client);
286
+ // TODO
287
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
288
+ events.forEach(async (plugin) => {
289
+ (await Promise.resolve(`${`./plugins/irc-events/${plugin}`}`).then(s => __importStar(require(s)))).default.apply(client, [
290
+ network.irc,
291
+ network,
292
+ ]);
293
+ });
294
+ if (network.userDisconnected) {
295
+ network.getLobby().pushMessage(client, new msg_1.default({
296
+ text: "You have manually disconnected from this network before, use the /connect command to connect again.",
297
+ }), true);
298
+ }
299
+ else if (!isStartup) {
300
+ // irc is created in createIrcFramework
301
+ // TODO; fix type
302
+ network.irc.connect();
303
+ }
304
+ if (!isStartup) {
305
+ client.save();
306
+ network.channels.forEach((channel) => channel.loadMessages(client, network));
307
+ }
308
+ }
309
+ generateToken(callback) {
310
+ crypto_1.default.randomBytes(64, (err, buf) => {
311
+ if (err) {
312
+ throw err;
313
+ }
314
+ callback(buf.toString("hex"));
315
+ });
316
+ }
317
+ calculateTokenHash(token) {
318
+ return crypto_1.default.createHash("sha512").update(token).digest("hex");
319
+ }
320
+ updateSession(token, ip, request) {
321
+ const client = this;
322
+ const agent = (0, ua_parser_js_1.default)(request.headers["user-agent"] || "");
323
+ let friendlyAgent = "";
324
+ if (agent.browser.name) {
325
+ friendlyAgent = `${agent.browser.name} ${agent.browser.major || ""}`;
326
+ }
327
+ else {
328
+ friendlyAgent = "Unknown browser";
329
+ }
330
+ if (agent.os.name) {
331
+ friendlyAgent += ` on ${agent.os.name}`;
332
+ if (agent.os.version) {
333
+ friendlyAgent += ` ${agent.os.version}`;
334
+ }
335
+ }
336
+ client.config.sessions[token] = lodash_1.default.assign(client.config.sessions[token], {
337
+ lastUse: Date.now(),
338
+ ip: ip,
339
+ agent: friendlyAgent,
340
+ });
341
+ client.save();
342
+ }
343
+ setPassword(hash, callback) {
344
+ const client = this;
345
+ const oldHash = client.config.password;
346
+ client.config.password = hash;
347
+ client.manager.saveUser(client, function (err) {
348
+ if (err) {
349
+ // If user file fails to write, reset it back
350
+ client.config.password = oldHash;
351
+ return callback(false);
352
+ }
353
+ return callback(true);
354
+ });
355
+ }
356
+ input(data) {
357
+ const client = this;
358
+ data.text.split("\n").forEach((line) => {
359
+ data.text = line;
360
+ client.inputLine(data);
361
+ });
362
+ }
363
+ inputLine(data) {
364
+ const client = this;
365
+ const target = client.find(data.target);
366
+ if (!target) {
367
+ return;
368
+ }
369
+ // Sending a message to a channel is higher priority than merely opening one
370
+ // so that reloading the page will open this channel
371
+ this.lastActiveChannel = target.chan.id;
372
+ let text = data.text;
373
+ // This is either a normal message or a command escaped with a leading '/'
374
+ if (text.charAt(0) !== "/" || text.charAt(1) === "/") {
375
+ if (target.chan.type === chan_2.ChanType.LOBBY) {
376
+ target.chan.pushMessage(this, new msg_1.default({
377
+ type: msg_2.MessageType.ERROR,
378
+ text: "Messages can not be sent to lobbies.",
379
+ }));
380
+ return;
381
+ }
382
+ text = "say " + text.replace(/^\//, "");
383
+ }
384
+ else {
385
+ text = text.substring(1);
386
+ }
387
+ const args = text.split(" ");
388
+ const cmd = args?.shift()?.toLowerCase() || "";
389
+ const irc = target.network.irc;
390
+ const connected = irc?.connected;
391
+ const emitFailureDisconnected = () => {
392
+ target.chan.pushMessage(this, new msg_1.default({
393
+ type: msg_2.MessageType.ERROR,
394
+ text: "You are not connected to the IRC network, unable to send your command.",
395
+ }));
396
+ };
397
+ const plugin = inputs_1.default.userInputs.get(cmd);
398
+ if (plugin) {
399
+ if (!connected && !plugin.allowDisconnected) {
400
+ emitFailureDisconnected();
401
+ return;
402
+ }
403
+ plugin.input.apply(client, [target.network, target.chan, cmd, args]);
404
+ return;
405
+ }
406
+ const extPlugin = inputs_1.default.pluginCommands.get(cmd);
407
+ if (extPlugin) {
408
+ if (!connected && !extPlugin.allowDisconnected) {
409
+ emitFailureDisconnected();
410
+ return;
411
+ }
412
+ extPlugin.input(new publicClient_1.default(client, extPlugin.packageInfo), { network: target.network, chan: target.chan }, cmd, args);
413
+ return;
414
+ }
415
+ if (!connected) {
416
+ emitFailureDisconnected();
417
+ return;
418
+ }
419
+ // TODO: fix
420
+ irc.raw(text);
421
+ }
422
+ compileCustomHighlights() {
423
+ function compileHighlightRegex(customHighlightString) {
424
+ if (typeof customHighlightString !== "string") {
425
+ return null;
426
+ }
427
+ // Ensure we don't have empty strings in the list of highlights
428
+ const highlightsTokens = customHighlightString
429
+ .split(",")
430
+ .map((highlight) => (0, escapeRegExp_1.default)(highlight.trim()))
431
+ .filter((highlight) => highlight.length > 0);
432
+ if (highlightsTokens.length === 0) {
433
+ return null;
434
+ }
435
+ return new RegExp(`(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`, "i");
436
+ }
437
+ this.highlightRegex = compileHighlightRegex(this.config.clientSettings.highlights);
438
+ this.highlightExceptionRegex = compileHighlightRegex(this.config.clientSettings.highlightExceptions);
439
+ }
440
+ more(data) {
441
+ const client = this;
442
+ const target = client.find(data.target);
443
+ if (!target) {
444
+ return null;
445
+ }
446
+ const chan = target.chan;
447
+ let messages = [];
448
+ let index = 0;
449
+ // If client requests -1, send last 100 messages
450
+ if (data.lastId < 0) {
451
+ index = chan.messages.length;
452
+ }
453
+ else {
454
+ index = chan.messages.findIndex((val) => val.id === data.lastId);
455
+ }
456
+ // If requested id is not found, an empty array will be sent
457
+ if (index > 0) {
458
+ let startIndex = index;
459
+ if (data.condensed) {
460
+ // Limit to 1000 messages (that's 10x normal limit)
461
+ const indexToStop = Math.max(0, index - 1000);
462
+ let realMessagesLeft = 100;
463
+ for (let i = index - 1; i >= indexToStop; i--) {
464
+ startIndex--;
465
+ // Do not count condensed messages towards the 100 messages
466
+ if (irc_1.condensedTypes.has(chan.messages[i].type)) {
467
+ continue;
468
+ }
469
+ // Count up actual 100 visible messages
470
+ if (--realMessagesLeft === 0) {
471
+ break;
472
+ }
473
+ }
474
+ }
475
+ else {
476
+ startIndex = Math.max(0, index - 100);
477
+ }
478
+ messages = chan.messages.slice(startIndex, index);
479
+ }
480
+ return {
481
+ chan: chan.id,
482
+ messages: messages,
483
+ totalMessages: chan.messages.length,
484
+ };
485
+ }
486
+ clearHistory(data) {
487
+ const client = this;
488
+ const target = client.find(data.target);
489
+ if (!target) {
490
+ return;
491
+ }
492
+ target.chan.messages = [];
493
+ target.chan.unread = 0;
494
+ target.chan.highlight = 0;
495
+ target.chan.firstUnread = 0;
496
+ client.emit("history:clear", {
497
+ target: target.chan.id,
498
+ });
499
+ if (!target.chan.isLoggable()) {
500
+ return;
501
+ }
502
+ for (const messageStorage of this.messageStorage) {
503
+ messageStorage.deleteChannel(target.network, target.chan).catch((e) => log_1.default.error(e));
504
+ }
505
+ }
506
+ async search(query) {
507
+ if (!this.messageProvider?.isEnabled) {
508
+ return {
509
+ ...query,
510
+ results: [],
511
+ };
512
+ }
513
+ return this.messageProvider.search(query);
514
+ }
515
+ open(socketId, target) {
516
+ // Due to how socket.io works internally, normal events may arrive later than
517
+ // the disconnect event, and because we can't control this timing precisely,
518
+ // process this event normally even if there is no attached client anymore.
519
+ const attachedClient = this.attachedClients[socketId] ||
520
+ {};
521
+ // Opening a window like settings
522
+ if (target === null) {
523
+ attachedClient.openChannel = -1;
524
+ return;
525
+ }
526
+ const targetNetChan = this.find(target);
527
+ if (!targetNetChan) {
528
+ return;
529
+ }
530
+ targetNetChan.chan.unread = 0;
531
+ targetNetChan.chan.highlight = 0;
532
+ if (targetNetChan.chan.messages.length > 0) {
533
+ targetNetChan.chan.firstUnread =
534
+ targetNetChan.chan.messages[targetNetChan.chan.messages.length - 1].id;
535
+ }
536
+ attachedClient.openChannel = targetNetChan.chan.id;
537
+ this.lastActiveChannel = targetNetChan.chan.id;
538
+ this.emit("open", targetNetChan.chan.id);
539
+ }
540
+ sortChannels(netid, order) {
541
+ const network = lodash_1.default.find(this.networks, { uuid: netid });
542
+ if (!network) {
543
+ return;
544
+ }
545
+ network.channels.sort((a, b) => {
546
+ // Always sort lobby to the top regardless of what the client has sent
547
+ // Because there's a lot of code that presumes channels[0] is the lobby
548
+ if (a.type === chan_2.ChanType.LOBBY) {
549
+ return -1;
550
+ }
551
+ else if (b.type === chan_2.ChanType.LOBBY) {
552
+ return 1;
553
+ }
554
+ return order.indexOf(a.id) - order.indexOf(b.id);
555
+ });
556
+ this.save();
557
+ // Sync order to connected clients
558
+ this.emit("sync_sort:channels", {
559
+ network: network.uuid,
560
+ order: network.channels.map((obj) => obj.id),
561
+ });
562
+ }
563
+ sortNetworks(order) {
564
+ this.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
565
+ this.save();
566
+ // Sync order to connected clients
567
+ this.emit("sync_sort:networks", {
568
+ order: this.networks.map((obj) => obj.uuid),
569
+ });
570
+ }
571
+ names(data) {
572
+ const client = this;
573
+ const target = client.find(data.target);
574
+ if (!target) {
575
+ return;
576
+ }
577
+ client.emit("names", {
578
+ id: target.chan.id,
579
+ users: target.chan.getSortedUsers(target.network.irc),
580
+ });
581
+ }
582
+ part(network, chan) {
583
+ const client = this;
584
+ network.channels = lodash_1.default.without(network.channels, chan);
585
+ client.mentions = client.mentions.filter((msg) => !(msg.chanId === chan.id));
586
+ chan.destroy();
587
+ client.save();
588
+ client.emit("part", {
589
+ chan: chan.id,
590
+ });
591
+ }
592
+ quit(signOut) {
593
+ const sockets = this.manager.sockets.sockets;
594
+ const room = sockets.adapter.rooms.get(this.id);
595
+ if (room) {
596
+ for (const user of room) {
597
+ const socket = sockets.sockets.get(user);
598
+ if (socket) {
599
+ if (signOut) {
600
+ socket.emit("sign-out");
601
+ }
602
+ socket.disconnect();
603
+ }
604
+ }
605
+ }
606
+ this.networks.forEach((network) => {
607
+ network.quit();
608
+ network.destroy();
609
+ });
610
+ for (const messageStorage of this.messageStorage) {
611
+ messageStorage.close().catch((e) => log_1.default.error(e));
612
+ }
613
+ }
614
+ clientAttach(socketId, token) {
615
+ const client = this;
616
+ if (client.awayMessage && lodash_1.default.size(client.attachedClients) === 0) {
617
+ client.networks.forEach(function (network) {
618
+ // Only remove away on client attachment if
619
+ // there is no away message on this network
620
+ if (network.irc && !network.awayMessage) {
621
+ network.irc.raw("AWAY");
622
+ }
623
+ });
624
+ }
625
+ const openChannel = client.lastActiveChannel;
626
+ client.attachedClients[socketId] = { token, openChannel };
627
+ }
628
+ clientDetach(socketId) {
629
+ const client = this;
630
+ delete this.attachedClients[socketId];
631
+ if (client.awayMessage && lodash_1.default.size(client.attachedClients) === 0) {
632
+ client.networks.forEach(function (network) {
633
+ // Only set away on client deattachment if
634
+ // there is no away message on this network
635
+ if (network.irc && !network.awayMessage) {
636
+ network.irc.raw("AWAY", client.awayMessage);
637
+ }
638
+ });
639
+ }
640
+ }
641
+ // TODO: type session to this.attachedClients
642
+ registerPushSubscription(session, subscription, noSave = false) {
643
+ if (!lodash_1.default.isPlainObject(subscription) ||
644
+ typeof subscription.endpoint !== "string" ||
645
+ !/^https?:\/\//.test(subscription.endpoint) ||
646
+ !lodash_1.default.isPlainObject(subscription.keys) ||
647
+ !subscription.keys || // TS compiler doesn't understand isPlainObject
648
+ typeof subscription.keys.p256dh !== "string" ||
649
+ typeof subscription.keys.auth !== "string") {
650
+ session.pushSubscription = null;
651
+ return;
652
+ }
653
+ const data = {
654
+ endpoint: subscription.endpoint,
655
+ keys: {
656
+ p256dh: subscription.keys.p256dh,
657
+ auth: subscription.keys.auth,
658
+ },
659
+ };
660
+ session.pushSubscription = data;
661
+ if (!noSave) {
662
+ this.save();
663
+ }
664
+ return data;
665
+ }
666
+ unregisterPushSubscription(token) {
667
+ this.config.sessions[token].pushSubscription = undefined;
668
+ this.save();
669
+ }
670
+ save = lodash_1.default.debounce(function SaveClient() {
671
+ if (config_1.default.values.public) {
672
+ return;
673
+ }
674
+ const client = this;
675
+ client.manager.saveUser(client);
676
+ }, 5000, { maxWait: 20000 });
677
+ }
678
+ exports.default = Client;