After hours

Install DotSIP, open the web admin, connect real SIP, and decide each call from your own HTTP API.

Outside opening hours your server, not DotSIP, looks at the clock and at the hours table. It returns one of three plans: an announcement and hangup, a gather for a callback number, or a recording ended with hash. The message file stays on the PBX. Your database stores only the file name.

1. Records in DotSIP

  1. Put closed.wav and callback.wav in data/media. Mono PCM16, 8000, 16000, or 24000 Hz.
  2. Under SIP accounts, the trunk that receives the call. Set its inbound route to route-tools, or leave that field empty so the call uses route-tools.
  3. route-tools URL http://127.0.0.1:9099/route. Digit and recording callbacks are paths on that process.

2. Your database

CREATE TABLE hours (id TEXT PRIMARY KEY, open_hour INTEGER NOT NULL, close_hour INTEGER NOT NULL, mode TEXT NOT NULL);
INSERT INTO hours(id, open_hour, close_hour, mode) VALUES ('desk', 8, 16, 'message');
CREATE TABLE callbacks (call_id TEXT PRIMARY KEY, digits TEXT NOT NULL);
CREATE TABLE recordings (call_id TEXT PRIMARY KEY, file TEXT NOT NULL);

mode is message, callback, or record. Change the row. The server uses it on the next call. Hours are compared to the server clock, hour 0–23. Inside 8–16 the sample hangs up. Outside, it uses mode.

3. The server

<?php
$db = new PDO("sqlite:" . __DIR__ . "/operator.sqlite");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE IF NOT EXISTS hours (id TEXT PRIMARY KEY, open_hour INTEGER NOT NULL, close_hour INTEGER NOT NULL, mode TEXT NOT NULL)");
$db->exec("INSERT OR IGNORE INTO hours(id, open_hour, close_hour, mode) VALUES ('desk', 8, 16, 'message')");
$db->exec("CREATE TABLE IF NOT EXISTS callbacks (call_id TEXT PRIMARY KEY, digits TEXT NOT NULL)");
$db->exec("CREATE TABLE IF NOT EXISTS recordings (call_id TEXT PRIMARY KEY, file TEXT NOT NULL)");
$body = json_decode(file_get_contents("php://input"), true);
header("Content-Type: application/json");
$format = is_array($body) && isset($body["format"]) ? $body["format"] : "";
if ($format === "dotsip.dtmf") {
    $stmt = $db->prepare("INSERT OR REPLACE INTO callbacks(call_id, digits) VALUES (?, ?)");
    $stmt->execute(array(isset($body["call_id"]) ? $body["call_id"] : "", isset($body["digits"]) ? $body["digits"] : ""));
    echo json_encode(array("commands" => array(array("do" => "hangup"))));
    return;
}
if ($format === "dotsip.recording") {
    $stmt = $db->prepare("INSERT OR REPLACE INTO recordings(call_id, file) VALUES (?, ?)");
    $stmt->execute(array(isset($body["call_id"]) ? $body["call_id"] : "", isset($body["file"]) ? $body["file"] : ""));
    echo json_encode(array("commands" => array(array("do" => "hangup"))));
    return;
}
if ($format !== "dotsip.route") {
    http_response_code(400);
    echo json_encode(array("error" => "bad request"));
    return;
}
$row = $db->query("SELECT open_hour, close_hour, mode FROM hours WHERE id = 'desk'")->fetch(PDO::FETCH_ASSOC);
$hour = (int) date("G");
$open = $hour >= (int) $row["open_hour"] && $hour < (int) $row["close_hour"];
if ($open) {
    $commands = array(array("do" => "hangup"));
} elseif ($row["mode"] === "callback") {
    $commands = array(
        array("do" => "answer"),
        array("do" => "play", "media" => "callback.wav", "barge_in" => false),
        array("do" => "gather", "max_digits" => 16, "timeout_ms" => 20000, "finish_on" => "#", "callback_url" => "http://127.0.0.1:9099/digits", "wait" => true),
    );
} elseif ($row["mode"] === "record") {
    $commands = array(
        array("do" => "answer"),
        array("do" => "play", "media" => "closed.wav", "barge_in" => false),
        array("do" => "record", "finish_on" => "#", "callback_url" => "http://127.0.0.1:9099/recording", "wait" => true),
    );
} else {
    $commands = array(
        array("do" => "answer"),
        array("do" => "play", "media" => "closed.wav", "barge_in" => false),
        array("do" => "hangup"),
    );
}
echo json_encode(array("format" => "dotsip.route-result", "format_version" => 2, "commands" => $commands));

Set mode to callback and call outside 8–16. Enter the number and press #. callbacks stores the digits. Set mode to record and the voice file is data/recordings/<call_id>.wav on the PBX. recordings.file stores only the name from dotsip.recording. finish_on defaults to #.