psyced has a facility which allows to spawn external processes and communicate with them. This is done via the external helper application erq.

This is a bit like CGI, only much simpler and bidirectional. Programs and scripts need to be installed in psyced's run directory, then they can be accessed as shown in the following examples:

Listening to an external program

Here's an example for a psyced chatroom that connects to an external program, implemented in other technologies than psyclpc, at creation time (just put the place into init.ls if you need it to autoload, or trust your /subscribe command to load it) and accepts events from it. The external program could be some dirty tail -f <something> or better.

#include <net.h>

inherit NET_PATH "spawn";

launch();

#define NAME "SpawnListener"
#define CRESET launch();
#include <place.gen>

incoming(response) {
    castmsg(ME, "_notice_application_listener", response, ([]));
}

launch() {
    spawn("listener.sh", 0, #'incoming);
}
  1. The net.h include gives us access to standard psyced macros like ME.
  2. By multiple inheritance we add spawn functionality and its interface, the spawn() and send() functions (used in the next example).
  3. If we reference functions that we'll define further below, we have to prototype them ahead. I hate that.
  4. The <place.gen> include gives us standard room functions as described in create place. Our code must follow that.
  5. In the CRESET hook we call launch() to spawn the external program. CRESET is executed both at room creation time and at every reset which is an event psyced issues every fifteen minutes or so. This would restart the external application if it crashed, but cause no harm if it didn't.
  6. incoming() receives text lines from the external application. In our example it calls castmsg() in order to send a notice to all members of the place. It's assumed that many real applications will have some additional logic that makes this useful.
  7. In launch(), the parameters for spawn are the program to be called, "listener.sh" in this case, the command line parameters to feed the program, and a pointer to the function to be called when there is output from the external program (via its stdout), which is "#'" and the function name. In this example incoming() shall be called whenever our external script returns data.

Sending to an external program

Here's how to code a room that consults an external program or script for comment each time a visitor says something.

#include <net.h>

inherit NET_PATH "spawn";

outgoing();

#define NAME "SpawnTest"
#define ON_CONVERSE outgoing(data);
#include <place.gen>

incoming(response) {
    castmsg(ME, "_notice_application_SpawnTest", response, ([]));
}

outgoing(data) {
    spawn("test.crm", 0, #'incoming);  // optimized to launch just once
    send(data);
}
  1. The ON_CONVERSE define tells the room what function to execute when there is somebody speaking in the room.
  2. The outgoing() function will be called whenever such a user's message comes in. The data will be the content of the message. We are calling the spawn function here because it's safe to do so (it only spawns the first time).
  3. The send function is from the same place as spawn. The contents of the data variable are set by psyced to the content of the message and will be written to stdin of the external script we spawned.

My external script in this case, test.crm, is a simple filter script that loops infinitely, reads data off stdin line by line, blocks when there is no input and flushes its output. To be more useful, this example could probably be extended to demonstrate more information being sent to the target script.