Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: CI

on:
push:
branches: [master]
pull_request:

jobs:
build:
name: Build Docker images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Prepare runtime files
run: |
mkdir -p simojs-data/html
cp settings.json.example simojs-data/settings.json
# Override IRC server to point at the ircdjs container
python3 -c "
import json
s = json.load(open('simojs-data/settings.json'))
s['general']['server'] = 'ircdjs'
s['general']['channels'] = ['#simo']
s['general']['botnick'] = 'Simo'
json.dump(s, open('simojs-data/settings.json', 'w'), indent=4)
"
echo '{}' > simojs-data/macros.js
cp settings_pythonsimo.cfg.example settings_pythonsimo.cfg
mkdir -p pythonsimo-data && touch pythonsimo-data/placeholder

- name: Build images
run: docker compose build ircdjs sandbox simojs

integration-tests:
name: IRC integration tests
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'

- name: Prepare runtime files
run: |
mkdir -p simojs-data/html
cp settings.json.example simojs-data/settings.json
python3 -c "
import json
s = json.load(open('simojs-data/settings.json'))
s['general']['server'] = 'ircdjs'
s['general']['channels'] = ['#simo']
s['general']['botnick'] = 'Simo'
json.dump(s, open('simojs-data/settings.json', 'w'), indent=4)
"
echo '{}' > simojs-data/macros.js
cp settings_pythonsimo.cfg.example settings_pythonsimo.cfg
mkdir -p pythonsimo-data && touch pythonsimo-data/placeholder

- name: Start services
run: docker compose up --build -d ircdjs sandbox simojs

- name: Wait for bot to connect
run: |
timeout 90 bash -c '
until docker compose logs simojs 2>/dev/null | grep -q "366 Simo"; do
sleep 2
done
'

- name: Run IRC integration tests
run: node test/runner.js

- name: Show service logs on failure
if: failure()
run: docker compose logs simojs sandbox ircdjs

- name: Stop services
if: always()
run: docker compose down
122 changes: 122 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# AGENTS.md

This file provides guidance to AI coding agents when working with code in this repository.

## Running

Development runs entirely through Docker Compose. There is no npm start or test command — the bot only runs inside containers.

```bash
# First-time setup
cp settings.json.example simojs-data/settings.json
# Edit simojs-data/settings.json with your IRC server, credentials, and API keys
cp settings_pythonsimo.cfg.example settings_pythonsimo.cfg
mkdir -p simojs-data/html pythonsimo-data
touch pythonsimo-data/placeholder
echo '{}' > simojs-data/macros.js

# Start
docker-compose up --no-deps redis sandbox pythonsimo simojs

# Tail logs
docker-compose logs -f simojs
```

`docker-compose.override.yml` is automatically merged and adds `ircdjs` for local dev. For local development without a real IRC server, point `settings.json` at it:

```bash
# In simojs-data/settings.json: "server": "ircdjs", "channels": ["#simo"], "botnick": "Simo"
docker-compose up --no-deps redis sandbox pythonsimo simojs ircdjs
```

Production deployments should skip the override:

```bash
docker compose -f docker-compose.yml up
```

The `simojs-data/` directory is mounted into the container at `/simojs-data/` and holds runtime state: `settings.json`, `macros.js`, SQLite DB, and generated HTML output.

## Running tests

```bash
node test/runner.js
```

Tests connect to localhost:6667, join `#simo`, and send IRC commands. The bot must be running. See `test/cases.json` for test cases and `test/client.js` for an interactive manual test client.

## Architecture

`main.js` is the entry point. It:
1. Reads `settings.json` from `/simojs-data/`
2. Loads all features from `features/index.js` (auto-discovery — every `.js` file except `index.js`)
3. Connects to IRC via `irc-upd`
4. Dispatches incoming messages through regex handlers, URL title fetcher, and `MultiCommand`

**Message dispatch flow:**
- Regex handlers in features run first on every message
- Non-command messages (no leading `!`) go to `lib/urltitle.js` for URL title fetching only
- Command messages (`!foo`) go through the hypermacro expander if they contain `!*`, then `MultiCommand`
- `MultiCommand` supports chaining up to depth 100: commands pipe output as input to the next command
- Unknown JS commands fall through to the `pythonsimo` HTTP service; if that fails and the command starts with `!+` or `!_`, it falls back to the sandcastle `!run`

## Macro system

Macros live in `/simojs-data/macros.js` (JSON). Three conventions:

| Prefix | Convention | Example |
|--------|-----------|---------|
| `!+name` | Takes args — `arg` variable holds the input | `exit(arg * 2)` |
| `!_name` | Prints — result is concat-merged with original line | `exit('hello world')` |
| `!*name` | Hypermacro — expands to a chain of other macros/commands | `+weatherapi _getlocation` |

Hypermacros are expanded by macrofy before dispatch. The expansion is recursive up to depth 100. Macros are re-read from disk on every message invocation.

Managing macros via IRC: `!addmacro`, `!delmacro`, `!printmacro`, `!listmacros`.

Sandbox scripts use `exit(value)` to return a result. Global variables from `lib/api.js` are available directly: `moment`, `axios`, `request`, `_` (lodash), `cheerio`, `rand`.

## Feature plugin system

Each file in `features/` is auto-loaded. A feature exports:

```js
module.exports = {
commands: {
'!cmd': function(client, channel, from, line) { ... }
},
regexes: {
'key': function(client, channel, from, line) { ... }
},
init: function(config, client) { ... } // optional, runs at startup
}
```

See `features/example.js` for the minimal template. Multiple features can register the same command — all handlers run.

## Key library files

- `lib/multicommand.js` — Command chaining engine; falls through to pythonsimo then sandcastle for `!+`/`!_` macros
- `lib/timerpoller.js` / `lib/timerdb.js` — Polls SQLite for scheduled timer messages
- `lib/urltitle.js` — Fetches and posts page titles for URLs in chat
- `lib/api.js` — API globals exposed to sandcastle scripts (`moment`, `axios`, `request`, `_`, etc.)
- `lib/simoInflux.js` — InfluxDB metric writer (currently disabled in dispatch)

## Services (docker-compose)

| Service | Purpose |
|---------|---------|
| `simojs` | Main IRC bot (Node.js 18) |
| `sandbox` | Sandcastle eval service (Node.js 8) — handles `!run` and user macros |
| `ircdjs` | Local IRC server — defined in `docker-compose.override.yml`, not used in production |
| `pythonsimo` | Python companion at port 8888; handles commands unknown to the JS side |
| `redis` | Data store (used by pythonsimo) |
| `influxdb` | Metrics storage (v1.8) |
| `llama` | Local LLM (llama.cpp) at port 8111, used by `!simogpt` / `!simoq` — needs a model file |

## Notes

- The sandbox service runs on Node.js 8 deliberately — sandcastle's vm isolation relies on pre-Node-12 behavior.
- Long LLM responses are written to `/simojs-data/html/` and served at `http://gpt.prototyping.xyz/`.
- IRC messages are truncated to 400 characters before sending; most features use 390 as a safe limit.
- `pythonsimo` crashes on startup without its word2vec model file — this is expected in dev and the bot handles it gracefully.
20 changes: 4 additions & 16 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
FROM debian:10
FROM node:18-slim

RUN apt-get update && apt-get install -y libicu-dev nodejs npm python make g++ openssh-client bash curl

#RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
ENV NVM_DIR /usr/local/nvm
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash

ENV NODE_VERSION v8.17.0
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION"

ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node/$NODE_VERSION/bin:$PATH
RUN apt-get update && apt-get install -y libicu-dev python3 python-is-python3 make g++ openssh-client bash curl && rm -rf /var/lib/apt/lists/*

RUN adduser --disabled-password simobot

Expand All @@ -30,13 +20,11 @@ ADD ./lib/* ./lib/
ADD ./macros/* /simobot/macros/
ADD ./resources/* /simobot/resources/
ADD ./features/* /simobot/features/
ADD addHost.sh backupSimo.sh dumpExpl.sh .gitignore joindb.js LightweightApi.py main.js marker migrate-logs.js package.json README.md redis.conf repeatSimo settings.json.example settings_pythonsimo.cfg settings_pythonsimo.cfg.example simojs.sqlite telegraf.conf test.log testscript ./
ADD addHost.sh backupSimo.sh dumpExpl.sh .gitignore joindb.js LightweightApi.py main.js marker migrate-logs.js package.json README.md redis.conf repeatSimo settings.json.example settings_pythonsimo.cfg settings_pythonsimo.cfg.example simojs.sqlite telegraf.conf ./
ADD ./templates/* /simobot/templates/

RUN usermod -u 1000 simobot
RUN chown -R simobot:simobot /simobot
#RUN chown -R simobot:simobot /simobot/simojs-data/
USER simobot

#CMD ["sh", "-c", "./addHost.sh && ./repeatSimo"]
CMD ["sh", "-c", "./repeatSimo"]
CMD ["node", "main.js"]
6 changes: 3 additions & 3 deletions Dockerfile_ircdjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM alpine:3.4
FROM node:18-alpine

RUN apk add --no-cache icu-dev nodejs git python make g++
RUN apk add --no-cache icu-dev git python3 make g++
RUN adduser -D nodejs
USER nodejs
WORKDIR /home/nodejs
RUN npm install ircdjs && npm cache clear
RUN npm install ircdjs && cd node_modules/ircdjs && npm install commander@2

EXPOSE 6667

Expand Down
12 changes: 12 additions & 0 deletions Dockerfile_sandbox
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:8-slim

RUN mkdir /sandbox
ADD ./sandbox/package.json /sandbox/
WORKDIR /sandbox
RUN npm install

ADD ./lib/api.js /sandbox/api.js
ADD ./sandbox/server.js /sandbox/

EXPOSE 3456
CMD ["node", "server.js"]
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,38 @@ Simo.js
Simo goes javascript

### Running local development version
- clone repo
- remove .example postfix from settings files
- uncomment ircdjs-server from docker-compose (only if you want to use local irc server)
- run docker-compose up

All logs are redirected to container stdouts so docker-compose logs <--follow> is your friend
```bash
# Clone and set up config
cp settings.json.example simojs-data/settings.json
# Edit simojs-data/settings.json with your IRC server, credentials, and API keys
cp settings_pythonsimo.cfg.example settings_pythonsimo.cfg
mkdir -p simojs-data/html pythonsimo-data
touch pythonsimo-data/placeholder
echo '{}' > simojs-data/macros.js

#### Feature development
docker-compose up --no-deps redis sandbox pythonsimo simojs
```

- See features/example.js
All logs go to container stdouts — `docker-compose logs -f simojs` is your friend.

#### Project Management
![](https://i.imgur.com/uJnLJcT.png)
`llama` is excluded above since it requires a model file. Add it back and mount a model under `./models/` when needed.

#### Local development with a local IRC server

Point `settings.json` at `ircdjs` and start it alongside the bot:

```bash
# In simojs-data/settings.json set: "server": "ircdjs"
docker-compose up --no-deps ircdjs redis sandbox pythonsimo simojs
```

### Running tests

```bash
node test/runner.js
```

### Feature development

See `features/example.js`.
6 changes: 0 additions & 6 deletions SimoStats/css/custom.css

This file was deleted.

Loading
Loading