User Guide

Welcome to Wheaties, Botfast of Champions!

Wheaties is a chat bot whose behavior is invented by you. Anyone can add "commands" that make Wheaties do things, such as respond with a greeting or display a tweet.

This user guide shows how to invoke Wheaties' commands in a chat room, and how to create new ones.

1. Using Commands

Wheaties responds to chat messages that start with a period (.) and the name of a command. For example, here’s how to invoke the ping command:

<Doolan> .ping
<Wheaties> pong!

The period is Wheaties' command prefix.

1.1. Arguments

Some commands accept parameters called arguments. (You might recognize the term "argument" from math functions or computer programming.) Commands can use arguments to change their behavior based on your input.

Wheaties' argument syntax is simple: elements separated by spaces. (If you’re familiar with Bash or Tcl, you’ll notice some similarities.) Wheaties understands several types of arguments: strings, numbers, regular expressions, lists, and commands.

1.1.1. Strings

Strings come in three flavors: naked, single-quoted, and double-quoted. Both regular and curly ("smart") quotes are supported.

Naked

Use naked strings for single words or short phrases. Escape spaces with a backslash (\).

Single-Quoted

Use single quotes for strings with lots of whitespace, or brackets that you don’t want to turn into commands.

Double-Quoted

Use double quotes for strings with commands inside of them.

Examples

Here are some examples of using each flavor of string. The echo command makes Wheaties reply with one message per argument.

Naked strings
<Doolan> .echo foo bar baz
<Wheaties> foo
<Wheaties> bar
<Wheaties> baz

<Doolan> .echo foo bar\ baz
<Wheaties> foo
<Wheaties> bar baz
Single-quoted strings
<Doolan> .echo foo 'bar baz'
<Wheaties> foo
<Wheaties> bar baz

<Doolan> .echo foo ‘bar baz’
<Wheaties> foo
<Wheaties> bar baz

<Doolan> .echo foo 'bar [smile]'
<Wheaties> foo
<Wheaties> bar [smile]
Double-quoted strings
<Doolan> .echo foo "bar [smile]"
<Wheaties> foo
<Wheaties> bar :)

<Doolan> .echo foo “bar [smile]”
<Wheaties> foo
<Wheaties> bar :)

1.1.2. Numbers

Numbers come in two flavors: integer and decimal.

Examples
<Doolan> .add 123 456
<Wheaties> 579

<Doolan> .add 12.3 0.7
<Wheaties> 13.0

1.1.3. Regular Expressions

Regular expressions are an advanced argument type used to define search patterns that can be used by commands. Most users won’t need to use them. You can learn more about regular expressions on Wikipedia.

Regular expressions are contained within forward slashes /…​/ with optional modifier characters after the trailing slash. The pattern can contain whitespace.

The parser uses Ruby’s Regexp class. See its documentation for details on supported features.

Modifiers

Wheaties supports the following optional regular expression modifiers:

i

Ignore case

m

Treat a newline as a character matched by .

x

Ignore whitespace

These behave the same as Ruby’s Regexp modifiers. Refer to Ruby’s documentation for details.

Examples
<Doolan> .delete /[aeiou\s]/ 'the quick brown fox'
<Wheaties> thqckbrwnfx

<Doolan> .replace /baz/i Qux 'Foo Bar Baz'
<Wheaties> Foo Bar Qux

1.1.4. Lists

Group arguments into lists using parentheses ( ). You can use any argument type inside the list, even other lists!

Examples

To send a memo to several people:

<Doolan> .memo (CCP Gaf Jep Mav) 'Anyone up for Worms tomorrow night?'
<Wheaties> I sent your memo to CCP, Gaf, Jep, and Mav!

Lists can contain any argument type, including other lists. This example uses the inspect command, which outputs a Ruby representation of the arguments.

<Doolan> .inspect foo (bar ('baz qux' [smile])) flonk
<Wheaties> ["foo", ["bar", ["baz qux", ":)"]], "flonk"]

1.1.5. Commands

Commands are wrapped in brackets [ ] and contain a command name and any number of arguments of any type. The command name or its arguments may even be other commands! Don’t forget that you can embed commands inside of double-quoted strings, too.

Examples
<Doolan> .echo [smile]
<Wheaties> :)

<Doolan> .echo [repeat 3 [smile]]
<Wheaties> :):):)

<Doolan> .echo "weird: [delete /[aeiou\s]/ 'the quick brown fox']"
<Wheaties> weird: thqckbrwnfx

<Doolan> .echo [[random smile frown] 3]
<Wheaties> :))) or :(((

2. Command Types

Anyone can create and edit Wheaties' commands using this website. Some commands can be created or modified by interacting with Wheaties in a chat room.

There are five types of commands: plain text, random line, Ruby, YAML, and built-in.

2.1. Plain Text

When a plain text command is invoked, Wheaties sends each line of text to the chat room. For example, the traps command contains four lines:

When setting a trap you always have to remember three things:
1.) NEVER SET SPIKES!
2.) WATCH OUT FOR THE CLOUDS!
3.) LAY THOSE BRICKS DOWN!

When invoked in a chat room, Wheaties sends each line as a chat message:

<Doolan> .traps
<Wheaties> When setting a trap you always have to remember three things:
<Wheaties> 1.) NEVER SET SPIKES!
<Wheaties> 2.) WATCH OUT FOR THE CLOUDS!
<Wheaties> 3.) LAY THOSE BRICKS DOWN!

2.2. Random Line

Random line commands behave similarly to plain text commands, except that instead of sending every line of the command as a message, Wheaties chooses one line at random. For example, the jcdenton command contains:

My vision is augmented.
When due process fails us, we really do live in a world of terror.
You mechs may have copper wiring to reroute your fear of pain, but I've got nerves of steel.
A BOMB!

Each time it is invoked, Wheaties chooses a random line:

<Doolan> .jcdenton
<Wheaties> A BOMB!
<Doolan> .jcdenton
<Wheaties> When due process fails us, we really do live in a world of terror.
<Doolan> .alert [jcdenton]
<Wheaties> /!\ My vision is augmented. /!\

2.3. Ruby

Ruby commands run in a Ruby interpreter, and have access to the entire Ruby programming language, several Ruby gems, and many Wheaties helper methods.

For more information about writing Ruby commands, see the Ruby Commands chapter.

2.3.1. Documenting Ruby Commands

Note
This section has not yet been written. It will explain how comments at the top of Ruby commands are used by the help command.

2.4. YAML

YAML commands are rarely used directly. Instead, they’re meant as a way to store structured data that can be easily used in Ruby commands.

YAML commands are parsed using Ruby’s YAML library. The contents of the YAML command are parsed using Psych.safe_load, and the return value of that method is returned from the command invocation.

If a YAML command is invoked directly in a chat room, Wheaties will interpret it based on the top-level object in the YAML document:

  • If the YAML document is a single string or a number, it will be sent as a single message.

  • If the YAML document is an array, each element will be sent as a message. If the array contains other arrays, they will be interpreted recursively according to these rules.

  • If the YAML document is a dictionary or null, no message will be sent.

For more details on the YAML language, see its website.

2.5. Built-In

Some of Wheaties' helper methods are available as "built-in" commands that can be invoked directly in a chat room.

Documentation
  • help — Get help for a command.

  • version — See Wheaties' version.

Environment
  • builtins — See a list of these built-in commands.

  • env — See information about Wheaties' Ruby process environment.

Formatting
  • bold(text), b(text) — Make text bold.

  • color(text), co(text) — Control the foreground and background color of text.

  • italic(text), i(text) — Make text italic.

  • underline(text), ul(text) — Make text underlined.

  • plain(text), pl(text), unformat(text), uf(text) — Strip all mIRC-style formatting from text.

Storage
  • increment(key), decrement(key) — Increment or decrement integers in Redis.

  • get(key), set(key, value), del(key) — Get, set, and delete string values in Redis.

  • hget(key, field), hset(key, field, value) — Get and set Redis hash fields.

  • jget(key), jset(key, value) — Get and set JSON-encoded values in Redis.

3. Ruby Commands

Ruby commands are the heart of Wheaties' interactivity. Within a Ruby command, you have access to information about the message that invoked the command, the chat channel and everyone in it, and a wide variety of third-party libraries and Wheaties helper methods.

Ruby commands can send messages back to the chat room, send private messages to individual users, or they can return standard Ruby objects for use by other Ruby commands.

If you’re new to Ruby, check out its official website and the community style guide to get started.

3.1. Environment

Wheaties evaluates Ruby commands using MRI Ruby 2.7.1.

3.1.1. Ruby Gems

These Ruby gems are loaded:

3.1.2. Redis

Ruby commands have access to a shared, persistent Redis database.

The redis helper method returns an instance of the Redis class from the redis Ruby gem, which can be used to interact directly with the Redis database. In addition, Wheaties provides several storage helper methods, like get(key) and set(key, value), which provide some syntactic sugar over the Redis instance methods.

3.2. Return Values

Like Ruby methods and blocks, the last statement of a Ruby command becomes its return value. Wheaties interprets different types of return values differently.

String or Numeric

If a command returns a String or any kind of Numeric, Wheaties will send it as a message to the channel or private message where the command was invoked.

Array

Each element of the array will be recursively evaluated according to these rules. For example, if a command returns ["foo", "bar", "baz"], Wheaties will send three messages to the channel or private message where the command was invoked.

Anything else

If a command returns any other kind of Ruby object, including nil, Wheaties will not send any message. If the command has been invoked by another command, its Ruby return value is available to the other command, no matter what type of Ruby object it is.

3.3. Helpers

Ruby commands have access to a number of Wheaties helper methods.

3.3.1. Command Helpers

args

Returns an array of arguments passed to the command.

halt(return_value = nil)

Halts execution of the current command, returning an optional value. halt is useful for implementing guard clauses, like when validating user input:

unless args.size >= 2
  halt('please supply at least two arguments')
end

"hello #{args[0]}, welcome to #{args[1]}!"
halt!(return_value = nil)

Behaves like halt, but halts execution of the entire command stack, even from within nested commands. halt! is useful for implementing guard clauses in commands that might be called from other commands, or for bailing on a command invocation in the event of a fatal error.

begin
  do_something_dangerous
rescue SomeError
  halt!
end
match

For commands whose name is a regular expression, match returns an instance of Ruby’s MatchData representing the pattern match.

top?

Returns true if the current command is at the top of the stack, i.e. it was the first command executed by the user or event; or false otherwise.

3.3.2. Debugging Helpers

debug(*args)

Calls #inspect on each argument, then sends the output to the sender in a private message prefixed with the name of the current command, and prints it to Wheaties' server log. (The server-side logging will only occur if Wheaties' server log level is set to "debug", which is normally not the case, so don’t worry about filling his logs with your debug output.)

debug is useful for inspecting variables while editing a command. Since it includes the current command name in its message, it’s especially useful when debugging output from nested commands.

3.3.3. Doc Helpers

help(command_name)

If the given command includes documentation, help returns a multi-line string describing how to use the command.

Command
# Greet someone.
# Usage: greet NAME

"Hello, #{args.first}!"
Chat
<Doolan> .help greet
<Wheaties> greet: Greet someone.
<Wheaties> Usage: greet NAME

<Doolan> .greet Gaf
<Wheaties> Hello, Gaf!

3.3.4. Environment Helpers

builtins

Returns an array of Wheaties' built-in command names.

env

Returns the name of Wheaties' current environment: development, staging, or production.

version

Returns Wheaties' current version.

3.3.5. Formatting Helpers

Wheaties' formatting helpers style text using mIRC-style formatting codes. If you have complex formatting needs, you can use Ruby’s sprintf to construct a string without needing to do lots of #{…​} interpolation:

sprintf('some %s text', b('emphasized'))

sprintf('hello %{name}, welcome to %{place}!',
  name: bold(italic(sender.nick)), place: green(underline('#perkele'))
)

All of the formatting helpers are available as built-in commands, so you can use them directly in chat:

<Doolan> .echo "hello [bold [italic Gaf]], welcome to [green [underline #perkele]]!"
<Wheaties> hello Gaf, welcome to #perkele!
bold(text)

Emphasizes text.

Aliased as
  • b(text)

color(foreground, background = nil, text)

Colors text with a foreground color and optional background color. foreground and background can be strings or symbols. The available colors are:

  • aqua

  • black

  • blue

  • brown

  • green

  • grey

  • lime

  • orange

  • pink

  • purple

  • red

  • royal

  • silver

  • teal

  • white

  • yellow

In addition to the color and co helper methods, each color has its own helper method that accepts a single text argument. These color-specific helper methods only set the foreground color, so if you want to control background color, you’ll need to use color or co.

Aliased as
  • co(foreground, background = nil, text)

Example
# Using co(...)
"Have some #{co(:green, 'colorful')} #{b(co(:white, :blue, 'text!'))}"

# Using the color-specific helper methods
"Have some #{green('colorful')} #{b(blue('text!'))}"
invert(text)

Inverts the foreground and background colors of text. If text doesn’t have any color formatting applied to it, most chat clients will invert whatever its default text color is.

Aliased as
  • inv(text)

italic(text)

Italicizes text.

Aliased as
  • i(text)

underline(text)

Underlines text.

Aliased as
  • ul(text)

unformat(text)

Strips all formatting from text.

Aliased as
  • pl(text)

  • plain(text)

  • uf(text)

3.3.6. Message Helpers

channel

Returns an instance of Cinch::Channel representing the channel in which the command was invoked, or nil if the command was invoked in a private message.

command?

Returns true if the command stack was invoked directly by a user in a chat room, or false if the command stack was invoked automatically in response to an event.

event?

Returns true if the command stack was invoked automatically in response to an event, or false if the command stack was invoked directly by a user in a chat room.

history

Returns an instance of Wheaties::TargetHistory for the channel in which the command was invoked, or for the user in whose private messages the command was invoked.

Wheaties::TargetHistory implements a selection of standard Ruby Enumerable methods, and convenience methods such as #for_matching_nick(nick) and #for_user(Cinch::User), which can be used to read the message history of the current channel or private messages.

msg

Returns an instance of Cinch::Message representing the chat message that invoked the command.

Message text
msg.message
Channel in which the message was sent (equivalent to calling the channel helper)
msg.channel
The user who sent the message (equivalent to calling the sender helper)
msg.user
pm?

Returns true if the command was invoked in a private message, or false if the command was invoked in a channel.

response
Important
This method is provided for backwards compatibility, and should not be used in new code. Instead of response.text, use the msg helper: msg.message.

Returns an instance of Wheaties::ResponseShim to provide backwards compatibility with old Wheaties commands that access the message text using response.text.

sender

Returns an instance of Cinch::User representing the user who invoked the command, whether they invoked it directly, or indirectly via an event.

Attributes of the user
sender.nick #=> "Doolan"
sender.user #=> "doolan"
sender.realname #=> "Harry Flashman"

3.3.7. Storage Helpers

Wheaties provides several storage helpers that work with his Redis database.

decrement(key, by: 1)

Decrements key in Redis by an integer amount. Equivalent to calling Redis#decrby, which calls Redis’s DECRBY command. Returns the integer value of key after the decrement.

Example
get('foo') #=> 3
decrement('foo') #=> 2
decrement('foo', by: 3) #=> -1
del(key)

Deletes key in Redis. Equivalent to calling Redis#del, which calls Redis’s DEL command.

get(key, default = nil)

Returns the string value of key in Redis. If key doesn’t exist, returns default. Equivalent to calling Redis#get, which calls Redis’s GET command.

hexists?(key, field)

Returns true if field exists in the Redis hash key, or false otherwise. Equivalent to calling Redis#hexists, which calls Redis’s HEXISTS command.

hget(key, field, default = nil)

Returns field from the Redis hash key, or default if key or field doesn’t exist. Equivalent to calling Redis#hget, which calls Redis’s HGET command.

hset(key, field, value)

Sets the value of field in the Redis hash key. Equivalent to calling Redis#hset, which calls Redis’s HSET command.

increment(key, by: 1)

Increments key in Redis by an integer amount. Equivalent to calling Redis#incrby, which calls Redis’s INCRBY command. Returns the integer value of key after the increment.

Example
increment('foo') #=> 1
increment('foo', by: 4) #=> 5
jget(key, default = nil)

Returns the JSON-decoded value of key in Redis. If key doesn’t exist, returns default. The value stored in Redis is decoded using Ruby’s JSON.parse.

jset(key, value)

Sets the JSON-encoded value of key in Redis. value is encoded using Ruby’s JSON.dump.

redis

Returns an instance of Redis that’s connected to Wheaties' Redis database. This is the same Redis client used by the other storage helpers.

set(key, value)

Sets the string value of key in Redis. Equivalent to calling Redis#set, which calls Redis’s SET command.

Tip
To use any of the options available to Redis#set, such as setting expiration time or only setting a value if key doesn’t already exist, use the Redis client directly, e.g. redis.set('foo', ex: 3600, nx: true).