@lordbex/thelounge 4.4.3-blowfish → 4.5.0-blowfish-pre
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 +31 -7
- package/dist/defaults/config.js +31 -2
- package/dist/package.json +94 -91
- package/dist/server/client.js +188 -194
- package/dist/server/clientManager.js +75 -72
- package/dist/server/command-line/index.js +44 -43
- package/dist/server/command-line/install.js +37 -70
- package/dist/server/command-line/outdated.js +12 -17
- package/dist/server/command-line/start.js +25 -26
- package/dist/server/command-line/storage.js +26 -31
- package/dist/server/command-line/uninstall.js +16 -23
- package/dist/server/command-line/upgrade.js +20 -26
- package/dist/server/command-line/users/add.js +33 -40
- package/dist/server/command-line/users/edit.js +18 -24
- package/dist/server/command-line/users/index.js +12 -16
- package/dist/server/command-line/users/list.js +11 -39
- package/dist/server/command-line/users/remove.js +16 -22
- package/dist/server/command-line/users/reset.js +34 -35
- package/dist/server/command-line/utils.js +231 -87
- package/dist/server/config.js +61 -52
- package/dist/server/helper.js +29 -28
- package/dist/server/identification.js +39 -34
- package/dist/server/index.js +1 -3
- package/dist/server/log.js +19 -16
- package/dist/server/models/chan.js +36 -33
- package/dist/server/models/msg.js +15 -19
- package/dist/server/models/network.js +102 -104
- package/dist/server/models/prefix.js +4 -7
- package/dist/server/models/user.js +5 -10
- package/dist/server/path-helper.js +8 -0
- package/dist/server/plugins/auth/ldap.js +177 -112
- package/dist/server/plugins/auth/local.js +10 -15
- package/dist/server/plugins/auth.js +6 -35
- package/dist/server/plugins/changelog.js +30 -27
- package/dist/server/plugins/clientCertificate.js +33 -37
- package/dist/server/plugins/dev-server.js +15 -21
- package/dist/server/plugins/inputs/action.js +9 -14
- package/dist/server/plugins/inputs/away.js +1 -3
- package/dist/server/plugins/inputs/ban.js +9 -14
- package/dist/server/plugins/inputs/blow.js +9 -14
- package/dist/server/plugins/inputs/connect.js +5 -10
- package/dist/server/plugins/inputs/ctcp.js +7 -12
- package/dist/server/plugins/inputs/disconnect.js +1 -3
- package/dist/server/plugins/inputs/ignore.js +23 -29
- package/dist/server/plugins/inputs/ignorelist.js +12 -18
- package/dist/server/plugins/inputs/index.js +8 -34
- package/dist/server/plugins/inputs/invite.js +7 -12
- package/dist/server/plugins/inputs/kick.js +7 -12
- package/dist/server/plugins/inputs/kill.js +1 -3
- package/dist/server/plugins/inputs/list.js +1 -3
- package/dist/server/plugins/inputs/mode.js +10 -15
- package/dist/server/plugins/inputs/msg.js +13 -18
- package/dist/server/plugins/inputs/mute.js +9 -15
- package/dist/server/plugins/inputs/nick.js +9 -14
- package/dist/server/plugins/inputs/notice.js +5 -7
- package/dist/server/plugins/inputs/part.js +11 -16
- package/dist/server/plugins/inputs/quit.js +7 -13
- package/dist/server/plugins/inputs/rainbow.js +55 -0
- package/dist/server/plugins/inputs/raw.js +1 -3
- package/dist/server/plugins/inputs/rejoin.js +7 -12
- package/dist/server/plugins/inputs/topic.js +7 -12
- package/dist/server/plugins/inputs/whois.js +1 -3
- package/dist/server/plugins/irc-events/away.js +14 -20
- package/dist/server/plugins/irc-events/cap.js +16 -22
- package/dist/server/plugins/irc-events/chghost.js +14 -13
- package/dist/server/plugins/irc-events/connection.js +61 -63
- package/dist/server/plugins/irc-events/ctcp.js +22 -28
- package/dist/server/plugins/irc-events/error.js +20 -26
- package/dist/server/plugins/irc-events/help.js +7 -13
- package/dist/server/plugins/irc-events/info.js +7 -13
- package/dist/server/plugins/irc-events/invite.js +7 -13
- package/dist/server/plugins/irc-events/join.js +30 -27
- package/dist/server/plugins/irc-events/kick.js +21 -17
- package/dist/server/plugins/irc-events/link.js +122 -109
- package/dist/server/plugins/irc-events/list.js +23 -26
- package/dist/server/plugins/irc-events/message.js +46 -52
- package/dist/server/plugins/irc-events/mode.js +66 -63
- package/dist/server/plugins/irc-events/modelist.js +29 -35
- package/dist/server/plugins/irc-events/motd.js +10 -16
- package/dist/server/plugins/irc-events/names.js +3 -6
- package/dist/server/plugins/irc-events/nick.js +26 -23
- package/dist/server/plugins/irc-events/part.js +19 -15
- package/dist/server/plugins/irc-events/quit.js +17 -14
- package/dist/server/plugins/irc-events/sasl.js +9 -15
- package/dist/server/plugins/irc-events/spgroups.js +38 -0
- package/dist/server/plugins/irc-events/spjoin.js +52 -0
- package/dist/server/plugins/irc-events/topic.js +12 -18
- package/dist/server/plugins/irc-events/unhandled.js +12 -12
- package/dist/server/plugins/irc-events/welcome.js +7 -13
- package/dist/server/plugins/irc-events/whois.js +20 -24
- package/dist/server/plugins/massEventAggregator.js +214 -0
- package/dist/server/plugins/messageStorage/sqlite.js +322 -141
- package/dist/server/plugins/messageStorage/text.js +21 -26
- package/dist/server/plugins/packages/index.js +105 -74
- package/dist/server/plugins/packages/publicClient.js +7 -16
- package/dist/server/plugins/packages/themes.js +11 -16
- package/dist/server/plugins/storage.js +28 -33
- package/dist/server/plugins/sts.js +12 -17
- package/dist/server/plugins/uploader.js +40 -43
- package/dist/server/plugins/webpush.js +23 -51
- package/dist/server/server.js +318 -271
- package/dist/server/storageCleaner.js +29 -37
- package/dist/server/utils/fish.js +349 -389
- package/dist/shared/irc.js +3 -6
- package/dist/shared/linkify.js +7 -14
- package/dist/shared/types/chan.js +6 -9
- package/dist/shared/types/changelog.js +1 -2
- package/dist/shared/types/config.js +1 -2
- package/dist/shared/types/mention.js +1 -2
- package/dist/shared/types/msg.js +3 -5
- package/dist/shared/types/network.js +1 -2
- package/dist/shared/types/storage.js +1 -2
- package/dist/shared/types/user.js +1 -2
- package/index.js +14 -10
- package/package.json +94 -91
- package/public/css/style.css +9 -6
- package/public/css/style.css.map +1 -1
- package/public/fonts/font-awesome/fa-brands-400.ttf +0 -0
- package/public/fonts/font-awesome/fa-brands-400.woff2 +0 -0
- package/public/fonts/font-awesome/fa-duotone-900.ttf +0 -0
- package/public/fonts/font-awesome/fa-duotone-900.woff2 +0 -0
- package/public/fonts/font-awesome/fa-light-300.ttf +0 -0
- package/public/fonts/font-awesome/fa-light-300.woff2 +0 -0
- package/public/fonts/font-awesome/fa-regular-400.ttf +0 -0
- package/public/fonts/font-awesome/fa-regular-400.woff2 +0 -0
- package/public/fonts/font-awesome/fa-solid-900.ttf +0 -0
- package/public/fonts/font-awesome/fa-solid-900.woff2 +0 -0
- package/public/fonts/font-awesome/fa-thin-100.ttf +0 -0
- package/public/fonts/font-awesome/fa-thin-100.woff2 +0 -0
- package/public/fonts/font-awesome/fa-v4compatibility.ttf +0 -0
- package/public/fonts/font-awesome/fa-v4compatibility.woff2 +0 -0
- 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.LICENSE.txt +24 -6
- 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/public/themes/default.css +1 -1
- package/public/themes/morning.css +1 -1
- package/dist/webpack.config.js +0 -224
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const msg_1 = __importDefault(require("../../models/msg"));
|
|
7
|
-
const msg_2 = require("../../../shared/types/msg");
|
|
8
|
-
exports.default = (function (irc, network) {
|
|
9
|
-
const client = this;
|
|
10
|
-
irc.on("invite", function (data) {
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
import { MessageType } from "../../../shared/types/msg.js";
|
|
3
|
+
export default (function (irc, network) {
|
|
4
|
+
irc.on("invite", (data) => {
|
|
11
5
|
let chan = network.getChannel(data.channel);
|
|
12
6
|
if (typeof chan === "undefined") {
|
|
13
7
|
chan = network.getLobby();
|
|
14
8
|
}
|
|
15
9
|
const invitedYou = data.invited === irc.user.nick;
|
|
16
|
-
const msg = new
|
|
17
|
-
type:
|
|
10
|
+
const msg = new Msg({
|
|
11
|
+
type: MessageType.INVITE,
|
|
18
12
|
time: data.time,
|
|
19
13
|
from: chan.getUser(data.nick),
|
|
20
14
|
target: chan.getUser(data.invited),
|
|
@@ -22,6 +16,6 @@ exports.default = (function (irc, network) {
|
|
|
22
16
|
highlight: invitedYou,
|
|
23
17
|
invitedYou: invitedYou,
|
|
24
18
|
});
|
|
25
|
-
chan.pushMessage(
|
|
19
|
+
chan.pushMessage(this, msg);
|
|
26
20
|
});
|
|
27
21
|
});
|
|
@@ -1,53 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const user_1 = __importDefault(require("../../models/user"));
|
|
8
|
-
const msg_2 = require("../../../shared/types/msg");
|
|
9
|
-
const chan_1 = require("../../../shared/types/chan");
|
|
10
|
-
exports.default = (function (irc, network) {
|
|
11
|
-
const client = this;
|
|
12
|
-
irc.on("join", function (data) {
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
import User from "../../models/user.js";
|
|
3
|
+
import { MessageType } from "../../../shared/types/msg.js";
|
|
4
|
+
import { ChanState } from "../../../shared/types/chan.js";
|
|
5
|
+
export default (function (irc, network) {
|
|
6
|
+
irc.on("join", (data) => {
|
|
13
7
|
let chan = network.getChannel(data.channel);
|
|
14
8
|
if (typeof chan === "undefined") {
|
|
15
|
-
chan =
|
|
9
|
+
chan = this.createChannel({
|
|
16
10
|
name: data.channel,
|
|
17
|
-
state:
|
|
11
|
+
state: ChanState.JOINED,
|
|
18
12
|
});
|
|
19
|
-
|
|
13
|
+
this.emit("join", {
|
|
20
14
|
network: network.uuid,
|
|
21
15
|
chan: chan.getFilteredClone(true),
|
|
22
16
|
shouldOpen: false,
|
|
23
17
|
index: network.addChannel(chan),
|
|
24
18
|
});
|
|
25
|
-
|
|
26
|
-
chan.loadMessages(
|
|
19
|
+
this.save();
|
|
20
|
+
chan.loadMessages(this, network);
|
|
27
21
|
// Request channels' modes
|
|
28
22
|
network.irc.raw("MODE", chan.name);
|
|
29
23
|
}
|
|
30
24
|
else if (data.nick === irc.user.nick) {
|
|
31
|
-
chan.state =
|
|
32
|
-
|
|
25
|
+
chan.state = ChanState.JOINED;
|
|
26
|
+
this.emit("channel:state", {
|
|
33
27
|
chan: chan.id,
|
|
34
28
|
state: chan.state,
|
|
35
29
|
});
|
|
36
30
|
}
|
|
37
|
-
const user = new
|
|
38
|
-
const msg = new
|
|
31
|
+
const user = new User({ nick: data.nick });
|
|
32
|
+
const msg = new Msg({
|
|
39
33
|
time: data.time,
|
|
40
34
|
from: user,
|
|
41
35
|
hostmask: data.ident + "@" + data.hostname,
|
|
42
36
|
gecos: data.gecos,
|
|
43
37
|
account: data.account,
|
|
44
|
-
type:
|
|
38
|
+
type: MessageType.JOIN,
|
|
45
39
|
self: data.nick === irc.user.nick,
|
|
46
40
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
// User list update callback - executed regardless of buffering
|
|
42
|
+
const updateUserList = () => {
|
|
43
|
+
chan.setUser(new User({ nick: data.nick }));
|
|
44
|
+
this.emit("users", {
|
|
45
|
+
chan: chan.id,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
// Try to process through mass event aggregator
|
|
49
|
+
const wasBuffered = this.massEventAggregator.processMessage(network, chan, msg, updateUserList);
|
|
50
|
+
if (!wasBuffered) {
|
|
51
|
+
// Not in mass event mode - process normally
|
|
52
|
+
chan.pushMessage(this, msg);
|
|
53
|
+
updateUserList();
|
|
54
|
+
}
|
|
52
55
|
});
|
|
53
56
|
});
|
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const msg_1 = __importDefault(require("../../models/msg"));
|
|
7
|
-
const msg_2 = require("../../../shared/types/msg");
|
|
8
|
-
const chan_1 = require("../../../shared/types/chan");
|
|
9
|
-
exports.default = (function (irc, network) {
|
|
10
|
-
const client = this;
|
|
11
|
-
irc.on("kick", function (data) {
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
import { MessageType } from "../../../shared/types/msg.js";
|
|
3
|
+
import { ChanState } from "../../../shared/types/chan.js";
|
|
4
|
+
export default (function (irc, network) {
|
|
5
|
+
irc.on("kick", (data) => {
|
|
12
6
|
const chan = network.getChannel(data.channel);
|
|
13
7
|
if (typeof chan === "undefined") {
|
|
14
8
|
return;
|
|
15
9
|
}
|
|
16
10
|
const user = chan.getUser(data.kicked);
|
|
17
|
-
const msg = new
|
|
18
|
-
type:
|
|
11
|
+
const msg = new Msg({
|
|
12
|
+
type: MessageType.KICK,
|
|
19
13
|
time: data.time,
|
|
20
14
|
from: chan.getUser(data.nick),
|
|
21
15
|
target: user,
|
|
@@ -23,17 +17,27 @@ exports.default = (function (irc, network) {
|
|
|
23
17
|
highlight: data.kicked === irc.user.nick,
|
|
24
18
|
self: data.nick === irc.user.nick,
|
|
25
19
|
});
|
|
26
|
-
|
|
20
|
+
// Self kicks should not be buffered and need special handling
|
|
27
21
|
if (data.kicked === irc.user.nick) {
|
|
22
|
+
chan.pushMessage(this, msg);
|
|
28
23
|
chan.users = new Map();
|
|
29
|
-
chan.state =
|
|
30
|
-
|
|
24
|
+
chan.state = ChanState.PARTED;
|
|
25
|
+
this.emit("channel:state", {
|
|
31
26
|
chan: chan.id,
|
|
32
27
|
state: chan.state,
|
|
33
28
|
});
|
|
29
|
+
return;
|
|
34
30
|
}
|
|
35
|
-
|
|
31
|
+
// User list update callback - executed regardless of buffering
|
|
32
|
+
const updateUserList = () => {
|
|
36
33
|
chan.removeUser(user);
|
|
34
|
+
};
|
|
35
|
+
// Try to process through mass event aggregator
|
|
36
|
+
const wasBuffered = this.massEventAggregator.processMessage(network, chan, msg, updateUserList);
|
|
37
|
+
if (!wasBuffered) {
|
|
38
|
+
// Not in mass event mode - process normally
|
|
39
|
+
chan.pushMessage(this, msg);
|
|
40
|
+
updateUserList();
|
|
37
41
|
}
|
|
38
42
|
});
|
|
39
43
|
});
|
|
@@ -1,47 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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"));
|
|
1
|
+
import * as cheerio from "cheerio";
|
|
2
|
+
import { URL } from "url";
|
|
3
|
+
import mime from "mime-types";
|
|
4
|
+
import log from "../../log.js";
|
|
5
|
+
import Config from "../../config.js";
|
|
6
|
+
import { findLinksWithSchema } from "../../../shared/linkify.js";
|
|
7
|
+
import storage from "../storage.js";
|
|
37
8
|
const currentFetchPromises = new Map();
|
|
38
9
|
const imageTypeRegex = /^image\/.+/;
|
|
39
10
|
const mediaTypeRegex = /^(audio|video)\/.+/;
|
|
40
|
-
function
|
|
41
|
-
if (!
|
|
11
|
+
export default function (client, chan, msg, cleanText) {
|
|
12
|
+
if (!Config.values.prefetch) {
|
|
42
13
|
return;
|
|
43
14
|
}
|
|
44
|
-
msg.previews =
|
|
15
|
+
msg.previews = findLinksWithSchema(cleanText).reduce((cleanLinks, link) => {
|
|
45
16
|
const url = normalizeURL(link.link);
|
|
46
17
|
// If the URL is invalid and cannot be normalized, don't fetch it
|
|
47
18
|
if (!url) {
|
|
@@ -65,23 +36,56 @@ function default_1(client, chan, msg, cleanText) {
|
|
|
65
36
|
shown: null,
|
|
66
37
|
};
|
|
67
38
|
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
|
-
});
|
|
39
|
+
const urlObj = new URL(url);
|
|
40
|
+
if ((urlObj.hostname.endsWith("youtube.com") && urlObj.pathname.includes("watch")) ||
|
|
41
|
+
urlObj.hostname.endsWith("youtu.be")) {
|
|
42
|
+
fetchYoutube(url, msg, chan, preview, client);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
fetchUrl(url, msg, chan, preview, client);
|
|
46
|
+
}
|
|
81
47
|
return cleanLinks;
|
|
82
48
|
}, []);
|
|
83
49
|
}
|
|
84
|
-
|
|
50
|
+
function fetchUrl(url, msg, chan, preview, client) {
|
|
51
|
+
fetch(url, {
|
|
52
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
53
|
+
language: client.config.browser?.language || "",
|
|
54
|
+
})
|
|
55
|
+
.then((res) => {
|
|
56
|
+
parse(msg, chan, preview, res, client);
|
|
57
|
+
})
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
preview.type = "error";
|
|
60
|
+
preview.error = "message";
|
|
61
|
+
preview.message = err.message;
|
|
62
|
+
emitPreview(client, chan, msg, preview);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function fetchYoutube(url, msg, chan, preview, client) {
|
|
66
|
+
const api_url = `https://www.youtube.com/oembed?url=${encodeURIComponent(url)}&format=json`;
|
|
67
|
+
fetch(api_url, {
|
|
68
|
+
accept: "application/json",
|
|
69
|
+
language: client.config.browser?.language || "",
|
|
70
|
+
})
|
|
71
|
+
.then((res) => {
|
|
72
|
+
const data = JSON.parse(res.data.toString());
|
|
73
|
+
let author = data.author_name || "";
|
|
74
|
+
author = author ? ` ~ ${author}` : "";
|
|
75
|
+
preview.type = "link";
|
|
76
|
+
preview.link = url;
|
|
77
|
+
preview.thumbActualUrl = data.thumbnail_url || "";
|
|
78
|
+
preview.head = data.title || "";
|
|
79
|
+
preview.body = author;
|
|
80
|
+
handlePreview(client, chan, msg, preview, res);
|
|
81
|
+
})
|
|
82
|
+
.catch((err) => {
|
|
83
|
+
preview.type = "error";
|
|
84
|
+
preview.error = "message";
|
|
85
|
+
preview.message = err.message;
|
|
86
|
+
emitPreview(client, chan, msg, preview);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
85
89
|
function parseHtml(preview, res, client) {
|
|
86
90
|
// TODO:
|
|
87
91
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
@@ -105,7 +109,7 @@ function parseHtml(preview, res, client) {
|
|
|
105
109
|
if (preview.body.length) {
|
|
106
110
|
preview.body = preview.body.substr(0, 300);
|
|
107
111
|
}
|
|
108
|
-
if (!
|
|
112
|
+
if (!Config.values.prefetchStorage && Config.values.disableMediaPreview) {
|
|
109
113
|
resolve(res);
|
|
110
114
|
return;
|
|
111
115
|
}
|
|
@@ -123,7 +127,7 @@ function parseHtml(preview, res, client) {
|
|
|
123
127
|
.then((resThumb) => {
|
|
124
128
|
if (resThumb !== null &&
|
|
125
129
|
imageTypeRegex.test(resThumb.type) &&
|
|
126
|
-
resThumb.size <=
|
|
130
|
+
resThumb.size <= Config.values.prefetchMaxImageSize * 1024) {
|
|
127
131
|
preview.thumbActualUrl = thumb;
|
|
128
132
|
}
|
|
129
133
|
resolve(resThumb);
|
|
@@ -136,11 +140,10 @@ function parseHtml(preview, res, client) {
|
|
|
136
140
|
});
|
|
137
141
|
});
|
|
138
142
|
}
|
|
139
|
-
// TODO: type $
|
|
140
143
|
function parseHtmlMedia($, preview, client) {
|
|
141
144
|
return new Promise((resolve, reject) => {
|
|
142
|
-
if (
|
|
143
|
-
reject();
|
|
145
|
+
if (Config.values.disableMediaPreview) {
|
|
146
|
+
reject(new Error("Media preview is disabled"));
|
|
144
147
|
return;
|
|
145
148
|
}
|
|
146
149
|
let foundMedia = false;
|
|
@@ -151,7 +154,7 @@ function parseHtmlMedia($, preview, client) {
|
|
|
151
154
|
if (openGraphType &&
|
|
152
155
|
!openGraphType.startsWith("video") &&
|
|
153
156
|
!openGraphType.startsWith("music")) {
|
|
154
|
-
reject();
|
|
157
|
+
reject(new Error("Open Graph type is not video or music"));
|
|
155
158
|
return;
|
|
156
159
|
}
|
|
157
160
|
["video", "audio"].forEach((type) => {
|
|
@@ -184,7 +187,7 @@ function parseHtmlMedia($, preview, client) {
|
|
|
184
187
|
})
|
|
185
188
|
.then((resMedia) => {
|
|
186
189
|
if (resMedia === null || !mediaTypeRegex.test(resMedia.type)) {
|
|
187
|
-
return reject();
|
|
190
|
+
return reject(new Error("Invalid media type"));
|
|
188
191
|
}
|
|
189
192
|
preview.type = type;
|
|
190
193
|
preview.media = mediaUrl;
|
|
@@ -197,7 +200,7 @@ function parseHtmlMedia($, preview, client) {
|
|
|
197
200
|
});
|
|
198
201
|
});
|
|
199
202
|
if (!foundMedia) {
|
|
200
|
-
reject();
|
|
203
|
+
reject(new Error("No media found"));
|
|
201
204
|
}
|
|
202
205
|
});
|
|
203
206
|
}
|
|
@@ -220,13 +223,13 @@ function parse(msg, chan, preview, res, client) {
|
|
|
220
223
|
case "image/jxl":
|
|
221
224
|
case "image/webp":
|
|
222
225
|
case "image/avif":
|
|
223
|
-
if (!
|
|
226
|
+
if (!Config.values.prefetchStorage && Config.values.disableMediaPreview) {
|
|
224
227
|
return removePreview(msg, preview);
|
|
225
228
|
}
|
|
226
|
-
if (res.size >
|
|
229
|
+
if (res.size > Config.values.prefetchMaxImageSize * 1024) {
|
|
227
230
|
preview.type = "error";
|
|
228
231
|
preview.error = "image-too-big";
|
|
229
|
-
preview.maxSize =
|
|
232
|
+
preview.maxSize = Config.values.prefetchMaxImageSize * 1024;
|
|
230
233
|
}
|
|
231
234
|
else {
|
|
232
235
|
preview.type = "image";
|
|
@@ -250,7 +253,7 @@ function parse(msg, chan, preview, res, client) {
|
|
|
250
253
|
if (!preview.link.startsWith("https://")) {
|
|
251
254
|
break;
|
|
252
255
|
}
|
|
253
|
-
if (
|
|
256
|
+
if (Config.values.disableMediaPreview) {
|
|
254
257
|
return removePreview(msg, preview);
|
|
255
258
|
}
|
|
256
259
|
preview.type = "audio";
|
|
@@ -263,7 +266,7 @@ function parse(msg, chan, preview, res, client) {
|
|
|
263
266
|
if (!preview.link.startsWith("https://")) {
|
|
264
267
|
break;
|
|
265
268
|
}
|
|
266
|
-
if (
|
|
269
|
+
if (Config.values.disableMediaPreview) {
|
|
267
270
|
return removePreview(msg, preview);
|
|
268
271
|
}
|
|
269
272
|
preview.type = "video";
|
|
@@ -281,13 +284,16 @@ function parse(msg, chan, preview, res, client) {
|
|
|
281
284
|
function handlePreview(client, chan, msg, preview, res) {
|
|
282
285
|
const thumb = preview.thumbActualUrl || "";
|
|
283
286
|
delete preview.thumbActualUrl;
|
|
284
|
-
if (!thumb.length || !
|
|
287
|
+
if (!thumb.length || !Config.values.prefetchStorage) {
|
|
285
288
|
preview.thumb = thumb;
|
|
286
289
|
return emitPreview(client, chan, msg, preview);
|
|
287
290
|
}
|
|
291
|
+
if (!res) {
|
|
292
|
+
return emitPreview(client, chan, msg, preview);
|
|
293
|
+
}
|
|
288
294
|
// Get the correct file extension for the provided content-type
|
|
289
295
|
// This is done to prevent user-input being stored in the file name (extension)
|
|
290
|
-
const extension =
|
|
296
|
+
const extension = mime.extension(res.type);
|
|
291
297
|
if (!extension) {
|
|
292
298
|
// For link previews, drop the thumbnail
|
|
293
299
|
// For other types, do not display preview at all
|
|
@@ -296,7 +302,7 @@ function handlePreview(client, chan, msg, preview, res) {
|
|
|
296
302
|
}
|
|
297
303
|
return emitPreview(client, chan, msg, preview);
|
|
298
304
|
}
|
|
299
|
-
|
|
305
|
+
storage.store(res.data, extension, (uri) => {
|
|
300
306
|
preview.thumb = uri;
|
|
301
307
|
emitPreview(client, chan, msg, preview);
|
|
302
308
|
});
|
|
@@ -330,7 +336,7 @@ function getRequestHeaders(headers) {
|
|
|
330
336
|
const formattedHeaders = {
|
|
331
337
|
// Certain websites like Amazon only add <meta> tags to known bots,
|
|
332
338
|
// lets pretend to be them to get the metadata
|
|
333
|
-
"User-Agent": "Mozilla/5.0 (compatible; The Lounge IRC Client; +https://github.com/
|
|
339
|
+
"User-Agent": "Mozilla/5.0 (compatible; The Lounge IRC Client; +https://github.com/lordbex/thelounge)" +
|
|
334
340
|
" facebookexternalhit/1.1 Twitterbot/1.0",
|
|
335
341
|
Accept: headers.accept || "*/*",
|
|
336
342
|
"X-Purpose": "preview",
|
|
@@ -347,69 +353,76 @@ function fetch(uri, headers) {
|
|
|
347
353
|
if (promise) {
|
|
348
354
|
return promise;
|
|
349
355
|
}
|
|
350
|
-
const prefetchTimeout =
|
|
356
|
+
const prefetchTimeout = Config.values.prefetchTimeout;
|
|
351
357
|
if (!prefetchTimeout) {
|
|
352
|
-
|
|
358
|
+
log.warn("prefetchTimeout is missing from your The Lounge configuration, defaulting to 5000 ms");
|
|
353
359
|
}
|
|
354
360
|
promise = new Promise((resolve, reject) => {
|
|
355
361
|
let buffer = Buffer.from("");
|
|
356
362
|
let contentLength = 0;
|
|
357
363
|
let contentType;
|
|
358
|
-
let limit =
|
|
364
|
+
let limit = Config.values.prefetchMaxImageSize * 1024;
|
|
359
365
|
try {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
timeout: prefetchTimeout || 5000, // milliseconds
|
|
366
|
+
const requestOptions = {
|
|
367
|
+
method: "GET",
|
|
363
368
|
headers: getRequestHeaders(headers),
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
.
|
|
368
|
-
|
|
369
|
-
|
|
369
|
+
signal: AbortSignal.timeout(prefetchTimeout || 5000),
|
|
370
|
+
};
|
|
371
|
+
globalThis
|
|
372
|
+
.fetch(uri, requestOptions)
|
|
373
|
+
.then(async (response) => {
|
|
374
|
+
contentLength = parseInt(response.headers.get("content-length") || "0", 10);
|
|
375
|
+
contentType = response.headers.get("content-type") || undefined;
|
|
370
376
|
if (contentType && imageTypeRegex.test(contentType)) {
|
|
371
377
|
// response is an image
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
378
|
+
if (contentLength > limit || !Config.values.prefetchStorage) {
|
|
379
|
+
resolve({
|
|
380
|
+
data: Buffer.alloc(0),
|
|
381
|
+
type: contentType,
|
|
382
|
+
size: contentLength,
|
|
383
|
+
});
|
|
384
|
+
return;
|
|
376
385
|
}
|
|
377
386
|
}
|
|
378
387
|
else if (contentType && mediaTypeRegex.test(contentType)) {
|
|
379
|
-
// We don't need to download the file any further
|
|
380
|
-
|
|
388
|
+
// We don't need to download the file any further
|
|
389
|
+
resolve({ data: Buffer.alloc(0), type: contentType, size: contentLength });
|
|
390
|
+
return;
|
|
381
391
|
}
|
|
382
392
|
else {
|
|
383
|
-
// if not image, limit download to the max search size
|
|
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
|
|
393
|
+
// if not image, limit download to the max search size
|
|
386
394
|
limit =
|
|
387
|
-
"prefetchMaxSearchSize" in
|
|
388
|
-
?
|
|
389
|
-
:
|
|
390
|
-
50 * 1024;
|
|
395
|
+
"prefetchMaxSearchSize" in Config.values
|
|
396
|
+
? Config.values.prefetchMaxSearchSize * 1024
|
|
397
|
+
: 50 * 1024;
|
|
391
398
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
399
|
+
if (!response.body) {
|
|
400
|
+
throw new Error("Response body is null");
|
|
401
|
+
}
|
|
402
|
+
const reader = response.body.getReader();
|
|
403
|
+
while (true) {
|
|
404
|
+
const { done, value } = await reader.read();
|
|
405
|
+
if (done) {
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
const chunkBuffer = Buffer.from(value);
|
|
409
|
+
buffer = Buffer.concat([buffer, chunkBuffer]);
|
|
410
|
+
if (buffer.length >= limit) {
|
|
411
|
+
await reader.cancel();
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
398
414
|
}
|
|
399
|
-
})
|
|
400
|
-
.on("end", () => gotStream.destroy())
|
|
401
|
-
.on("close", () => {
|
|
402
415
|
let type = "";
|
|
403
|
-
// If we downloaded more data then specified in Content-Length, use real data size
|
|
404
416
|
const size = contentLength > buffer.length ? contentLength : buffer.length;
|
|
405
417
|
if (contentType) {
|
|
406
418
|
type = contentType.split(/ *; */).shift() || "";
|
|
407
419
|
}
|
|
408
420
|
resolve({ data: buffer, type, size });
|
|
409
|
-
})
|
|
421
|
+
})
|
|
422
|
+
.catch((e) => reject(e instanceof Error ? e : new Error(String(e))));
|
|
410
423
|
}
|
|
411
424
|
catch (e) {
|
|
412
|
-
return reject(e);
|
|
425
|
+
return reject(e instanceof Error ? e : new Error(String(e)));
|
|
413
426
|
}
|
|
414
427
|
});
|
|
415
428
|
const removeCache = () => currentFetchPromises.delete(cacheKey);
|
|
@@ -419,7 +432,7 @@ function fetch(uri, headers) {
|
|
|
419
432
|
}
|
|
420
433
|
function normalizeURL(link, baseLink, disallowHttp = false) {
|
|
421
434
|
try {
|
|
422
|
-
const url = new
|
|
435
|
+
const url = new URL(link, baseLink);
|
|
423
436
|
// Only fetch http and https links
|
|
424
437
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
425
438
|
return undefined;
|
|
@@ -435,7 +448,7 @@ function normalizeURL(link, baseLink, disallowHttp = false) {
|
|
|
435
448
|
url.hash = "";
|
|
436
449
|
return url.toString();
|
|
437
450
|
}
|
|
438
|
-
catch
|
|
451
|
+
catch {
|
|
439
452
|
// if an exception was thrown, the url is not valid
|
|
440
453
|
}
|
|
441
454
|
return undefined;
|
|
@@ -1,35 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const chan_1 = require("../../../shared/types/chan");
|
|
4
|
-
exports.default = (function (irc, network) {
|
|
5
|
-
const client = this;
|
|
1
|
+
import { ChanType, SpecialChanType } from "../../../shared/types/chan.js";
|
|
2
|
+
export default (function (irc, network) {
|
|
6
3
|
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
4
|
function updateListStatus(msg) {
|
|
24
5
|
let chan = network.getChannel("Channel List");
|
|
25
6
|
if (typeof chan === "undefined") {
|
|
26
|
-
chan =
|
|
27
|
-
type:
|
|
28
|
-
special:
|
|
7
|
+
chan = this.createChannel({
|
|
8
|
+
type: ChanType.SPECIAL,
|
|
9
|
+
special: SpecialChanType.CHANNELLIST,
|
|
29
10
|
name: "Channel List",
|
|
30
11
|
data: msg,
|
|
31
12
|
});
|
|
32
|
-
|
|
13
|
+
this.emit("join", {
|
|
33
14
|
network: network.uuid,
|
|
34
15
|
chan: chan.getFilteredClone(true),
|
|
35
16
|
shouldOpen: false,
|
|
@@ -38,10 +19,26 @@ exports.default = (function (irc, network) {
|
|
|
38
19
|
}
|
|
39
20
|
else {
|
|
40
21
|
chan.data = msg;
|
|
41
|
-
|
|
22
|
+
this.emit("msg:special", {
|
|
42
23
|
chan: chan.id,
|
|
43
24
|
data: msg,
|
|
44
25
|
});
|
|
45
26
|
}
|
|
46
27
|
}
|
|
28
|
+
irc.on("channel list start", () => {
|
|
29
|
+
network.chanCache = [];
|
|
30
|
+
updateListStatus.call(this, {
|
|
31
|
+
text: "Loading channel list, this can take a moment...",
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
irc.on("channel list", (channels) => {
|
|
35
|
+
Array.prototype.push.apply(network.chanCache, channels);
|
|
36
|
+
updateListStatus.call(this, {
|
|
37
|
+
text: `Loaded ${network.chanCache.length} channels...`,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
irc.on("channel list end", () => {
|
|
41
|
+
updateListStatus.call(this, network.chanCache.sort((a, b) => b.num_users - a.num_users).slice(0, MAX_CHANS));
|
|
42
|
+
network.chanCache = [];
|
|
43
|
+
});
|
|
47
44
|
});
|