type type_event = { timestamp: int; kind: string; data: any; }; type type_user = { name: string; role: string; }; type type_connection = { events: Array; users: Array; client: any; }; type type_internal_request = { id: (null | string); action: string; data: any; }; type type_internal_response = any; function get_timestamp(): int { return Math.floor(Date.now()/1000); } function generate_id(): string { return (Math.random() * (1 << 24)).toFixed(0).padStart(8, '0'); } var nm_irc: any = require("irc"); var _connections: Record = {}; var _conf: any = {}; function log(level: int, incident: string, details: Record = {}): void { if (level <= _conf["verbosity"]) { process.stderr.write(`-- ${incident} | ${lib_json.encode(details)}\n`); } } function get_connection(id: string): type_connection { if (! _connections.hasOwnProperty(id)) { throw (new Error("no connection for ID '" + id + "'")); } else { return _connections[id]; } } async function execute(internal_request: type_internal_request, ip_address: string): Promise { switch (internal_request.action) { default: { throw (new Error("unhandled action: " + internal_request.action)); break; } case "connect": { if (_connections.hasOwnProperty(internal_request.id)) { throw (new Error("already connected")); } else { const id: string = generate_id(); const client = new nm_irc.Client ( internal_request.data["server"], internal_request.data["nickname"], { "realName": "webirc", "userName": lib_sha256.get(ip_address).slice(0, 8), "channels": internal_request.data["channels"], "showErrors": true, "autoConnect": false, } ); let connection: type_connection = { "client": client, "events": [], "users": [], }; client.addListener ( "message", (from, to, message) => { connection.events.push ({ "timestamp": get_timestamp(), "kind": "channel_message", "data": {"from": from, "to": to, "message": message} }); } ); client.addListener ( "pm", (from, message) => { connection.events.push ({ "timestamp": get_timestamp(), "kind": "private_message", "data": {"from": from, "message": message} }); } ); client.addListener ( "names", (channel, users) => { connection.users = Object.entries(users).map(([key, value]) => ({"name": key, "role": value.toString()})); } ); client.addListener ( "error", (error) => { log(0, "irc_error", {"reason": error.message}); } ); client.connect ( 3, () => { _connections[id] = connection; } ); return Promise.resolve(id); } break; } case "check": { try { get_connection(internal_request.id); return Promise.resolve(true); } catch (error) { return Promise.resolve(false); } break; } case "disconnect": { const connection: type_connection = get_connection(internal_request.id); delete _connections[internal_request.id]; connection.client.disconnect("", () => {}); return Promise.resolve(null); break; } case "send": { const connection: type_connection = get_connection(internal_request.id); connection.client.say(internal_request.data["channel"], internal_request.data["message"]); return Promise.resolve(null); break; } case "fetch": { const connection: type_connection = get_connection(internal_request.id); const internal_response: type_internal_response = { "users": connection.users, "events": connection.events, }; connection.events = []; return Promise.resolve(internal_response); break; } } } async function main(): Promise { _conf = await lib_plankton.file.read("conf.json").then(x => lib_json.decode(x)); const server: lib_server.class_server = new lib_server.class_server ( _conf["port"], async (input: string, metadata?: lib_server.type_metadata): Promise => { const http_request: lib_http.type_request = lib_http.decode_request(input); log(2, "http_request", http_request); const internal_request: type_internal_request = lib_json.decode(http_request.body); log(1, "internal_request", internal_request); let internal_response: type_internal_response; let error: (null | Error); try { internal_response = await execute(internal_request, metadata.ip_address); error = null; } catch (error_) { internal_response = null; error = error_; } if (error !== null) { log(0, "error_in_execution", {"reason": error.toString()}); } const http_response: lib_http.type_response = ( (error !== null) ? { "statuscode": 500, "headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "text/plain"}, "body": "error executing the request; check the server logs for details", } : { "statuscode": 200, "headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "application/json"}, "body": lib_json.encode(internal_response) } ); log(2, "http_response", http_response); const output: string = lib_http.encode_response(http_response); return Promise.resolve(output); } ); return server.start(); } main();