Skip to content

Commit 0ee47a4

Browse files
committed
refactor(plugins/ctcp): emit 'raw_ctcp:*' events
refactor(plugins/ctcp): emit distinct events for CTCP query and reply
1 parent 29ee09e commit 0ee47a4

File tree

7 files changed

+142
-161
lines changed

7 files changed

+142
-161
lines changed

plugins/action.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,16 @@ interface ActionFeatures {
2727

2828
export default createPlugin("action", [ctcp])<ActionFeatures>((client) => {
2929
// Sends CTCP ACTION command.
30+
3031
client.action = client.me = (target, text) => {
3132
client.ctcp(target, "ACTION", text);
3233
};
3334

3435
// Emits 'ctcp_action' event.
35-
client.on("ctcp", (msg) => {
36-
if (
37-
msg.command === "ACTION" &&
38-
msg.params.param
39-
) {
40-
const { source, params: { target, param: text } } = msg;
36+
37+
client.on("raw_ctcp:action", (msg) => {
38+
const { source, params: { target, arg: text } } = msg;
39+
if (text !== undefined) {
4140
client.emit("ctcp_action", { source, params: { target, text } });
4241
}
4342
});

plugins/clientinfo.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,29 @@ export default createPlugin(
4242
[ctcp],
4343
)<ClientinfoFeatures>((client, options) => {
4444
// Sends CTCP CLIENTINFO command.
45+
4546
client.clientinfo = (target) => {
4647
client.ctcp(target, "CLIENTINFO");
4748
};
4849

4950
// Emits 'ctcp_clientinfo' and 'ctcp_clientinfo_reply' events.
50-
client.on("ctcp", (msg) => {
51-
if (msg.command !== "CLIENTINFO") return;
52-
const { source, params: { type, target, param } } = msg;
53-
54-
switch (type) {
55-
case "query": {
56-
client.emit("ctcp_clientinfo", { source, params: { target } });
57-
break;
58-
}
59-
case "reply": {
60-
const supported = (param?.split(" ") ?? []) as AnyCtcpCommand[];
61-
client.emit("ctcp_clientinfo_reply", { source, params: { supported } });
62-
break;
63-
}
64-
}
51+
52+
client.on("raw_ctcp:clientinfo", (msg) => {
53+
const { source, params: { target } } = msg;
54+
client.emit("ctcp_clientinfo", { source, params: { target } });
55+
});
56+
57+
client.on("raw_ctcp:clientinfo_reply", (msg) => {
58+
const { source, params: { arg } } = msg;
59+
const supported = (arg?.split(" ") ?? []) as AnyCtcpCommand[];
60+
client.emit("ctcp_clientinfo_reply", { source, params: { supported } });
6561
});
6662

63+
// Replies to CTCP CLIENTINFO.
64+
6765
const replyEnabled = options.ctcpReplies?.clientinfo ?? REPLY_ENABLED;
6866
if (!replyEnabled) return;
6967

70-
// Replies to CTCP CLIENTINFO.
7168
client.on("ctcp_clientinfo", (msg) => {
7269
const { source } = msg;
7370

plugins/ctcp.ts

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
11
import { type Message, type Raw } from "../core/parsers.ts";
22
import { createPlugin } from "../core/plugins.ts";
33

4-
export type AnyCtcpCommand =
5-
| "ACTION"
6-
| "CLIENTINFO"
7-
| "PING"
8-
| "TIME"
9-
| "VERSION";
10-
11-
export interface CtcpEventParams {
12-
/** Target of the CTCP.
4+
const CTCP_COMMANDS = {
5+
ACTION: "action",
6+
CLIENTINFO: "clientinfo",
7+
PING: "ping",
8+
TIME: "time",
9+
VERSION: "version",
10+
} as const;
11+
12+
type AnyRawCtcpCommand = keyof typeof CTCP_COMMANDS;
13+
export type AnyCtcpCommand = typeof CTCP_COMMANDS[AnyRawCtcpCommand];
14+
15+
export interface RawCtcpEventParams {
16+
/** Target of the CTCP query.
1317
*
1418
* Can be either a channel or a nick. */
1519
target: string;
1620

17-
/** Type of the CTCP (`"query"` or `"reply"`). */
18-
type: "query" | "reply";
21+
/** Optional argument of the CTCP query. */
22+
arg?: string;
23+
}
24+
25+
export type RawCtcpEvent = Message<RawCtcpEventParams> & {
26+
/** Name of the CTCP command. */
27+
command: AnyCtcpCommand;
28+
};
1929

20-
/** Optional param of the CTCP. */
21-
param?: string;
30+
export interface RawCtcpReplyEventParams {
31+
/** Argument of the CTCP reply. */
32+
arg: string;
2233
}
2334

24-
export type CtcpEvent = Message<CtcpEventParams> & {
35+
export type RawCtcpReplyEvent = Message<RawCtcpReplyEventParams> & {
2536
/** Name of the CTCP command. */
2637
command: AnyCtcpCommand;
2738
};
@@ -36,14 +47,14 @@ interface CtcpFeatures {
3647
* - `ping`
3748
* - `time`
3849
* - `version` */
39-
ctcp(target: string, command: AnyCtcpCommand, param?: string): void;
40-
};
41-
events: {
42-
"ctcp": CtcpEvent;
50+
ctcp(target: string, command: AnyRawCtcpCommand, param?: string): void;
4351
};
52+
events:
53+
& { [K in `raw_ctcp:${AnyCtcpCommand}`]: RawCtcpEvent }
54+
& { [K in `raw_ctcp:${AnyCtcpCommand}_reply`]: RawCtcpReplyEvent };
4455
utils: {
4556
isCtcp: (msg: Raw) => boolean;
46-
createCtcp: (command: AnyCtcpCommand, param?: string) => string;
57+
createCtcp: (command: AnyRawCtcpCommand, param?: string) => string;
4758
};
4859
}
4960

@@ -55,48 +66,39 @@ export default createPlugin("ctcp", [])<CtcpFeatures>((client) => {
5566
client.send("PRIVMSG", target, ctcp);
5667
};
5768

58-
// Emits 'ctcp' event.
69+
// Emits 'raw:ctcp:*' events.
5970

6071
client.on(["raw:privmsg", "raw:notice"], (msg) => {
61-
if (!client.utils.isCtcp(msg)) return;
72+
if (client.utils.isCtcp(msg)) {
73+
const { source, params: [target, rawCtcp] } = msg;
6274

63-
const { source, params: [target, rawCtcp] } = msg;
75+
// Parses raw CTCP
6476

65-
const i = rawCtcp.indexOf(" ", 1);
66-
const command = rawCtcp.slice(1, i) as AnyCtcpCommand;
67-
const ctcpParam = i === -1 ? undefined : rawCtcp.slice(i + 1, -1);
68-
const type = msg.command === "privmsg" ? "query" : "reply";
77+
const i = rawCtcp.indexOf(" ", 1);
78+
const rawCommand = rawCtcp.slice(1, i) as AnyRawCtcpCommand;
79+
const command = CTCP_COMMANDS[rawCommand];
80+
const param = i === -1 ? undefined : rawCtcp.slice(i + 1, -1);
6981

70-
const ctcp: CtcpEvent = { source, command, params: { target, type } };
71-
if (ctcpParam) ctcp.params.param = ctcpParam;
82+
const type = msg.command === "privmsg" ? "" : "_reply";
7283

73-
client.emit("ctcp", ctcp);
84+
const ctcp: RawCtcpEvent = { source, command, params: { target } };
85+
if (param) ctcp.params.arg = param;
86+
87+
client.emit(`raw_ctcp:${ctcp.command}${type}`, ctcp);
88+
}
7489
});
7590

7691
// Utils.
7792

7893
client.utils.isCtcp = (msg) => {
7994
const { params } = msg;
80-
81-
if (params.length !== 2) {
82-
return false;
83-
}
84-
85-
if (
86-
params[1][0] !== "\x01" ||
87-
params[1][params[1].length - 1] !== "\x01"
88-
) {
89-
return false;
90-
}
91-
92-
if (
93-
msg.command !== "privmsg" &&
94-
msg.command !== "notice"
95-
) {
96-
return false;
97-
}
98-
99-
return true;
95+
return (
96+
// should have 2 parameters
97+
params.length === 2 &&
98+
// should be wrapped with '\x01'
99+
params[1].charAt(0) === "\x01" &&
100+
params[1].slice(-1) === "\x01"
101+
);
100102
};
101103

102104
client.utils.createCtcp = (command, param) => {

plugins/ctcp_test.ts

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,43 +19,30 @@ describe("plugins/ctcp", (test) => {
1919
);
2020
});
2121

22-
test("emit 'ctcp' on CTCP", async () => {
22+
test("emit 'ctcp:*' on CTCP", async () => {
2323
const { client, server } = await mock();
2424
const messages = [];
2525

2626
server.send(
27-
":someone!user@host PRIVMSG #channel :\x01CTCP_COMMAND\x01",
27+
":someone!user@host PRIVMSG me :\x01PING key\x01",
2828
);
29-
messages.push(await client.once("ctcp"));
29+
messages.push(await client.once("raw_ctcp:ping"));
3030

3131
server.send(
32-
":someone!user@host NOTICE #channel :\x01CTCP_COMMAND param\x01",
32+
":someone!user@host NOTICE me :\x01PING key\x01",
3333
);
34-
messages.push(await client.once("ctcp"));
34+
messages.push(await client.once("raw_ctcp:ping_reply"));
3535

3636
assertEquals(messages, [
3737
{
38-
source: {
39-
name: "someone",
40-
mask: { user: "user", host: "host" },
41-
},
42-
command: "CTCP_COMMAND",
43-
params: {
44-
target: "#channel",
45-
type: "query",
46-
},
38+
source: { name: "someone", mask: { user: "user", host: "host" } },
39+
command: "ping",
40+
params: { target: "me", arg: "key" },
4741
},
4842
{
49-
source: {
50-
name: "someone",
51-
mask: { user: "user", host: "host" },
52-
},
53-
command: "CTCP_COMMAND",
54-
params: {
55-
target: "#channel",
56-
type: "reply",
57-
param: "param",
58-
},
43+
source: { name: "someone", mask: { user: "user", host: "host" } },
44+
command: "ping",
45+
params: { target: "me", arg: "key" },
5946
},
6047
]);
6148
});
@@ -64,9 +51,10 @@ describe("plugins/ctcp", (test) => {
6451
const { client } = await mock();
6552

6653
assertEquals(
67-
client.utils.isCtcp(
68-
{ command: "privmsg", params: ["nick", "Hello world"] },
69-
),
54+
client.utils.isCtcp({
55+
command: "privmsg",
56+
params: ["nick", "Hello world"],
57+
}),
7058
false,
7159
);
7260

@@ -81,41 +69,49 @@ describe("plugins/ctcp", (test) => {
8169
assertEquals(
8270
client.utils.isCtcp({
8371
command: "privmsg",
84-
params: ["nick", "\x01Hello world\x01"],
72+
params: ["nick"],
8573
}),
86-
true,
74+
false,
8775
);
8876

8977
assertEquals(
9078
client.utils.isCtcp({
91-
command: "notice",
92-
params: ["nick", "\x01Hello world\x01"],
79+
command: "privmsg",
80+
params: ["nick", "\x01COMMAND\x01"],
9381
}),
9482
true,
9583
);
9684

9785
assertEquals(
9886
client.utils.isCtcp({
99-
command: "join",
100-
params: ["nick", "\x01Hello world\x01"],
87+
command: "privmsg",
88+
params: ["nick", "\x01COMMAND argument\x01"],
10189
}),
102-
false,
90+
true,
10391
);
10492

10593
assertEquals(
10694
client.utils.isCtcp({
107-
command: "join",
95+
command: "notice",
10896
params: ["nick"],
10997
}),
11098
false,
11199
);
112100

113101
assertEquals(
114102
client.utils.isCtcp({
115-
command: "join",
116-
params: [],
103+
command: "notice",
104+
params: ["nick", "\x01COMMAND\x01"],
117105
}),
118-
false,
106+
true,
107+
);
108+
109+
assertEquals(
110+
client.utils.isCtcp({
111+
command: "notice",
112+
params: ["nick", "\x01COMMAND argument\x01"],
113+
}),
114+
true,
119115
);
120116
});
121117
});

plugins/ping.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ interface PingFeatures {
6060
const CTCP_REPLY_ENABLED = true;
6161

6262
export default createPlugin("ping", [ctcp])<PingFeatures>((client, options) => {
63-
const ctcpReplyEnabled = options.ctcpReplies?.ping ?? CTCP_REPLY_ENABLED;
64-
6563
// Sends PING command.
6664

6765
client.ping = (target) => {
@@ -86,26 +84,20 @@ export default createPlugin("ping", [ctcp])<PingFeatures>((client, options) => {
8684
client.emit("pong", { source, params: { daemon, key } });
8785
});
8886

89-
// Emits 'ctcp_ping' event.
90-
91-
client.on("ctcp", (msg) => {
92-
if (
93-
msg.command === "PING" &&
94-
msg.params.param !== undefined
95-
) {
96-
const { source, params: { type, target, param: key } } = msg;
97-
98-
switch (type) {
99-
case "query":
100-
client.emit("ctcp_ping", { source, params: { target, key } });
101-
break;
102-
case "reply":
103-
client.emit("ctcp_ping_reply", { source, params: { key } });
104-
break;
105-
}
87+
// Emits 'ctcp_ping' and 'ctcp_ping_reply' events.
88+
89+
client.on("raw_ctcp:ping", (msg) => {
90+
const { source, params: { target, arg: key } } = msg;
91+
if (key !== undefined) {
92+
client.emit("ctcp_ping", { source, params: { target, key } });
10693
}
10794
});
10895

96+
client.on("raw_ctcp:ping_reply", (msg) => {
97+
const { source, params: { arg: key } } = msg;
98+
client.emit("ctcp_ping_reply", { source, params: { key } });
99+
});
100+
109101
// Replies to PING.
110102

111103
client.on("ping", (msg) => {
@@ -114,7 +106,9 @@ export default createPlugin("ping", [ctcp])<PingFeatures>((client, options) => {
114106

115107
// Replies to CTCP PING.
116108

109+
const ctcpReplyEnabled = options.ctcpReplies?.ping ?? CTCP_REPLY_ENABLED;
117110
if (!ctcpReplyEnabled) return;
111+
118112
client.on("ctcp_ping", (msg) => {
119113
const { source, params: { key } } = msg;
120114
if (source) {

0 commit comments

Comments
 (0)