Skip to content

Commit

Permalink
refactor(plugins/ctcp): emit 'raw_ctcp:*' events
Browse files Browse the repository at this point in the history
refactor(plugins/ctcp): emit distinct events for CTCP query and reply
  • Loading branch information
jeromeludmann committed Feb 10, 2022
1 parent 29ee09e commit 0ee47a4
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 161 deletions.
11 changes: 5 additions & 6 deletions plugins/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@ interface ActionFeatures {

export default createPlugin("action", [ctcp])<ActionFeatures>((client) => {
// Sends CTCP ACTION command.

client.action = client.me = (target, text) => {
client.ctcp(target, "ACTION", text);
};

// Emits 'ctcp_action' event.
client.on("ctcp", (msg) => {
if (
msg.command === "ACTION" &&
msg.params.param
) {
const { source, params: { target, param: text } } = msg;

client.on("raw_ctcp:action", (msg) => {
const { source, params: { target, arg: text } } = msg;
if (text !== undefined) {
client.emit("ctcp_action", { source, params: { target, text } });
}
});
Expand Down
29 changes: 13 additions & 16 deletions plugins/clientinfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,32 +42,29 @@ export default createPlugin(
[ctcp],
)<ClientinfoFeatures>((client, options) => {
// Sends CTCP CLIENTINFO command.

client.clientinfo = (target) => {
client.ctcp(target, "CLIENTINFO");
};

// Emits 'ctcp_clientinfo' and 'ctcp_clientinfo_reply' events.
client.on("ctcp", (msg) => {
if (msg.command !== "CLIENTINFO") return;
const { source, params: { type, target, param } } = msg;

switch (type) {
case "query": {
client.emit("ctcp_clientinfo", { source, params: { target } });
break;
}
case "reply": {
const supported = (param?.split(" ") ?? []) as AnyCtcpCommand[];
client.emit("ctcp_clientinfo_reply", { source, params: { supported } });
break;
}
}

client.on("raw_ctcp:clientinfo", (msg) => {
const { source, params: { target } } = msg;
client.emit("ctcp_clientinfo", { source, params: { target } });
});

client.on("raw_ctcp:clientinfo_reply", (msg) => {
const { source, params: { arg } } = msg;
const supported = (arg?.split(" ") ?? []) as AnyCtcpCommand[];
client.emit("ctcp_clientinfo_reply", { source, params: { supported } });
});

// Replies to CTCP CLIENTINFO.

const replyEnabled = options.ctcpReplies?.clientinfo ?? REPLY_ENABLED;
if (!replyEnabled) return;

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

Expand Down
100 changes: 51 additions & 49 deletions plugins/ctcp.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { type Message, type Raw } from "../core/parsers.ts";
import { createPlugin } from "../core/plugins.ts";

export type AnyCtcpCommand =
| "ACTION"
| "CLIENTINFO"
| "PING"
| "TIME"
| "VERSION";

export interface CtcpEventParams {
/** Target of the CTCP.
const CTCP_COMMANDS = {
ACTION: "action",
CLIENTINFO: "clientinfo",
PING: "ping",
TIME: "time",
VERSION: "version",
} as const;

type AnyRawCtcpCommand = keyof typeof CTCP_COMMANDS;
export type AnyCtcpCommand = typeof CTCP_COMMANDS[AnyRawCtcpCommand];

export interface RawCtcpEventParams {
/** Target of the CTCP query.
*
* Can be either a channel or a nick. */
target: string;

/** Type of the CTCP (`"query"` or `"reply"`). */
type: "query" | "reply";
/** Optional argument of the CTCP query. */
arg?: string;
}

export type RawCtcpEvent = Message<RawCtcpEventParams> & {
/** Name of the CTCP command. */
command: AnyCtcpCommand;
};

/** Optional param of the CTCP. */
param?: string;
export interface RawCtcpReplyEventParams {
/** Argument of the CTCP reply. */
arg: string;
}

export type CtcpEvent = Message<CtcpEventParams> & {
export type RawCtcpReplyEvent = Message<RawCtcpReplyEventParams> & {
/** Name of the CTCP command. */
command: AnyCtcpCommand;
};
Expand All @@ -36,14 +47,14 @@ interface CtcpFeatures {
* - `ping`
* - `time`
* - `version` */
ctcp(target: string, command: AnyCtcpCommand, param?: string): void;
};
events: {
"ctcp": CtcpEvent;
ctcp(target: string, command: AnyRawCtcpCommand, param?: string): void;
};
events:
& { [K in `raw_ctcp:${AnyCtcpCommand}`]: RawCtcpEvent }
& { [K in `raw_ctcp:${AnyCtcpCommand}_reply`]: RawCtcpReplyEvent };
utils: {
isCtcp: (msg: Raw) => boolean;
createCtcp: (command: AnyCtcpCommand, param?: string) => string;
createCtcp: (command: AnyRawCtcpCommand, param?: string) => string;
};
}

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

// Emits 'ctcp' event.
// Emits 'raw:ctcp:*' events.

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

const { source, params: [target, rawCtcp] } = msg;
// Parses raw CTCP

const i = rawCtcp.indexOf(" ", 1);
const command = rawCtcp.slice(1, i) as AnyCtcpCommand;
const ctcpParam = i === -1 ? undefined : rawCtcp.slice(i + 1, -1);
const type = msg.command === "privmsg" ? "query" : "reply";
const i = rawCtcp.indexOf(" ", 1);
const rawCommand = rawCtcp.slice(1, i) as AnyRawCtcpCommand;
const command = CTCP_COMMANDS[rawCommand];
const param = i === -1 ? undefined : rawCtcp.slice(i + 1, -1);

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

client.emit("ctcp", ctcp);
const ctcp: RawCtcpEvent = { source, command, params: { target } };
if (param) ctcp.params.arg = param;

client.emit(`raw_ctcp:${ctcp.command}${type}`, ctcp);
}
});

// Utils.

client.utils.isCtcp = (msg) => {
const { params } = msg;

if (params.length !== 2) {
return false;
}

if (
params[1][0] !== "\x01" ||
params[1][params[1].length - 1] !== "\x01"
) {
return false;
}

if (
msg.command !== "privmsg" &&
msg.command !== "notice"
) {
return false;
}

return true;
return (
// should have 2 parameters
params.length === 2 &&
// should be wrapped with '\x01'
params[1].charAt(0) === "\x01" &&
params[1].slice(-1) === "\x01"
);
};

client.utils.createCtcp = (command, param) => {
Expand Down
72 changes: 34 additions & 38 deletions plugins/ctcp_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,30 @@ describe("plugins/ctcp", (test) => {
);
});

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

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

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

assertEquals(messages, [
{
source: {
name: "someone",
mask: { user: "user", host: "host" },
},
command: "CTCP_COMMAND",
params: {
target: "#channel",
type: "query",
},
source: { name: "someone", mask: { user: "user", host: "host" } },
command: "ping",
params: { target: "me", arg: "key" },
},
{
source: {
name: "someone",
mask: { user: "user", host: "host" },
},
command: "CTCP_COMMAND",
params: {
target: "#channel",
type: "reply",
param: "param",
},
source: { name: "someone", mask: { user: "user", host: "host" } },
command: "ping",
params: { target: "me", arg: "key" },
},
]);
});
Expand All @@ -64,9 +51,10 @@ describe("plugins/ctcp", (test) => {
const { client } = await mock();

assertEquals(
client.utils.isCtcp(
{ command: "privmsg", params: ["nick", "Hello world"] },
),
client.utils.isCtcp({
command: "privmsg",
params: ["nick", "Hello world"],
}),
false,
);

Expand All @@ -81,41 +69,49 @@ describe("plugins/ctcp", (test) => {
assertEquals(
client.utils.isCtcp({
command: "privmsg",
params: ["nick", "\x01Hello world\x01"],
params: ["nick"],
}),
true,
false,
);

assertEquals(
client.utils.isCtcp({
command: "notice",
params: ["nick", "\x01Hello world\x01"],
command: "privmsg",
params: ["nick", "\x01COMMAND\x01"],
}),
true,
);

assertEquals(
client.utils.isCtcp({
command: "join",
params: ["nick", "\x01Hello world\x01"],
command: "privmsg",
params: ["nick", "\x01COMMAND argument\x01"],
}),
false,
true,
);

assertEquals(
client.utils.isCtcp({
command: "join",
command: "notice",
params: ["nick"],
}),
false,
);

assertEquals(
client.utils.isCtcp({
command: "join",
params: [],
command: "notice",
params: ["nick", "\x01COMMAND\x01"],
}),
false,
true,
);

assertEquals(
client.utils.isCtcp({
command: "notice",
params: ["nick", "\x01COMMAND argument\x01"],
}),
true,
);
});
});
32 changes: 13 additions & 19 deletions plugins/ping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ interface PingFeatures {
const CTCP_REPLY_ENABLED = true;

export default createPlugin("ping", [ctcp])<PingFeatures>((client, options) => {
const ctcpReplyEnabled = options.ctcpReplies?.ping ?? CTCP_REPLY_ENABLED;

// Sends PING command.

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

// Emits 'ctcp_ping' event.

client.on("ctcp", (msg) => {
if (
msg.command === "PING" &&
msg.params.param !== undefined
) {
const { source, params: { type, target, param: key } } = msg;

switch (type) {
case "query":
client.emit("ctcp_ping", { source, params: { target, key } });
break;
case "reply":
client.emit("ctcp_ping_reply", { source, params: { key } });
break;
}
// Emits 'ctcp_ping' and 'ctcp_ping_reply' events.

client.on("raw_ctcp:ping", (msg) => {
const { source, params: { target, arg: key } } = msg;
if (key !== undefined) {
client.emit("ctcp_ping", { source, params: { target, key } });
}
});

client.on("raw_ctcp:ping_reply", (msg) => {
const { source, params: { arg: key } } = msg;
client.emit("ctcp_ping_reply", { source, params: { key } });
});

// Replies to PING.

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

// Replies to CTCP PING.

const ctcpReplyEnabled = options.ctcpReplies?.ping ?? CTCP_REPLY_ENABLED;
if (!ctcpReplyEnabled) return;

client.on("ctcp_ping", (msg) => {
const { source, params: { key } } = msg;
if (source) {
Expand Down
Loading

0 comments on commit 0ee47a4

Please sign in to comment.