signal-bot-orx is a webhook-driven Signal bot powered by
signal-cli-rest-api and OpenRouter.
It is designed for group chats and direct messages.
Development Status: This project is in active development. Some features may be broken or behave unexpectedly between updates.
Primary behavior:
- Mention-triggered group chat replies.
- Direct-message chat replies without mention syntax.
- DDGS-powered search via slash commands (default privacy mode).
- Optional
/imagine <prompt>image generation via OpenRouter.
- Handles incoming Signal webhooks (
POST /webhook/signal). - Optional WhatsApp bridge webhooks (
POST /webhook/whatsapp) when enabled. - Optional Telegram webhooks (
POST /webhook/telegram) when enabled. - Enforces number/group allowlists unless auth bypass is explicitly enabled.
- Supports metadata mention detection with alias fallback.
- Maintains and supports customizable in-memory conversation history per group.
- Deduplicates repeated webhook deliveries.
- Supports image generation via OpenRouter when configured.
- Provides current weather and 5-day forecasts via OpenWeatherMap.
uv- Running
signal-cli-rest-api - OpenRouter API key for chat
- OpenWeatherMap API key (optional) for weather commands
From source:
uv sync --devFrom package index (after publish):
uv tool install signal-bot-orxor
pip install signal-bot-orxCreate .env from .env.example and set required values.
cp .env.example .envRequired:
OPENROUTER_CHAT_API_KEY
Enable at least one transport:
- Signal:
SIGNAL_ENABLED=true(default) - WhatsApp:
WHATSAPP_ENABLED=true - Telegram:
TELEGRAM_ENABLED=true
Signal required values (when SIGNAL_ENABLED=true):
SIGNAL_API_BASE_URLSIGNAL_SENDER_NUMBER
Auth/allowlist (set at least one unless SIGNAL_DISABLE_AUTH=true):
SIGNAL_ALLOWED_NUMBERSIGNAL_ALLOWED_NUMBERSSIGNAL_ALLOWED_GROUP_IDS
Optional WhatsApp bridge:
WHATSAPP_ENABLED(default:false)WHATSAPP_BRIDGE_BASE_URL(required when enabled)WHATSAPP_BRIDGE_TOKEN(optional bearer token)WHATSAPP_ALLOWED_NUMBERS(required unlessWHATSAPP_DISABLE_AUTH=true)WHATSAPP_DISABLE_AUTH(default:false)
Optional Telegram transport:
TELEGRAM_ENABLED(default:false)TELEGRAM_BOT_TOKEN(required when enabled)TELEGRAM_WEBHOOK_SECRET(optional, validatesX-Telegram-Bot-Api-Secret-Token)TELEGRAM_ALLOWED_USER_IDS(required unlessTELEGRAM_DISABLE_AUTH=true)TELEGRAM_ALLOWED_CHAT_IDS(optional group allowlist)TELEGRAM_DISABLE_AUTH(default:false)TELEGRAM_BOT_USERNAME(optional, used for group mention/reply detection)
Chat options:
OPENROUTER_MODEL(default:openai/gpt-4o-mini)OPENROUTER_TIMEOUT_SECONDS(default:45)OPENROUTER_MAX_OUTPUT_TOKENS(default:300)BOT_CHAT_TEMPERATURE(default:0.6)BOT_CHAT_CONTEXT_TURNS(default:6)BOT_CHAT_CONTEXT_TTL_SECONDS(default:1800)BOT_CHAT_SYSTEM_PROMPT(optional) This env var overrides file-based prompts when set.- Prompt files:
local-only override:
src/signal_bot_orx/chat_system_prompt.md(git-ignored) distribution default:src/signal_bot_orx/default_chat_system_prompt.md(tracked) BOT_CHAT_FORCE_PLAIN_TEXT(default:true)BOT_MENTION_ALIASES(default:@signalbot,@bot)BOT_MAX_PROMPT_CHARS(default:700)
Search options:
BOT_SEARCH_ENABLED(default:true)BOT_SEARCH_CONTEXT_MODE(no_context|context, default:no_context)BOT_SEARCH_MODE_SEARCH_ENABLED(default:true, controls/search)BOT_SEARCH_MODE_NEWS_ENABLED(default:true, controls/news)BOT_SEARCH_MODE_WIKI_ENABLED(default:true, controls/wiki)BOT_SEARCH_MODE_IMAGES_ENABLED(default:true, controls/images)BOT_SEARCH_MODE_VIDEOS_ENABLED(default:true, controls/videos)BOT_SEARCH_DEBUG_LOGGING(default:false, logs routing/summarization decisions without raw message/result payloads)BOT_SEARCH_PERSONA_ENABLED(default:false, reuse core personality for search summaries)BOT_SEARCH_USE_HISTORY_FOR_SUMMARY(default:false, include last 2 turns in search summary prompt)BOT_SEARCH_REGION(default:us-en)BOT_SEARCH_SAFESEARCH(on|moderate|off, default:moderate)BOT_SEARCH_BACKEND_STRATEGY(first_non_empty|aggregate, default:first_non_empty)BOT_SEARCH_BACKEND_SEARCH_ORDER(default:duckduckgo,bing,google,yandex,grokipedia)BOT_SEARCH_BACKEND_NEWS_ORDER(default:duckduckgo,bing,yahoo)BOT_SEARCH_BACKEND_SEARCH(default:auto, backend for/search+ auto modesearch)BOT_SEARCH_BACKEND_NEWS(default:auto, backend for/news+ auto modenews)BOT_SEARCH_BACKEND_WIKI(default:wikipedia, backend for/wiki+ auto modewiki)BOT_SEARCH_BACKEND_IMAGES(default:duckduckgo, backend for/images)BOT_SEARCH_BACKEND_VIDEOS(default:youtube, backend for/videos)BOT_SEARCH_TEXT_MAX_RESULTS(default:5)BOT_SEARCH_NEWS_MAX_RESULTS(default:5)BOT_SEARCH_WIKI_MAX_RESULTS(default:3)BOT_SEARCH_IMAGES_MAX_RESULTS(default:3, top image sent)BOT_SEARCH_VIDEOS_MAX_RESULTS(default:5, numbered list size for/videos)BOT_SEARCH_JMAIL_MAX_RESULTS(default:10)BOT_SEARCH_LOLCOW_CYRAXX_MAX_RESULTS(default:3)BOT_SEARCH_LOLCOW_LARSON_MAX_RESULTS(default:3)BOT_SEARCH_TIMEOUT_SECONDS(default:8)BOT_SEARCH_SOURCE_TTL_SECONDS(default:1800)
Default search privacy behavior:
BOT_SEARCH_CONTEXT_MODE=no_contextmeans DDGS requests only run for explicit slash commands.- Set
BOT_SEARCH_CONTEXT_MODE=contextto allow model-routed auto-search from normal chat messages. /videosis always explicit command mode and never selected by auto-routing.- In
contextmode, ambiguous follow-ups (for example, pronoun-only references) may triggerWho are you referring to?; a short next reply can be applied to continue the pending query. - You can disable any explicit DDGS command with its corresponding
BOT_SEARCH_MODE_*_ENABLED=falseflag. - Set
BOT_SEARCH_DEBUG_LOGGING=trueto inspect auto-search decisions (should_search,mode, query length, path) during debugging. BOT_SEARCH_BACKEND_STRATEGY=first_non_emptystops at first backend with results.BOT_SEARCH_BACKEND_STRATEGY=aggregatequeries all backends in order, merges/de-dupes by URL, then caps final output toBOT_SEARCH_TEXT_MAX_RESULTS/BOT_SEARCH_NEWS_MAX_RESULTS.BOT_SEARCH_BACKEND_*_ORDERdefines fallback chains for generalsearchandnews.- If an order var is set, it takes precedence over legacy single-backend vars.
/wikiremains explicit encyclopedia mode and usesBOT_SEARCH_BACKEND_WIKI.- News backend order rejects encyclopedia backends (
wikipedia,grokipedia). - With
BOT_SEARCH_PERSONA_ENABLED=true, search summaries reuseBOT_CHAT_SYSTEM_PROMPTbut keep factual constraints.
Image mode (/imagine) options:
OPENROUTER_IMAGE_API_KEYOPENROUTER_IMAGE_MODELOPENROUTER_IMAGE_TIMEOUT_SECONDS(default:90)
Weather options (OpenWeatherMap):
WEATHER_API_KEY(required for weather commands)WEATHER_UNITS(metric|imperial, default:metric)WEATHER_DEFAULT_LOCATION(optional default for empty location args)
Webhook/runtime options:
BOT_GROUP_REPLY_MODE(groupordm_fallback, default:group)BOT_WEBHOOK_HOST(default:127.0.0.1)BOT_WEBHOOK_PORT(default:8001).env.exampleusesBOT_WEBHOOK_HOST=0.0.0.0as a deployment-friendly bind example.
NOTE: You can use the same key for both OPENROUTER_CHAT_API_KEY and
OPENROUTER_IMAGE_API_KEY if you are okay sharing limits/settings across chat
and image calls.
Load environment variables before starting the bot:
set -a
source .env
set +aThen start the service:
uv run signal-bot-orxEndpoints:
GET /healthzPOST /webhook/signalPOST /webhook/whatsapp(only active whenWHATSAPP_ENABLED=true)POST /webhook/telegram(only active whenTELEGRAM_ENABLED=true)
If you skip exporting .env, startup will fail with missing required
environment variable errors.
Configure signal-cli-rest-api callback target:
http://<BOT_WEBHOOK_HOST>:<BOT_WEBHOOK_PORT>/webhook/signal
Configure Telegram webhook manually:
curl -sS "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
-d "url=https://<YOUR_PUBLIC_HOST>/webhook/telegram" \
-d 'allowed_updates=["message","edited_message"]' \
-d "secret_token=${TELEGRAM_WEBHOOK_SECRET}"Group behavior on Telegram: the bot responds only when mentioned (@<bot_username>) or when users reply to the bot.
Group chat:
@bot summarize the key decisions from this thread
Direct message chat:
summarize the key decisions from this thread
Explicit search commands:
/search latest OpenRouter announcements
/news major AI regulation updates
/wiki Ada Lovelace
/images red fox watercolor
/videos nick land interview
Video follow-up selection:
1
Source follow-ups:
/source Ada claim
source for the second claim
Optional image mode:
/imagine a watercolor fox reading a map
Weather commands:
/weather London
/forecast Tokyo
uv run ruff check .
uv run ruff format . && ./scripts/format.sh
uv run ty check .
uv run pytest
uv buildSee docs/README.md.
This project is licensed under the MIT License. See LICENSE.
Third-party dependency notices are documented in THIRD_PARTY_NOTICES.md.
