ComicChat
I stumbled upon ComicChat recently and have been having a great deal
of fun learning node.js
while messing around with it.
So far I've added:
-
UI/UX
-
inline linkification
-
mIRC color, bold, italics, and underline formatting
-
force wrapping for longer phrases
-
ability to break panels based on total character count (patched upstream)
-
rotating backgrounds (patched upstream)
-
-
IRC relay (relay.js)
-
ability to join multiple channels (patched upstream)
-
-
Custom IRC relay (relay+.js)
-
ability to command join/part via PM to bot
-
-
Discord relay (discord-comicchat on Sourcehut)
-
/join
- "slash command" to join discord text channels
-
-
"Creative Controller", avatars customization, etc. (eventually)
In this post I'll start by giving some tips on getting starting running ComicChat. I'll also describe some additional tools I created or found while learning to with it/node.js and, finally, describe some of my ideas for what else could be possible with this cool project.
I'm leading with the "head-first" in this post, but I'll cover the design of ComicChat more generally (including describing some further ideas I have) toward the end of the post.
Using ComicChat
In the next example, we'll see how I typically start server.js
. This
is the program which creates the "service" (always running process) to
receive, store, and play-back text messages for the "client" to
transform into a web-comic.
nohup node.js /var/bots/comicchat/server/server.js >/var/bots/comicchat/log/server.log 2>&1 &
We feed the call to node via nohup
, which will keep the process
running even in case our shell session is interrupted. We also
redirect standard output (>
) as well as standard error outputs (2>&1
)
to a log file, overwriting it if it exists. Starting others
(e.g. relay.js
) is identical % file names.
A complete deployment includes server.js
, one or more of relay.js
,
relay+.js
, or discord-relay.js
(given you want messages sent in from
IRC/discord), and serving the files in the client
folder via a
web-server (I'm using apache2).
You can see an example here: http://dungeon-mode.com/toon/#dungeon
The (Original) Programs
Originally developed by Microsoft's David Kurlander in 1996, my work is based on a 2013 reimplementation in node.js by Ng Guoyou. I started looking at the code while experimenting with installing and configuring it and was at once taken by the direct minimal-but-readable style. Let's look each of Ng's programs before turning what I've created and am thinking of.
server/Server.js
This is the main ("ComicChat service") program. While it's running it maintains key data memory. The program doesn't have a confutation file, instead, defining important settings at the top of each program.
var wsServerPort = args.port || 8042;
var historySize = args.historySize || 1000;
var clients = [];
var rooms = {};
The server port must be exposed to the internet for web-socket
connections. You can change this port but must also change
client/js/client.js
to match.
The historySize
variable stores a number of text messages to keep in
memory for each channel. (Pedantic: In fact, messages are truncated
after adding new messages, so the functional max size of the history
is historySize + 1
while processing a new message to a channel with
max messages kept already.)
The clients
list stores the connections ("listeners") to the server.
The value for each element in this list is a node WebSocket connection
used to send data back to that client.
These track, for each text channel that the server instance is
tracking, a number of items including a history of messages. This is
perhaps best explained by a look at the initRoom
function.
function initRoom (name) {
// Init room if doesn't exist
rooms[name] = rooms[name] || {};
rooms[name].history = rooms[name].history || [];
rooms[name].clients = rooms[name].clients || [];
}
The name
and history
properties are hopefully self-evident. The
client
list stores, once again, node WebSocket
connections. Each
connection mentioned in a given channel's list revives a push of each
new message sent to that channel, allowing the web-comic to update in
Somewhat Real-Timeβ’, as messages are sent from IRC and/or Discord.
We call the initRoom
function when the server "joins" a channel.
Membership in the servers rooms
list controls whether that server will
accept messages targeted to that room.
Server Join vs Relay Join
It's worth noting, nothing discussed so far is to do with any "bot" or IRC or discord network/channel/server/connection: each such must track for itself what channels it should be present it, and keep that current with the server by sending messages to it over the Socket/WebSocket connection mentioned earlier.
Interprocess Messaging
Pograms pass instructions around to control each other, and
function as a coordinated system. For example if a bot (relay+
,
discord-comicchat
) receives a command to join a channel from a
user, in addition to joining in the chat network, the relay/bot
must send a message to the server, allowing it to initialize the
channel, etc. Meanwhile, any bot would use this same
functionality to pass messages text back from the room to the
server, and thus to clients/listeners.
Because it has several always/often running parts, ComicChat needs
a means for communication and coordination between them. The
WebSocket
provides the technical transportation component. For
the rest we craft custom JSON message, according to a certain
design, and take care to send/use those messages consistently
throughout all the programs in application. Taken together we
have a simple Protocol.
From ComicChat's Readme:
Connect to the WebSocket server and start pushing JSON. Subject to change.
Send
{
type: 'join',
room: 'room'
}
-
`history`, `join`, `part` require `room`
-
`message` requires `room` and `text`, `spoof: true` optional for relays
Receive
-
`history` β `type`, `history` (an array of messages for the requested room)
-
`message` β `type`, `room`, `time`, `text`, `author`
More Server Message Examples
White this may seem terse to the point of cryptic, in fact it provides for a quite functional API ("Application Programing Interface").
A example join message is above. A part message is similar:
{
type: 'part',
room: 'room'
}
The message object, as mentioned must include type
, room
, and text
. A minmal example is:
{
type: 'message',
message: 'Hello, ComicChat',
room: 'room'
}
When a message also provides spoof
(with a truthy value), we can
additionally pass an explicit author. (In fact, this is the usual
case, as when relaying from IRC or Discord. The alternate case, when
we assume the author, supports text messages originating from the
web-client. We'll come back to this when we discuss client/js/ui.js
.)
Here's a message example providing an author, "someone":
{
type: 'message',
message: 'Hello again, ComicChat',
author: "someone",
spoof: true,
room: 'room'
}
relay/relay.js
Relay provide a means of "feeding" messages into the ComicChat
server from some other source. In the case of relay
.js (in the
relay folder of the ComicChat project), provides a bare-bones IRC
bot (automated client for an IRC network) able to join a
predefined set of channels and passes the messages it sees in them
to the ComicChat server. There aer no interactive commands
(e.g. via public/private message with the bot via the chat network).
As with the server component, relay is configured within the program. Relay has a more substantial set of "options":
var config = {
cchat: {
nick: 'petroglyph',
room: '##freenode',
host: 'dungeon-mode.com', // hidoi.moebros.org
port: 8042,
roomLink: 'http://dungeon-mode.com/toon/#dungeon'
},
irc: {
nick: 'petroglyph',
user: 'comic',
real: 'relay',
channels: [ '#dungeon' ],
host: 'irc.libera.chat',
port: 6697,
ssl: true
}
};
The first group of options ccchat
coordinates with configuration given
in/to the server
, described above, and with your (my) web-server
configuration discussed below.
Most important here are host
, which is the host or IP (localhost
should work, here) where we'll connect to the ComicChat server, and
port, which should match what you have in server.js
.
In the IRC block we configure our simple IRC client/bot with the information it will need to connect the network and join channels.
The nick
used on IRC can be different from config.cchat.nick
, the
latter of which is used for messages sent to the sever (so, for the
comic) from the bot. The bot makes brief "hard-coded" messages
channel on the ComicChat server as well as an entry message each time
it joins an IRC channel. The IRC nick on the other hand
(config.irc.nick
) is used to establish a name for the bot on the IRC
network. (IRC servers don't repeat our own messages back to us.)