An attempt to explain the say2 protocol in a less terse way.

C:  client sends
S:  server sends


Concept: 'nick' vs 'id'.  Most messages from the server that identify a
specific client will attach both a 'nick' and an 'id'.  The 'nick' is always
the current nickname the client is using, while 'id' is the user's
authoritative identifier (whatever name the user registered).  For example,
my 'id' is "Robey", so usually my 'nick' and 'id' are both set to "Robey".
Whenever I change my nickname to "RobeyZzz", my 'nick' field will change to
"RobeyZzz", but my 'id' field will remain as "Robey".  Clients can use this
to uniquely identify a registered user (or a single continuous guest session)
despite repeated nickname changes.  Most commands that take a 'nick' will
also take an 'id' for the same reason: to provide a little flexibility in
how a client (or the end user) decides to address someone else.


Most errors are reported this way:
S: error:{ command? text }
        command: the client command that caused this response (for example,
               "message" or "whois") -- not all errors are associated with
               a specific client command, though
	text: human-readable text explaining the error

But, for some operations, specific error messages may be defined that give
more context to the client.  (Because in some cases the client may want to
know what kind of thing went wrong.)  The "error" response only covers a
generic, otherwise non-specific error.


*** SERVER GREETINGS

S: hello:{ }
S: server-info:{ server version text? uptime? }
	server: human-readable text describing this server software
		(example: "SuperServer")
	version: version string for the server software (example: "1.1")
        text: human-readable text to be displayed to the user -- this can
		contain anything at all
	uptime: epoch time (seconds since 1970) since the server started
S: motd:{ text }
	text: human-readble text to be displayed as a "message of the day"
		for the server -- sent only on initial login and never again.

The 'hello' message is an initial greeting intended only to confirm that the
server connected correctly and is speaking say2 protocol.

A client can periodically request the server-info block again by sending an
empty 'server-info' command.


*** LOGIN AS GUEST

C: login:{ nick email tagline? client? }
	nick: requested nickname (max 32 chars: first char from [A-Za-z_-],
        	others from [A-Za-z0-9_-])
        email: email address (max 40 chars)
	tagline: human-readable text displayed in /whois but otherwise not
		important to the server (max 60 chars)
	client: human-readable text that identifies the client version
		(example: "say2.pl v0.3") (max 60 chars)
S: welcome:{ nick id }
   	nick: the client's nickname, echoed back
	id: a unique ID identifying this user

All errors reported as "error" from above, except this one:
S: bad-nick:{ nick text }
   	nick: the nickname that was unacceptable
        text: a human-readable reason why


*** LOGIN AS REGISTERED USER

C: login:{ id password client? }
	id: the registered nickname (the 'nick' tag is acceptable here, too)
	password: the user's password
	client: human-readable text that identifies the client version
		(example: "say2.pl v0.3") (max 60 chars)
S: welcome:{ nick id }
   	nick: the client's registered nickname, echoed back
        id: the client's registered nickname, echoed back
        (yes, this is redundant, but meant to be consistent with guest logins)

All errors are reported as "error".
The tagline is pulled from the user's preferences.


*** PING

S: ping:{ }
C: ping:{ }

The server will periodically send out a 'ping' message to clients, to make
sure they're not stoned.  Clients should respond by echoing the 'ping' back.
Clients that don't respond within a set period of time (usually a matter of
minutes) will be disconnected.


*** CATCHING SIGNON/SIGNOFF

S: signon:{ nick id time email }
	nick: nickname of the user who just signed on
        id: registered id of the user who just signed on
        time: signon time, in epoch (seconds since 1970)
        email: user's email address [probably useless]
S: signoff:{ nick id time }
        nick: nickname of the user who just signed off
        id: registered id of the user who just signed off
	time: signoff time, in epoch


*** CHANGING USER PREFERENCES

C: prefs:{ tagline? notify? password? icon? }
   	tagline: human-readable text displayed in /whois but otherwise not
		important to the server (max 60 chars)
	notify: ("on" or "off") when notify is on, the client is notified
		of all logins/logouts on the server (this makes more sense
		for channels than for the registry server, obviously)
	password: yep, this is how you change your password
        icon: binary icon data (GUI clients may use this to display a custom
                icon for the user -- this isn't fully defined yet really);
                some servers may limit the size of this: sephiroth limits it
                to 4k or less
S: prefs:{ tagline notify password-changed? icon? }
	tagline: echoed back as it was saved
	notify: echoed back as it was saved
	password-changed: this key is present if the password has just been
		changed
        icon: this key is present if you have an icon set (the icon is NOT
                echoed back)

Note that the client doesn't have to send ANY tags in the 'prefs' command,
in which case it's just asking to see what the current values are.  (Any
preferences that aren't specified remain unchanged.)  The server never
echoes back a password; instead it includes the 'password-changed' tag if
the password has just been changed.

It is expected that the possible tags for this command will grow.


*** CHANGING STATUS

C: status:{ mode? text? }
	mode: ("active", "busy", or "away") the client's current level of
        	activity: largely user-defined what these mean.
        text: human-readable description of the current user's state
		(60 chars max)
S: status:{ nick id mode text? }
   	nick: nickname of the user
	id: unique ID for the user
	mode: ("active", "busy", "away" or "idle") whatever the client has
		set as its mode most recently, but if the client's mode is
		set as "active" and the server hasn't seen activity in a
                while, it may unilaterally change the mode to "idle".  a
                client can't ever set its mode to "idle".  a status update
		may not be sent by the server when a client's status mode
                changes from "active" to "idle".
	text: any human-readable description set by the client

Generally a descriptive text is set for the "busy" and "away" states, so that
other users are told why this user is busy or away.  For example, if a user
is set "away", the descriptive text might be "in a meeting".  A client may
set a descriptive text even when in "active" mode (unlike, for example, IRC),
and it's still displayed.  (Perhaps the user is here, but also watching TV.)

A client can probe its own status by sending an empty 'status' command.

When other users change their status mode or text, the server will broadcast
the new info in unsolicited 'status' messages.  This should probably follow
the client's "notify" preference, but currently it doesn't: FIXME.


*** CHANGING NICKNAME

C: change-nick:{ nick }
   	nick: new chosen nickname
S: change-nick:{ id old-nick nick }
	id: unique identifier for the user
	old-nick: previous nickname
        nick: new chosen nickname

Note that the 'id' field will remain the same across any nickname changes.
A user may always change nicknames to match her id, but can never change to
use the nickname of any other user or any other user's id.

When other users change their nicknames, the server will broadcast the new
info in unsolicited 'change-nick' messages.  This should probably follow the
client's "notify" preference, but currently it doesn't: FIXME.


*** LIST PEOPLE   [OBSOLETE -- DO NOT USE -- SEE "LIST CLIENTS" BELOW]

C: list-people:{ grep? verbose? }
	grep: if present, only list users whose nicknames or ids contain the
		grep text
	verbose: (a flag) if present, give the long response form

Short response:
S: members:{ type:"person" id-list:[ ] list:[ ] }
	type: always "person" for this request
	id-list: list of ids that are online (and matched the grep)
	list: list of nicknames that are online (and matched the grep)

Long response:
S: members-verbose:{ count fields:[ ] users:[ [ ] ] }
	count: number of users listed
	fields: list of fields returned
	users: list of users, where each user is a list of their values for
		the fields given in the 'fields' tag -- this is confusing
		and stupid, and is going away RSN


*** LIST CLIENTS   [as of v0.3.1]

C: list-clients:{ type grep? verbose? by-id? }
  	type: ("user", "channel" or "all") whether to list only users or
		channels, or to list both
	grep: if present, only list users whose nicknames or ids contain the
		grep text
	verbose: (a flag) if present, give the long response form
        by-id: (a flag) if present, list (and grep) users by their ids, not
		by their nicknames

Short response:
S: members:{ type list:[ ] }
	type: ("user", "channel" or "all") echoes what the client requested
	list: list of nicknames or ids that are online (and matched by the
		grep) -- channels are always listed by id since they have no
		nickname

Long response:
S: begin-response:{ count total response:"client-info" }
	count: number of messages to follow
        total: number of answers there were (might be larger than count, if
		the server thinks the answer would be too much to handle)
	response: always "user-info" in this case
S: client-info:{ ... }
   	There will always be as many of these as were listed in 'count' above.
S: end-response:{ response:"client-info" }


*** WHOIS USER  (user is online)

C: whois: { nick|id type? icon? }
	nick: nickname of the user to lookup  (or:)
        id: registered id of the user to lookup
        type: "user" (optional: if missing, "user" is assumed)
        icon: flag present if you want the icon attribute included in the
                response
S: client-info:{ id nick type:"user" email login idle tagline? mode status?
   status-time host client? icon? }
	id: registered id of the user
        nick: current nickname of the user (might be the same as the id)
        type: "user" always for users
        email: registered email address of the user
        login: login time, in epoch (seconds since 1970)
        idle: # of seconds since the user last sent a command
        tagline: the user's chosen human-readable description, if present
        mode: "active", "idle", "busy", or "away"
        status: human-readable text set by the client, if present
        status-time: time (in epoch) that the mode or status was last changed
        host: internet host the user is connected from
        client: human-readable description of the user's client software
        icon: binary icon data (if the "icon" flag was present in the
                request, and the user has an icon set)

S: not-present:{ nick|id type }
        nick: nickname of the whois query  (or:)
        id: registered id of the whois query
        type: "user"

The user mode is set by the user manually between "active", "busy", and
"away".  Usually the user's status text is a description explaining the
mode.  (For example, when the mode is "busy", the status text might be
"working on the server".)  The say2 server may sometimes change "active"
to "idle" if the user hasn't sent any commands in an arbitrary amount of
time.


*** WHOIS CHANNEL  (channel is online)

C: whois:{ id type }
	id: the channel's registered id
        type: "channel"
S: client-info:{ id type:"channel" email login idle tagline? host client?
   address }
	id: registered id of the channel
        type: always "channel" for channels
        email: registered email address of the channel
        login: login time, in epoch (seconds since 1970)
        idle: # of seconds since the channel last issued a command
        	[this makes NO sense]
        tagline: the channel's chosen human-readable description, if present
        host: internet host the channel is connected from
        client: human-readable description of the channel's software
        address: hostname (or IP) and port, in "hostname:port" format, where
        	the channel is accepting connections


*** WHOIS  (offline client)

C: whois: { id type? }
        id: registered id of the user to lookup
        type: "user" or "channel" (optional: if missing, "user" is assumed)
S: client-info:{ offline id type email tagline? last-login last-logout }
        offline: flag to underline the fact that this client is not currently
                logged in
	id: registered id of the user or channel
        type: "user" or "channel"
        email: registered email address of the user
        tagline: the user's chosen human-readable description, if present
	last-login: epoch time of the last login by this user
	last-logout: epoch time of the last logout by this user

S: no-such-client:{ id type }
        id: the registered id from the whois query
        type: "user" or "channel"


*** LOGIN AS CHANNEL

C: login:{ channel password client? address }
	channel: the registered channel name
	password: the channel's password
	client: human-readable text that identifies the client version
		(example: "sephiroth v0.4") (max 60 chars)
	address: "host:port" address that clients can use to connect
		to the channel
S: welcome:{ channel }
   	nick: the channel's registered name, echoed back
