Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
446
extensions/irc/src/client.ts
Normal file
446
extensions/irc/src/client.ts
Normal file
@@ -0,0 +1,446 @@
|
||||
// Irc plugin module implements client behavior.
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
parseIrcLine,
|
||||
parseIrcPrefix,
|
||||
sanitizeIrcOutboundText,
|
||||
sanitizeIrcTarget,
|
||||
} from "./protocol.js";
|
||||
|
||||
const IRC_ERROR_CODES = new Set(["432", "464", "465"]);
|
||||
const IRC_NICK_COLLISION_CODES = new Set(["433", "436"]);
|
||||
|
||||
type IrcPrivmsgEvent = {
|
||||
senderNick: string;
|
||||
senderUser?: string;
|
||||
senderHost?: string;
|
||||
target: string;
|
||||
text: string;
|
||||
rawLine: string;
|
||||
};
|
||||
|
||||
export type IrcClientOptions = {
|
||||
host: string;
|
||||
port: number;
|
||||
tls: boolean;
|
||||
nick: string;
|
||||
username: string;
|
||||
realname: string;
|
||||
password?: string;
|
||||
nickserv?: IrcNickServOptions;
|
||||
channels?: string[];
|
||||
connectTimeoutMs?: number;
|
||||
messageChunkMaxChars?: number;
|
||||
abortSignal?: AbortSignal;
|
||||
onPrivmsg?: (event: IrcPrivmsgEvent) => void | Promise<void>;
|
||||
onNotice?: (text: string, target?: string) => void;
|
||||
onError?: (error: Error) => void;
|
||||
onLine?: (line: string) => void;
|
||||
};
|
||||
|
||||
type IrcNickServOptions = {
|
||||
enabled?: boolean;
|
||||
service?: string;
|
||||
password?: string;
|
||||
register?: boolean;
|
||||
registerEmail?: string;
|
||||
};
|
||||
|
||||
export type IrcClient = {
|
||||
nick: string;
|
||||
isReady: () => boolean;
|
||||
sendRaw: (line: string) => void;
|
||||
join: (channel: string) => void;
|
||||
sendPrivmsg: (target: string, text: string) => void;
|
||||
quit: (reason?: string) => void;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
function toError(err: unknown): Error {
|
||||
if (err instanceof Error) {
|
||||
return err;
|
||||
}
|
||||
return new Error(typeof err === "string" ? err : JSON.stringify(err));
|
||||
}
|
||||
|
||||
let nickCollisionFallbackSeq = 0;
|
||||
|
||||
export function buildFallbackNick(nick: string): string {
|
||||
const normalized = nick.replace(/\s+/g, "");
|
||||
const safe = normalized.replace(/[^A-Za-z0-9_\-[\]\\`^{}|]/g, "");
|
||||
const base = safe || "openclaw";
|
||||
const seq = ++nickCollisionFallbackSeq;
|
||||
const suffix = seq === 1 ? "_" : `_${seq}`;
|
||||
const maxNickLen = 30;
|
||||
if (base.length >= maxNickLen) {
|
||||
return `${base.slice(0, maxNickLen - suffix.length)}${suffix}`;
|
||||
}
|
||||
return `${base}${suffix}`;
|
||||
}
|
||||
|
||||
function normalizeIrcNick(value: string): string {
|
||||
return normalizeLowercaseStringOrEmpty(value);
|
||||
}
|
||||
|
||||
export function buildIrcNickServCommands(options?: IrcNickServOptions): string[] {
|
||||
if (!options || options.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
const password = sanitizeIrcOutboundText(options.password ?? "");
|
||||
if (!password) {
|
||||
return [];
|
||||
}
|
||||
const service = sanitizeIrcTarget(options.service?.trim() || "NickServ");
|
||||
const commands = [`PRIVMSG ${service} :IDENTIFY ${password}`];
|
||||
if (options.register) {
|
||||
const registerEmail = sanitizeIrcOutboundText(options.registerEmail ?? "");
|
||||
if (!registerEmail) {
|
||||
throw new Error("IRC NickServ register requires registerEmail");
|
||||
}
|
||||
commands.push(`PRIVMSG ${service} :REGISTER ${password} ${registerEmail}`);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
export async function connectIrcClient(options: IrcClientOptions): Promise<IrcClient> {
|
||||
const timeoutMs = options.connectTimeoutMs != null ? options.connectTimeoutMs : 15000;
|
||||
const messageChunkMaxChars =
|
||||
options.messageChunkMaxChars != null ? options.messageChunkMaxChars : 350;
|
||||
|
||||
if (!options.host.trim()) {
|
||||
throw new Error("IRC host is required");
|
||||
}
|
||||
if (!options.nick.trim()) {
|
||||
throw new Error("IRC nick is required");
|
||||
}
|
||||
|
||||
const desiredNick = options.nick.trim();
|
||||
let currentNick = desiredNick;
|
||||
let ready = false;
|
||||
let closed = false;
|
||||
let nickServRecoverAttempted = false;
|
||||
let fallbackNickAttempted = false;
|
||||
let removeAbortListener: (() => void) | null = null;
|
||||
|
||||
const socket = options.tls
|
||||
? tls.connect({
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
servername: options.host,
|
||||
})
|
||||
: net.connect({ host: options.host, port: options.port });
|
||||
|
||||
socket.setEncoding("utf8");
|
||||
|
||||
let resolveReady: (() => void) | null = null;
|
||||
let rejectReady: ((error: Error) => void) | null = null;
|
||||
const readyPromise = new Promise<void>((resolve, reject) => {
|
||||
resolveReady = resolve;
|
||||
rejectReady = reject;
|
||||
});
|
||||
|
||||
const fail = (err: unknown) => {
|
||||
const error = toError(err);
|
||||
if (options.onError) {
|
||||
options.onError(error);
|
||||
}
|
||||
if (!ready && rejectReady) {
|
||||
rejectReady(error);
|
||||
rejectReady = null;
|
||||
resolveReady = null;
|
||||
}
|
||||
};
|
||||
|
||||
const failAndClose = (err: unknown) => {
|
||||
fail(err);
|
||||
close();
|
||||
};
|
||||
|
||||
const sendRaw = (line: string) => {
|
||||
const cleaned = line.replace(/[\r\n]+/g, "").trim();
|
||||
if (!cleaned) {
|
||||
throw new Error("IRC command cannot be empty");
|
||||
}
|
||||
socket.write(`${cleaned}\r\n`);
|
||||
};
|
||||
|
||||
const tryRecoverNickCollision = (): boolean => {
|
||||
const nickServEnabled = options.nickserv?.enabled !== false;
|
||||
const nickservPassword = sanitizeIrcOutboundText(options.nickserv?.password ?? "");
|
||||
if (nickServEnabled && !nickServRecoverAttempted && nickservPassword) {
|
||||
nickServRecoverAttempted = true;
|
||||
try {
|
||||
const service = sanitizeIrcTarget(options.nickserv?.service?.trim() || "NickServ");
|
||||
sendRaw(`PRIVMSG ${service} :GHOST ${desiredNick} ${nickservPassword}`);
|
||||
sendRaw(`NICK ${desiredNick}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
fail(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fallbackNickAttempted) {
|
||||
fallbackNickAttempted = true;
|
||||
const fallbackNick = buildFallbackNick(desiredNick);
|
||||
if (normalizeIrcNick(fallbackNick) !== normalizeIrcNick(currentNick)) {
|
||||
try {
|
||||
sendRaw(`NICK ${fallbackNick}`);
|
||||
currentNick = fallbackNick;
|
||||
return true;
|
||||
} catch (err) {
|
||||
fail(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const join = (channel: string) => {
|
||||
const target = sanitizeIrcTarget(channel);
|
||||
if (!target.startsWith("#") && !target.startsWith("&")) {
|
||||
throw new Error(`IRC JOIN target must be a channel: ${channel}`);
|
||||
}
|
||||
sendRaw(`JOIN ${target}`);
|
||||
};
|
||||
|
||||
const sendPrivmsg = (target: string, text: string) => {
|
||||
const normalizedTarget = sanitizeIrcTarget(target);
|
||||
const cleaned = sanitizeIrcOutboundText(text);
|
||||
if (!cleaned) {
|
||||
return;
|
||||
}
|
||||
let remaining = cleaned;
|
||||
while (remaining.length > 0) {
|
||||
let chunk = remaining;
|
||||
if (chunk.length > messageChunkMaxChars) {
|
||||
let splitAt = chunk.lastIndexOf(" ", messageChunkMaxChars);
|
||||
if (splitAt < Math.floor(messageChunkMaxChars / 2)) {
|
||||
splitAt = messageChunkMaxChars;
|
||||
}
|
||||
chunk = chunk.slice(0, splitAt).trim();
|
||||
}
|
||||
if (!chunk) {
|
||||
break;
|
||||
}
|
||||
sendRaw(`PRIVMSG ${normalizedTarget} :${chunk}`);
|
||||
remaining = remaining.slice(chunk.length).trimStart();
|
||||
}
|
||||
};
|
||||
|
||||
const quit = (reason?: string) => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
removeAbortListener?.();
|
||||
removeAbortListener = null;
|
||||
const safeReason = sanitizeIrcOutboundText(reason != null ? reason : "bye");
|
||||
try {
|
||||
if (safeReason) {
|
||||
sendRaw(`QUIT :${safeReason}`);
|
||||
} else {
|
||||
sendRaw("QUIT");
|
||||
}
|
||||
} catch {
|
||||
// Ignore quit failures while shutting down.
|
||||
}
|
||||
socket.end();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
removeAbortListener?.();
|
||||
removeAbortListener = null;
|
||||
socket.destroy();
|
||||
};
|
||||
|
||||
let buffer = "";
|
||||
socket.on("data", (chunk: string) => {
|
||||
buffer += chunk;
|
||||
let idx = buffer.indexOf("\n");
|
||||
while (idx !== -1) {
|
||||
const rawLine = buffer.slice(0, idx).replace(/\r$/, "");
|
||||
buffer = buffer.slice(idx + 1);
|
||||
idx = buffer.indexOf("\n");
|
||||
|
||||
if (!rawLine) {
|
||||
continue;
|
||||
}
|
||||
if (options.onLine) {
|
||||
options.onLine(rawLine);
|
||||
}
|
||||
|
||||
const line = parseIrcLine(rawLine);
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.command === "PING") {
|
||||
const payload =
|
||||
line.trailing != null ? line.trailing : line.params[0] != null ? line.params[0] : "";
|
||||
sendRaw(`PONG :${payload}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.command === "NICK") {
|
||||
const prefix = parseIrcPrefix(line.prefix);
|
||||
if (prefix.nick && normalizeIrcNick(prefix.nick) === normalizeIrcNick(currentNick)) {
|
||||
const next =
|
||||
line.trailing != null
|
||||
? line.trailing
|
||||
: line.params[0] != null
|
||||
? line.params[0]
|
||||
: currentNick;
|
||||
currentNick = next.trim();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ready && IRC_NICK_COLLISION_CODES.has(line.command)) {
|
||||
if (tryRecoverNickCollision()) {
|
||||
continue;
|
||||
}
|
||||
const detail =
|
||||
line.trailing != null ? line.trailing : line.params.join(" ") || "nickname in use";
|
||||
fail(new Error(`IRC login failed (${line.command}): ${detail}`));
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ready && IRC_ERROR_CODES.has(line.command)) {
|
||||
const detail =
|
||||
line.trailing != null ? line.trailing : line.params.join(" ") || "login rejected";
|
||||
fail(new Error(`IRC login failed (${line.command}): ${detail}`));
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.command === "001") {
|
||||
ready = true;
|
||||
const nickParam = line.params[0];
|
||||
if (nickParam && nickParam.trim()) {
|
||||
currentNick = nickParam.trim();
|
||||
}
|
||||
try {
|
||||
const nickServCommands = buildIrcNickServCommands(options.nickserv);
|
||||
for (const command of nickServCommands) {
|
||||
sendRaw(command);
|
||||
}
|
||||
} catch (err) {
|
||||
fail(err);
|
||||
}
|
||||
for (const channel of options.channels || []) {
|
||||
const trimmed = channel.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
join(trimmed);
|
||||
} catch (err) {
|
||||
fail(err);
|
||||
}
|
||||
}
|
||||
if (resolveReady) {
|
||||
resolveReady();
|
||||
}
|
||||
resolveReady = null;
|
||||
rejectReady = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.command === "NOTICE") {
|
||||
if (options.onNotice) {
|
||||
options.onNotice(line.trailing != null ? line.trailing : "", line.params[0]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.command === "PRIVMSG") {
|
||||
const targetParam = line.params[0];
|
||||
const target = targetParam ? targetParam.trim() : "";
|
||||
const text = line.trailing != null ? line.trailing : "";
|
||||
const prefix = parseIrcPrefix(line.prefix);
|
||||
const senderNick = prefix.nick ? prefix.nick.trim() : "";
|
||||
if (!target || !senderNick || !text.trim()) {
|
||||
continue;
|
||||
}
|
||||
if (options.onPrivmsg) {
|
||||
void Promise.resolve(
|
||||
options.onPrivmsg({
|
||||
senderNick,
|
||||
senderUser: prefix.user ? prefix.user.trim() : undefined,
|
||||
senderHost: prefix.host ? prefix.host.trim() : undefined,
|
||||
target,
|
||||
text,
|
||||
rawLine,
|
||||
}),
|
||||
).catch((error: unknown) => {
|
||||
fail(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.once("connect", () => {
|
||||
try {
|
||||
if (options.password && options.password.trim()) {
|
||||
sendRaw(`PASS ${options.password.trim()}`);
|
||||
}
|
||||
sendRaw(`NICK ${options.nick.trim()}`);
|
||||
sendRaw(`USER ${options.username.trim()} 0 * :${sanitizeIrcOutboundText(options.realname)}`);
|
||||
} catch (err) {
|
||||
fail(err);
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
socket.once("error", (err: unknown) => {
|
||||
fail(err);
|
||||
});
|
||||
|
||||
socket.once("close", () => {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
if (!ready) {
|
||||
fail(new Error("IRC connection closed before ready"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (options.abortSignal) {
|
||||
const abort = () => {
|
||||
if (!ready) {
|
||||
failAndClose(new Error("IRC connect aborted"));
|
||||
return;
|
||||
}
|
||||
quit("shutdown");
|
||||
};
|
||||
if (options.abortSignal.aborted) {
|
||||
abort();
|
||||
} else {
|
||||
options.abortSignal.addEventListener("abort", abort, { once: true });
|
||||
removeAbortListener = () => options.abortSignal?.removeEventListener("abort", abort);
|
||||
}
|
||||
}
|
||||
|
||||
await withTimeout(readyPromise, timeoutMs, "IRC connect");
|
||||
|
||||
return {
|
||||
get nick() {
|
||||
return currentNick;
|
||||
},
|
||||
isReady: () => ready && !closed,
|
||||
sendRaw,
|
||||
join,
|
||||
sendPrivmsg,
|
||||
quit,
|
||||
close,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user