2026-04-06 23:40:03 +02:00
|
|
|
// Hello plugin — Phase C Step 9 proof-of-life.
|
|
|
|
|
//
|
|
|
|
|
// Build with TinyGo:
|
|
|
|
|
//
|
|
|
|
|
// tinygo build -o hello.wasm -target wasi ./main.go
|
|
|
|
|
//
|
|
|
|
|
// The module exports the five functions the OwnCord plugin ABI requires:
|
|
|
|
|
//
|
|
|
|
|
// allocate(size) → ptr — host writes input into module memory
|
|
|
|
|
// deallocate(ptr, size) — host signals it's done with a buffer
|
|
|
|
|
// list_commands() → (ptr, len) — JSON array of command names
|
|
|
|
|
// command_dispatch(p, l) → (ptr, len) — handle a slash command, return JSON reply
|
|
|
|
|
// on_event(ptr, len) — receive a broadcast event (no-op here)
|
2026-04-07 05:28:53 +00:00
|
|
|
//
|
|
|
|
|
// The file is guarded with the "tinygo" build constraint so the standard Go
|
|
|
|
|
// toolchain (go build / go vet) ignores it. Compile with:
|
|
|
|
|
//
|
|
|
|
|
// tinygo build -o hello.wasm -target wasi ./main.go
|
|
|
|
|
|
|
|
|
|
//go:build tinygo
|
|
|
|
|
|
2026-04-06 23:40:03 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"unsafe"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {}
|
|
|
|
|
|
|
|
|
|
// allocations keeps live references so the GC does not reclaim buffers the
|
|
|
|
|
// host is still holding a pointer to.
|
|
|
|
|
var allocations [][]byte
|
|
|
|
|
|
|
|
|
|
//export allocate
|
|
|
|
|
func allocate(size uint32) uint32 {
|
|
|
|
|
buf := make([]byte, size)
|
|
|
|
|
allocations = append(allocations, buf)
|
|
|
|
|
return uint32(uintptr(unsafe.Pointer(&buf[0])))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//export deallocate
|
|
|
|
|
func deallocate(ptr uint32, size uint32) {
|
|
|
|
|
for i, b := range allocations {
|
|
|
|
|
if uint32(len(b)) == size && uint32(uintptr(unsafe.Pointer(&b[0]))) == ptr {
|
|
|
|
|
allocations = append(allocations[:i], allocations[i+1:]...)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// listCommandsJSON is the static payload returned by list_commands.
|
|
|
|
|
var listCommandsJSON = []byte(`["hello"]`)
|
|
|
|
|
|
|
|
|
|
//export list_commands
|
|
|
|
|
func listCommands() (uint32, uint32) {
|
|
|
|
|
return uint32(uintptr(unsafe.Pointer(&listCommandsJSON[0]))), uint32(len(listCommandsJSON))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type dispatchInput struct {
|
|
|
|
|
UserID int64 `json:"user_id"`
|
|
|
|
|
ChannelID int64 `json:"channel_id"`
|
|
|
|
|
Command string `json:"command"`
|
|
|
|
|
Args []string `json:"args"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type dispatchOutput struct {
|
|
|
|
|
Reply string `json:"reply"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// resultBuf holds the most recent command_dispatch result. Safe because the
|
|
|
|
|
// host is single-threaded within a plugin invocation.
|
|
|
|
|
var resultBuf []byte
|
|
|
|
|
|
|
|
|
|
//export command_dispatch
|
|
|
|
|
func commandDispatch(ptr uint32, length uint32) (uint32, uint32) {
|
|
|
|
|
raw := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), length)
|
|
|
|
|
|
|
|
|
|
var in dispatchInput
|
|
|
|
|
if err := json.Unmarshal(raw, &in); err != nil {
|
|
|
|
|
resultBuf = []byte(`{"reply":"hello: malformed payload"}`)
|
|
|
|
|
} else {
|
|
|
|
|
reply := "Hello from the hello plugin!"
|
|
|
|
|
if len(in.Args) > 0 {
|
|
|
|
|
reply = "Hello, " + in.Args[0] + "!"
|
|
|
|
|
}
|
|
|
|
|
out, _ := json.Marshal(dispatchOutput{Reply: reply})
|
|
|
|
|
resultBuf = out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return uint32(uintptr(unsafe.Pointer(&resultBuf[0]))), uint32(len(resultBuf))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//export on_event
|
|
|
|
|
func onEvent(_ uint32, _ uint32) {}
|