@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,442 @@
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 cheerio = __importStar(require("cheerio"));
30
+ const got_1 = __importDefault(require("got"));
31
+ const url_1 = require("url");
32
+ const mime_types_1 = __importDefault(require("mime-types"));
33
+ const log_1 = __importDefault(require("../../log"));
34
+ const config_1 = __importDefault(require("../../config"));
35
+ const linkify_1 = require("../../../shared/linkify");
36
+ const storage_1 = __importDefault(require("../storage"));
37
+ const currentFetchPromises = new Map();
38
+ const imageTypeRegex = /^image\/.+/;
39
+ const mediaTypeRegex = /^(audio|video)\/.+/;
40
+ function default_1(client, chan, msg, cleanText) {
41
+ if (!config_1.default.values.prefetch) {
42
+ return;
43
+ }
44
+ msg.previews = (0, linkify_1.findLinksWithSchema)(cleanText).reduce((cleanLinks, link) => {
45
+ const url = normalizeURL(link.link);
46
+ // If the URL is invalid and cannot be normalized, don't fetch it
47
+ if (!url) {
48
+ return cleanLinks;
49
+ }
50
+ // If there are too many urls in this message, only fetch first X valid links
51
+ if (cleanLinks.length > 4) {
52
+ return cleanLinks;
53
+ }
54
+ // Do not fetch duplicate links twice
55
+ if (cleanLinks.some((l) => l.link === link.link)) {
56
+ return cleanLinks;
57
+ }
58
+ const preview = {
59
+ type: "loading",
60
+ head: "",
61
+ body: "",
62
+ thumb: "",
63
+ size: -1,
64
+ link: link.link, // Send original matched link to the client
65
+ shown: null,
66
+ };
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
+ });
81
+ return cleanLinks;
82
+ }, []);
83
+ }
84
+ exports.default = default_1;
85
+ function parseHtml(preview, res, client) {
86
+ // TODO:
87
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
88
+ return new Promise((resolve) => {
89
+ const $ = cheerio.load(res.data);
90
+ return parseHtmlMedia($, preview, client)
91
+ .then((newRes) => resolve(newRes))
92
+ .catch(() => {
93
+ preview.type = "link";
94
+ preview.head =
95
+ $('meta[property="og:title"]').attr("content") ||
96
+ $("head > title, title").first().text() ||
97
+ "";
98
+ preview.body =
99
+ $('meta[property="og:description"]').attr("content") ||
100
+ $('meta[name="description"]').attr("content") ||
101
+ "";
102
+ if (preview.head.length) {
103
+ preview.head = preview.head.substr(0, 100);
104
+ }
105
+ if (preview.body.length) {
106
+ preview.body = preview.body.substr(0, 300);
107
+ }
108
+ if (!config_1.default.values.prefetchStorage && config_1.default.values.disableMediaPreview) {
109
+ resolve(res);
110
+ return;
111
+ }
112
+ let thumb = $('meta[property="og:image"]').attr("content") ||
113
+ $('meta[name="twitter:image:src"]').attr("content") ||
114
+ $('link[rel="image_src"]').attr("href") ||
115
+ "";
116
+ // Make sure thumbnail is a valid and absolute url
117
+ if (thumb.length) {
118
+ thumb = normalizeURL(thumb, preview.link) || "";
119
+ }
120
+ // Verify that thumbnail pic exists and is under allowed size
121
+ if (thumb.length) {
122
+ fetch(thumb, { language: client.config.browser?.language || "" })
123
+ .then((resThumb) => {
124
+ if (resThumb !== null &&
125
+ imageTypeRegex.test(resThumb.type) &&
126
+ resThumb.size <= config_1.default.values.prefetchMaxImageSize * 1024) {
127
+ preview.thumbActualUrl = thumb;
128
+ }
129
+ resolve(resThumb);
130
+ })
131
+ .catch(() => resolve(null));
132
+ }
133
+ else {
134
+ resolve(res);
135
+ }
136
+ });
137
+ });
138
+ }
139
+ // TODO: type $
140
+ function parseHtmlMedia($, preview, client) {
141
+ return new Promise((resolve, reject) => {
142
+ if (config_1.default.values.disableMediaPreview) {
143
+ reject();
144
+ return;
145
+ }
146
+ let foundMedia = false;
147
+ const openGraphType = $('meta[property="og:type"]').attr("content");
148
+ // Certain news websites may include video and audio tags,
149
+ // despite actually being an article (as indicated by og:type).
150
+ // If there is og:type tag, we will only select video or audio if it matches
151
+ if (openGraphType &&
152
+ !openGraphType.startsWith("video") &&
153
+ !openGraphType.startsWith("music")) {
154
+ reject();
155
+ return;
156
+ }
157
+ ["video", "audio"].forEach((type) => {
158
+ if (foundMedia) {
159
+ return;
160
+ }
161
+ $(`meta[property="og:${type}:type"]`).each(function (i) {
162
+ const mimeType = $(this).attr("content");
163
+ if (!mimeType) {
164
+ return;
165
+ }
166
+ if (mediaTypeRegex.test(mimeType)) {
167
+ // If we match a clean video or audio tag, parse that as a preview instead
168
+ let mediaUrl = $($(`meta[property="og:${type}"]`).get(i)).attr("content");
169
+ if (!mediaUrl) {
170
+ return;
171
+ }
172
+ // Make sure media is a valid url
173
+ mediaUrl = normalizeURL(mediaUrl, preview.link, true);
174
+ // Make sure media is a valid url
175
+ if (!mediaUrl) {
176
+ return;
177
+ }
178
+ foundMedia = true;
179
+ fetch(mediaUrl, {
180
+ accept: type === "video"
181
+ ? "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5"
182
+ : "audio/webm, audio/ogg, audio/wav, audio/*;q=0.9, application/ogg;q=0.7, video/*;q=0.6; */*;q=0.5",
183
+ language: client.config.browser?.language || "",
184
+ })
185
+ .then((resMedia) => {
186
+ if (resMedia === null || !mediaTypeRegex.test(resMedia.type)) {
187
+ return reject();
188
+ }
189
+ preview.type = type;
190
+ preview.media = mediaUrl;
191
+ preview.mediaType = resMedia.type;
192
+ resolve(resMedia);
193
+ })
194
+ .catch(reject);
195
+ return false;
196
+ }
197
+ });
198
+ });
199
+ if (!foundMedia) {
200
+ reject();
201
+ }
202
+ });
203
+ }
204
+ function parse(msg, chan, preview, res, client) {
205
+ let promise = null;
206
+ preview.size = res.size;
207
+ switch (res.type) {
208
+ case "text/html":
209
+ preview.size = -1;
210
+ promise = parseHtml(preview, res, client);
211
+ break;
212
+ case "text/plain":
213
+ preview.type = "link";
214
+ preview.body = res.data.toString().substr(0, 300);
215
+ break;
216
+ case "image/png":
217
+ case "image/gif":
218
+ case "image/jpg":
219
+ case "image/jpeg":
220
+ case "image/jxl":
221
+ case "image/webp":
222
+ case "image/avif":
223
+ if (!config_1.default.values.prefetchStorage && config_1.default.values.disableMediaPreview) {
224
+ return removePreview(msg, preview);
225
+ }
226
+ if (res.size > config_1.default.values.prefetchMaxImageSize * 1024) {
227
+ preview.type = "error";
228
+ preview.error = "image-too-big";
229
+ preview.maxSize = config_1.default.values.prefetchMaxImageSize * 1024;
230
+ }
231
+ else {
232
+ preview.type = "image";
233
+ preview.thumbActualUrl = preview.link;
234
+ }
235
+ break;
236
+ case "audio/midi":
237
+ case "audio/mpeg":
238
+ case "audio/mpeg3":
239
+ case "audio/ogg":
240
+ case "audio/wav":
241
+ case "audio/x-wav":
242
+ case "audio/x-mid":
243
+ case "audio/x-midi":
244
+ case "audio/x-mpeg":
245
+ case "audio/x-mpeg-3":
246
+ case "audio/flac":
247
+ case "audio/x-flac":
248
+ case "audio/mp4":
249
+ case "audio/x-m4a":
250
+ if (!preview.link.startsWith("https://")) {
251
+ break;
252
+ }
253
+ if (config_1.default.values.disableMediaPreview) {
254
+ return removePreview(msg, preview);
255
+ }
256
+ preview.type = "audio";
257
+ preview.media = preview.link;
258
+ preview.mediaType = res.type;
259
+ break;
260
+ case "video/webm":
261
+ case "video/ogg":
262
+ case "video/mp4":
263
+ if (!preview.link.startsWith("https://")) {
264
+ break;
265
+ }
266
+ if (config_1.default.values.disableMediaPreview) {
267
+ return removePreview(msg, preview);
268
+ }
269
+ preview.type = "video";
270
+ preview.media = preview.link;
271
+ preview.mediaType = res.type;
272
+ break;
273
+ default:
274
+ return removePreview(msg, preview);
275
+ }
276
+ if (!promise) {
277
+ return handlePreview(client, chan, msg, preview, res);
278
+ }
279
+ void promise.then((newRes) => handlePreview(client, chan, msg, preview, newRes));
280
+ }
281
+ function handlePreview(client, chan, msg, preview, res) {
282
+ const thumb = preview.thumbActualUrl || "";
283
+ delete preview.thumbActualUrl;
284
+ if (!thumb.length || !config_1.default.values.prefetchStorage) {
285
+ preview.thumb = thumb;
286
+ return emitPreview(client, chan, msg, preview);
287
+ }
288
+ // Get the correct file extension for the provided content-type
289
+ // This is done to prevent user-input being stored in the file name (extension)
290
+ const extension = mime_types_1.default.extension(res.type);
291
+ if (!extension) {
292
+ // For link previews, drop the thumbnail
293
+ // For other types, do not display preview at all
294
+ if (preview.type !== "link") {
295
+ return removePreview(msg, preview);
296
+ }
297
+ return emitPreview(client, chan, msg, preview);
298
+ }
299
+ storage_1.default.store(res.data, extension, (uri) => {
300
+ preview.thumb = uri;
301
+ emitPreview(client, chan, msg, preview);
302
+ });
303
+ }
304
+ function emitPreview(client, chan, msg, preview) {
305
+ // If there is no title but there is preview or description, set title
306
+ // otherwise bail out and show no preview
307
+ if (!preview.head.length && preview.type === "link") {
308
+ if (preview.thumb.length || preview.body.length) {
309
+ preview.head = "Untitled page";
310
+ }
311
+ else {
312
+ return removePreview(msg, preview);
313
+ }
314
+ }
315
+ client.emit("msg:preview", {
316
+ id: msg.id,
317
+ chan: chan.id,
318
+ preview: preview,
319
+ });
320
+ }
321
+ function removePreview(msg, preview) {
322
+ // If a preview fails to load, remove the link from msg object
323
+ // So that client doesn't attempt to display an preview on page reload
324
+ const index = msg.previews.indexOf(preview);
325
+ if (index > -1) {
326
+ msg.previews.splice(index, 1);
327
+ }
328
+ }
329
+ function getRequestHeaders(headers) {
330
+ const formattedHeaders = {
331
+ // Certain websites like Amazon only add <meta> tags to known bots,
332
+ // 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)" +
334
+ " facebookexternalhit/1.1 Twitterbot/1.0",
335
+ Accept: headers.accept || "*/*",
336
+ "X-Purpose": "preview",
337
+ };
338
+ if (headers.language) {
339
+ formattedHeaders["Accept-Language"] = headers.language;
340
+ }
341
+ return formattedHeaders;
342
+ }
343
+ function fetch(uri, headers) {
344
+ // Stringify the object otherwise the objects won't compute to the same value
345
+ const cacheKey = JSON.stringify([uri, headers]);
346
+ let promise = currentFetchPromises.get(cacheKey);
347
+ if (promise) {
348
+ return promise;
349
+ }
350
+ const prefetchTimeout = config_1.default.values.prefetchTimeout;
351
+ if (!prefetchTimeout) {
352
+ log_1.default.warn("prefetchTimeout is missing from your The Lounge configuration, defaulting to 5000 ms");
353
+ }
354
+ promise = new Promise((resolve, reject) => {
355
+ let buffer = Buffer.from("");
356
+ let contentLength = 0;
357
+ let contentType;
358
+ let limit = config_1.default.values.prefetchMaxImageSize * 1024;
359
+ try {
360
+ const gotStream = got_1.default.stream(uri, {
361
+ retry: 0,
362
+ timeout: prefetchTimeout || 5000, // milliseconds
363
+ headers: getRequestHeaders(headers),
364
+ localAddress: config_1.default.values.bind,
365
+ });
366
+ gotStream
367
+ .on("response", function (res) {
368
+ contentLength = parseInt(res.headers["content-length"], 10) || 0;
369
+ contentType = res.headers["content-type"];
370
+ if (contentType && imageTypeRegex.test(contentType)) {
371
+ // response is an image
372
+ // if Content-Length header reports a size exceeding the prefetch limit, abort fetch
373
+ // and if file is not to be stored we don't need to download further either
374
+ if (contentLength > limit || !config_1.default.values.prefetchStorage) {
375
+ gotStream.destroy();
376
+ }
377
+ }
378
+ else if (contentType && mediaTypeRegex.test(contentType)) {
379
+ // We don't need to download the file any further after we received content-type header
380
+ gotStream.destroy();
381
+ }
382
+ else {
383
+ // if not image, limit download to the max search size, since we need only meta tags
384
+ // twitter.com sends opengraph meta tags within ~20kb of data for individual tweets, the default is set to 50.
385
+ // for sites like Youtube the og tags are in the first 300K and hence this is configurable by the admin
386
+ limit =
387
+ "prefetchMaxSearchSize" in config_1.default.values
388
+ ? config_1.default.values.prefetchMaxSearchSize * 1024
389
+ : // set to the previous size if config option is unset
390
+ 50 * 1024;
391
+ }
392
+ })
393
+ .on("error", (e) => reject(e))
394
+ .on("data", (data) => {
395
+ buffer = Buffer.concat([buffer, data], buffer.length + data.length);
396
+ if (buffer.length >= limit) {
397
+ gotStream.destroy();
398
+ }
399
+ })
400
+ .on("end", () => gotStream.destroy())
401
+ .on("close", () => {
402
+ let type = "";
403
+ // If we downloaded more data then specified in Content-Length, use real data size
404
+ const size = contentLength > buffer.length ? contentLength : buffer.length;
405
+ if (contentType) {
406
+ type = contentType.split(/ *; */).shift() || "";
407
+ }
408
+ resolve({ data: buffer, type, size });
409
+ });
410
+ }
411
+ catch (e) {
412
+ return reject(e);
413
+ }
414
+ });
415
+ const removeCache = () => currentFetchPromises.delete(cacheKey);
416
+ promise.then(removeCache).catch(removeCache);
417
+ currentFetchPromises.set(cacheKey, promise);
418
+ return promise;
419
+ }
420
+ function normalizeURL(link, baseLink, disallowHttp = false) {
421
+ try {
422
+ const url = new url_1.URL(link, baseLink);
423
+ // Only fetch http and https links
424
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
425
+ return undefined;
426
+ }
427
+ if (disallowHttp && url.protocol === "http:") {
428
+ return undefined;
429
+ }
430
+ // Do not fetch links without hostname or ones that contain authorization
431
+ if (!url.hostname || url.username || url.password) {
432
+ return undefined;
433
+ }
434
+ // Drop hash from the url, if any
435
+ url.hash = "";
436
+ return url.toString();
437
+ }
438
+ catch (e) {
439
+ // if an exception was thrown, the url is not valid
440
+ }
441
+ return undefined;
442
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chan_1 = require("../../../shared/types/chan");
4
+ exports.default = (function (irc, network) {
5
+ const client = this;
6
+ const MAX_CHANS = 500;
7
+ irc.on("channel list start", function () {
8
+ network.chanCache = [];
9
+ updateListStatus({
10
+ text: "Loading channel list, this can take a moment...",
11
+ });
12
+ });
13
+ irc.on("channel list", function (channels) {
14
+ Array.prototype.push.apply(network.chanCache, channels);
15
+ updateListStatus({
16
+ text: `Loaded ${network.chanCache.length} channels...`,
17
+ });
18
+ });
19
+ irc.on("channel list end", function () {
20
+ updateListStatus(network.chanCache.sort((a, b) => b.num_users - a.num_users).slice(0, MAX_CHANS));
21
+ network.chanCache = [];
22
+ });
23
+ function updateListStatus(msg) {
24
+ let chan = network.getChannel("Channel List");
25
+ if (typeof chan === "undefined") {
26
+ chan = client.createChannel({
27
+ type: chan_1.ChanType.SPECIAL,
28
+ special: chan_1.SpecialChanType.CHANNELLIST,
29
+ name: "Channel List",
30
+ data: msg,
31
+ });
32
+ client.emit("join", {
33
+ network: network.uuid,
34
+ chan: chan.getFilteredClone(true),
35
+ shouldOpen: false,
36
+ index: network.addChannel(chan),
37
+ });
38
+ }
39
+ else {
40
+ chan.data = msg;
41
+ client.emit("msg:special", {
42
+ chan: chan.id,
43
+ data: msg,
44
+ });
45
+ }
46
+ }
47
+ });
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const msg_1 = __importDefault(require("../../models/msg"));
7
+ const link_1 = __importDefault(require("./link"));
8
+ const irc_1 = require("../../../shared/irc");
9
+ const helper_1 = __importDefault(require("../../helper"));
10
+ const msg_2 = require("../../../shared/types/msg");
11
+ const chan_1 = require("../../../shared/types/chan");
12
+ const fish_1 = require("../../utils/fish");
13
+ const nickRegExp = /(?:\x03[0-9]{1,2}(?:,[0-9]{1,2})?)?([\w[\]\\`^{|}-]+)/g;
14
+ function convertForHandle(type, data) {
15
+ return { ...data, type: type };
16
+ }
17
+ exports.default = (function (irc, network) {
18
+ const client = this;
19
+ irc.on("notice", function (data) {
20
+ handleMessage(convertForHandle(msg_2.MessageType.NOTICE, data));
21
+ });
22
+ irc.on("action", function (data) {
23
+ handleMessage(convertForHandle(msg_2.MessageType.ACTION, data));
24
+ });
25
+ irc.on("privmsg", function (data) {
26
+ handleMessage(convertForHandle(msg_2.MessageType.MESSAGE, data));
27
+ });
28
+ irc.on("wallops", function (data) {
29
+ data.from_server = true;
30
+ handleMessage(convertForHandle(msg_2.MessageType.WALLOPS, data));
31
+ });
32
+ function handleMessage(data) {
33
+ let chan;
34
+ let from;
35
+ let highlight = false;
36
+ let showInActive = false;
37
+ const self = data.nick === irc.user.nick;
38
+ // Some servers send messages without any nickname
39
+ if (!data.nick) {
40
+ data.from_server = true;
41
+ data.nick = data.hostname || network.host;
42
+ }
43
+ // Check if the sender is in our ignore list
44
+ const shouldIgnore = !self &&
45
+ network.ignoreList.some(function (entry) {
46
+ return helper_1.default.compareHostmask(entry, data);
47
+ });
48
+ // Server messages that aren't targeted at a channel go to the server window
49
+ if (data.from_server &&
50
+ (!data.target ||
51
+ !network.getChannel(data.target) ||
52
+ network.getChannel(data.target)?.type !== chan_1.ChanType.CHANNEL)) {
53
+ chan = network.getLobby();
54
+ from = chan.getUser(data.nick);
55
+ }
56
+ else {
57
+ if (shouldIgnore) {
58
+ return;
59
+ }
60
+ let target = data.target;
61
+ // If the message is targeted at us, use sender as target instead
62
+ if (target.toLowerCase() === irc.user.nick.toLowerCase()) {
63
+ target = data.nick;
64
+ }
65
+ chan = network.getChannel(target);
66
+ if (typeof chan === "undefined") {
67
+ // Send notices that are not targeted at us into the server window
68
+ if (data.type === msg_2.MessageType.NOTICE) {
69
+ showInActive = true;
70
+ chan = network.getLobby();
71
+ }
72
+ else {
73
+ chan = client.createChannel({
74
+ type: chan_1.ChanType.QUERY,
75
+ name: target,
76
+ });
77
+ client.emit("join", {
78
+ network: network.uuid,
79
+ chan: chan.getFilteredClone(true),
80
+ shouldOpen: false,
81
+ index: network.addChannel(chan),
82
+ });
83
+ client.save();
84
+ chan.loadMessages(client, network);
85
+ }
86
+ }
87
+ from = chan.getUser(data.nick);
88
+ // Attempt mIRC FiSH Blowfish decryption if applicable
89
+ if (chan.blowfishKey) {
90
+ const decrypted = (0, fish_1.tryDecryptFishLine)(data.message, chan.blowfishKey);
91
+ if (decrypted !== null) {
92
+ data.message = decrypted;
93
+ }
94
+ }
95
+ // Query messages (unless self or muted) always highlight
96
+ if (chan.type === chan_1.ChanType.QUERY) {
97
+ highlight = !self;
98
+ }
99
+ else if (chan.type === chan_1.ChanType.CHANNEL) {
100
+ from.lastMessage = data.time || Date.now();
101
+ }
102
+ }
103
+ // msg is constructed down here because `from` is being copied in the constructor
104
+ const msg = new msg_1.default({
105
+ type: data.type,
106
+ time: data.time ? new Date(data.time) : undefined,
107
+ text: data.message,
108
+ self: self,
109
+ from: from,
110
+ highlight: highlight,
111
+ users: [],
112
+ });
113
+ if (showInActive) {
114
+ msg.showInActive = true;
115
+ }
116
+ // remove IRC formatting for custom highlight testing
117
+ const cleanMessage = (0, irc_1.cleanIrcMessage)(data.message);
118
+ // Self messages in channels are never highlighted
119
+ // Non-self messages are highlighted as soon as the nick is detected
120
+ if (!msg.highlight && !msg.self) {
121
+ msg.highlight = network.highlightRegex?.test(data.message);
122
+ // If we still don't have a highlight, test against custom highlights if there's any
123
+ if (!msg.highlight && client.highlightRegex) {
124
+ msg.highlight = client.highlightRegex.test(cleanMessage);
125
+ }
126
+ }
127
+ // if highlight exceptions match, do not highlight at all
128
+ if (msg.highlight && client.highlightExceptionRegex) {
129
+ msg.highlight = !client.highlightExceptionRegex.test(cleanMessage);
130
+ }
131
+ if (data.group) {
132
+ msg.statusmsgGroup = data.group;
133
+ }
134
+ let match;
135
+ while ((match = nickRegExp.exec(data.message))) {
136
+ if (chan.findUser(match[1])) {
137
+ msg.users.push(match[1]);
138
+ }
139
+ }
140
+ // No prefetch URLs unless are simple MESSAGE or ACTION types
141
+ if ([msg_2.MessageType.MESSAGE, msg_2.MessageType.ACTION].includes(data.type)) {
142
+ (0, link_1.default)(client, chan, msg, cleanMessage);
143
+ }
144
+ chan.pushMessage(client, msg, !msg.self);
145
+ // Do not send notifications if the channel is muted or for messages older than 15 minutes (znc buffer for example)
146
+ if (!chan.muted && msg.highlight && (!data.time || data.time > Date.now() - 900000)) {
147
+ let title = chan.name;
148
+ let body = cleanMessage;
149
+ if (msg.type === msg_2.MessageType.ACTION) {
150
+ // For actions, do not include colon in the message
151
+ body = `${data.nick} ${body}`;
152
+ }
153
+ else if (chan.type !== chan_1.ChanType.QUERY) {
154
+ // In channels, prepend sender nickname to the message
155
+ body = `${data.nick}: ${body}`;
156
+ }
157
+ // If a channel is active on any client, highlight won't increment and notification will say (0 mention)
158
+ if (chan.highlight > 0) {
159
+ title += ` (${chan.highlight} ${chan.type === chan_1.ChanType.QUERY ? "new message" : "mention"}${chan.highlight > 1 ? "s" : ""})`;
160
+ }
161
+ if (chan.highlight > 1) {
162
+ body += `\n\n… and ${chan.highlight - 1} other message${chan.highlight > 2 ? "s" : ""}`;
163
+ }
164
+ client.manager.webPush.push(client, {
165
+ type: "notification",
166
+ chanId: chan.id,
167
+ timestamp: data.time || Date.now(),
168
+ title: title,
169
+ body: body,
170
+ }, true);
171
+ }
172
+ // Keep track of all mentions in channels for this client
173
+ if (msg.highlight && chan.type === chan_1.ChanType.CHANNEL) {
174
+ client.mentions.push({
175
+ chanId: chan.id,
176
+ msgId: msg.id,
177
+ type: msg.type,
178
+ time: msg.time,
179
+ text: msg.text,
180
+ from: msg.from,
181
+ });
182
+ if (client.mentions.length > 100) {
183
+ client.mentions.splice(0, client.mentions.length - 100);
184
+ }
185
+ }
186
+ }
187
+ });