glirc 2.40 → 2.40.1
raw patch · 25 files changed
+1290/−885 lines, 25 filesdep ~basedep ~time
Dependency ranges changed: base, time
Files
- ChangeLog.md +11/−0
- README.md +123/−256
- doc/cmds_chanop.adoc +67/−0
- doc/cmds_chat.adoc +203/−0
- doc/cmds_client.adoc +88/−0
- doc/cmds_integration.adoc +24/−0
- doc/cmds_net.adoc +52/−0
- doc/cmds_oper.adoc +102/−0
- doc/cmds_queries.adoc +95/−0
- doc/cmds_toggles.adoc +32/−0
- doc/cmds_window.adoc +148/−0
- glirc.cabal +8/−6
- src/Client/Commands.hs +12/−52
- src/Client/Commands/Certificate.hs +3/−24
- src/Client/Commands/Channel.hs +9/−41
- src/Client/Commands/Chat.hs +20/−232
- src/Client/Commands/Connection.hs +8/−15
- src/Client/Commands/Docs.hs +59/−0
- src/Client/Commands/Exec.hs +18/−11
- src/Client/Commands/Operator.hs +28/−26
- src/Client/Commands/Queries.hs +28/−45
- src/Client/Commands/Toggles.hs +9/−8
- src/Client/Commands/Window.hs +15/−151
- src/Client/Commands/ZNC.hs +7/−18
- src/Client/Docs.hs +121/−0
ChangeLog.md view
@@ -1,5 +1,16 @@ # Revision history for glirc +## 2.40.1++* Fix exception on /exec with a non-zero exit code+* Externalize command documentation++## 2.40.0.1++* Build on GHC 9.0++## 2.40+ * vty-6 support * deduplicated /url * locally processed /who
README.md view
@@ -1,18 +1,46 @@ GLIRC - Advanced Console IRC Client ============= -* **glirc** [](https://hackage.haskell.org/package/glirc) [](http://travis-ci.org/glguy/irc-core)+<a href="https://repology.org/project/glirc/versions">+ <img src="https://repology.org/badge/vertical-allrepos/glirc.svg?exclude_unsupported=1" alt="Packaging status" align="right">+</a>++* **glirc** [](https://hackage.haskell.org/package/glirc) * **irc-core** [](https://hackage.haskell.org/package/irc-core) * **hookup** [](https://hackage.haskell.org/package/hookup) -[Wiki Documentation](https://github.com/glguy/irc-core/wiki)+Client Features+--------------- +* Support for [a wide variety of IRCv3 capabilities](https://ircv3.net/software/clients#desktop-clients).+* Joins, parts, and quits fold into one line to save space without sacrificing context.+Detailed view (F2) shows additional info, such as part/quit messages.+* Ignore support that folds ignored messages into the joins and parts. Detailed view shows the messages.+* Searchable channel list, who and names list, and list modes (beqI) views separate from chat messages.+* Dynamic, in-place message and list view searching using `/grep`.+* View and open long URLs with `/url`.+* Context-sensitive tab completion and command hints.+* WYSIWYG mIRC formatting input.+* Multi-line editing; Enter sends one line at a time.+* Atheme-flavored-ChanServ integration.+* Nicknames in chat messages are colored to match messages from that nickname.+* Correct handling of [STATUSMSG](https://modern.ircdocs.horse/#statusmsg-parameter), including CTCP ACTIONs.+* Command macros.+* Support for a wide variety of SASL authentication mechanisms, including `ECDSA-NIST256P-CHALLENGE`.+* Split-screen view.+* Configurable color palette.+* Desktop notifications on important messages while unfocused. Can run arbitrary commands instead (or turn them off).+* Extensions, a Lua plugin system, and trivial piping of IRC messages to arbitrary commands with `/exec`.+* Plenty more, but this list is getting a bit long.+  Building ======== -glirc uses recent versions of packages, make sure you package databases are+[](https://github.com/glguy/irc-core/actions/workflows/haskell-ci.yml)++glirc uses recent versions of packages, make sure your package databases are up-to-date: ```@@ -32,37 +60,17 @@ $ cabal build ``` -Client Features-===============--* All views and transformation are dynamic and don't change the underlying model.-* Subsequent joins and parts fold into one line and do not scroll chat messages off the screen-* Ignore support that folds ignored messages into the joins and parts. Toggle it off to see previously hidden messages-* Detailed view to see all the messages in a channel in full detail with hostmask and timestamp (F2)-* Context sensitive tab completion-* Searchable ban, quiet, invex, and exception view separate from chat messages-* Searchable user list, detailed view shows full hostmasks-* WYSIWYG mIRC formatting input-* Multi-line editing-* Dynamic, in-place message searching-* Chanserv integration-* Nicknames in chat messages are colored to match messages from that nickname-* Support for /STATUSMSG/ messages (messages only voice or op users can see)-* Run commands upon connection-* Command macros-* CERTFP and SASL authentication-* Split-screen view-* Configurable color palette- Startup ======= ``` glirc [FLAGS] INITIAL_NETWORKS...- -c PATH --config=PATH Configuration file path- -! --noconnect Disable autoconnecting- -h --help Show help- -v --version Show version+ -c PATH --config=PATH Configuration file path+ -! --noconnect Disable autoconnecting+ -h --help Show help+ --config-format Show configuration file format+ -v --version Show version+ --full-version Show version and versions of all linked Haskell libraries ``` Environment variables@@ -74,52 +82,50 @@ Configuration file ================= -A configuration file can currently be used to provide some default values instead of-using command line arguments. If any value is missing the default will be used.--The default configuration file path is `~/.config/glirc/config`+Most of glirc's settings are specified using a configuration file.+The file format is [config-value](http://hackage.haskell.org/package/config-value),+an indentation-sensitive format that resembles YAML.+It has macros which are documented+[here](https://hackage.haskell.org/package/config-value/docs/Config-Macro.html). +The default configuration file path is `~/.config/glirc/config`. Relative paths are relative to the home directory. -Learn more about this file format at [config-value](http://hackage.haskell.org/package/config-value)+To view the full list of configuration variables,+run `glirc --config-format | less`.+If any variable is unspecified, a default value will be used instead. ``` -- vim: filetype=config-value -- Grab the Vim syntax highlighting file from the config-value package --- Learn more about these settings with `glirc --config-format`- -- Defaults used when not specified on command line defaults: nick: "yournick" username: "yourusername" realname: "Your real name"- password: "IRC server password" tls: yes -- or: no, or: starttls -- enabling tls automatically uses port 6697- tls-verify: yes -- or: no -- Override the defaults when connecting to specific servers servers: * name: "libera"- hostname: "irc.libera.chat"+ hostname: "irc.libera.chat" sasl: username: "someuser" password: "somepass"- socks-host: "socks5.example.com"- socks-port: 8080 -- defaults to 1080- log-dir: "/home/myuser/ircLogs"-- * name: "example"- hostname: "example.com"- port: 7000 -- override the default port+ log-dir: "/home/myuser/ircLogs" connect-cmds:- * "join #favoritechannel,#otherchannel"- * "msg mybot another command"+ * "join #glirc,#someotherchannel" - -- Specify additional certificates beyond the system CAs- -- relative to home directory- tls-server-cert: "extra/certificate.pem"+ * name: "znc"+ hostname: "znc.example.com"+ port: 7000 -- Override the default port+ password: "IRC server password"+ tls-verify: no+ message-hooks: ["buffextras"] -- Use this when using ZNC's "buffextra" module to get correct playback+ flood-penalty: -1 -- Disable flood controls+ flood-threshold: -1 macros: * name: "wipe"@@ -129,20 +135,20 @@ * name: "mysplits" commands:- * "splits fn:#haskell fn:#haskell-offtopic"+ * "splits libera:#haskell libera:#haskell-offtopic" -- Example use of macro in combination with an extension * name: "extra" commands:- * "extension Lua some-parameter $network $channel"+ * "extension Lua somecommand $network $channel" -extra-highlights: ["glirc", "lens"]+extra-highlights: ["glirc", "hello"] nick-padding:- side: left -- try right if you don't like left padding+ side: left -- Try "right" if you don't like left padding width: 13 -url-opener: "open" -- This works on macOS, "xdg-open" for most Linuxes+url-opener: "open" -- This works on macOS; use "xdg-open" for most Linuxes key-bindings: * bind: "C-M-b"@@ -153,7 +159,7 @@ time: fg: [10,10,10] -- RGB values for color for timestamps bg: blue- nick-colors:+ identifier-colors: -- Used for nicknames and channel names [ cyan, magenta, green, yellow, blue , bright-cyan, bright-magenta, bright-green, bright-blue , 218, 88, 89, 124, 160, 205, 212, 224 -- reds@@ -163,206 +169,72 @@ , 25, 27, 33, 39, 51, 80, 81, 75 -- blues , 69, 61, 56, 54, 129, 93, 99, 147 -- purples ]-``` -Configuration sections:-----------| setting | type | description |-|------------------------|---------------------|--------------------------------------------------------------------------------------------|-| `defaults` | server | These settings are used for all connections |-| `servers` | list of servers | These settings are used to override defaults when the hostname matches |-| `palette` | palette | Client color overrides |-| `window-names` | text | Names of windows (typically overridden on non QWERTY layouts) |-| `nick-padding` | nonnegative integer | Nicks are padded until they have the specified length |-| `indent-wrapped-lines` | nonnegative integer | How far to indent lines when they are wrapped |-| `extra-highlights` | list of text | Extra words/nicks to highlight |-| `extensions` | list of text | Filenames of extension to load |-| `url-opener` | text | Command to execute with URL parameter for `/url` e.g. xdg-open on most Linuxes or open on macOS |-| `ignores` | list of text | Initial list of nicknames to ignore |-| `activity-bar` | yes or no | Initial setting for visibility of activity bar (default no) |-| `bell-on-mention` | yes or no | Sound terminal bell on transition from not mentioned to mentioned (default no) |-| `macros` | list of macros | User-configurable client commands |--Server Settings------------------| setting | type | description |-|-----------------------|----------------------|----------------------------------------------------------------|-| `name` | text | name of server entry, defaults to `hostname` |-| `hostname` | text | hostname used to connect and to specify the server |-| `port` | number | port number, defaults to 6667 without TLS and 6697 with TLS |-| `nick` | text or list of text | nicknames to try in order |-| `username` | text | server username |-| `realname` | text | real name / GECOS |-| `password` | text | server password |-| `sasl` | sasl-settings | SASL authentication settings |-| `tls` | yes/no/starttls | use TLS to connect |-| `tls-verify` | yes/no | enable/ disable TLS certificate checks |-| `tls-client-cert` | text | path to TLS client certificate |-| `tls-client-key` | text | path to TLS client key |-| `tls-server-cert` | text | CA certificate to use when validating certificates |-| `tls-ciphers` | text | OpenSSL cipher suite description string |-| `connect-cmds` | list of text | client commands to send upon connection |-| `socks-host` | text | hostname of SOCKS proxy to connect through |-| `socks-port` | number | port number of SOCKS proxy to connect through |-| `chanserv-channels` | list of text | list of channels with chanserv op permission |-| `flood-penalty` | number | cost in seconds per message |-| `flood-threshold` | number | threshold in seconds for burst |-| `message-hooks` | list of text | names of hooks to enable |-| `reconnect-attempts` | int | number of reconnections to attempt on error |-| `autoconnect` | yes or no | automatically connect at client startup |-| `nick-completion` | default or slack | set this to slack to use `@` sigils when completing nicks |--SASL Settings----------------By default SASL will use PLAIN mode, but you can specify one of: `plain`, `external`, or `ecdsa-nist256p-challenge`.--| setting | type | description |-|-----------------------|----------------------|----------------------------------------------------------------|-| `mechanism` | optional mechanism | SASL mechanism (defaults to PLAIN) |-| `username` | text | SASL username (PLAIN and ECDSA-NIST256P-CHALLENGE mode) |-| `password` | text | SASL password (PLAIN mode) |-| `private-key` | text | Path to ECDSA private key file (ECDSA-NIST256P-CHALLENGE mode) |-| `authzid` | text | Authorization identity (very rarely needed) |--Palette----------| entry | type | description |-|-----------------------|--------------|------------------------------------------|-| `nick-colors` | list of attr | Use for nick highlights |-| `self` | attr | our own nickname(s) outside of mentions |-| `self-highlight` | attr | our own nickname(s) in mentions |-| `time` | attr | timestamp on messages |-| `meta` | attr | metadata (joins/parts/etc.) |-| `sigil` | attr | sigils (+@) |-| `label` | attr | information labels |-| `latency` | attr | latency time |-| `error` | attr | error messages |-| `textbox` | attr | textbox edges (^$) |-| `window-name` | attr | current window name |-| `activity` | attr | activity notification |-| `mention` | attr | mention notification |-| `command` | attr | recognized command |-| `command-prefix` | attr | prefix of known command |-| `command-ready` | attr | recognized command with arguments filled |-| `command-placeholder` | attr | command argument placeholder |-| `window-divider` | attr | the dividing line between split windows |-| `line-marker` | attr | the dividing line for new messages |--Text Attributes------------------Text attributes can be specified either as a single foreground color or section of attributes.--* `<number>` - Maps to a terminal color-* `<name>` - Direct selection of standard 16 terminal colors-* `[red-number, blue-number, green-number]` - List of 3 numbers in range 0-255 map to an approximation of the RGB color.--Attributes--* `fg` - foreground color-* `bg` - background color-* `style` - single style or list of styles--Styles--* `blink`-* `bold`-* `dim`-* `standout`-* `reverse-video`-* `underline`+notifications: terminal-notifier -- Use terminal-notifier for nicer notifications (macOS only)+``` Commands ======== -Client commands+glirc has built-in documentation for all of its commands.+To view the full list of commands and what they do, use `/help`.+To view help on a specific command, use `/help <command>`. +Unlike some other clients, glirc does not send unknown commands to the server.+Use `/quote` to send arbitrary IRC commands.++The following is a curated list of commands for basic use:+ * `/help [command]` - Show in-client help * `/exit` - Terminate the client-* `/quit` - Gracefully terminate connection to the current server-* `/connect <name>` - Connect to the given server-* `/disconnect` - Forcefully terminate connection to the current server-* `/reconnect` - Reconnect to the current server * `/reload [path]` - Load a new configuration file (optional path) * `/palette` - Show the client palette-* `/digraphs` - Show the table of digraphs-* `/mentions` - Show all the highlighted lines across all windows-* `/extension <extension name> <params...>` - Send the given params to the named extension-* `/exec [-n network] [-c channel] <command> <arguments...>` - Execute a command, If no network or channel are provided send output to client window, if network and channel are provided send output as messages, if network is provided send output as raw IRC messages. * `/url [n]` - Execute url-opener on the nth URL in the current window (defaults to first)--View toggles-* `/toggle-detail` - toggle full detail view of messages * `/toggle-activity-bar` - toggle channel names in activity bar-* `/toggle-metadata` - toggle visibility of channel metadata-* `/toggle-layout` - toggle split-screen layout between 1 and 2 column view+* `/toggle-detail` - toggle full detail view of messages+* `/toggle-metadata` - toggle visibility of channel metadata (joins, parts, quits, nick changes, etc) -Connection commands+Connection +* `/connect <name>` - Connect to the given server+* `/quit [message]` - Gracefully terminate connection to the current server+* `/reconnect` - Reconnect to the current server * `/nick <nick>` - Change nickname-* `/away <message>` - Set away status+* `/away [message]` - Set away status; no message removes away status Window management * `/windows [filter]` - List all open windows (filters: networks, channels, users)-* `/focus <server>` - Change focus to server window-* `/focus <server> <channel>` - Change focus to channel window+* `/setname [letter]` - Assign a one-letter name to the given window.+* `/channel <channel>` - Change focus to channel/user on current network (alias: `/c`)+* `/channel <network>:[channel]` - Change focus to channel/user on the specified network (alias: `/c`) * `/clear [network] [channel]` - Clear contents of current or specified window-* `/ignore` - Show all ignore masks-* `/ignore <mask>...` - Toggle ignore status on a list of masks-* `/channel <channel>` - Change focus to channel on current network (alias: `/c`) * `/splits [focuses...]` - Enable split-screen view. Focuses should be space delimited list of NETWORK:CHANNEL * `/splits+ [focuses...]` - Incremental addition to splits * `/splits- [focuses...]` - Incremental removal from splits--Channel membership--* `/join <channel>` - Join a channel (alias: `/j`)-* `/part` - Part from current channel+* `/toggle-layout` - toggle split-screen layout between 1 and 2 column view Chat commands -* `/query <target> [<msg>]` - Switch focus to target window on current server, optionally send message+* `/join <channel>` - Join a channel (alias: `/j`)+* `/part [msg]` - Part from current channel+* `/query <target> [msg]` - Switch focus to target window on current server, optionally send message (alias: `/q`) * `/msg <target> <msg>` - Send a message on the current server to target-* `/notice <target> <msg>` - Send a notice message on the current server to target-* `/ctcp <target> <command> <args>` - Send a ctcp command on the current server to target * `/me <msg>` - Send action message to channel-* `/say <msg>` - Send normal message to channel--Channel management--* `/mode <mode> <params>` - Change modes on the current channel (advanced tab completion)-* `/kick <nick>` - Kick a user-* `/kickban <nick>` - Kick and ban a user-* `/remove` - Gracefully kick a user-* `/topic <topic>` - Change the topic (tab completion for topic)-* `/invite <nick>` - Invite a user to the current channel--Queries--* `/who <query>` - Perform WHO query (use detailed view to see output)-* `/whois <nick>` - Perform WHOIS query-* `/whowas <nick>` - Perform WHOWAS query-* `/ison <nick>` - Perform ISON query-* `/userhost <nick>` - Perform USERHOST query-* `/links <server>` - Perform LINKS query-* `/time` - Perform TIME query-* `/stats <query>` - Perform STATS query+* `/say <msg>` - Send normal message to channel; useful for macros and messages starting with a slash+* `/ignore <mask>...` - Toggle ignore status on a list of masks+* `/topic [msg]` - Display or set the current topic of a channel -Channel information+Views -* `/users` - Show channel user list-* `/masks <mode>` - Show channel bans(b), quiets(q), exempts(e), or invex(I) * `/channelinfo` - Show channel topic, creation, url--Window filters--* `/grep` - Filter chat messages using a regular expression-* `/grepi` - Filter chat messages using a case-insensitive regular expression on the message+* `/grep [flags] <regex>` - Filter using a regular expression+* `/ignore` - Show all ignore masks+* `/list` - View the list of public channels on the network+* `/masks <mode>` - Show channel bans(b), quiets(q), exempts(e), or invex(I)+* `/mentions` - Show all the highlighted lines across all windows+* `/names` - Show the user list for the current channel+* `/who [channel] [options]` - Perform WHO query, sending options to the server, or show the results of the previous query. ZNC-specific @@ -371,8 +243,11 @@ * `/znc-playback <time>` - ZNC playback module - play everything start at the given time today * `/znc-playback <date> <time>` - ZNC playback module - play everything start at the given time -Low-level+Miscellaneous +* `/dump <filename>` - Dump current window to file+* `/extension <extension name> <params...>` - Send the given params to the named extension+* `/exec [-n network] [-c channel] <command> <arguments...>` - Execute a command; if no network or channel are provided send output to client window, if network and channel are provided send output as messages, if network is provided send output as raw IRC messages. * `/quote <raw command>` - Send a raw IRC command to the server Keyboard Shortcuts@@ -382,18 +257,35 @@ control and press A". `M-a` means "hold meta key and press A". On most modern keyboards the *Meta* key is labeled *Alt* or *Option*. -Window navigation+To view the full list of keybindings and what they do,+use `/keymap` from within glirc. +The following is a curated list of default keybinds for basic use:++Navigation++* `Page Up` scroll up+* `Page Down` scroll down * `C-n` next window * `C-p` previous window-* `C-x` next network-* `M-#` jump to window - `1234567890qwertyuiop!@#$%^&*()QWERTYUIOP`+* `C-x` next network window+* `M-<name>` jump to window with the given one-letter name * `M-a` jump to activity * `M-s` jump to previous window * `ESC` return to messages view (from userlist, masklist, help, etc) Editing +* `C-b` bold+* `C-c` color+* `C-v` invert foreground/background+* `C-_` underline+* `C-]` italic+* `C-o` reset formatting++* `Tab` autocompletion+* `M-k` replace 2 characters before the cursor with a character specified in `/digraphs`+ * `C-a` beginning of line * `C-e` end of line * `C-k` delete to end@@ -409,29 +301,13 @@ * `M-Backspace` delete word backwards * `M-d` delete word forwards * `M-Enter` insert newline-* `M-k` insert digraph (2-characters before cursor) -* `Tab` nickname completion- Client settings * `F2` toggle detailed view * `F3` toggle detailed activity bar * `F4` toggle metadata visibility--Scrolling--* `Page Up` scroll up-* `Page Down` scroll down--Formatting--* `C-b` bold-* `C-c` color-* `C-v` reverse video-* `C-_` underline-* `C-]` italic-* `C-o` reset formatting+* `F7` toggle Enter key lock Macros ======@@ -462,12 +338,3 @@ * `0` - first argument * `1` - second argument (etc.)--Hooks-=====--buffextras-------------Enable this hook when using ZNC and the `buffextra` module in order to reinterpret-this module's messages natively in the client.
+ doc/cmds_chanop.adoc view
@@ -0,0 +1,67 @@+= Channel Management Commands+:toc:++== /invite++Invite a user to the current channel.++== /kick++Kick a user from the current channel.++See also: kickban, remove++== /kickban++Ban and kick a user from the current channel.++Users are banned by hostname match.++See also: kick, remove++== /masks++Show mask lists for current channel.++The `mode` argument is typically one of the following:++`b`: bans ++`i`: invite exemptions (op view only) ++`e`: ban exemptions (op view only) ++`q`: quiets (on Charybdis/Solanum-based networks)++== /mode++Set IRC modes.++When executed in a channel window, mode changes are applied to the channel.+When executed in a network window, mode changes are applied to your user.++This command has parameter sensitive tab-completion.++See also: masks, channelinfo++=== Examples++`+/mode -t+` - Remove the topic lock ++`+/mode +b *!*@hostname+` - Set a ban ++`+/mode -q *!*@hostname+` - Remove a quiet ++`+/mode +vv user1 user2+` - Voice two users ++`+/mode +v-o user1 user1+` - Demote an op to voice++== /remove++Remove a user from the current channel.++Remove works like /kick except it results in a PART.++Not all servers support removal in this manner.+Refer to your server/network's documentation.++See also: kick, kickban++== /topic++View or set the topic of the current channel.++Tab-completion with no `message` specified will load the current topic.
+ doc/cmds_chat.adoc view
@@ -0,0 +1,203 @@+= Chat Commands+:toc:++== /join++Join the given channels.++Multiple channels and keys may be provided as comma-separated lists.++When keys are provided, they should occur in the same order as the channels.++=== Examples++`+/join #friends+` ++`+/join #secret thekey+` ++`+/join #secret1,#secret2,#public key1,key2+`++See also: channel, clear, part++== /me++Sends an action message to the currently focused channel.+Most clients will render these messages prefixed with+only your nickname as though describing an action.++=== Examples++`+/me shrugs+`++See also: notice, msg, say++== /msg++Send a chat message to a user or a channel.++Multiple targets may be provided as a comma-separated list.++On servers with STATUSMSG support,+the channel name can be prefixed with a sigil to+restrict the recipients to those with the given mode.++=== Examples++`+/msg buddy I'm sending you a message.+` ++`+/msg #friends This message is for the whole channel.+` ++`+/msg him,her I'm chatting with two people.+` ++`+/msg @#users This message is only for ops!+`++See also: notice, me, say++== /part++Leave the currently-focused channel,+optionally with the provided message.++=== Examples++`+/part+` ++`+/part It's not me, it's you+`++See also: clear, join, quit++== /query++Switch the client focus to the given+target and optionally send a message to that target.++See also: msg, channel, focus++=== Examples++`+/q libera:#haskell+` ++`+/q #haskell+` ++`+/q lambdabot @messages+` ++`+/q irc_friend How are you?+`++== /say++Send a message to the current chat window.++This can be useful for sending a chat message with+a leading '/' to the current chat window.++=== Examples++`+/say /help is the right place to start!+`++See also: notice, me, msg++== /away++Mark yourself as away.+The away message is used by the server to update+status in /whois and to provide automated responses.++Omit the `message` parameter to clear your away status.++=== Examples++`+/away Out getting some sun+` ++`+/away+`++== /channelinfo++Show information about the current channel.+Information includes topic, creation time, URL, and modes.++See also: masks, mode, topic, users++== /ctcp++Client-to-client protocol (CTCP) commands can be used+to query information from another user's client application+directly. Common CTCP commands include: ACTION, PING, VERSION,+USERINFO, CLIENTINFO, and TIME.++glirc does not automatically respond to CTCP commands.++=== Parameters++`target` - Comma-separated list of nicknames and channels ++`command` - CTCP command name ++`arguments` - CTCP command arguments++=== Examples++`+/ctcp myfriend VERSION+` ++`+/ctcp myfriend TIME+`++== /knock++Request entry to an invite-only channel.++== /monitor++Monitor is a protocol for getting server-side notifications+when users become online/offline.++=== Subcommands++`+/monitor + target[,target2]*+` - Add nicknames to monitor list ++`+/monitor - target[,target2]*+` - Remove nicknames to monitor list ++`+/monitor C+` - Clear monitor list ++`+/monitor L+` - Show monitor list ++`+/monitor S+` - Show status of nicknames on monitor list++== /names++Show the user list for the current channel.+Detailed view (default key F2) shows full hostmask.++See also: channelinfo, masks++== /nick++Change your nickname.++=== Examples++`+/nick guest123+` ++`+/nick better_nick+`++== /notice++Send a chat notice to a user or a channel.++Notice messages were originally intended to be used by bots.+Different clients will render these in different ways.++Multiple targets may be provided as a comma-separated list.++On servers with STATUSMSG support,+the channel name can be prefixed with a sigil to+restrict the recipients to those with the given mode.++=== Examples++`+/notice buddy I'm sending you a notice.+` ++`+/notice #friends This notice is for the whole channel.+` ++`+/notice him,her I'm informing two people.+` ++`+/notice @#users This notice is only for ops!+`++See also: me, msg, say++== /operwall++Send a network-wide WALLOPS message to opers only.++See also: me, msg, say++== /wallops++Send a network-wide WALLOPS message.+These messages go out to users who have the 'w' usermode set.++See also: me, msg, say++== /quote++Send a raw IRC command.++The argument to this command is sent as-is.+No additional word-splitting is done.
+ doc/cmds_client.adoc view
@@ -0,0 +1,88 @@+= Client Commands+:toc:++== /exit++Exit the client immediately.++== /reload++Reload the client configuration file.++If `filename` is provided it will be used to reload.+Otherwise the previously loaded configuration file will be reloaded.++== /extension++Run a command provided by an extension.++`extension` should be the name of a loaded extension with+a process_command callback.++== /palette++Show the current palette settings and a color chart to help pick new colors.++== /digraphs++Show the table of digraphs. A digraph is a pair of characters+that can be used together to represent an uncommon character.+Type the two-character digraph corresponding to the desired+output character and then press M-k (default binding).++The digraphs list is searchable with `+/grep+`.++See also: grep++== /keymap++Show the key binding map.++Key bindings can be changed in configuration file.+Run `+glirc --config-format+` for details.++== /rtsstats++Show the GHC RTS statistics.++== /exec++Execute a command synchnonously, optionally sending the output to IRC.++Arguments to this command are:+`+[-n[network]] [-c[channel]] [-i input] command [arguments...]+`++`input` is sent to the standard input of the command.++When neither `network` nor `channel` are specified,+output goes to client window (focus name "*").+When `network` is specified,+output is sent as raw IRC traffic to the network.+When `channel` is specified,+output is sent as chat to the given channel on the current network.+When `network` and `channel` are specified,+output is sent as chat to the given channel on the given network.++`arguments` is split on spaces into words before being+processed by getopt. Use Haskell string literal syntax to+create arguments with escaped characters and spaces inside.++== /url++Open a URL seen in chat.++The URL is opened using the executable configured under `url-opener`.++When this command is active in the textbox,+chat messages are filtered to only show ones with URLs.++When `number` is omitted it defaults to 1.+The number selects the URL to open, counting back from the most recent.++== /help++Show command documentation.++When `command` is omitted, a list of all commands and macros is shown.+When `command` is specified, detailed help for that command is shown.+
+ doc/cmds_integration.adoc view
@@ -0,0 +1,24 @@+= Integrations+:toc:++== /znc++Send command directly to ZNC.++The advantage of this over `+/msg+` is that+responses are not broadcast to call clients.++== /znc-playback++Request playback from the ZNC `playback` module.+Note that the playback module is not installed in ZNC by default!++When `date` is omitted,+uses the most recent date in the past that makes sense.+When both `time` and `date` are omitted,+all playback is requested.++Time format: `HOURS:MINUTES` (example: 7:00)++Date format: `YEAR-MONTH-DAY` (example: 2016-06-16)+
+ doc/cmds_net.adoc view
@@ -0,0 +1,52 @@+= Connection Commands+:toc:++== /connect++Connect to `network` by name.++If `network` does not correspond to the name of a server in the config,+it is treated as a hostname.++== /reconnect++(Re)connect to the currently-focused server.++== /quit++Gracefully disconnect the current network connection,+optionally sending the provided reason.++See also: disconnect, exit++== /disconnect++Immediately terminate the current network connection.++See also: quit, exit++== /umode++Apply a user-mode change. ++== /cert++Show the TLS certificate for the current connection.++== /new-self-signed-cert++Generate a new self-signed client certificate for+network service identification using CertFP.++The generated certificate can be used via the per-server+`tls-client-cert` and `tls-client-key-password` (if applicable) keys+in your config file. ++This command generates an RSA key pair by default.+This may change in the future.++=== Parameters++`filename`: Certificate and private key PEM output ++`keysize`: Public-key size (default 2048, range 1024-8192) ++`passphrase`: Optional AES-128 private key passphrase
+ doc/cmds_oper.adoc view
@@ -0,0 +1,102 @@+= Operator Commands+:toc:++== /oper++Authenticate as a server operator.++== /kill++Kill a client connection to the server.++== /kline++Ban a client from the server.++== /unkline++Unban a client from the server.++== /undline++Unban a client from the server.++== /unxline++Unban a gecos from the server.++== /unresv++Unban a channel or nickname from the server.++== /testline++Check matching I/K/D lines for a `[[nick!]user@]host`.++== /testkline++Check matching K/D lines for a `[user@]host`.++== /testgecos++Check matching X lines for a `gecos`.++== /testmask++Test how many local and global clients match a mask.++== /masktrace++Outputs a list of local users matching the given masks.++== /chantrace++Outputs a list of channel members in etrace format.++== /trace++Outputs a list users on a server.++== /etrace++Outputs a list users on a server.++== /map++Display network map.++== /sconnect++Connect two servers together.++== /squit++Split a server away from your side of the network.++== /modload++Load an IRCd module.++== /modunload++Unload an IRCd module.++== /modlist++List loaded IRCd modules.++== /modrestart++Reload all IRCd modules.++== /modreload++Reload an IRCd module.++== /grant++Manually assign a privset to a user.++== /privs++Check operator privileges of the target.
+ doc/cmds_queries.adoc view
@@ -0,0 +1,95 @@+= Queries+:toc:++== /who++Send WHO query to server with given arguments.++If no query is provided, shows the results of the previous query.++== /whois++Send WHOIS query to server with given arguments.++== /whowas++Send WHOWAS query to server with given arguments.++== /ison++Send ISON query to server with given arguments.++See also: monitor++== /userhost++Send USERHOST query to server with given arguments.++== /time++Send TIME query to server with given arguments.++== /stats++Send STATS query to server with given arguments.++== /lusers++Send LUSERS query to a given server.++== /users++Send USERS query to a given server.++== /motd++Send MOTD query to server.++== /admin++Send ADMIN query to server.++== /rules++Send RULES query to server.++== /info++Send INFO query to server.++== /list++View the list of public channels on the server.++Sends a LIST query and caches the result;+on larger networks, this may take several seconds to complete.+The view may be exited while loading.++`clientarg` is an optionally-comma-separated list of options.+A single comma may be used to denote an empty list.++`serverarg` is sent as-is to the server.+It is generally used as the ELIST parameter to LIST.+glirc does not validate this parameter against the ELIST ISUPPORT token.++=== Client Options++`>n`: Show only channels with more than `n` users. ++`<n`: Show only channels with less than `n` users. ++`~`: Force a refresh of the list.++=== Examples++`+/list+` - List public channels.+`+/list >99+` - List public channels with at least 100 users.+`+/list >50<1000+` - List public channels with between 51 and 999 users.+`+/list ~ <20+` - List public channels with fewer than 20 users. +`+/list , *-ops+` - List public channels whose names end with "-ops".++== /links++Send LINKS query to server with given arguments.++== /version++Send VERSION query to server.
+ doc/cmds_toggles.adoc view
@@ -0,0 +1,32 @@+= Toggles+:toc:++== /toggle-detail++Toggle detailed message view.++== /toggle-activity-bar++Toggle detailed detailed activity information in status bar.++== /toggle-show-ping++Toggle visibility of ping round-trip time.++== /toggle-metadata++Toggle visibility of metadata in chat windows.++== /toggle-layout++Toggle multi-window layout mode.++== /toggle-editor++Toggle between single-line and multi-line editor mode.++== /toggle-edit-lock++Toggle editor lock mode.++When the editor is locked, Enter (or C-j) does nothing.
+ doc/cmds_window.adoc view
@@ -0,0 +1,148 @@+= Window Commands+:toc:++== /channel++Set the current focused window.++See also: focus++=== Examples++`+/c libera:#haskell+` ++`+/c #haskell+` ++`+/c libera:+`++== /focus++Set the current focused window.++When only `network` is specified this switches to the network status window.+When `network` and `target` are specified this switches to that chat window.++Nickname and channels can be specified in the `target` parameter.++See also: query, channel++== /clear++Clear a window.++If no arguments are provided, the current window is cleared.+If `channel` is not provided, the `network` window is cleared.+If `channel` is `*`, all windows for `network` are cleared.+If `channel` is provided, that chat window is cleared.++If a window is cleared and no longer active (e.g. due to leaving a channel),+that window will be removed entirely.++== /windows++Show a list of all windows.++`kind`, if specified, is one of `networks`, `channels` or `users`,+and limits the list to the type of window specified.++== /splits++This command defines the set of focuses that will+always be visible, even when unfocused.+If no focuses are listed, the set will be cleared.++=== Examples++`+/splits #haskell #haskell-lens nickserv+` ++`+/splits * libera:#haskell libera:chanserv+` ++`+/splits+`++See also: splits+, splits-++== /splits+++Add focuses to the splits set.+Omit the list of focuses to add the current window.++=== Examples++`+/splits+ #haskell #haskell-lens+` ++`+/splits+ libera:#libera+` ++`+/splits++`++== /splits-++Remove focuses from the splits set.+Omit the list of focuses to remove the current window.++== /ignore++Toggle the soft-ignore on each of the space-delimited given+nicknames. Ignores can use `*` (many) and `?` (one) wildcards.+Masks can be of the form `nick[[!user]@host]`+and use a case-insensitive comparison.++If no masks are specified the current ignore list is displayed.++=== Examples++`+/ignore+` ++`+/ignore nick1 nick2 nick3+` ++`+/ignore nick@host+` ++`+/ignore nick!user@host+` ++`+/ignore *@host+` ++`+/ignore *!baduser@*+`++== /grep++Filter view contents using a regular expression.++Clear the regular expression by calling this without an argument.++`/grep` is case-sensitive by default.++=== Flags++`-A n` - Show n messages after match+`-B n` - Show n messages before match+`-C n` - Show n messages before and after match+`-F` - Use plain-text match instead of regular expression+`-i` - Case insensitive match+`-v` - Invert pattern match+`-m n` - Limit results to n matches+`--` - Stop processing flags++== /dump++Dump the current window's contents to a file.++This command always outputs as if detailed mode is active.++== /mentions++Show a list of all messages that were highlighted as important.++When using `/grep` the important messages are those matching+the regular expression instead.++== /setwindow++Set window property.++=== Properties++`louder`: Upgrades normal messages to important. ++`loud`: Uses default message importance. ++`imponly`: Downgrades normal messages to boring. ++`quiet`: Downgrades important messages to normal. ++`quieter`: Downgrades message importance one step. ++`silent`: Downgrades message importance to boring.++`show` / `hide`: Toggles if window appears in window command shortcuts.++== /setname++Set window shortcut letter. If no letter is provided the next available+letter will automatically be assigned.++Available letters are configured in the `window-names` config setting,+which defaults to the characters available from+the top letter and number rows on a QWERTY keyboard.
glirc.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: glirc-version: 2.40+version: 2.40.1 synopsis: Console IRC client description: Console IRC client .@@ -16,10 +16,10 @@ category: Network extra-source-files: exec/linux_exported_symbols.txt exec/macos_exported_symbols.txt-extra-doc-files: glirc.1 ChangeLog.md README.md+extra-doc-files: glirc.1 ChangeLog.md README.md doc/*.adoc homepage: https://github.com/glguy/irc-core bug-reports: https://github.com/glguy/irc-core/issues-tested-with: GHC==9.4.7+tested-with: GHC == 9.0.2, GHC == 9.4.7, GHC == 9.8.1 custom-setup setup-depends: base >=4.12 && <4.20,@@ -76,6 +76,7 @@ Client.Commands.Chat Client.Commands.Certificate Client.Commands.Connection+ Client.Commands.Docs Client.Commands.Exec Client.Commands.Interpolation Client.Commands.Operator@@ -93,6 +94,7 @@ Client.Configuration.Notifications Client.Configuration.ServerSettings Client.Configuration.Sts+ Client.Docs Client.EventLoop Client.EventLoop.Actions Client.EventLoop.Errors@@ -163,7 +165,7 @@ Build_glirc build-depends:- base >=4.11 && <4.20,+ base >=4.15 && <4.20, HsOpenSSL >=0.11 && <0.12, async >=2.2 && <2.3, attoparsec ^>=0.14,@@ -179,7 +181,7 @@ githash ^>=0.1.6, hashable >=1.2.4 && <1.5, hookup ^>=0.8,- irc-core ^>=2.11,+ irc-core ^>=2.12, kan-extensions >=5.0 && <5.3, lens >=4.14 && <5.3, random >=1.1 && <1.3,@@ -192,7 +194,7 @@ stm >=2.4 && <2.6, template-haskell >=2.11 && <2.22, text >=1.2.2 && <2.2,- time >=1.10 && <1.14,+ time >=1.9 && <1.14, transformers >=0.5.2 && <0.7, unix >=2.7 && <2.9, unordered-containers >=0.2.11 && <0.3,
src/Client/Commands.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE BangPatterns, OverloadedStrings, ExistentialQuantification #-}+{-# LANGUAGE BangPatterns, OverloadedStrings, TemplateHaskell, ExistentialQuantification #-} {-| Module : Client.Commands@@ -27,6 +27,7 @@ import Client.Commands.Arguments.Parser (parse) import Client.Commands.Arguments.Spec (optionalArg, optionalNumberArg, remainingArg, simpleToken)+import Client.Commands.Docs (clientDocs, cmdDoc) import Client.Commands.Exec import Client.Commands.Interpolation (resolveMacroExpansions, Macro(Macro), MacroSpec(MacroSpec)) import Client.Commands.Recognizer (fromCommands, keys, recognize, Recognition(Exact), Recognizer)@@ -227,93 +228,55 @@ [ Command (pure "exit") (pure ())- "Exit the client immediately.\n"+ $(clientDocs `cmdDoc` "exit") $ ClientCommand cmdExit noClientTab , Command (pure "reload") (optionalArg (simpleToken "[filename]"))- "Reload the client configuration file.\n\- \\n\- \If \^Bfilename\^B is provided it will be used to reload.\n\- \Otherwise the previously loaded configuration file will be reloaded.\n"+ $(clientDocs `cmdDoc` "reload") $ ClientCommand cmdReload tabReload , Command (pure "extension") (liftA2 (,) (simpleToken "extension") (remainingArg "arguments"))- "Calls the process_command callback of the given extension.\n\- \\n\- \\^Bextension\^B should be the name of the loaded extension.\n"+ $(clientDocs `cmdDoc` "extension") $ ClientCommand cmdExtension simpleClientTab , Command (pure "palette") (pure ())- "Show the current palette settings and a color chart to help pick new colors.\n"+ $(clientDocs `cmdDoc` "palette") $ ClientCommand cmdPalette noClientTab , Command (pure "digraphs") (pure ())- "\^BDescription:\^B\n\- \\n\- \ Show the table of digraphs. A digraph is a pair of characters\n\- \ can be used together to represent an uncommon character. Type\n\- \ the two-character digraph corresponding to the desired output\n\- \ character and then press M-k (default binding).\n\- \\n\- \ Note that the digraphs list is searchable with /grep.\n\- \\n\- \\^BSee also:\^B grep\n"+ $(clientDocs `cmdDoc` "digraphs") $ ClientCommand cmdDigraphs noClientTab , Command (pure "keymap") (pure ())- "Show the key binding map.\n\- \\n\- \Key bindings can be changed in configuration file. See `glirc --config-format`.\n"+ $(clientDocs `cmdDoc` "keymap") $ ClientCommand cmdKeyMap noClientTab , Command (pure "rtsstats") (pure ())- "Show the GHC RTS statistics.\n"+ $(clientDocs `cmdDoc` "rtsstats") $ ClientCommand cmdRtsStats noClientTab , Command (pure "exec") (remainingArg "arguments")- "Execute a command synchnonously sending the to a configuration destination.\n\- \\n\- \\^Barguments\^B: [-n[network]] [-c[channel]] [-i input] command [arguments...]\n\- \\n\- \When \^Binput\^B is specified it is sent to the stdin.\n\- \\n\- \When neither \^Bnetwork\^B nor \^Bchannel\^B are specified output goes to client window (*).\n\- \When \^Bnetwork\^B is specified output is sent as raw IRC traffic to the network.\n\- \When \^Bchannel\^B is specified output is sent as chat to the given channel on the current network.\n\- \When \^Bnetwork\^B and \^Bchannel\^B are specified output is sent as chat to the given channel on the given network.\n\- \\n\- \\^Barguments\^B is divided on spaces into words before being processed\- \ by getopt. Use Haskell string literal syntax to create arguments with\- \ escaped characters and spaces inside.\n\- \\n"+ $(clientDocs `cmdDoc` "exec") $ ClientCommand cmdExec simpleClientTab , Command (pure "url") optionalNumberArg- "Open a URL seen in chat.\n\- \\n\- \The URL is opened using the executable configured under \^Burl-opener\^B.\n\- \\n\- \When this command is active in the textbox, chat messages are filtered to\- \ only show ones with URLs.\n\- \\n\- \When \^Bnumber\^B is omitted it defaults to \^B1\^B. The number selects the\- \ URL to open counting back from the most recent.\n"+ $(clientDocs `cmdDoc` "url") $ ClientCommand cmdUrl noClientTab , newCertificateCommand@@ -321,10 +284,7 @@ , Command (pure "help") (optionalArg (simpleToken "[command]"))- "Show command documentation.\n\- \\n\- \When \^Bcommand\^B is omitted a list of all commands is displayed.\n\- \When \^Bcommand\^B is specified detailed help for that command is shown.\n"+ $(clientDocs `cmdDoc` "help") $ ClientCommand cmdHelp tabHelp ------------------------------------------------------------------------
src/Client/Commands/Certificate.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Certificate Description : Certificate management commands@@ -10,6 +10,7 @@ module Client.Commands.Certificate (newCertificateCommand) where import Client.Commands.Arguments.Spec+import Client.Commands.Docs (netDocs, cmdDoc) import Client.Commands.TabCompletion (noClientTab) import Client.Commands.Types import Client.State (recordError, recordSuccess)@@ -45,29 +46,7 @@ Command (pure "new-self-signed-cert") (liftA2 (,) (simpleToken "filename") keysizeArg)- "\^BParameters:\^B\n\- \\n\- \ filename: Certificate and private key PEM output\n\- \ keysize: Public-key size (default 2048, range 1024-8192)\n\- \ passphrase: Optional AES-128 private key passphrase\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Generate a new self-signed certificate for network service\n\- \ identification.\n\- \\n\- \\^BExample command:\^B\n\- \\n\- \ /new-self-signed-cert /home/me/.glirc/config/my.pem 2048 SeCrEt\n\- \\n\- \\^BExample configuration:\^B\n\- \ servers:\n\- \ * name: \"fn\"\n\- \ hostname: \"irc.libera.chat\"\n\- \ sasl: mechanism: external\n\- \ tls: yes\n\- \ tls-client-cert: \"my.pem\"\n\- \ tls-client-key-password: \"SeCrEt\"\n"+ $(netDocs `cmdDoc` "new-self-signed-cert") (ClientCommand cmdNewCert noClientTab) cmdNewCert :: ClientCommand (String, Maybe (Int, String))
src/Client/Commands/Channel.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Channel Description : Channel management command implementations@@ -10,6 +10,7 @@ module Client.Commands.Channel (channelCommands) where import Client.Commands.Arguments.Spec+import Client.Commands.Docs (chanopDocs, cmdDoc) import Client.Commands.TabCompletion (activeNicks, noChannelTab, simpleChannelTab, simpleTabCompletion) import Client.Commands.Types import Client.Commands.WordCompletion (plainWordCompleteMode)@@ -39,76 +40,43 @@ [ Command (pure "mode") (fromMaybe [] <$> optionalArg (extensionArg "[modes]" modeParamArgs))- "Sets IRC modes.\n\- \\n\- \Examples:\n\- \Setting a ban: /mode +b *!*@hostname\n\- \Removing a quiet: /mode -q *!*@hostname\n\- \Voicing two users: /mode +vv user1 user2\n\- \Demoting an op to voice: /mode +v-o user1 user1\n\- \\n\- \When executed in a network window, mode changes are applied to your user.\n\- \When executed in a channel window, mode changes are applied to the channel.\n\- \\n\- \This command has parameter sensitive tab-completion.\n\- \\n\- \See also: /masks /channelinfo\n"+ $(chanopDocs `cmdDoc` "mode") $ NetworkCommand cmdMode tabMode , Command (pure "masks") (simpleToken "mode")- "Show mask lists for current channel.\n\- \\n\- \Common \^Bmode\^B values:\n\- \\^Bb\^B: bans\n\- \\^Bq\^B: quiets\n\- \\^BI\^B: invite exemptions (op view only)\n\- \\^Be\^B: ban exemption (op view only)s\n\- \\n\- \To populate the mask lists for the first time use: /mode \^Bmode\^B\n\- \\n\- \See also: /mode\n"+ $(chanopDocs `cmdDoc` "masks") $ ChannelCommand cmdMasks noChannelTab , Command (pure "invite") (simpleToken "nick")- "Invite a user to the current channel.\n"+ $(chanopDocs `cmdDoc` "invite") $ ChannelCommand cmdInvite simpleChannelTab , Command (pure "topic") (remainingArg "message")- "Set the topic on the current channel.\n\- \\n\- \Tab-completion with no \^Bmessage\^B specified will load the current topic for editing.\n"+ $(chanopDocs `cmdDoc` "topic") $ ChannelCommand cmdTopic tabTopic , Command (pure "kick") (liftA2 (,) (simpleToken "nick") (remainingArg "reason"))- "Kick a user from the current channel.\n\- \\n\- \See also: /kickban /remove\n"+ $(chanopDocs `cmdDoc` "kick") $ ChannelCommand cmdKick simpleChannelTab , Command (pure "kickban") (liftA2 (,) (simpleToken "nick") (remainingArg "reason"))- "Ban and kick a user from the current channel.\n\- \\n\- \Users are banned by hostname match.\n\- \See also: /kick /remove\n"+ $(chanopDocs `cmdDoc` "kickban") $ ChannelCommand cmdKickBan simpleChannelTab , Command (pure "remove") (liftA2 (,) (simpleToken "nick") (remainingArg "reason"))- "Remove a user from the current channel.\n\- \\n\- \Remove works like /kick except it results in a PART.\n\- \See also: /kick /kickban\n"+ $(chanopDocs `cmdDoc` "remove") $ ChannelCommand cmdRemove simpleChannelTab ]
src/Client/Commands/Chat.hs view
@@ -1,4 +1,4 @@-{-# Language BangPatterns, OverloadedStrings #-}+{-# Language BangPatterns, OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Chat Description : Common user IRC commands@@ -31,6 +31,7 @@ import Irc.Identifier (Identifier, idText, mkId) import Irc.Message (IrcMsg(Privmsg, Notice, Ctcp), Source(Source)) import Irc.RawIrcMsg (RawIrcMsg, parseRawIrcMsg)+import Client.Commands.Docs (chatDocs, cmdDoc) chatCommands :: CommandSection chatCommands = CommandSection "IRC commands"@@ -39,319 +40,106 @@ [ Command ("join" :| ["j"]) (liftA2 (,) (simpleToken "channels") (optionalArg (simpleToken "[keys]")))- "\^BParameters:\^B\n\- \\n\- \ channels: Comma-separated list of channels\n\- \ keys: Comma-separated list of keys\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Join the given channels. When keys are provided, they should\n\- \ occur in the same order as the channels.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /join #friends\n\- \ /join #secret thekey\n\- \ /join #secret1,#secret2 key1,key2\n\- \\n\- \\^BSee also:\^B channel, clear, part\n"+ $(chatDocs `cmdDoc` "join") $ NetworkCommand cmdJoin simpleNetworkTab , Command (pure "part") (remainingArg "reason")- "\^BParameters:\^B\n\- \\n\- \ reason: Optional message sent to channel as part reason\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Part from the current channel.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /part\n\- \ /part It's not me, it's you\n\- \\n\- \\^BSee also:\^B clear, join, quit\n"+ $(chatDocs `cmdDoc` "part") $ ChannelCommand cmdPart simpleChannelTab , Command (pure "msg") (liftA2 (,) (simpleToken "target") (remainingArg "message"))- "\^BParameters:\^B\n\- \\n\- \ target: Comma-separated list of nicknames and channels\n\- \ message: Formatted message body\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Send a chat message to a user or a channel. On servers\n\- \ with STATUSMSG support, the channel name can be prefixed\n\- \ with a sigil to restrict the recipients to those with the\n\- \ given mode.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /msg buddy I'm sending you a message.\n\- \ /msg #friends This message is for the whole channel.\n\- \ /msg him,her I'm chatting with two people.\n\- \ /msg @#users This message is only for ops!\n\- \\n\- \\^BSee also:\^B notice, me, say\n"+ $(chatDocs `cmdDoc` "msg") $ NetworkCommand cmdMsg simpleNetworkTab , Command (pure "me") (remainingArg "message")- "\^BParameters:\^B\n\- \\n\- \ message: Body of action message\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Sends an action message to the currently focused channel.\n\- \ Most clients will render these messages prefixed with\n\- \ only your nickname as though describing an action.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /me shrugs\n\- \\n\- \\^BSee also:\^B notice, msg, say\n"+ $(chatDocs `cmdDoc` "me") $ ChatCommand cmdMe simpleChannelTab , Command (pure "say") (remainingArg "message")- "\^BParameters:\^B\n\- \\n\- \ message: Body of message\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Send a message to the current chat window. This can be useful\n\- \ for sending a chat message with a leading '/' to the current\n\- \ chat window.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /say /help is the right place to start!\n\- \\n\- \\^BSee also:\^B notice, me, msg\n"+ $(chatDocs `cmdDoc` "say") $ ChatCommand cmdSay simpleChannelTab , Command ("query" :| ["q"]) (liftA2 (,) (simpleToken "target") (remainingArg "message"))- "\^BParameters:\^B\n\- \\n\- \ target: Focus name\n\- \ message: Optional message\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ This command switches the client focus to the given\n\- \ target and optionally sends a message to that target.\n\- \\n\- \ Channel: \^_#channel\^_\n\- \ Channel: \^_network\^_:\^_#channel\^_\n\- \ User: \^_nick\^_\n\- \ User: \^_network\^_:\^_nick\^_\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /q fn:#haskell\n\- \ /q #haskell\n\- \ /q lambdabot @messages\n\- \ /q irc_friend How are you?\n\- \\n\- \\^BSee also:\^B msg channel focus\n"+ $(chatDocs `cmdDoc` "query") $ ClientCommand cmdQuery simpleClientTab , Command (pure "notice") (liftA2 (,) (simpleToken "target") (remainingArg "message"))- "\^BParameters:\^B\n\- \\n\- \ target: Comma-separated list of nicknames and channels\n\- \ message: Formatted message body\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Send a chat notice to a user or a channel. On servers\n\- \ with STATUSMSG support, the channel name can be prefixed\n\- \ with a sigil to restrict the recipients to those with the\n\- \ given mode. Notice messages were originally intended to be\n\- \ used by bots. Different clients will render these in different\n\- \ ways.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /notice buddy I'm sending you a message.\n\- \ /notice #friends This message is for the whole channel.\n\- \ /notice him,her I'm chatting with two people.\n\- \ /notice @#users This message is only for ops!\n\- \\n\- \\^BSee also:\^B me, msg, say\n"+ $(chatDocs `cmdDoc` "notice") $ NetworkCommand cmdNotice simpleNetworkTab , Command (pure "wallops") (remainingArg "message to +w users")- "\^BParameters:\^B\n\- \\n\- \ message: Formatted message body\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Send a network-wide WALLOPS message. These message go out\n\- \ to users who have the 'w' usermode set.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /wallops Hi everyone, thanks for using this network!\n\- \\n\- \\^BSee also:\^B me, msg, say\n"+ $(chatDocs `cmdDoc` "wallops") $ NetworkCommand cmdWallops simpleNetworkTab , Command (pure "operwall") (remainingArg "message to +z opers")- "\^BParameters:\^B\n\- \\n\- \ message: Formatted message body\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Send a network-wide WALLOPS message to opers. These message go\n\- \ out to opers who have the 'z' usermode set.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /operwall What's this even for?\n\- \\n\- \\^BSee also:\^B me, msg, say\n"+ $(chatDocs `cmdDoc` "operwall") $ NetworkCommand cmdOperwall simpleNetworkTab , Command (pure "ctcp") (liftA3 (,,) (simpleToken "target") (simpleToken "command") (remainingArg "arguments"))- "\^BParameters:\^B\n\- \\n\- \ target: Comma-separated list of nicknames and channels\n\- \ command: CTCP command name\n\- \ arguments: CTCP command arguments\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Client-to-client protocol (CTCP) commands can be used\n\- \ to query information from another user's client application\n\- \ directly. Common CTCP commands include: ACTION, PING, VERSION,\n\- \ USERINFO, CLIENTINFO, and TIME. glirc does not automatically\n\- \ respond to CTCP commands.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /ctcp myfriend VERSION\n\- \ /ctcp myfriend CLIENTINFO\n"+ $(chatDocs `cmdDoc` "ctcp") $ NetworkCommand cmdCtcp simpleNetworkTab , Command (pure "nick") (simpleToken "nick")- "\^BParameters:\^B\n\- \\n\- \ nick: New nickname\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Change your nickname on the currently focused server.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /nick guest123\n\- \ /nick better_nick\n"+ $(chatDocs `cmdDoc` "nick") $ NetworkCommand cmdNick simpleNetworkTab , Command (pure "away") (remainingArg "message")- "\^BParameters:\^B\n\- \\n\- \ message: Optional away message\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Change your nickname on the currently focused server.\n\- \ Omit the message parameter to clear your away status.\n\- \ The away message is only used by the server to update\n\- \ status in /whois and to provide automated responses.\n\- \ It is not used by this client directly.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /away\n\- \ /away Out getting some sun\n"+ $(chatDocs `cmdDoc` "away") $ NetworkCommand cmdAway simpleNetworkTab , Command (pure "names") (pure ())- "\^BDescription:\^B\n\- \\n\- \ Show the user list for the current channel.\n\- \ Detailed view (default key F2) shows full hostmask.\n\- \ Hostmasks can be populated with /who #channel.\n\- \ Press ESC to exit the userlist.\n\- \\n\- \\^BSee also:\^B channelinfo, masks\n"+ $(chatDocs `cmdDoc` "names") $ ChannelCommand cmdChanNames noChannelTab , Command (pure "channelinfo") (pure ())- "\^BDescription:\^B\n\- \\n\- \ Show information about the current channel.\n\- \ Press ESC to exit the channel info window.\n\- \\n\- \ Information includes topic, creation time, URL, and modes.\n\- \\n\- \\^BSee also:\^B masks, mode, topic, users\n"+ $(chatDocs `cmdDoc` "channelinfo") $ ChannelCommand cmdChannelInfo noChannelTab , Command (pure "knock") (liftA2 (,) (simpleToken "channel") (remainingArg "message"))- "Request entry to an invite-only channel.\n"+ $(chatDocs `cmdDoc` "knock") $ NetworkCommand cmdKnock simpleNetworkTab , Command (pure "quote") (remainingArg "raw IRC command")- "Send a raw IRC command.\n"+ $(chatDocs `cmdDoc` "quote") $ NetworkCommand cmdQuote simpleNetworkTab , Command (pure "monitor") (extensionArg "[+-CLS]" monitorArgs)- "\^BSubcommands:\^B\n\- \\n\- \ /monitor + target[,target2]* - Add nicknames to monitor list\n\- \ /monitor - target[,target2]* - Remove nicknames to monitor list\n\- \ /monitor C - Clear monitor list\n\- \ /monitor L - Show monitor list\n\- \ /monitor S - Show status of nicknames on monitor list\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Monitor is a protocol for getting server-side notifications\n\- \ when users become online/offline.\n"+ $(chatDocs `cmdDoc` "monitor") $ NetworkCommand cmdMonitor simpleNetworkTab - ]+ ] monitorArgs :: ClientState -> String -> Maybe (Args ClientState [String]) monitorArgs _ str =
src/Client/Commands/Connection.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Connection Description : Connection command implementations@@ -10,6 +10,7 @@ module Client.Commands.Connection (connectionCommands) where import Client.Commands.Arguments.Spec ( remainingArg, simpleToken )+import Client.Commands.Docs (netDocs, cmdDoc) import Client.Commands.TabCompletion import Client.Commands.Types import Client.Commands.WordCompletion ( plainWordCompleteMode )@@ -28,45 +29,37 @@ [ Command (pure "connect") (simpleToken "network")- "Connect to \^Bnetwork\^B by name.\n\- \\n\- \If no name is configured the hostname is the 'name'.\n"+ $(netDocs `cmdDoc` "connect") $ ClientCommand cmdConnect tabConnect , Command (pure "reconnect") (pure ())- "Reconnect to the current network.\n"+ $(netDocs `cmdDoc` "reconnect") $ ClientCommand cmdReconnect noClientTab , Command (pure "disconnect") (pure ())- "Immediately terminate the current network connection.\n\- \\n\- \See also: /quit /exit\n"+ $(netDocs `cmdDoc` "disconnect") $ NetworkCommand cmdDisconnect noNetworkTab , Command (pure "quit") (remainingArg "reason")- "Gracefully disconnect the current network connection.\n\- \\n\- \\^Breason\^B: optional quit reason\n\- \\n\- \See also: /disconnect /exit\n"+ $(netDocs `cmdDoc` "quit") $ NetworkCommand cmdQuit simpleNetworkTab , Command (pure "cert") (pure ())- "Show the TLS certificate for the current connection.\n"+ $(netDocs `cmdDoc` "cert") $ NetworkCommand cmdCert noNetworkTab , Command (pure "umode") (remainingArg "modes")- "Apply a user-mode change.\n"+ $(netDocs `cmdDoc` "umode") $ NetworkCommand cmdUmode noNetworkTab ]
+ src/Client/Commands/Docs.hs view
@@ -0,0 +1,59 @@+{-# Language OverloadedStrings, TemplateHaskell #-}++{-|+Module : Client.Commands.Docs+Description : Command documentation+Copyright : (c) TheDaemoness 2023+License : ISC+Maintainer : emertens@gmail.com++This module contains definitions for all of glirc's command docs.+-}+module Client.Commands.Docs+ ( cmdDoc+ , chanopDocs+ , chatDocs+ , clientDocs+ , integrationDocs+ , netDocs+ , operDocs+ , queriesDocs+ , togglesDocs+ , windowDocs+ ) where++import Client.Docs (Docs, loadDoc, lookupDoc, makeHeader)+import Language.Haskell.TH (Q, Exp)+import Language.Haskell.TH.Syntax (lift)++cmdDoc :: Docs -> String -> Q Exp+cmdDoc docs key = lookupDoc (makeHeader "Description") ('/':key) docs++-- TODO: Replace each id with something that splits off the command name.++chanopDocs :: Docs+chanopDocs = $(loadDoc id "cmds_chanop" >>= lift)++chatDocs :: Docs+chatDocs = $(loadDoc id "cmds_chat" >>= lift)++clientDocs :: Docs+clientDocs = $(loadDoc id "cmds_client" >>= lift)++integrationDocs :: Docs+integrationDocs = $(loadDoc id "cmds_integration" >>= lift)++netDocs :: Docs+netDocs = $(loadDoc id "cmds_net" >>= lift)++operDocs :: Docs+operDocs = $(loadDoc id "cmds_oper" >>= lift)++queriesDocs :: Docs+queriesDocs = $(loadDoc id "cmds_queries" >>= lift)++togglesDocs :: Docs+togglesDocs = $(loadDoc id "cmds_toggles" >>= lift)++windowDocs :: Docs+windowDocs = $(loadDoc id "cmds_window" >>= lift)
src/Client/Commands/Exec.hs view
@@ -31,8 +31,8 @@ import Data.List (unfoldr) import Data.Text qualified as Text import Data.Text.Encoding qualified as Text-import System.Console.GetOpt (getOpt, ArgDescr(ReqArg, OptArg), ArgOrder(RequireOrder), OptDescr(..))-import System.Process.Typed (byteStringInput, proc, readProcessStdout_, setStdin)+import System.Console.GetOpt (getOpt, ArgDescr(ReqArg, OptArg, NoArg), ArgOrder(RequireOrder), OptDescr(..))+import System.Process.Typed (byteStringInput, proc, readProcessStdout, setStdin, ExitCode (ExitFailure)) -- | Settings for @/exec@ command. --@@ -53,6 +53,7 @@ , _execCommand :: String -- ^ command filename , _execStdIn :: String -- ^ stdin source , _execArguments :: [String] -- ^ command arguments+ , _execIgnoreError :: Bool -- ^ ignore the process exit code } deriving (Read,Show) @@ -69,6 +70,7 @@ , _execCommand = error "no default command" , _execStdIn = "" , _execArguments = []+ , _execIgnoreError = False } options :: [OptDescr (ExecCmd -> ExecCmd)]@@ -83,6 +85,9 @@ , Option "i" ["input"] (ReqArg (set execStdIn) "INPUT") "Use string as stdin"+ , Option "e" ["error"]+ (NoArg (set execIgnoreError True))+ "Ignore process error codes" ] -- | Parse the arguments to @/exec@ looking for various flags@@ -106,15 +111,17 @@ ExecCmd {- ^ exec configuration -} -> IO (Either [String] [String]) {- ^ error lines or output lines -} runExecCmd cmd =- do res <- try (readProcessStdout_- (setStdin (byteStringInput (L.fromStrict (Text.encodeUtf8 (Text.pack (view execStdIn cmd)))))- (proc (view execCommand cmd) (view execArguments cmd))))- return $! case res of- Left er -> Left [displayException (er :: IOError)]- Right out ->- case Text.decodeUtf8' (L.toStrict out) of- Right str -> Right (lines (Text.unpack str))- Left e -> Left [displayException e]+ do res <-+ readProcessStdout+ (setStdin (byteStringInput (L.fromStrict (Text.encodeUtf8 (Text.pack (view execStdIn cmd)))))+ (proc (view execCommand cmd) (view execArguments cmd)))+ return $! case res of+ (ExitFailure code, _) | not (view execIgnoreError cmd) ->+ Left ["Process failed with exit code " ++ show code]+ (_, out) ->+ case Text.decodeUtf8' (L.toStrict out) of+ Right str -> Right (lines (Text.unpack str))+ Left e -> Left [displayException e] -- | Power words is similar to 'words' except that when it encounters -- a word formatted as a Haskell 'String' literal it parses it as
src/Client/Commands/Operator.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Operator Description : Operator command implementations@@ -10,6 +10,7 @@ module Client.Commands.Operator (operatorCommands) where import Client.Commands.Arguments.Spec (optionalArg, remainingArg, simpleToken)+import Client.Commands.Docs (operDocs, cmdDoc) import Client.Commands.TabCompletion (noNetworkTab, simpleNetworkTab) import Client.Commands.Types import Client.State.Network (sendMsg)@@ -25,152 +26,153 @@ [ Command (pure "oper") (liftA2 (,) (simpleToken "user") (simpleToken "password"))- "Authenticate as a server operator.\n"+ $(operDocs `cmdDoc` "oper") $ NetworkCommand cmdOper noNetworkTab , Command (pure "kill") (liftA2 (,) (simpleToken "client") (remainingArg "reason"))- "Kill a client connection to the server.\n"+ $(operDocs `cmdDoc` "kill") $ NetworkCommand cmdKill simpleNetworkTab , Command (pure "kline") (liftA3 (,,) (simpleToken "minutes") (simpleToken "user@host") (remainingArg "reason"))- "Ban a client from the server.\n"+ $(operDocs `cmdDoc` "kline") $ NetworkCommand cmdKline simpleNetworkTab , Command (pure "unkline") (liftA2 (,) (simpleToken "[user@]host") (optionalArg (simpleToken "[servername]")))- "Unban a client from the server.\n"+ $(operDocs `cmdDoc` "unkline") $ NetworkCommand cmdUnkline simpleNetworkTab , Command (pure "undline") (liftA2 (,) (simpleToken "host") (optionalArg (simpleToken "[servername]")))- "Unban a client from the server.\n"+ $(operDocs `cmdDoc` "undline") $ NetworkCommand cmdUndline simpleNetworkTab , Command (pure "unxline") (liftA2 (,) (simpleToken "gecos") (optionalArg (simpleToken "[servername]")))- "Unban a gecos from the server.\n"+ $(operDocs `cmdDoc` "unxline") $ NetworkCommand cmdUnxline simpleNetworkTab , Command (pure "unresv") (liftA2 (,) (simpleToken "channel|nick") (optionalArg (simpleToken "[servername]")))- "Unban a channel or nickname from the server.\n"+ $(operDocs `cmdDoc` "unresv") $ NetworkCommand cmdUnresv simpleNetworkTab , Command (pure "testline") (simpleToken "[[nick!]user@]host")- "Check matching I/K/D lines for a [[nick!]user@]host\n"+ $(operDocs `cmdDoc` "testline") $ NetworkCommand cmdTestline simpleNetworkTab , Command (pure "testkline") (simpleToken "[user@]host")- "Check matching K/D lines for a [user@]host\n"+ $(operDocs `cmdDoc` "testkline") $ NetworkCommand cmdTestkline simpleNetworkTab , Command (pure "testgecos") (simpleToken "gecos")- "Check matching X lines for a gecos\n"+ $(operDocs `cmdDoc` "testgecos") $ NetworkCommand cmdTestGecos simpleNetworkTab , Command (pure "testmask") (liftA2 (,) (simpleToken "[nick!]user@host") (remainingArg "[gecos]"))- "Test how many local and global clients match a mask.\n"+ $(operDocs `cmdDoc` "testmask") $ NetworkCommand cmdTestmask simpleNetworkTab , Command (pure "masktrace") (liftA2 (,) (simpleToken "[nick!]user@host") (remainingArg "[gecos]"))- "Outputs a list of local users matching the given masks.\n"+ $(operDocs `cmdDoc` "masktrace") $ NetworkCommand cmdMasktrace simpleNetworkTab , Command (pure "chantrace") (simpleToken "channel")- "Outputs a list of channel members in etrace format.\n"+ $(operDocs `cmdDoc` "chantrace") $ NetworkCommand cmdChantrace simpleNetworkTab , Command (pure "trace") (optionalArg (liftA2 (,) (simpleToken "[server|nick]") (optionalArg (simpleToken "[location]"))))- "Outputs a list users on a server.\n"+ $(operDocs `cmdDoc` "trace") $ NetworkCommand cmdTrace simpleNetworkTab , Command (pure "etrace") (optionalArg (simpleToken "[-full|-v4|-v6|nick]"))- "Outputs a list users on a server.\n"+ $(operDocs `cmdDoc` "etrace") $ NetworkCommand cmdEtrace simpleNetworkTab , Command (pure "map") (pure ())- "Display network map.\n"+ $(operDocs `cmdDoc` "map") $ NetworkCommand cmdMap simpleNetworkTab , Command (pure "sconnect") (liftA2 (,) (simpleToken "connect_to") (optionalArg (liftA2 (,) (simpleToken "[port]") (optionalArg (simpleToken "[remote]")))))- "Connect two servers together.\n"+ $(operDocs `cmdDoc` "sconnect") $ NetworkCommand cmdSconnect simpleNetworkTab , Command (pure "squit") (liftA2 (,) (simpleToken "server") (remainingArg "[reason]"))- "Split a server away from your side of the network.\n"+ $(operDocs `cmdDoc` "squit") $ NetworkCommand cmdSquit simpleNetworkTab , Command (pure "modload") (liftA2 (,) (simpleToken "[path/]module") (optionalArg (simpleToken "[remote]")))- "Load an IRCd module.\n"+ $(operDocs `cmdDoc` "modload") $ NetworkCommand cmdModload simpleNetworkTab , Command (pure "modunload") (liftA2 (,) (simpleToken "module") (optionalArg (simpleToken "[remote]")))- "Unload an IRCd module.\n"+ $(operDocs `cmdDoc` "modunload") $ NetworkCommand cmdModunload simpleNetworkTab , Command (pure "modlist") (optionalArg (liftA2 (,) (simpleToken "pattern") (optionalArg (simpleToken "[remote]"))))- "List loaded IRCd modules.\n"+ $(operDocs `cmdDoc` "modlist") $ NetworkCommand cmdModlist simpleNetworkTab , Command (pure "modrestart") (optionalArg (simpleToken "[server]"))- "Reload all IRCd modules.\n"+ $(operDocs `cmdDoc` "modrestart") $ NetworkCommand cmdModrestart simpleNetworkTab , Command (pure "modreload") (liftA2 (,) (simpleToken "module") (optionalArg (simpleToken "[remote]")))- "Reload an IRCd module.\n"+ $(operDocs `cmdDoc` "modreload") $ NetworkCommand cmdModreload simpleNetworkTab , Command (pure "grant") (liftA2 (,) (simpleToken "target") (simpleToken "privset"))- "Manually assign a privset to a user.\n"+ $(operDocs `cmdDoc` "grant") $ NetworkCommand cmdGrant simpleNetworkTab , Command (pure "privs") (optionalArg (simpleToken "[target]"))- "Check operator privileges of the target.\n"+ $(operDocs `cmdDoc` "privs") $ NetworkCommand cmdPrivs simpleNetworkTab+ ] cmdGrant :: NetworkCommand (String, String)
src/Client/Commands/Queries.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ViewPatterns #-} {-|@@ -12,6 +12,7 @@ module Client.Commands.Queries (queryCommands) where import Client.Commands.Arguments.Spec (optionalArg, remainingArg, simpleToken, extensionArg, Args)+import Client.Commands.Docs (queriesDocs, cmdDoc) import Client.Commands.TabCompletion (noNetworkTab, simpleNetworkTab) import Client.Commands.Types (commandSuccess, commandSuccessUpdateCS, Command(Command), CommandImpl(NetworkCommand), CommandSection(CommandSection), NetworkCommand) import Client.State (changeSubfocus, ClientState)@@ -31,115 +32,97 @@ [ Command (pure "who") (optionalArg (liftA2 (,) (simpleToken "[channel|nick|mask]") (optionalArg (simpleToken "[options]"))))- "Send WHO query to server with given arguments, or just show the who view.\n"+ $(queriesDocs `cmdDoc` "who") $ NetworkCommand cmdWho simpleNetworkTab , Command (pure "whois") (remainingArg "arguments")- "Send WHOIS query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "whois") $ NetworkCommand cmdWhois simpleNetworkTab , Command (pure "whowas") (remainingArg "arguments")- "Send WHOWAS query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "whowas") $ NetworkCommand cmdWhowas simpleNetworkTab , Command (pure "ison") (remainingArg "arguments")- "Send ISON query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "ison") $ NetworkCommand cmdIson simpleNetworkTab , Command (pure "userhost") (remainingArg "arguments")- "Send USERHOST query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "userhost") $ NetworkCommand cmdUserhost simpleNetworkTab , Command (pure "time") (optionalArg (simpleToken "[servername]"))- "Send TIME query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "time") $ NetworkCommand cmdTime simpleNetworkTab , Command (pure "stats") (remainingArg "arguments")- "Send STATS query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "stats") $ NetworkCommand cmdStats simpleNetworkTab , Command (pure "lusers") (optionalArg (simpleToken "[servername]"))- "Send LUSERS query to a given server.\n"+ $(queriesDocs `cmdDoc` "lusers") $ NetworkCommand cmdLusers simpleNetworkTab , Command (pure "users") (optionalArg (simpleToken "[servername]"))- "Send USERS query to a given server.\n"+ $(queriesDocs `cmdDoc` "users") $ NetworkCommand cmdUsers simpleNetworkTab , Command- (pure "motd") (optionalArg (simpleToken "[servername]"))- "Send MOTD query to server.\n"+ (pure "motd")+ (optionalArg (simpleToken "[servername]"))+ $(queriesDocs `cmdDoc` "motd") $ NetworkCommand cmdMotd simpleNetworkTab , Command- (pure "admin") (optionalArg (simpleToken "[servername]"))- "Send ADMIN query to server.\n"+ (pure "admin")+ (optionalArg (simpleToken "[servername]"))+ $(queriesDocs `cmdDoc` "admin") $ NetworkCommand cmdAdmin simpleNetworkTab , Command- (pure "rules") (optionalArg (simpleToken "[servername]"))- "Send RULES query to server.\n"+ (pure "rules")+ (optionalArg (simpleToken "[servername]"))+ $(queriesDocs `cmdDoc` "rules") $ NetworkCommand cmdRules simpleNetworkTab , Command- (pure "info") (pure ())- "Send INFO query to server.\n"+ (pure "info")+ (pure ())+ $(queriesDocs `cmdDoc` "info") $ NetworkCommand cmdInfo noNetworkTab , Command (pure "list") (optionalArg (extensionArg "[clientarg]" listArgs))- "\^BParameters:\^B\n\- \\n\- \ clientarg: An optionally-comma-separated list of\n\- \ flags for controlling the list.\n\- \ ~: Always refresh the list.\n\- \ >n: Show only channels with more than \^Bn\^B users.\n\- \ <n: Show only channels with less than \^Bn\^B users.\n\- \\n\- \ serverarg: The ELIST argument to send to the server.\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ View the list of public channels on the server.\n\- \\n\- \ Sends a LIST query and caches the result;\n\- \ on larger networks on slower connections,\n\- \ this may take a while to complete.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /list\n\- \ /list >100\n\- \ /list ~ <20\n\- \ /list , *-ops"+ $(queriesDocs `cmdDoc` "list") $ NetworkCommand cmdList simpleNetworkTab , Command (pure "links") (remainingArg "arguments")- "Send LINKS query to server with given arguments.\n"+ $(queriesDocs `cmdDoc` "links") $ NetworkCommand cmdLinks simpleNetworkTab , Command- (pure "version") (optionalArg (simpleToken "[servername]"))- "Send VERSION query to server.\n"+ (pure "version")+ (optionalArg (simpleToken "[servername]"))+ $(queriesDocs `cmdDoc` "version") $ NetworkCommand cmdVersion simpleNetworkTab ]
src/Client/Commands/Toggles.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Toggles Description : View modality command implementations@@ -9,6 +9,7 @@ module Client.Commands.Toggles (togglesCommands) where +import Client.Commands.Docs (togglesDocs, cmdDoc) import Client.Commands.TabCompletion (noClientTab) import Client.Commands.Types import Client.Configuration (EditMode(SingleLineEditor, MultiLineEditor), LayoutMode(OneColumn, TwoColumn))@@ -21,43 +22,43 @@ [ Command (pure "toggle-detail") (pure ())- "Toggle detailed message view.\n"+ $(togglesDocs `cmdDoc` "toggle-detail") $ ClientCommand cmdToggleDetail noClientTab , Command (pure "toggle-activity-bar") (pure ())- "Toggle detailed detailed activity information in status bar.\n"+ $(togglesDocs `cmdDoc` "toggle-activity-bar") $ ClientCommand cmdToggleActivityBar noClientTab , Command (pure "toggle-show-ping") (pure ())- "Toggle visibility of ping round-trip time.\n"+ $(togglesDocs `cmdDoc` "toggle-show-ping") $ ClientCommand cmdToggleShowPing noClientTab , Command (pure "toggle-metadata") (pure ())- "Toggle visibility of metadata in chat windows.\n"+ $(togglesDocs `cmdDoc` "toggle-metadata") $ ClientCommand cmdToggleMetadata noClientTab , Command (pure "toggle-layout") (pure ())- "Toggle multi-window layout mode.\n"+ $(togglesDocs `cmdDoc` "toggle-layout") $ ClientCommand cmdToggleLayout noClientTab , Command (pure "toggle-editor") (pure ())- "Toggle between single-line and multi-line editor mode.\n"+ $(togglesDocs `cmdDoc` "toggle-editor") $ ClientCommand cmdToggleEditor noClientTab , Command (pure "toggle-edit-lock") (pure ())- "Toggle editor lock mode. When editor is locked pressing Enter is disabled.\n"+ $(togglesDocs `cmdDoc` "toggle-edit-lock") $ ClientCommand cmdToggleEditLock noClientTab ]
src/Client/Commands/Window.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language OverloadedStrings, TemplateHaskell #-} {-| Module : Client.Commands.Window Description : Window command implementations@@ -10,6 +10,7 @@ module Client.Commands.Window (windowCommands, parseFocus) where import Client.Commands.Arguments.Spec+import Client.Commands.Docs (windowDocs, cmdDoc) import Client.Commands.TabCompletion import Client.Commands.Types import Client.Commands.WordCompletion (plainWordCompleteMode)@@ -41,216 +42,79 @@ [ Command (pure "focus") (liftA2 (,) (simpleToken "network") (optionalArg (simpleToken "[target]")))- "Change the focused window.\n\- \\n\- \When only \^Bnetwork\^B is specified this switches to the network status window.\n\- \When \^Bnetwork\^B and \^Btarget\^B are specified this switches to that chat window.\n\- \\n\- \Nickname and channels can be specified in the \^Btarget\^B parameter.\n\- \See also: /query (aliased /c /channel) to switch to a target on the current network.\n"+ $(windowDocs `cmdDoc` "focus") $ ClientCommand cmdFocus tabFocus , Command ("c" :| ["channel"]) (simpleToken "focus")- "\^BParameters:\^B\n\- \\n\- \ focuses: Focus name\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ This command sets the current window focus. When\n\- \ no network is specified, the current network will\n\- \ be used.\n\- \\n\- \ Client: *\n\- \ Network: \^_network\^_:\n\- \ Channel: \^_#channel\^_\n\- \ Channel: \^_network\^_:\^_#channel\^_\n\- \ User: \^_nick\^_\n\- \ User: \^_network\^_:\^_nick\^_\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /c fn:#haskell\n\- \ /c #haskell\n\- \ /c fn:\n\- \ /c *:\n\- \\n\- \\^BSee also:\^B focus\n"+ $(windowDocs `cmdDoc` "channel") $ ClientCommand cmdChannel tabChannel , Command (pure "clear") (optionalArg (liftA2 (,) (simpleToken "[network]") (optionalArg (simpleToken "[channel]"))))- "Clear a window.\n\- \\n\- \If no arguments are provided the current window is cleared.\n\- \If \^Bnetwork\^B is provided the that network window is cleared.\n\- \If \^Bnetwork\^B and \^Bchannel\^B are provided that chat window is cleared.\n\- \If \^Bnetwork\^B is provided and \^Bchannel\^B is \^B*\^O all windows for that network are cleared.\n\- \\n\- \If a window is cleared and no longer active that window will be removed from the client.\n"+ $(windowDocs `cmdDoc` "clear") $ ClientCommand cmdClear tabFocus , Command (pure "windows") (optionalArg (simpleToken "[kind]"))- "Show a list of all windows with an optional argument to limit the kinds of windows listed.\n\- \\n\- \\^Bkind\^O: one of \^Bnetworks\^O, \^Bchannels\^O, \^Busers\^O\n\- \\n"+ $(windowDocs `cmdDoc` "windows") $ ClientCommand cmdWindows tabWindows , Command (pure "splits") (remainingArg "focuses")- "\^BParameters:\^B\n\- \\n\- \ focuses: List of focus names\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ This command sents the set of focuses that will always\n\- \ be visible, even when unfocused. When the client is focused\n\- \ to an active network, the network can be omitted when\n\- \ specifying a focus. If no focuses are listed, they will\n\- \ all be cleared.\n\- \\n\- \ Client: *\n\- \ Network: \^_network\^_:\n\- \ Channel: \^_#channel\^_\n\- \ Channel: \^_network\^_:\^_#channel\^_\n\- \ User: \^_nick\^_\n\- \ User: \^_network\^_:\^_nick\^_\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /splits * fn:#haskell fn:chanserv\n\- \ /splits #haskell #haskell-lens nickserv\n\- \ /splits\n\- \\n\- \\^BSee also:\^B splits+, splits-\n"+ $(windowDocs `cmdDoc` "splits") $ ClientCommand cmdSplits tabSplits , Command (pure "splits+") (remainingArg "focuses")- "Add windows to the splits list. Omit the list of focuses to add the\- \ current window.\n\- \\n\- \\^Bfocuses\^B: space delimited list of focus names.\n\- \\n\- \Client: *\n\- \Network: \^BNETWORK\^B\n\- \Channel: \^BNETWORK\^B:\^B#CHANNEL\^B\n\- \User: \^BNETWORK\^B:\^BNICK\^B\n\- \\n\- \If the network part is omitted, the current network will be used.\n"+ $(windowDocs `cmdDoc` "splits") $ ClientCommand cmdSplitsAdd tabSplits , Command (pure "splits-") (remainingArg "focuses")- "Remove windows from the splits list. Omit the list of focuses to\- \ remove the current window.\n\- \\n\- \\^Bfocuses\^B: space delimited list of focus names.\n\- \\n\- \Client: *\n\- \Network: \^BNETWORK\^B\n\- \Channel: \^BNETWORK\^B:\^B#CHANNEL\^B\n\- \User: \^BNETWORK\^B:\^BNICK\^B\n\- \\n\- \If the network part is omitted, the current network will be used.\n"+ $(windowDocs `cmdDoc` "splits") $ ClientCommand cmdSplitsDel tabActiveSplits , Command (pure "ignore") (remainingArg "masks")- "\^BParameters:\^B\n\- \\n\- \ masks: List of masks\n\- \\n\- \\^BDescription:\^B\n\- \\n\- \ Toggle the soft-ignore on each of the space-delimited given\n\- \ nicknames. Ignores can use \^B*\^B (many) and \^B?\^B (one) wildcards.\n\- \ Masks can be of the form: nick[[!user]@host]\n\- \ Masks use a case-insensitive comparison.\n\- \\n\- \ If no masks are specified the current ignore list is displayed.\n\- \\n\- \\^BExamples:\^B\n\- \\n\- \ /ignore\n\- \ /ignore nick1 nick2 nick3\n\- \ /ignore nick@host\n\- \ /ignore nick!user@host\n\- \ /ignore *@host\n\- \ /ignore *!baduser@*\n"+ $(windowDocs `cmdDoc` "ignore") $ ClientCommand cmdIgnore tabIgnore , Command (pure "grep") (remainingArg "regular-expression")- "Set the persistent regular expression.\n\- \\n\- \\^BFlags:\^B\n\- \ -A n Show n messages after match\n\- \ -B n Show n messages before match\n\- \ -C n Show n messages before and after match\n\- \ -F Use plain-text match instead of regular expression\n\- \ -i Case insensitive match\n\- \ -v Invert pattern match\n\- \ -m n Limit results to n matches\n\- \ -- Stop processing flags\n\- \\n\- \Clear the regular expression by calling this without an argument.\n\- \\n\- \\^B/grep\^O is case-sensitive.\n"+ $(windowDocs `cmdDoc` "grep") $ ClientCommand cmdGrep simpleClientTab , Command (pure "dump") (simpleToken "filename")- "Dump current buffer to file.\n"+ $(windowDocs `cmdDoc` "dump") $ ClientCommand cmdDump simpleClientTab , Command (pure "mentions") (pure ())- "Show a list of all message that were highlighted as important.\n\- \\n\- \When using \^B/grep\^B the important messages are those matching\n\- \the regular expression instead.\n"+ $(windowDocs `cmdDoc` "mentions") $ ClientCommand cmdMentions noClientTab , Command (pure "setwindow") (simpleToken ("hide|show" ++ concatMap ('|':) activityFilterStrings))- "Set window property.\n\- \\n\- \\^Bsilent\^B / \^Bquieter\^B / \^Bquiet\^B / \^Bimponly\^B / \^Bloud\^B / \^Blouder\^B\n\- \ Changes the importance of normal and important messages:\n\- \ \^Blouder\^B: Upgrades normal to important.\n\- \ \^Bloud\^B: Uses default values.\n\- \ \^Bimponly\^B: Downgrades normal to boring.\n\- \ \^Bquiet\^B: Downgrades important to normal.\n\- \ \^Bquieter\^B: Downgrades both one step.\n\- \ \^Bsilent\^B: Downgrades both to boring.\n\- \\n\- \\^Bshow\^B / \^Bhide\^B\n\- \ Toggles if window appears in window command shortcuts.\n"+ $(windowDocs `cmdDoc` "setwindow") $ ClientCommand cmdSetWindow tabSetWindow , Command (pure "setname") (optionalArg (simpleToken "[letter]"))- "Set window shortcut letter. If no letter is provided the next available\n\- \letter will automatically be assigned.\n\- \\n\- \Available letters are configured in the 'window-names' configuration setting.\n"+ $(windowDocs `cmdDoc` "setname") $ ClientCommand cmdSetWindowName noClientTab ]
src/Client/Commands/ZNC.hs view
@@ -1,4 +1,4 @@-{-# Language OverloadedStrings #-}+{-# Language TemplateHaskell, OverloadedStrings #-} {-| Module : Client.Commands.ZNC Description : ZNC command implementations@@ -9,8 +9,9 @@ module Client.Commands.ZNC (zncCommands) where -import Control.Applicative (asum, liftA2)+import Control.Applicative ((<|>), empty, liftA2) import Client.Commands.Arguments.Spec (optionalArg, remainingArg, simpleToken)+import Client.Commands.Docs (integrationDocs, cmdDoc) import Client.Commands.TabCompletion (noNetworkTab, simpleNetworkTab) import Client.Commands.Types import Client.State.Network (sendMsg)@@ -26,26 +27,13 @@ [ Command (pure "znc") (remainingArg "arguments")- "Send command directly to ZNC.\n\- \\n\- \The advantage of this over /msg is that responses are not broadcast to call clients.\n"+ $(integrationDocs `cmdDoc` "znc") $ NetworkCommand cmdZnc simpleNetworkTab , Command (pure "znc-playback") (optionalArg (liftA2 (,) (simpleToken "[time]") (optionalArg (simpleToken "[date]"))))- "Request playback from the ZNC 'playback' module.\n\- \\n\- \\^Btime\^B determines the time to playback since.\n\- \\^Bdate\^B determines the date to playback since.\n\- \\n\- \When both \^Btime\^B and \^Bdate\^B are omitted, all playback is requested.\n\- \When both \^Bdate\^B is omitted it is defaulted the most recent date in the past that makes sense.\n\- \\n\- \Time format: HOURS:MINUTES (example: 7:00)\n\- \Date format: YEAR-MONTH-DAY (example: 2016-06-16)\n\- \\n\- \Note that the playback module is not installed in ZNC by default!\n"+ $(integrationDocs `cmdDoc` "znc-playback") $ NetworkCommand cmdZncPlayback noNetworkTab ]@@ -91,7 +79,8 @@ timeFormats = ["%k:%M:%S","%k:%M"] dateFormats = ["%F"] parseFormats formats str =- asum (map (parseTimeM False defaultTimeLocale ?? str) formats)+ -- asum requires base >= 4.16+ foldr (<|>) empty (map (parseTimeM False defaultTimeLocale ?? str) formats) successZoned = success . formatTime defaultTimeLocale "%s"
+ src/Client/Docs.hs view
@@ -0,0 +1,121 @@+{-# Language OverloadedStrings #-}++{-|+Module : Client.Docs+Description : Compile-time documentation injection+Copyright : (c) TheDaemoness 2023+License : ISC+Maintainer : emertens@gmail.com++This module adds the requisite functions to load and parse+a subset of AsciiDoc and embed it using Template Haskell.+-}+module Client.Docs+ ( Docs+ , loadDoc+ , lookupDoc+ , makeHeader+ ) where++import Prelude hiding (readFile)++import Control.Applicative ((<|>))+import qualified Data.Attoparsec.Text as Parse+import Data.ByteString (readFile)+import Data.Char (isSpace)+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import Data.Text.Encoding (decodeUtf8)+import Language.Haskell.TH (Exp, Q, runIO)+import Language.Haskell.TH.Syntax (lift)+import qualified Data.Text.Lazy.Builder as Builder++type Docs = HashMap String LText.Text++data Line+ = Discarded+ | Section Text+ | Subsection Text+ | Contents LText.Text++makeHeader :: LText.Text -> LText.Text+makeHeader header = LText.append "\^B" (LText.append header ":\^B\n")++loadDoc :: (String -> String) -> FilePath -> Q Docs+loadDoc keymod path = runIO (readFile splicePath >>= renderDoc)+ where+ splicePath = "doc/" ++ path ++ ".adoc"+ renderDoc doc = case Parse.parseOnly lineParser $ decodeUtf8 doc of+ Right docs -> return $ buildDocs keymod docs+ Left errorMsg -> fail ("Parser failed on `" ++ splicePath ++ "`: " ++ errorMsg)++lookupDoc :: LText.Text -> String -> Docs -> Q Exp+lookupDoc header name docs =+ case HashMap.lookup name docs of+ Just doc -> lift $ LText.toStrict $ LText.append header doc+ Nothing -> fail failMsg+ where+ failMsg = "No docs for `" ++ name ++ "` (have " ++ show (HashMap.keys docs) ++ ")"++buildDocs :: (String -> String) -> [Line] -> Docs+buildDocs keymod parsedLines = docs+ where+ folded = foldl (addLine keymod) (HashMap.empty, "", LText.empty) parsedLines+ (docs, _, _) = addLine keymod folded (Section "")++data RenderContentsState+ = NormalState+ | CodeStartState+ | CodeEndState+ | CodeBlockState++renderContents :: RenderContentsState -> LText.Text -> LText.Text+renderContents state = Builder.toLazyText . snd . foldl renderContents' (state, mempty) . LText.unpack+ where+ renderContents' (st, text) char = case (st, char) of+ (CodeStartState, '+') -> (CodeBlockState, text <> Builder.fromText "\^_")+ (CodeStartState, _ ) -> (NormalState, text <> Builder.fromText "\^B" <> Builder.singleton char)+ (CodeEndState, '`') -> (NormalState, text <> Builder.fromText "\^_")+ (CodeEndState, _ ) -> (CodeBlockState, text <> Builder.singleton '+' <> Builder.singleton char)+ (NormalState, '`') -> (CodeStartState, text)+ (CodeBlockState, '+') -> (CodeEndState, text)+ (_, _ ) -> (st, text <> Builder.singleton char)++addLine :: (String -> String) -> (Docs, Text, LText.Text) -> Line -> (Docs, Text, LText.Text)+addLine _ (docs, section, text) Discarded = (docs, section, text)+addLine _ (docs, "", _) (Section s') = (docs, s', LText.empty)+addLine _ (docs, "", text) _ = (docs, "", text)+addLine keymod (docs, section, text) line = case line of+ -- TODO: Keep renderContents state across lines.+ -- Otherwise start in NormalState after each newline.+ Contents text' -> (docs, section, append' $ renderContents NormalState text')+ Subsection text' -> (docs, section, append' (makeHeader (LText.fromStrict text')))+ Section s' -> (HashMap.insert (keymod $ Text.unpack section) text docs, s', LText.empty)+ where+ append' = LText.append text++lineParser :: Parse.Parser [Line]+lineParser = Parse.many1' (sectionParser <|> contentsParser) <* Parse.endOfInput+ where+ sectionParser = Parse.char '=' >> (sectionL2Parser <|> sectionL3Parser <|> return Discarded)+ where+ sectionL2Parser = do+ _ <- Parse.string "= "+ name <- Parse.takeWhile1 (not . isSpace)+ eolParser+ return (Section name)+ sectionL3Parser = do+ _ <- Parse.takeWhile1 (== '=')+ Parse.skipWhile (== ' ')+ chars <- Parse.manyTill Parse.anyChar eolParser+ return (Subsection $ Text.pack chars)+ contentsParser = do+ chars <- Parse.manyTill Parse.anyChar eolParser+ return $ Contents $ LText.fromChunks [" ", Text.pack chars, "\n"]+ eolParser = do+ spaces <- Parse.takeWhile (== ' ')+ _ <- if Text.null spaces then pure '+' else Parse.option '+' (Parse.char '+')+ Parse.endOfLine