/** * WebIRC Chat - IRC Commands Handler * * @package WebIRC_Chat * @license GPL-2.0-or-later * @since 0.1.0 */ import { logLine } from './ui'; import type { UiElements, Config } from './types'; import { maskSensitive } from './util'; /** * Command context interface for passing state to command handlers. * * @since 0.1.0 */ export interface CommandContext { send: (line: string) => void; ui: UiElements; cfg: Config; nick: string; currentChannel: string; lastAttemptedNick: string; updateNick: (newNick: string) => void; } /** * Handle IRC slash commands from user input. * * @since 0.1.0 */ export function handleCommand(input: string, context: CommandContext): void { const parts = input.slice(1).split(' '); const cmd = parts[0].toLowerCase(); const args = parts.slice(1); const { ui } = context; switch (cmd) { case 'join': handleJoinCommand(args, context); break; case 'part': case 'leave': handlePartCommand(args, context); break; case 'nick': handleNickCommand(args, context); break; case 'msg': case 'privmsg': handleMsgCommand(args, context); break; case 'query': handleQueryCommand(args, context); break; case 'me': handleMeCommand(args, context); break; case 'whois': handleWhoisCommand(args, context); break; case 'who': handleWhoCommand(args, context); break; case 'list': handleListCommand(args, context); break; case 'topic': handleTopicCommand(args, context); break; case 'names': handleNamesCommand(args, context); break; case 'away': handleAwayCommand(args, context); break; case 'back': handleBackCommand(args, context); break; case 'ping': handlePingCommand(args, context); break; case 'time': handleTimeCommand(args, context); break; case 'version': handleVersionCommand(args, context); break; case 'quit': handleQuitCommand(args, context); break; case 'raw': handleRawCommand(args, context); break; case 'notice': handleNoticeCommand(args, context); break; case 'ctcp': handleCtcpCommand(args, context); break; case 'motd': handleMotdCommand(args, context); break; case 'admin': handleAdminCommand(args, context); break; case 'info': handleInfoCommand(args, context); break; case 'stats': handleStatsCommand(args, context); break; case 'links': handleLinksCommand(args, context); break; case 'lusers': handleLusersCommand(args, context); break; case 'userhost': handleUserhostCommand(args, context); break; case 'ison': handleIsonCommand(args, context); break; case 'finger': handleFingerCommand(args, context); break; case 'help': handleHelpCommand(args, context); break; default: logLine( ui.log, `• Unknown command: /${cmd}. Type /help for available commands.`, 'notice' ); } } /** * Handle /join command. * * @since 0.1.0 */ function handleJoinCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length > 0) { let channel = args[0]; if (!channel.startsWith('#')) { channel = '#' + channel; } send(`JOIN ${channel}`); logLine(ui.log, `• Joining ${channel}...`, 'sys'); } else { logLine(ui.log, '• Usage: /join #channel', 'notice'); } } /** * Handle /part command. * * @since 0.1.0 */ function handlePartCommand(args: string[], context: CommandContext): void { const { send, ui, currentChannel } = context; const channelToLeave = args.length > 0 ? args[0] : currentChannel; send(`PART ${channelToLeave}`); logLine(ui.log, `• Leaving ${channelToLeave}...`, 'sys'); } /** * Handle /nick command. * * @since 0.1.0 */ function handleNickCommand(args: string[], context: CommandContext): void { const { send, ui, updateNick } = context; if (args.length > 0) { const newNick = args[0]; send(`NICK ${newNick}`); updateNick(newNick); logLine(ui.log, `• Changing nickname to ${newNick}...`, 'sys'); } else { logLine(ui.log, '• Usage: /nick newnickname', 'notice'); } } /** * Handle /msg command. * * @since 0.1.0 */ function handleMsgCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 2) { logLine(ui.log, '• Usage: /msg ', 'notice'); } else { const [target, ...messageParts] = args; const message = messageParts.join(' '); send(`PRIVMSG ${target} :${message}`); logLine(ui.log, `[PM to ${target}] ${message}`, 'self'); } } /** * Handle /query command. * * @since 0.1.0 */ function handleQueryCommand(args: string[], context: CommandContext): void { const { ui } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /query ', 'notice'); } else { logLine( ui.log, `• Private chat with ${args[0]} opened. Use /msg ${args[0]} to send messages.`, 'sys' ); } } /** * Handle /me command. * * @since 0.1.0 */ function handleMeCommand(args: string[], context: CommandContext): void { const { send, ui, nick, currentChannel } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /me ', 'notice'); } else { const action = args.join(' '); send(`PRIVMSG ${currentChannel} :\u0001ACTION ${action}\u0001`); logLine(ui.log, `* ${nick} ${action}`, 'action'); } } /** * Handle /whois command. * * @since 0.1.0 */ function handleWhoisCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /whois ', 'notice'); } else { send(`WHOIS ${args[0]}`); logLine(ui.log, `• Getting info for ${args[0]}...`, 'sys'); } } /** * Handle /who command. * * @since 0.1.0 */ function handleWhoCommand(args: string[], context: CommandContext): void { const { send, ui, currentChannel } = context; const whoTarget = args.length > 0 ? args[0] : currentChannel; send(`WHO ${whoTarget}`); logLine(ui.log, `• Listing users in ${whoTarget}...`, 'sys'); } /** * Handle /list command. * * @since 0.1.0 */ function handleListCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const pattern = args.length > 0 ? args[0] : ''; send(`LIST ${pattern}`); logLine(ui.log, '• Listing channels...', 'sys'); } /** * Handle /topic command. * * @since 0.1.0 */ function handleTopicCommand(args: string[], context: CommandContext): void { const { send, ui, currentChannel } = context; if (args.length === 0) { send(`TOPIC ${currentChannel}`); } else { const newTopic = args.join(' '); send(`TOPIC ${currentChannel} :${newTopic}`); logLine(ui.log, `• Setting topic: ${newTopic}`, 'sys'); } } /** * Handle /names command. * * @since 0.1.0 */ function handleNamesCommand(args: string[], context: CommandContext): void { const { send, currentChannel } = context; const namesChannel = args.length > 0 ? args[0] : currentChannel; send(`NAMES ${namesChannel}`); } /** * Handle /away command. * * @since 0.1.0 */ function handleAwayCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length === 0) { send('AWAY'); logLine(ui.log, '• Away status removed', 'sys'); } else { const awayMsg = args.join(' '); send(`AWAY :${awayMsg}`); logLine(ui.log, `• Away: ${awayMsg}`, 'sys'); } } /** * Handle /back command. * * @since 0.1.0 */ function handleBackCommand(args: string[], context: CommandContext): void { const { send, ui } = context; send('AWAY'); logLine(ui.log, '• You are no longer away', 'sys'); } /** * Handle /ping command. * * @since 0.1.0 */ function handlePingCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /ping ', 'notice'); } else { send(`PRIVMSG ${args[0]} :\u0001PING ${Date.now()}\u0001`); logLine(ui.log, `• Pinging ${args[0]}...`, 'sys'); } } /** * Handle /time command. * * @since 0.1.0 */ function handleTimeCommand(args: string[], context: CommandContext): void { const { send } = context; const timeTarget = args.length > 0 ? args[0] : ''; send(`TIME ${timeTarget}`); } /** * Handle /version command. * * @since 0.1.0 */ function handleVersionCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { send('VERSION'); } else { send(`PRIVMSG ${args[0]} :\u0001VERSION\u0001`); logLine(ui.log, `• Requesting version from ${args[0]}...`, 'sys'); } } /** * Handle /quit command. * * @since 0.1.0 */ function handleQuitCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const quitMsg = args.length > 0 ? args.join(' ') : 'WebIRC Chat'; send(`QUIT :${quitMsg}`); logLine(ui.log, `• Disconnecting: ${quitMsg}`, 'sys'); } /** * Handle /raw command. * * @since 0.1.0 */ function handleRawCommand(args: string[], context: CommandContext): void { const { send, ui, cfg } = context; // Security gate if (!cfg.allowRaw) { logLine(ui.log, '• /raw is disabled on this site.', 'notice'); return; } if (args.length < 1) { logLine(ui.log, '• Usage: /raw ', 'notice'); return; } const rawCmd = args.join(' '); send(rawCmd); logLine(ui.log, `• Sent: ${maskSensitive(rawCmd)}`, 'notice'); } /** * Handle /notice command. * * @since 0.1.0 */ function handleNoticeCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 2) { logLine(ui.log, '• Usage: /notice ', 'notice'); logLine( ui.log, '• Sends a notice message (less intrusive than regular messages)', 'notice' ); } else { const [target, ...messageParts] = args; const message = messageParts.join(' '); send(`NOTICE ${target} :${message}`); logLine(ui.log, `[Notice to ${target}] ${message}`, 'self'); } } /** * Handle /ctcp command. * * @since 0.1.0 */ function handleCtcpCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 2) { logLine(ui.log, '• Usage: /ctcp ', 'notice'); logLine( ui.log, '• Common commands: VERSION, TIME, PING, FINGER', 'notice' ); } else { const [nick, ...commandParts] = args; const command = commandParts.join(' ').toUpperCase(); send(`PRIVMSG ${nick} :\u0001${command}\u0001`); logLine(ui.log, `• Sent CTCP ${command} to ${nick}`, 'sys'); } } /** * Handle /motd command. * * @since 0.1.0 */ function handleMotdCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const server = args.length > 0 ? args[0] : ''; send(`MOTD ${server}`); logLine( ui.log, `• Requesting message of the day${server ? ` from ${server}` : ''}...`, 'sys' ); } /** * Handle /admin command. * * @since 0.1.0 */ function handleAdminCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const server = args.length > 0 ? args[0] : ''; send(`ADMIN ${server}`); logLine( ui.log, `• Requesting admin info${server ? ` from ${server}` : ''}...`, 'sys' ); } /** * Handle /info command. * * @since 0.1.0 */ function handleInfoCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const server = args.length > 0 ? args[0] : ''; send(`INFO ${server}`); logLine( ui.log, `• Requesting server info${server ? ` from ${server}` : ''}...`, 'sys' ); } /** * Handle /stats command. * * @since 0.1.0 */ function handleStatsCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /stats [server]', 'notice'); logLine( ui.log, '• Common queries: u (uptime), c (connections), i (auth), o (operators)', 'notice' ); return; } const [query, server] = args; send(`STATS ${query} ${server || ''}`); logLine( ui.log, `• Requesting stats ${query}${server ? ` from ${server}` : ''}...`, 'sys' ); } /** * Handle /links command. * * @since 0.1.0 */ function handleLinksCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const params = args.join(' '); send(`LINKS ${params}`); logLine(ui.log, '• Requesting server links...', 'sys'); } /** * Handle /lusers command. * * @since 0.1.0 */ function handleLusersCommand(args: string[], context: CommandContext): void { const { send, ui } = context; const params = args.join(' '); send(`LUSERS ${params}`); logLine(ui.log, '• Requesting user statistics...', 'sys'); } /** * Handle /userhost command. * * @since 0.1.0 */ function handleUserhostCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { logLine( ui.log, '• Usage: /userhost [nick2] [nick3]...', 'notice' ); return; } const nicks = args.join(' '); send(`USERHOST ${nicks}`); logLine(ui.log, `• Getting host info for: ${nicks}`, 'sys'); } /** * Handle /ison command. * * @since 0.1.0 */ function handleIsonCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /ison [nick2] [nick3]...', 'notice'); return; } const nicks = args.join(' '); send(`ISON ${nicks}`); logLine(ui.log, `• Checking if online: ${nicks}`, 'sys'); } /** * Handle /finger command. * * @since 0.1.0 */ function handleFingerCommand(args: string[], context: CommandContext): void { const { send, ui } = context; if (args.length < 1) { logLine(ui.log, '• Usage: /finger ', 'notice'); return; } const nick = args[0]; send(`PRIVMSG ${nick} :\u0001FINGER\u0001`); logLine(ui.log, `• Sending FINGER request to ${nick}`, 'sys'); } /** * Handle /help command. * * @since 0.1.0 */ function handleHelpCommand(args: string[], context: CommandContext): void { const { ui } = context; logLine(ui.log, '• Available commands:', 'notice'); logLine(ui.log, ' Basic Commands:', 'notice'); logLine(ui.log, ' /join #channel - Join a channel', 'notice'); logLine( ui.log, ' /part [#channel] - Leave current or specified channel', 'notice' ); logLine(ui.log, ' /nick newname - Change nickname', 'notice'); logLine(ui.log, ' /quit [message] - Disconnect', 'notice'); logLine(ui.log, ' Communication:', 'notice'); logLine( ui.log, ' /msg - Send private message', 'notice' ); logLine( ui.log, ' /notice - Send notice message', 'notice' ); logLine(ui.log, ' /me - Send action message', 'notice'); logLine(ui.log, ' /ctcp - Send CTCP command', 'notice'); logLine(ui.log, ' /finger - Send FINGER request', 'notice'); logLine(ui.log, ' User Information:', 'notice'); logLine( ui.log, ' /whois - Get detailed user information', 'notice' ); logLine(ui.log, ' /who [channel] - List users in channel', 'notice'); logLine( ui.log, ' /userhost - Get user host information', 'notice' ); logLine( ui.log, ' /ison [nick2]... - Check if users are online', 'notice' ); logLine(ui.log, ' Channel Information:', 'notice'); logLine(ui.log, ' /list [pattern] - List available channels', 'notice'); logLine( ui.log, ' /names [channel] - List nicknames in channel', 'notice' ); logLine(ui.log, ' /topic [text] - View/set channel topic', 'notice'); logLine(ui.log, ' Server Information:', 'notice'); logLine(ui.log, ' /motd [server] - View message of the day', 'notice'); logLine(ui.log, ' /admin [server] - View server admin info', 'notice'); logLine(ui.log, ' /info [server] - View server information', 'notice'); logLine( ui.log, ' /stats [server] - View server statistics', 'notice' ); logLine(ui.log, ' /links [mask] - List server links', 'notice'); logLine(ui.log, ' /lusers [mask] - List user statistics', 'notice'); logLine(ui.log, ' Status:', 'notice'); logLine(ui.log, ' /away [message] - Set away status', 'notice'); logLine(ui.log, ' /back - Remove away status', 'notice'); logLine(ui.log, ' /ping - Ping user', 'notice'); logLine(ui.log, ' /time [server] - Get server time', 'notice'); logLine(ui.log, ' /version [nick] - Get version info', 'notice'); logLine(ui.log, ' Advanced:', 'notice'); logLine(ui.log, ' /query - Open private chat context', 'notice'); if (context.cfg.allowRaw) { logLine( ui.log, ' /raw - Send raw IRC command', 'notice' ); } }