@lordbex/thelounge 4.4.4-blowfish → 4.5.1-blowfish
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/defaults/config.js +31 -2
- package/dist/package.json +93 -91
- package/dist/server/client.js +188 -194
- package/dist/server/clientManager.js +65 -63
- 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 +88 -86
- 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 +75 -96
- 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 +7 -14
- 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 +93 -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/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,13 +1,7 @@
|
|
|
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("part", 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("part", (data) => {
|
|
11
5
|
if (!data.channel) {
|
|
12
6
|
return;
|
|
13
7
|
}
|
|
@@ -16,20 +10,30 @@ exports.default = (function (irc, network) {
|
|
|
16
10
|
return;
|
|
17
11
|
}
|
|
18
12
|
const user = chan.getUser(data.nick);
|
|
19
|
-
const msg = new
|
|
20
|
-
type:
|
|
13
|
+
const msg = new Msg({
|
|
14
|
+
type: MessageType.PART,
|
|
21
15
|
time: data.time,
|
|
22
16
|
text: data.message || "",
|
|
23
17
|
hostmask: data.ident + "@" + data.hostname,
|
|
24
18
|
from: user,
|
|
25
19
|
self: data.nick === irc.user.nick,
|
|
26
20
|
});
|
|
27
|
-
|
|
21
|
+
// Self parts should not be buffered and need special handling
|
|
28
22
|
if (data.nick === irc.user.nick) {
|
|
29
|
-
|
|
23
|
+
chan.pushMessage(this, msg);
|
|
24
|
+
this.part(network, chan);
|
|
25
|
+
return;
|
|
30
26
|
}
|
|
31
|
-
|
|
27
|
+
// User list update callback - executed regardless of buffering
|
|
28
|
+
const updateUserList = () => {
|
|
32
29
|
chan.removeUser(user);
|
|
30
|
+
};
|
|
31
|
+
// Try to process through mass event aggregator
|
|
32
|
+
const wasBuffered = this.massEventAggregator.processMessage(network, chan, msg, updateUserList);
|
|
33
|
+
if (!wasBuffered) {
|
|
34
|
+
// Not in mass event mode - process normally
|
|
35
|
+
chan.pushMessage(this, msg);
|
|
36
|
+
updateUserList();
|
|
33
37
|
}
|
|
34
38
|
});
|
|
35
39
|
});
|
|
@@ -1,27 +1,30 @@
|
|
|
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("quit", 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("quit", (data) => {
|
|
11
5
|
network.channels.forEach((chan) => {
|
|
12
6
|
const user = chan.findUser(data.nick);
|
|
13
7
|
if (typeof user === "undefined") {
|
|
14
8
|
return;
|
|
15
9
|
}
|
|
16
|
-
const msg = new
|
|
10
|
+
const msg = new Msg({
|
|
17
11
|
time: data.time,
|
|
18
|
-
type:
|
|
12
|
+
type: MessageType.QUIT,
|
|
19
13
|
text: data.message || "",
|
|
20
14
|
hostmask: data.ident + "@" + data.hostname,
|
|
21
15
|
from: user,
|
|
22
16
|
});
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
// User list update callback - executed regardless of buffering
|
|
18
|
+
const updateUserList = () => {
|
|
19
|
+
chan.removeUser(user);
|
|
20
|
+
};
|
|
21
|
+
// Try to process through mass event aggregator
|
|
22
|
+
const wasBuffered = this.massEventAggregator.processMessage(network, chan, msg, updateUserList);
|
|
23
|
+
if (!wasBuffered) {
|
|
24
|
+
// Not in mass event mode - process normally
|
|
25
|
+
chan.pushMessage(this, msg);
|
|
26
|
+
updateUserList();
|
|
27
|
+
}
|
|
25
28
|
});
|
|
26
29
|
// If user with the nick we are trying to keep has quit, try to get this nick
|
|
27
30
|
if (network.keepNick === data.nick) {
|
|
@@ -1,26 +1,20 @@
|
|
|
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;
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
import { MessageType } from "../../../shared/types/msg.js";
|
|
3
|
+
export default (function (irc, network) {
|
|
10
4
|
irc.on("loggedin", (data) => {
|
|
11
5
|
const lobby = network.getLobby();
|
|
12
|
-
const msg = new
|
|
13
|
-
type:
|
|
6
|
+
const msg = new Msg({
|
|
7
|
+
type: MessageType.LOGIN,
|
|
14
8
|
text: "Logged in as: " + data.account,
|
|
15
9
|
});
|
|
16
|
-
lobby.pushMessage(
|
|
10
|
+
lobby.pushMessage(this, msg, true);
|
|
17
11
|
});
|
|
18
12
|
irc.on("loggedout", () => {
|
|
19
13
|
const lobby = network.getLobby();
|
|
20
|
-
const msg = new
|
|
21
|
-
type:
|
|
14
|
+
const msg = new Msg({
|
|
15
|
+
type: MessageType.LOGOUT,
|
|
22
16
|
text: "Logged out",
|
|
23
17
|
});
|
|
24
|
-
lobby.pushMessage(
|
|
18
|
+
lobby.pushMessage(this, msg, true);
|
|
25
19
|
});
|
|
26
20
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import log from "../../log.js";
|
|
2
|
+
export default (function (irc, network) {
|
|
3
|
+
// Handle SPGROUPS command from seedpool/enhanced capable servers
|
|
4
|
+
// Format: :SeedServ SPGROUPS #channel :{"groups":[{"name":"Sysop","users":["admin1"]}, ...]}
|
|
5
|
+
irc.on("unknown command", (command) => {
|
|
6
|
+
if (command.command !== "SPGROUPS") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const channelName = command.params[0];
|
|
10
|
+
const jsonPayload = command.params[1];
|
|
11
|
+
if (!channelName || !jsonPayload) {
|
|
12
|
+
log.warn("SPGROUPS: Missing channel or payload");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const chan = network.getChannel(channelName);
|
|
16
|
+
if (!chan) {
|
|
17
|
+
log.warn(`SPGROUPS: Channel ${channelName} not found`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const data = JSON.parse(jsonPayload);
|
|
22
|
+
if (!data.groups || !Array.isArray(data.groups)) {
|
|
23
|
+
log.warn("SPGROUPS: Invalid payload format, expected {groups: [...]}");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Store groups on the channel, sorted by position (highest first)
|
|
27
|
+
chan.groups = data.groups.sort((a, b) => b.position - a.position);
|
|
28
|
+
// Emit to client
|
|
29
|
+
this.emit("channel:groups", {
|
|
30
|
+
chan: chan.id,
|
|
31
|
+
groups: chan.groups,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
log.error(`SPGROUPS: Failed to parse JSON payload: ${String(err)}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import log from "../../log.js";
|
|
2
|
+
export default (function (irc, network) {
|
|
3
|
+
// Handle SPJOIN command from seedpool/enhanced capable servers
|
|
4
|
+
// Format: :SeedServ SPJOIN #channel nickname :GroupName
|
|
5
|
+
irc.on("unknown command", (command) => {
|
|
6
|
+
if (command.command !== "SPJOIN") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const channelName = command.params[0];
|
|
10
|
+
const nickname = command.params[1];
|
|
11
|
+
const groupName = command.params[2];
|
|
12
|
+
if (!channelName || !nickname || !groupName) {
|
|
13
|
+
log.warn("SPJOIN: Missing channel, nickname, or group");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const chan = network.getChannel(channelName);
|
|
17
|
+
if (!chan) {
|
|
18
|
+
log.warn(`SPJOIN: Channel ${channelName} not found`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!chan.groups) {
|
|
22
|
+
chan.groups = [];
|
|
23
|
+
}
|
|
24
|
+
// Remove user from any existing group (in case of group change)
|
|
25
|
+
for (const group of chan.groups) {
|
|
26
|
+
const lowerUsers = group.users.map((u) => u.toLowerCase());
|
|
27
|
+
const userIndex = lowerUsers.indexOf(nickname.toLowerCase());
|
|
28
|
+
if (userIndex !== -1) {
|
|
29
|
+
group.users.splice(userIndex, 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Find the target group or create it
|
|
33
|
+
let targetGroup = chan.groups.find((g) => g.name === groupName);
|
|
34
|
+
if (!targetGroup) {
|
|
35
|
+
// Find the lowest existing position and go below it
|
|
36
|
+
const lowestPosition = chan.groups.length > 0 ? Math.min(...chan.groups.map((g) => g.position)) - 1 : 0;
|
|
37
|
+
targetGroup = { name: groupName, position: lowestPosition, users: [] };
|
|
38
|
+
chan.groups.push(targetGroup);
|
|
39
|
+
}
|
|
40
|
+
// Add user to the group
|
|
41
|
+
if (!targetGroup.users.map((u) => u.toLowerCase()).includes(nickname.toLowerCase())) {
|
|
42
|
+
targetGroup.users.push(nickname);
|
|
43
|
+
}
|
|
44
|
+
// Sort groups by position (highest first) before emitting
|
|
45
|
+
chan.groups.sort((a, b) => b.position - a.position);
|
|
46
|
+
// Emit updated groups to client
|
|
47
|
+
this.emit("channel:groups", {
|
|
48
|
+
chan: chan.id,
|
|
49
|
+
groups: chan.groups,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,42 +1,36 @@
|
|
|
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("topic", 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("topic", (data) => {
|
|
11
5
|
const chan = network.getChannel(data.channel);
|
|
12
6
|
if (typeof chan === "undefined") {
|
|
13
7
|
return;
|
|
14
8
|
}
|
|
15
|
-
const msg = new
|
|
9
|
+
const msg = new Msg({
|
|
16
10
|
time: data.time,
|
|
17
|
-
type:
|
|
11
|
+
type: MessageType.TOPIC,
|
|
18
12
|
from: data.nick && chan.getUser(data.nick),
|
|
19
13
|
text: data.topic,
|
|
20
14
|
self: data.nick === irc.user.nick,
|
|
21
15
|
});
|
|
22
|
-
chan.pushMessage(
|
|
16
|
+
chan.pushMessage(this, msg);
|
|
23
17
|
chan.topic = data.topic;
|
|
24
|
-
|
|
18
|
+
this.emit("topic", {
|
|
25
19
|
chan: chan.id,
|
|
26
20
|
topic: chan.topic,
|
|
27
21
|
});
|
|
28
22
|
});
|
|
29
|
-
irc.on("topicsetby",
|
|
23
|
+
irc.on("topicsetby", (data) => {
|
|
30
24
|
const chan = network.getChannel(data.channel);
|
|
31
25
|
if (typeof chan === "undefined") {
|
|
32
26
|
return;
|
|
33
27
|
}
|
|
34
|
-
const msg = new
|
|
35
|
-
type:
|
|
28
|
+
const msg = new Msg({
|
|
29
|
+
type: MessageType.TOPIC_SET_BY,
|
|
36
30
|
from: chan.getUser(data.nick),
|
|
37
31
|
when: new Date(data.when * 1000),
|
|
38
32
|
self: data.nick === irc.user.nick,
|
|
39
33
|
});
|
|
40
|
-
chan.pushMessage(
|
|
34
|
+
chan.pushMessage(this, msg);
|
|
41
35
|
});
|
|
42
36
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
import { MessageType } from "../../../shared/types/msg.js";
|
|
3
|
+
// Commands handled by other plugins - don't display as unhandled
|
|
4
|
+
const handledCommands = new Set(["SPGROUPS", "SPJOIN"]);
|
|
5
|
+
export default (function (irc, network) {
|
|
6
|
+
irc.on("unknown command", (command) => {
|
|
7
|
+
// Skip commands that are handled by other plugins
|
|
8
|
+
if (handledCommands.has(command.command)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
11
|
let target = network.getLobby();
|
|
12
12
|
// Do not display users own name
|
|
13
13
|
if (command.params.length > 0 && command.params[0] === network.irc.user.nick) {
|
|
@@ -22,8 +22,8 @@ exports.default = (function (irc, network) {
|
|
|
22
22
|
target = channel;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
target.pushMessage(
|
|
26
|
-
type:
|
|
25
|
+
target.pushMessage(this, new Msg({
|
|
26
|
+
type: MessageType.UNHANDLED,
|
|
27
27
|
command: command.command,
|
|
28
28
|
params: command.params,
|
|
29
29
|
}), true);
|
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const msg_1 = __importDefault(require("../../models/msg"));
|
|
7
|
-
exports.default = (function (irc, network) {
|
|
8
|
-
const client = this;
|
|
9
|
-
irc.on("registered", function (data) {
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
export default (function (irc, network) {
|
|
3
|
+
irc.on("registered", (data) => {
|
|
10
4
|
network.setNick(data.nick);
|
|
11
5
|
const lobby = network.getLobby();
|
|
12
|
-
const msg = new
|
|
6
|
+
const msg = new Msg({
|
|
13
7
|
text: "You're now known as " + data.nick,
|
|
14
8
|
});
|
|
15
|
-
lobby.pushMessage(
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
lobby.pushMessage(this, msg);
|
|
10
|
+
this.save();
|
|
11
|
+
this.emit("nick", {
|
|
18
12
|
network: network.uuid,
|
|
19
13
|
nick: data.nick,
|
|
20
14
|
});
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const chan_1 = require("../../../shared/types/chan");
|
|
9
|
-
exports.default = (function (irc, network) {
|
|
10
|
-
const client = this;
|
|
11
|
-
irc.on("whois", handleWhois);
|
|
1
|
+
import Msg from "../../models/msg.js";
|
|
2
|
+
import { MessageType } from "../../../shared/types/msg.js";
|
|
3
|
+
import { ChanType } from "../../../shared/types/chan.js";
|
|
4
|
+
export default (function (irc, network) {
|
|
5
|
+
irc.on("whois", (data) => {
|
|
6
|
+
handleWhois.call(this, data);
|
|
7
|
+
});
|
|
12
8
|
irc.on("whowas", (data) => {
|
|
13
9
|
data.whowas = true;
|
|
14
|
-
handleWhois(data);
|
|
10
|
+
handleWhois.call(this, data);
|
|
15
11
|
});
|
|
16
12
|
function handleWhois(data) {
|
|
17
13
|
let chan = network.getChannel(data.nick);
|
|
@@ -21,37 +17,37 @@ exports.default = (function (irc, network) {
|
|
|
21
17
|
chan = network.getLobby();
|
|
22
18
|
}
|
|
23
19
|
else {
|
|
24
|
-
chan =
|
|
25
|
-
type:
|
|
20
|
+
chan = this.createChannel({
|
|
21
|
+
type: ChanType.QUERY,
|
|
26
22
|
name: data.nick,
|
|
27
23
|
});
|
|
28
|
-
|
|
24
|
+
this.emit("join", {
|
|
29
25
|
network: network.uuid,
|
|
30
26
|
chan: chan.getFilteredClone(true),
|
|
31
27
|
shouldOpen: true,
|
|
32
28
|
index: network.addChannel(chan),
|
|
33
29
|
});
|
|
34
|
-
chan.loadMessages(
|
|
35
|
-
|
|
30
|
+
chan.loadMessages(this, network);
|
|
31
|
+
this.save();
|
|
36
32
|
}
|
|
37
33
|
}
|
|
38
34
|
let msg;
|
|
39
35
|
if (data.error) {
|
|
40
|
-
msg = new
|
|
41
|
-
type:
|
|
36
|
+
msg = new Msg({
|
|
37
|
+
type: MessageType.ERROR,
|
|
42
38
|
text: "No such nick: " + data.nick,
|
|
43
39
|
});
|
|
44
40
|
}
|
|
45
41
|
else {
|
|
46
42
|
// Absolute datetime in milliseconds since nick is idle
|
|
47
|
-
data.idleTime = Date.now() - data.idle * 1000;
|
|
43
|
+
data.idleTime = Date.now() - (data.idle ?? 0) * 1000;
|
|
48
44
|
// Absolute datetime in milliseconds when nick logged on.
|
|
49
|
-
data.logonTime = data.logon * 1000;
|
|
50
|
-
msg = new
|
|
51
|
-
type:
|
|
45
|
+
data.logonTime = (data.logon ?? 0) * 1000;
|
|
46
|
+
msg = new Msg({
|
|
47
|
+
type: MessageType.WHOIS,
|
|
52
48
|
whois: data,
|
|
53
49
|
});
|
|
54
50
|
}
|
|
55
|
-
chan.pushMessage(
|
|
51
|
+
chan.pushMessage(this, msg);
|
|
56
52
|
}
|
|
57
53
|
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import Msg from "../models/msg.js";
|
|
2
|
+
import Config from "../config.js";
|
|
3
|
+
import log from "../log.js";
|
|
4
|
+
import { MessageType } from "../../shared/types/msg.js";
|
|
5
|
+
import { condensedTypes } from "../../shared/irc.js";
|
|
6
|
+
class MassEventAggregator {
|
|
7
|
+
channelStates = new Map();
|
|
8
|
+
client;
|
|
9
|
+
constructor(client) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
}
|
|
12
|
+
getOrCreateState(chanId) {
|
|
13
|
+
if (!this.channelStates.has(chanId)) {
|
|
14
|
+
this.channelStates.set(chanId, {
|
|
15
|
+
isActive: false,
|
|
16
|
+
buffer: [],
|
|
17
|
+
preBuffer: [],
|
|
18
|
+
startTime: null,
|
|
19
|
+
cooldownTimer: null,
|
|
20
|
+
maxDurationTimer: null,
|
|
21
|
+
recentTimestamps: [],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return this.channelStates.get(chanId);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Process an incoming status message.
|
|
28
|
+
* Returns true if the message was buffered (mass event active).
|
|
29
|
+
* Returns false if the message should be sent normally.
|
|
30
|
+
*/
|
|
31
|
+
processMessage(network, chan, msg, userUpdateCallback) {
|
|
32
|
+
// Check if mass event detection is enabled
|
|
33
|
+
if (!Config.values.massEventDetection?.enable) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
// Only process condensable message types
|
|
37
|
+
if (!condensedTypes.has(msg.type)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
// Never buffer self messages or highlights
|
|
41
|
+
if (msg.self || msg.highlight) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const config = Config.values.massEventDetection;
|
|
45
|
+
const state = this.getOrCreateState(chan.id);
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
// Update recent timestamps (sliding window)
|
|
48
|
+
state.recentTimestamps.push(now);
|
|
49
|
+
const windowStart = now - config.windowMs;
|
|
50
|
+
state.recentTimestamps = state.recentTimestamps.filter((t) => t > windowStart);
|
|
51
|
+
// Check if we should activate mass event mode
|
|
52
|
+
if (!state.isActive) {
|
|
53
|
+
// Track message in preBuffer for accurate counting when activation happens
|
|
54
|
+
state.preBuffer.push({ msg, timestamp: now });
|
|
55
|
+
// Clean old messages from preBuffer (keep only within window)
|
|
56
|
+
state.preBuffer = state.preBuffer.filter((m) => m.timestamp > windowStart);
|
|
57
|
+
if (state.recentTimestamps.length >= config.threshold) {
|
|
58
|
+
log.debug(`MassEvent: ACTIVATING for ${chan.name} (${state.recentTimestamps.length} msgs, ${state.preBuffer.length} in preBuffer)`);
|
|
59
|
+
// Move preBuffer to main buffer when activating
|
|
60
|
+
state.buffer = [...state.preBuffer];
|
|
61
|
+
state.preBuffer = [];
|
|
62
|
+
this.activateMassEvent(state, chan, network, now);
|
|
63
|
+
// Execute user update callback
|
|
64
|
+
if (userUpdateCallback) {
|
|
65
|
+
userUpdateCallback();
|
|
66
|
+
}
|
|
67
|
+
// Reset cooldown timer
|
|
68
|
+
this.resetCooldownTimer(state, chan, network);
|
|
69
|
+
return true; // Message was captured in preBuffer, now in main buffer
|
|
70
|
+
}
|
|
71
|
+
return false; // Not in mass event mode, process normally
|
|
72
|
+
}
|
|
73
|
+
// We're in mass event mode - buffer the message
|
|
74
|
+
state.buffer.push({ msg, timestamp: now });
|
|
75
|
+
// Execute user update callback (for real-time user list updates)
|
|
76
|
+
if (userUpdateCallback) {
|
|
77
|
+
userUpdateCallback();
|
|
78
|
+
}
|
|
79
|
+
// Reset cooldown timer
|
|
80
|
+
this.resetCooldownTimer(state, chan, network);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
activateMassEvent(state, chan, network, now) {
|
|
84
|
+
state.isActive = true;
|
|
85
|
+
state.startTime = now;
|
|
86
|
+
const config = Config.values.massEventDetection;
|
|
87
|
+
// Set maximum duration timer
|
|
88
|
+
state.maxDurationTimer = setTimeout(() => {
|
|
89
|
+
this.endMassEvent(state, chan, network);
|
|
90
|
+
}, config.maxDurationMs);
|
|
91
|
+
}
|
|
92
|
+
resetCooldownTimer(state, chan, network) {
|
|
93
|
+
if (state.cooldownTimer) {
|
|
94
|
+
clearTimeout(state.cooldownTimer);
|
|
95
|
+
}
|
|
96
|
+
const config = Config.values.massEventDetection;
|
|
97
|
+
state.cooldownTimer = setTimeout(() => {
|
|
98
|
+
this.endMassEvent(state, chan, network);
|
|
99
|
+
}, config.cooldownMs);
|
|
100
|
+
}
|
|
101
|
+
endMassEvent(state, chan, network) {
|
|
102
|
+
if (!state.isActive) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
log.debug(`MassEvent: ENDING for ${chan.name} (${state.buffer.length} buffered msgs)`);
|
|
106
|
+
// Clear timers
|
|
107
|
+
if (state.cooldownTimer) {
|
|
108
|
+
clearTimeout(state.cooldownTimer);
|
|
109
|
+
state.cooldownTimer = null;
|
|
110
|
+
}
|
|
111
|
+
if (state.maxDurationTimer) {
|
|
112
|
+
clearTimeout(state.maxDurationTimer);
|
|
113
|
+
state.maxDurationTimer = null;
|
|
114
|
+
}
|
|
115
|
+
// Generate summary
|
|
116
|
+
const summary = this.generateSummary(state);
|
|
117
|
+
log.debug(`MassEvent: Summary - joins=${summary.joins} parts=${summary.parts} quits=${summary.quits}`);
|
|
118
|
+
// Create summary message
|
|
119
|
+
const summaryMsg = new Msg({
|
|
120
|
+
type: MessageType.MASS_EVENT,
|
|
121
|
+
time: new Date(),
|
|
122
|
+
massEventSummary: summary,
|
|
123
|
+
});
|
|
124
|
+
// Push summary message through normal channel
|
|
125
|
+
chan.pushMessage(this.client, summaryMsg);
|
|
126
|
+
// Reset state
|
|
127
|
+
state.isActive = false;
|
|
128
|
+
state.buffer = [];
|
|
129
|
+
state.preBuffer = [];
|
|
130
|
+
state.startTime = null;
|
|
131
|
+
state.recentTimestamps = [];
|
|
132
|
+
// Refresh user list if configured
|
|
133
|
+
if (Config.values.massEventDetection.refreshNamesAfter && network.irc) {
|
|
134
|
+
network.irc.raw("NAMES", chan.name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
generateSummary(state) {
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const summary = {
|
|
140
|
+
joins: 0,
|
|
141
|
+
parts: 0,
|
|
142
|
+
quits: 0,
|
|
143
|
+
modes: 0,
|
|
144
|
+
nicks: 0,
|
|
145
|
+
kicks: 0,
|
|
146
|
+
chghosts: 0,
|
|
147
|
+
away: 0,
|
|
148
|
+
back: 0,
|
|
149
|
+
duration: now - (state.startTime || now),
|
|
150
|
+
startTime: new Date(state.startTime || now),
|
|
151
|
+
endTime: new Date(),
|
|
152
|
+
};
|
|
153
|
+
for (const { msg } of state.buffer) {
|
|
154
|
+
switch (msg.type) {
|
|
155
|
+
case MessageType.JOIN:
|
|
156
|
+
summary.joins++;
|
|
157
|
+
break;
|
|
158
|
+
case MessageType.PART:
|
|
159
|
+
summary.parts++;
|
|
160
|
+
break;
|
|
161
|
+
case MessageType.QUIT:
|
|
162
|
+
summary.quits++;
|
|
163
|
+
break;
|
|
164
|
+
case MessageType.MODE:
|
|
165
|
+
// Count individual mode changes
|
|
166
|
+
const modeText = msg.text || "";
|
|
167
|
+
const modeChanges = modeText
|
|
168
|
+
.split(" ")[0]
|
|
169
|
+
.split("")
|
|
170
|
+
.filter((c) => c !== "+" && c !== "-").length;
|
|
171
|
+
summary.modes += modeChanges || 1;
|
|
172
|
+
break;
|
|
173
|
+
case MessageType.NICK:
|
|
174
|
+
summary.nicks++;
|
|
175
|
+
break;
|
|
176
|
+
case MessageType.KICK:
|
|
177
|
+
summary.kicks++;
|
|
178
|
+
break;
|
|
179
|
+
case MessageType.CHGHOST:
|
|
180
|
+
summary.chghosts++;
|
|
181
|
+
break;
|
|
182
|
+
case MessageType.AWAY:
|
|
183
|
+
summary.away++;
|
|
184
|
+
break;
|
|
185
|
+
case MessageType.BACK:
|
|
186
|
+
summary.back++;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return summary;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Clean up state for a channel (e.g., when leaving)
|
|
194
|
+
*/
|
|
195
|
+
cleanup(chanId) {
|
|
196
|
+
const state = this.channelStates.get(chanId);
|
|
197
|
+
if (state) {
|
|
198
|
+
if (state.cooldownTimer) {
|
|
199
|
+
clearTimeout(state.cooldownTimer);
|
|
200
|
+
}
|
|
201
|
+
if (state.maxDurationTimer) {
|
|
202
|
+
clearTimeout(state.maxDurationTimer);
|
|
203
|
+
}
|
|
204
|
+
this.channelStates.delete(chanId);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if mass event is active for a channel
|
|
209
|
+
*/
|
|
210
|
+
isActive(chanId) {
|
|
211
|
+
return this.channelStates.get(chanId)?.isActive || false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export default MassEventAggregator;
|