wgrok

package module
v1.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 6, 2026 License: MIT Imports: 24 Imported by: 0

README

wgrok

v1.2.4 | PyPI | npm | crates.io | Go

A message bus protocol over social messaging platforms. Uses platform APIs (Webex, Slack, Discord) as transport to allow agents, services, and orchestrators to communicate across network boundaries without inbound webhooks.

Install

pip install wgrok                          # Python
npm install wgrok-message-bus              # TypeScript
cargo add wgrok                            # Rust
go get github.com/3rg0n/wgrok/go@v1.2.4   # Go

Protocol

All wgrok messages use a four-field colon-delimited format:

./echo:{to}:{from}:{flags}:{payload}
  • to — destination slug (which agent should process this)
  • from — sender identifier (return address, or - for anonymous)
  • flags — compression/encryption/chunking metadata (-, z, e, ze, 1/3, z2/5, ze1/3)
  • payload — message body (can contain colons)

The ./echo: prefix signals "this is a wgrok message". The router bot strips it and relays the remaining four fields transparently.

The payload is opaque — wgrok never inspects or transforms it. JSON, CSV, NDJSON, plain text, base64, YAML — whatever the sender puts in, the receiver gets out verbatim.

Wire format
Sending (echo):    ./echo:{to}:{from}:{flags}:{payload}
Receiving (response): {to}:{from}:{flags}:{payload}

The router bot strips the ./echo: prefix, routes on the to field, and passes through from and flags unchanged.

Flags
Flag Meaning
- No compression, no encryption, no chunking
z Payload is gzip+base64 compressed
e Payload is AES-256-GCM encrypted
ze Compressed then encrypted
1/3 Chunk 1 of 3 (uncompressed)
z2/5 Chunk 2 of 5 (compressed then chunked)
e1/3 Chunk 1 of 3 (encrypted)
ze2/5 Chunk 2 of 5 (compressed, encrypted, chunked)

Flags are ordered: [z][e][N/M]. Compression, encryption, and chunking are handled automatically by the sender/receiver libraries. The router bot never inspects flags.

Mode B — Agent Bus

Agents share the same messaging token — all see all messages. The to field identifies which agent should process the result.

Sender ──./echo:deploy-agent:sender:-:start deploy──► Router Bot
                                                          │
Receiver ◄──deploy-agent:sender:-:start deploy──────── Router Bot
(only processes because to="deploy-agent" matches its slug)

Simple, lightweight. Best for same trust zone, same network boundary. No registry needed — agents self-select by slug.

Mode C — Registered Agents

Agents have their own bot identities and register with the routing bot. The routing bot maintains a registry mapping slugs to bot identities. Agents don't need shared tokens. Cross-platform routing is possible.

Routing bot registry:
  deploy = deploy-bot@spark.com
  status = status-bot@foo.com

Sender ──./echo:deploy:myagent:-:start──► Routing Bot
                                              │ (looks up "deploy" → deploy-bot@spark.com)
deploy-bot@spark.com ◄──deploy:myagent:-:start── Routing Bot

The from field tells the receiving agent who sent the message, enabling reply-to patterns.

Protocol layers
Echo (sent):      ./echo:{to}:{from}:{flags}:{payload}   ← what senders produce
Response (recv):  {to}:{from}:{flags}:{payload}           ← what receivers consume

Mode B and Mode C share the same wire format. The difference is how the routing bot resolves to:

  • Mode B: echo back to sender (agents self-select by slug)
  • Mode C: look up to in registry, route to registered bot
  • Fallback: registered slugs get routed, unregistered slugs get echoed — Mode B and C coexist on the same routing bot
Codec

The sender/receiver libraries include a built-in codec for large payloads:

  • Compression: compress=True gzips the payload and base64-encodes it. The z flag tells the receiver to decompress.
  • Auto-chunking: When a message exceeds the platform limit, the sender splits it into numbered chunks (1/3, 2/3, 3/3). The receiver buffers and reassembles automatically.
Platform Message limit
Webex 7,439 bytes
Slack 4,000 chars
Discord 2,000 chars
IRC 400 bytes

Codec is transparent — application code sends and receives full payloads without knowing about compression or chunking.

Encryption

Optional AES-256-GCM encryption for payload confidentiality. On/off mode — set WGROK_ENCRYPT_KEY to enable, omit to disable.

# Generate a 32-byte key (base64-encoded)
WGROK_ENCRYPT_KEY=$(openssl rand -base64 32)

When the key is configured:

  • Sender auto-encrypts every outgoing payload and sets the e flag
  • Receiver auto-decrypts when it sees the e flag
  • Router bot relays encrypted payloads transparently (never inspects them)

Pipeline order: compress → encrypt → base64 → chunk (send), reassemble → base64 → decrypt → decompress (receive).

Wire format: base64(12-byte IV || ciphertext || 16-byte GCM tag). Each message gets a random IV. The GCM tag provides both authenticity and integrity — tampered messages are rejected.

The same key must be shared between sender and receiver. The router bot does not need the key.

Language Crypto library
Python cryptography (AES-NI accelerated via OpenSSL)
Go crypto/aes + crypto/cipher (stdlib, AES-NI accelerated)
TypeScript node:crypto (AES-NI accelerated via OpenSSL)
Rust aes-gcm crate (AES-NI accelerated)

Use cases

App routing — One bot, every internal API behind it. ./jira:..., ./deploy:..., ./grafana:... — developers integrate with one SDK instead of building individual integrations for each service.

Firewall traversal — GitHub Actions orchestrator talks to on-prem orchestrator. Both share a token, router bot relays through Webex. ./echo:{to}:{from}:{flags}:{payload} traverses the firewall without inbound ports.

Multi-agent pub/sub — Multiple agents on one account, each with a unique slug. All agents see all messages but only process their own slug. NATS-style message bus over a chat platform.

Cross-boundary agents — Agents with their own bot identities register with the routing bot. A deploy agent on Webex, a status agent on Slack, a Jira agent wrapping the REST API — all reachable through the same routing bot. WGROK_ROUTES maps slugs to bot identities.

Languages

Implemented in four languages with identical behavior and shared test cases:

Language Directory Package
Python python/ wgrok
Go go/ github.com/3rg0n/wgrok/go
TypeScript ts/ wgrok
Rust rust/ wgrok

Quick start

1. Register a bot

Create a bot on your messaging platform. You need at minimum two tokens:

  • One for the bot (the relay/routing service)
  • One shared token for senders and receivers (Mode B), or individual tokens per agent (Mode C)
2. Configure environment
cp python/.env.example python/.env   # or go/, ts/, rust/
# Sender / Receiver
WGROK_TOKEN=<shared token>
WGROK_PLATFORM=webex
WGROK_TARGET=bot@webex.bot
WGROK_SLUG=myagent
WGROK_DOMAINS=example.com

# Routing Bot (separate .env)
WGROK_WEBEX_TOKENS=token1,token2,token3
WGROK_SLACK_TOKENS=xoxb-token1,xoxb-token2
WGROK_DOMAINS=example.com

# Agent Registry (Mode C — optional)
WGROK_ROUTES=deploy:deploy-bot@spark.com,status:status-bot@foo.com

# Webhook endpoint (routing bot — optional)
WGROK_WEBHOOK_PORT=8080
WGROK_WEBHOOK_SECRET=shared-secret

# Encryption (optional — on/off, same key on sender + receiver)
WGROK_ENCRYPT_KEY=<base64-encoded 32-byte key>

# Optional
WGROK_DEBUG=true
WGROK_PROXY=http://proxy.corp.com:8080
3. Use in your project

Python:

from wgrok import WgrokSender, WgrokReceiver, SenderConfig, ReceiverConfig

# Send
sender = WgrokSender(SenderConfig.from_env())
await sender.send("hello world")
await sender.send("large payload", compress=True)  # gzip+base64, auto-chunks
await sender.close()

# Receive
async def handler(slug, payload, cards, from_slug):
    print(f"Got from {from_slug}: {payload}")

receiver = WgrokReceiver(ReceiverConfig.from_env(), handler)
await receiver.listen()

Go:

import wgrok "github.com/3rg0n/wgrok/go"

// Send
cfg, _ := wgrok.SenderConfigFromEnv()
sender := wgrok.NewSender(cfg)
sender.Send("hello world", nil)

// Receive
rcfg, _ := wgrok.ReceiverConfigFromEnv()
receiver := wgrok.NewReceiver(rcfg, func(slug, payload string, cards []interface{}, fromSlug string) {
    fmt.Printf("Got from %s: %s\n", fromSlug, payload)
})
receiver.Listen(ctx)

TypeScript:

import { WgrokSender, WgrokReceiver, senderConfigFromEnv, receiverConfigFromEnv } from 'wgrok';

// Send
const sender = new WgrokSender(senderConfigFromEnv());
await sender.send('hello world');

// Receive
const receiver = new WgrokReceiver(receiverConfigFromEnv(), (slug, payload, cards, fromSlug) => {
  console.log(`Got from ${fromSlug}: ${payload}`);
});
await receiver.listen();

Rust:

use wgrok::{WgrokSender, WgrokReceiver, SenderConfig, ReceiverConfig};

// Send
let cfg = SenderConfig::from_env()?;
let sender = WgrokSender::new(cfg);
sender.send("hello world", None).await?;

// Receive
let cfg = ReceiverConfig::from_env()?;
let receiver = WgrokReceiver::new(cfg, Box::new(|slug, payload, cards, from_slug| {
    println!("Got from {from_slug}: {payload}");
}));
receiver.listen(shutdown_rx).await?;

Transport bindings

The protocol is transport-agnostic. Each platform is a transport binding with a send API and a receive mechanism:

Platform Send Receive (Persistent) Receive (Webhook) Status
Webex REST /v1/messages Mercury WebSocket Webhook registration Send + Receive
Slack chat.postMessage Socket Mode WebSocket Events API Send + Receive
Discord REST /channels/{id}/messages Gateway WebSocket Interactions endpoint Send + Receive
IRC PRIVMSG Persistent TCP/TLS N/A Send + Receive
Platform tokens

The routing bot supports multiple platforms simultaneously and multiple tokens per platform for load balancing:

# Multiple Webex tokens (load balanced across outbound sends)
WGROK_WEBEX_TOKENS=token1,token2,token3

# Multiple Slack tokens
WGROK_SLACK_TOKENS=xoxb-token1,xoxb-token2

# Single Discord token
WGROK_DISCORD_TOKENS=bot-token1

# IRC (connection string format: nick:password@server:port/channel)
WGROK_IRC_TOKENS=wgrok-bot:pass@irc.libera.chat:6697/#wgrok

Each WGROK_{PLATFORM}_TOKENS env var accepts CSV. The routing bot:

  • Opens a WebSocket listener per token (receives messages from all connected platforms)
  • Load balances outbound sends across tokens for the same platform
  • Routes cross-platform: a message arriving on Webex can be delivered to a Slack agent

For senders and receivers (simple case), a single token with explicit platform:

WGROK_TOKEN=<token>
WGROK_PLATFORM=webex

WGROK_PLATFORM defaults to webex for backward compatibility.

Webhook endpoint

The routing bot can optionally expose an HTTP webhook endpoint for environments that allow inbound traffic:

WGROK_WEBHOOK_PORT=8080
WGROK_WEBHOOK_SECRET=shared-secret   # required when WGROK_WEBHOOK_PORT is set

WGROK_WEBHOOK_SECRET is mandatory when the webhook port is configured — the router bot refuses to start without it. Request bodies are limited to 1 MB.

When enabled, the routing bot starts an HTTP server that accepts POST requests. This is useful for:

  • Platforms that prefer webhooks over WebSocket (e.g., Teams Bot Framework)
  • Non-chat integrations (CI/CD, monitoring, cron jobs) that want to post to the bus without a messaging platform token
  • High-throughput environments where webhook is more efficient than WebSocket
POST /wgrok HTTP/1.1
Authorization: Bearer <WGROK_WEBHOOK_SECRET>
Content-Type: application/json

{
  "text": "./echo:deploy:ci-pipeline:-:start deploy",
  "from": "ci-pipeline@example.com"
}

The webhook endpoint processes messages through the same pipeline as WebSocket messages — allowlist check, protocol parsing, routing.

Allowlist / ACL

The WGROK_DOMAINS environment variable controls who can send messages through the system. Granular access control at the library level:

Pattern Matches
example.com Any *@example.com (bare domain)
*@example.com Any *@example.com (wildcard prefix)
user@example.com Exact match only (case-insensitive)

Patterns containing [, ], or ? are rejected. All matching is case-insensitive.

All modes enforce the allowlist. The minimum configuration is a .env file. Developers can wrap the library with their own ACL solution (OpenBao, Postgres, LDAP, etc.) if needed.

Agent registry

The WGROK_ROUTES environment variable maps slugs to bot identities for Mode C:

WGROK_ROUTES=deploy:deploy-bot@spark.com,status:status-bot@foo.com,jira:jira-agent@webex.bot

CSV format — same whether loaded from .env, a database column, or an API. Parse: split on ,, split each on first :.

The routing bot uses this registry to resolve slugs:

  • Slug found in registry → route to registered bot (Mode C)
  • Slug not found → echo back to sender (Mode B fallback)

Proxy support

All outbound HTTP and WebSocket connections can be routed through a proxy via the WGROK_PROXY environment variable:

WGROK_PROXY=http://proxy.corp.com:8080

Each language implementation wires the proxy through its native HTTP client:

Language Mechanism
Python aiohttp connector (aiohttp_socks / ProxyConnector)
Go http.Client with proxy transport
TypeScript undici ProxyAgent (passed as dispatcher)
Rust reqwest::Client with proxy config

Scope

wgrok provides the core message bus protocol. It is deliberately minimal — wrap it with whatever you need.

In scope: message bus protocol (three modes), sender/relay/receiver libraries, agent registry, allowlist/ACL, .env configuration, multi-platform multi-token support, webhook endpoint, outbound proxy support, optional AES-256-GCM encryption.

Out of scope (wrap these around the library): secret management (OpenBao, Vault), database-backed ACLs (Postgres), observability backends (OpenTelemetry, Loki), authentication beyond the allowlist, UI/dashboards, agent command vocabularies.

Specification

The formal protocol specification is in asyncapi.yaml (AsyncAPI 3.0.0).

Build and test

Python
cd python
pip install -e ".[dev]"
ruff check src/ tests/
pytest tests/ -v
Go
cd go
go test ./... -v
go run ./cmd/sender <payload>
go run ./cmd/routerbot
go run ./cmd/receiver
TypeScript
cd ts
npm install
npx tsc --noEmit
npm test
Rust
cd rust
cargo build
cargo test
cargo clippy
Cross-language test report
bash tests/run_all.sh

Testing architecture

Test cases are defined once as JSON in tests/ and consumed by all four languages via thin shims:

tests/
├── protocol_cases.json
├── codec_cases.json
├── allowlist_cases.json
├── config_cases.json
├── webex_cases.json
├── slack_cases.json
├── discord_cases.json
├── irc_cases.json
├── platform_dispatch_cases.json
├── sender_cases.json
├── router_bot_cases.json
└── receiver_cases.json

Fix a bug in a test case — it applies to all languages. Add a new case — all languages must pass it.

Project structure

wgrok/
├── python/           # Python SDK
│   ├── src/wgrok/    # Source modules
│   └── tests/        # Test shims
├── go/               # Go SDK
│   ├── cmd/          # Runner commands
│   └── *_test.go     # Test shims
├── ts/               # TypeScript SDK
│   ├── src/          # Source modules
│   └── tests/        # Test shims
├── rust/             # Rust SDK
│   ├── src/          # Source modules
│   └── tests/        # Test shims
├── tests/            # Shared JSON test cases
├── asyncapi.yaml     # Protocol specification
├── .plan/            # Design docs
└── README.md

Security

  • Allowlist enforcement on all message paths (WebSocket and webhook)
  • AES-256-GCM encryption (optional, end-to-end, router-transparent)
  • Webhook authentication mandatory when webhook endpoint is enabled
  • Payload redaction in logs — metadata only (slug, from, target, length), never payload content
  • Security event logging always emitted (WARN/ERROR) regardless of debug mode
  • Chunk validation — sequence indices verified before reassembly, 5-minute timeout eviction
  • Fail-closed crypto — decrypt/decompress errors reject the message, never pass through broken data
  • 1 MB request size limit on webhook endpoint
  • Dependencies pinned with version ranges; GitHub Actions pinned to commit SHAs

See THREAT_MODEL.md for the full MAESTRO threat model.

License

MIT

Contributing

Contributions welcome. All four language implementations must maintain feature parity — if you add a feature to one language, add it to all four with shared test cases in tests/.

Documentation

Overview

Package wgrok implements an ngrok clone using the Webex API as a message bus.

Index

Constants

View Source
const (
	WebexAPIBase            = "https://webexapis.com/v1"
	AdaptiveCardContentType = "application/vnd.microsoft.card.adaptive"
	MaxRetries              = 3
)
View Source
const (
	DiscordAPIBase = "https://discord.com/api/v10"
)
View Source
const EchoPrefix = "./echo:"
View Source
const PauseCmd = "./pause"
View Source
const ResumeCmd = "./resume"
View Source
const (
	SlackAPIBase = "https://slack.com/api"
)

Variables

View Source
var (
	WebexMessagesURL          = WebexAPIBase + "/messages"
	WebexAttachmentActionsURL = WebexAPIBase + "/attachment/actions"
)

Package-level URLs, overridable in tests.

View Source
var (
	DiscordChannelMessagesURL = func(channelID string) string {
		return DiscordAPIBase + "/channels/" + channelID + "/messages"
	}
)

Package-level URL, overridable in tests.

View Source
var PlatformLimits = map[string]int{
	"webex":   7439,
	"slack":   4000,
	"discord": 2000,
	"irc":     400,
}

PlatformLimits defines message size limits per platform.

View Source
var (
	SlackPostMessageURL = SlackAPIBase + "/chat.postMessage"
)

Package-level URL, overridable in tests.

Functions

func Chunk

func Chunk(payload string, maxSize int) ([]string, error)

Chunk splits payload into raw chunks of at most maxSize bytes each. Returns just the raw chunk data (no N/T: prefix).

func Compress

func Compress(data string) (string, error)

Compress gzip-compresses data and returns a base64 string (no prefix).

func Decompress

func Decompress(data string) (string, error)

Decompress attempts to base64-decode and gzip-decompress data. If decompression fails, returns an error (fail-closed).

func Decrypt

func Decrypt(data string, key []byte) (string, error)

Decrypt decrypts data encrypted with Encrypt. Expects base64(IV || ciphertext_with_tag).

func Encrypt

func Encrypt(data string, key []byte) (string, error)

Encrypt encrypts data using AES-256-GCM with a random 12-byte IV. Returns base64(IV || ciphertext_with_tag).

func ExtractCards

func ExtractCards(message map[string]interface{}) []interface{}

ExtractCards extracts adaptive card content from a message's attachments.

func FormatEcho

func FormatEcho(to, from, flags, payload string) string

FormatEcho formats an outgoing echo message: ./echo:{to}:{from}:{flags}:{payload}

func FormatFlags

func FormatFlags(compressed, encrypted bool, chunkSeq, chunkTotal int) string

FormatFlags formats a flags string from components. If chunkSeq is 0, no chunking. Otherwise format is "N/T" or "zN/T" if compressed, "eN/T" if encrypted, "zeN/T" if both.

func FormatResponse

func FormatResponse(to, from, flags, payload string) string

FormatResponse formats a response message: {to}:{from}:{flags}:{payload}

func GetAttachmentAction

func GetAttachmentAction(token, actionID string, client *http.Client) (map[string]interface{}, error)

GetAttachmentAction fetches an attachment action (card submission) by ID.

func GetLogger

func GetLogger(debug bool, module string) wmh.Logger

GetLogger returns an NdjsonLogger if debug is true, otherwise a minLevelWgrokLogger that only outputs WARN/ERROR.

func GetMessage

func GetMessage(token, messageID string, client *http.Client) (map[string]interface{}, error)

GetMessage fetches full message details by ID (includes attachments).

func IsEcho

func IsEcho(text string) bool

IsEcho checks if text is an echo-formatted message.

func IsPause

func IsPause(text string) bool

IsPause checks if text is a pause control message.

func IsResume

func IsResume(text string) bool

IsResume checks if text is a resume control message.

func ParseEcho

func ParseEcho(text string) (to, from, flags, payload string, err error)

ParseEcho parses an echo message, returning (to, from, flags, payload).

func ParseFlags

func ParseFlags(flags string) (compressed, encrypted bool, chunkSeq, chunkTotal int, err error)

ParseFlags parses a flags string and returns (compressed, encrypted, chunkSeq, chunkTotal). Format: "-" for no flags, "z" for compressed, "e" for encrypted, "N/T" for chunk N of T. Combinations: "z", "e", "ze", "1/3", "z1/3", "e1/3", "ze1/3" etc. If chunkSeq is 0, there is no chunking.

func ParseResponse

func ParseResponse(text string) (to, from, flags, payload string, err error)

ParseResponse parses a response message, returning (to, from, flags, payload).

func PlatformSendCard

func PlatformSendCard(platform, token, target, text string, card interface{}, client *http.Client) (map[string]interface{}, error)

PlatformSendCard sends a message with card/rich content via the specified platform.

func PlatformSendCardToRoom

func PlatformSendCardToRoom(platform, token, roomID, text string, card interface{}, client *http.Client) (map[string]interface{}, error)

PlatformSendCardToRoom sends a message with card/rich content to a room via the specified platform.

func PlatformSendMessage

func PlatformSendMessage(platform, token, target, text string, client *http.Client) (map[string]interface{}, error)

PlatformSendMessage sends a text message via the specified platform.

func PlatformSendMessageToRoom

func PlatformSendMessageToRoom(platform, token, roomID, text string, client *http.Client) (map[string]interface{}, error)

PlatformSendMessageToRoom sends a text message to a room via the specified platform.

func SendCard

func SendCard(token, toEmail, text string, card interface{}, client *http.Client) (map[string]interface{}, error)

SendCard sends a Webex message with an adaptive card attachment.

func SendCardToRoom

func SendCardToRoom(token, roomID, text string, card interface{}, client *http.Client) (map[string]interface{}, error)

SendCardToRoom sends a Webex message with an adaptive card attachment to a room by room ID.

func SendDiscordCard

func SendDiscordCard(token, channelID, text string, card interface{}, client *http.Client) (map[string]interface{}, error)

SendDiscordCard sends a Discord message with an embed.

func SendDiscordMessage

func SendDiscordMessage(token, channelID, text string, client *http.Client) (map[string]interface{}, error)

SendDiscordMessage sends a text-only Discord message to a channel.

func SendIRCCard

func SendIRCCard(connStr, target, text string, card interface{}) (map[string]interface{}, error)

SendIRCCard sends a message via IRC. Cards are not supported - only text is sent.

func SendIRCMessage

func SendIRCMessage(connStr, target, text string) (map[string]interface{}, error)

SendIRCMessage sends a message via IRC (not implemented - would require persistent connection). Returns a map indicating the message was queued for sending.

func SendMessage

func SendMessage(token, toEmail, text string, client *http.Client) (map[string]interface{}, error)

SendMessage sends a text-only Webex message to a person by email.

func SendMessageToRoom

func SendMessageToRoom(token, roomID, text string, client *http.Client) (map[string]interface{}, error)

SendMessageToRoom sends a text-only Webex message to a room by room ID.

func SendSlackCard

func SendSlackCard(token, channel, text string, card interface{}, client *http.Client) (map[string]interface{}, error)

SendSlackCard sends a Slack message with Block Kit blocks.

func SendSlackMessage

func SendSlackMessage(token, channel, text string, client *http.Client) (map[string]interface{}, error)

SendSlackMessage sends a text-only Slack message to a channel.

func StripBotMention

func StripBotMention(text, html string) string

StripBotMention strips a bot display name prefix from text using spark-mention tags in HTML.

Types

type Allowlist

type Allowlist struct {
	// contains filtered or unexported fields
}

Allowlist validates email addresses against a list of allowed patterns.

Supported formats:

  • "domain.tld" → matches *@domain.tld (bare domain)
  • "*@domain.tld" → wildcard prefix match
  • "user@domain.tld" → exact match (case-insensitive)

Patterns with [, ], or ? are rejected (dangerous).

func NewAllowlist

func NewAllowlist(patterns []string) *Allowlist

NewAllowlist creates an Allowlist from the given patterns.

func (*Allowlist) IsAllowed

func (a *Allowlist) IsAllowed(email string) bool

IsAllowed checks if an email address matches any allowed pattern (case-insensitive).

type BotConfig

type BotConfig struct {
	WebexToken     string
	Domains        []string
	Routes         map[string]string
	PlatformTokens map[string][]string
	WebhookPort    *int
	WebhookSecret  *string
	Debug          bool
}

BotConfig holds configuration for WgrokRouterBot.

func BotConfigFromEnv

func BotConfigFromEnv() (*BotConfig, error)

BotConfigFromEnv loads a BotConfig from environment variables.

type CardAttachment

type CardAttachment struct {
	ContentType string      `json:"contentType"`
	Content     interface{} `json:"content"`
}

CardAttachment represents a Webex message card attachment.

type ControlHandler

type ControlHandler func(cmd string)

ControlHandler is the callback type for control messages (pause/resume). It receives the control command name.

type DefaultSlackHTTPClient

type DefaultSlackHTTPClient struct{}

DefaultSlackHTTPClient is the default HTTP client for Slack API calls.

func (*DefaultSlackHTTPClient) PostJSON

func (c *DefaultSlackHTTPClient) PostJSON(url, token string, body interface{}) (map[string]interface{}, error)

PostJSON makes a POST request and returns JSON response.

type DiscordListener

type DiscordListener struct {
	// contains filtered or unexported fields
}

DiscordListener listens for Discord messages via Gateway WebSocket.

func NewDiscordListener

func NewDiscordListener(token string, logger wmh.Logger) *DiscordListener

NewDiscordListener creates a new Discord listener.

func (*DiscordListener) Connect

func (l *DiscordListener) Connect(ctx context.Context) error

Connect establishes the WebSocket connection to Discord Gateway.

func (*DiscordListener) Disconnect

func (l *DiscordListener) Disconnect(ctx context.Context) error

Disconnect closes the connection to Discord.

func (*DiscordListener) OnMessage

func (l *DiscordListener) OnMessage(callback MessageCallback)

OnMessage registers a callback for incoming messages.

type IRCParams

type IRCParams struct {
	Nick     string
	Password string
	Server   string
	Port     int
	Channel  string
}

IRCParams holds parsed IRC connection string components.

func ParseIRCConnectionString

func ParseIRCConnectionString(connStr string) (*IRCParams, error)

ParseIRCConnectionString parses an IRC connection string into components. Format: nick:password@server:port/channel Example: wgrok-bot:pass@irc.libera.chat:6697/#wgrok

type IncomingMessage

type IncomingMessage struct {
	Sender   string
	Text     string
	HTML     string
	RoomID   string
	RoomType string
	MsgID    string
	Platform string
	Cards    []interface{}
}

IncomingMessage is the normalized incoming message from any platform.

type IrcConnection

type IrcConnection struct {
	// contains filtered or unexported fields
}

IrcConnection manages a persistent TCP/TLS connection to an IRC server.

func NewIrcConnection

func NewIrcConnection(connStr string) (*IrcConnection, error)

NewIrcConnection creates a new IRC connection handler.

func (*IrcConnection) Connect

func (ic *IrcConnection) Connect() error

Connect establishes a TLS connection to the IRC server.

func (*IrcConnection) Disconnect

func (ic *IrcConnection) Disconnect() error

Disconnect closes the IRC connection.

func (*IrcConnection) ReadLine

func (ic *IrcConnection) ReadLine(timeout time.Duration) (string, error)

ReadLine reads a line from the IRC server with timeout.

type IrcListener

type IrcListener struct {
	// contains filtered or unexported fields
}

IrcListener listens for IRC messages via persistent TCP/TLS connection.

func NewIrcListener

func NewIrcListener(connStr string, logger wmh.Logger) *IrcListener

NewIrcListener creates a new IRC listener.

func (*IrcListener) Connect

func (l *IrcListener) Connect(ctx context.Context) error

Connect establishes the connection to IRC.

func (*IrcListener) Disconnect

func (l *IrcListener) Disconnect(ctx context.Context) error

Disconnect closes the connection to IRC.

func (*IrcListener) OnMessage

func (l *IrcListener) OnMessage(callback MessageCallback)

OnMessage registers a callback for incoming messages.

type MessageCallback

type MessageCallback func(IncomingMessage)

MessageCallback is the callback type for received messages.

type MessageHandler

type MessageHandler func(slug, payload string, cards []interface{}, fromSlug string)

MessageHandler is the callback type for received messages. It receives the slug, payload, cards, and fromSlug (sender identifier).

type NdjsonLogger

type NdjsonLogger struct {
	Module string
}

NdjsonLogger emits NDJSON log lines to stderr.

func (*NdjsonLogger) Debug

func (l *NdjsonLogger) Debug(msg string, _ ...any)

func (*NdjsonLogger) Error

func (l *NdjsonLogger) Error(msg string, _ ...any)

func (*NdjsonLogger) Info

func (l *NdjsonLogger) Info(msg string, _ ...any)

func (*NdjsonLogger) Warn

func (l *NdjsonLogger) Warn(msg string, _ ...any)

type PlatformListener

type PlatformListener interface {
	OnMessage(callback MessageCallback)
	Connect(ctx context.Context) error
	Disconnect(ctx context.Context) error
}

PlatformListener defines the interface for platform-specific listeners.

func CreateListener

func CreateListener(platform, token string, logger wmh.Logger) (PlatformListener, error)

CreateListener creates the right listener for a platform. For IRC, the token parameter should be the connection string (nick:password@server:port/channel).

type ReceiverConfig

type ReceiverConfig struct {
	WebexToken string
	Slug       string
	Domains    []string
	Platform   string
	Debug      bool
	EncryptKey []byte // nil if not set; must be 32 bytes for AES-256
}

ReceiverConfig holds configuration for WgrokReceiver.

func ReceiverConfigFromEnv

func ReceiverConfigFromEnv() (*ReceiverConfig, error)

ReceiverConfigFromEnv loads a ReceiverConfig from environment variables.

type SenderConfig

type SenderConfig struct {
	WebexToken string
	Target     string
	Slug       string
	Domains    []string
	Platform   string
	Debug      bool
	EncryptKey []byte // nil if not set; must be 32 bytes for AES-256
}

SenderConfig holds configuration for WgrokSender.

func SenderConfigFromEnv

func SenderConfigFromEnv() (*SenderConfig, error)

SenderConfigFromEnv loads a SenderConfig from environment variables.

type SimpleHTTPClient

type SimpleHTTPClient interface {
	PostJSON(url, token string, body interface{}) (map[string]interface{}, error)
}

SimpleHTTPClient interface for dependency injection.

type SlackListener

type SlackListener struct {
	// contains filtered or unexported fields
}

SlackListener listens for Slack messages via Socket Mode WebSocket.

func NewSlackListener

func NewSlackListener(token string, logger wmh.Logger) *SlackListener

NewSlackListener creates a new Slack listener.

func (*SlackListener) Connect

func (l *SlackListener) Connect(ctx context.Context) error

Connect establishes the WebSocket connection to Slack Socket Mode.

func (*SlackListener) Disconnect

func (l *SlackListener) Disconnect(ctx context.Context) error

Disconnect closes the connection to Slack.

func (*SlackListener) OnMessage

func (l *SlackListener) OnMessage(callback MessageCallback)

OnMessage registers a callback for incoming messages.

type WebexListener

type WebexListener struct {
	// contains filtered or unexported fields
}

WebexListener wraps the webex-message-handler and normalizes messages.

func NewWebexListener

func NewWebexListener(token string, logger wmh.Logger) *WebexListener

NewWebexListener creates a new Webex listener.

func (*WebexListener) Connect

func (l *WebexListener) Connect(ctx context.Context) error

Connect establishes the connection to Webex.

func (*WebexListener) Disconnect

func (l *WebexListener) Disconnect(ctx context.Context) error

Disconnect closes the connection to Webex.

func (*WebexListener) OnMessage

func (l *WebexListener) OnMessage(callback MessageCallback)

OnMessage registers a callback for incoming messages.

type WgrokReceiver

type WgrokReceiver struct {
	OnControl ControlHandler
	// contains filtered or unexported fields
}

WgrokReceiver listens for response messages, matches slug, invokes handler callback.

func NewReceiver

func NewReceiver(config *ReceiverConfig, handler MessageHandler) *WgrokReceiver

NewReceiver creates a new WgrokReceiver.

func (*WgrokReceiver) FetchAction

func (r *WgrokReceiver) FetchAction(actionID string) (map[string]interface{}, error)

FetchAction fetches an attachment action (card form submission) by ID.

func (*WgrokReceiver) Listen

func (r *WgrokReceiver) Listen(ctx context.Context) error

Listen connects to the configured platform and listens for response messages matching the configured slug. Blocks until ctx is cancelled or Stop is called.

func (*WgrokReceiver) Stop

func (r *WgrokReceiver) Stop(ctx context.Context)

Stop disconnects the receiver.

type WgrokRouterBot

type WgrokRouterBot struct {
	// contains filtered or unexported fields
}

WgrokRouterBot listens for messages, validates allowlist, strips prefix, relays back.

func NewRouterBot

func NewRouterBot(config *BotConfig) *WgrokRouterBot

NewRouterBot creates a new WgrokRouterBot.

func (*WgrokRouterBot) Pause

func (b *WgrokRouterBot) Pause() error

Pause sends a pause control message to all Mode C route targets.

func (*WgrokRouterBot) Resume

func (b *WgrokRouterBot) Resume() error

Resume sends a resume control message to all Mode C route targets.

func (*WgrokRouterBot) Run

func (b *WgrokRouterBot) Run(ctx context.Context) error

Run connects to configured platforms and listens for messages. Blocks until ctx is cancelled or Stop is called.

func (*WgrokRouterBot) Stop

func (b *WgrokRouterBot) Stop(ctx context.Context)

Stop disconnects the router bot.

type WgrokSender

type WgrokSender struct {
	// contains filtered or unexported fields
}

WgrokSender wraps payloads in the echo protocol and sends via Webex.

func NewSender

func NewSender(config *SenderConfig) *WgrokSender

NewSender creates a new WgrokSender.

func (*WgrokSender) Pause

func (s *WgrokSender) Pause(notify bool) error

Pause pauses message delivery. If notify is true, sends a pause control message to the target.

func (*WgrokSender) Resume

func (s *WgrokSender) Resume(notify bool) error

Resume resumes message delivery. If notify is true, sends a resume control message to the target. Then flushes any buffered messages.

func (*WgrokSender) Send

func (s *WgrokSender) Send(payload string, card interface{}) (map[string]interface{}, error)

Send formats payload as an echo message and sends to the configured target. If card is non-nil, it is attached as an adaptive card. If compress is true, the payload is gzip+base64 encoded before sending.

func (*WgrokSender) SendWithOptions

func (s *WgrokSender) SendWithOptions(payload string, card interface{}, compress bool) (map[string]interface{}, error)

SendWithOptions is like Send but with an explicit compress flag.

Directories

Path Synopsis
cmd
receiver command
routerbot command
sender command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL