Skip to content
Merged
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Edit `config.toml`:
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allowed_channels = ["YOUR_CHANNEL_ID"]
# allowed_users = ["YOUR_USER_ID"] # optional: restrict who can use the bot

[agent]
command = "kiro-cli"
Expand Down Expand Up @@ -164,6 +165,7 @@ env = { GEMINI_API_KEY = "${GEMINI_API_KEY}" }
[discord]
bot_token = "${DISCORD_BOT_TOKEN}" # supports env var expansion
allowed_channels = ["123456789"] # channel ID allowlist
# allowed_users = ["987654321"] # user ID allowlist (empty = all users)

[agent]
command = "kiro-cli" # CLI command
Expand Down
8 changes: 7 additions & 1 deletion charts/openab/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ data:
{{- fail (printf "discord.allowedChannels contains a mangled ID: %s — use --set-string instead of --set for channel IDs" (toString .)) }}
{{- end }}
{{- end }}
allowed_channels = [{{ range $i, $ch := $cfg.discord.allowedChannels }}{{ if $i }}, {{ end }}"{{ $ch }}"{{ end }}]
allowed_channels = {{ $cfg.discord.allowedChannels | default list | toJson }}
{{- range $cfg.discord.allowedUsers }}
{{- if regexMatch "e\\+|E\\+" (toString .) }}
{{- fail (printf "discord.allowedUsers contains a mangled ID: %s — use --set-string instead of --set for user IDs" (toString .)) }}
{{- end }}
{{- end }}
allowed_users = {{ $cfg.discord.allowedUsers | default list | toJson }}

[agent]
command = "{{ $cfg.command }}"
Expand Down
3 changes: 3 additions & 0 deletions charts/openab/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ agents:
# # ⚠️ Use --set-string for channel IDs to avoid float64 precision loss
# allowedChannels:
# - "YOUR_CHANNEL_ID"
# allowedUsers: []
# workingDir: /home/agent
# env: {}
# envFrom: []
Expand Down Expand Up @@ -57,6 +58,8 @@ agents:
# ⚠️ Use --set-string for channel IDs to avoid float64 precision loss
allowedChannels:
- "YOUR_CHANNEL_ID"
# ⚠️ Use --set-string for user IDs to avoid float64 precision loss
allowedUsers: [] # empty = allow all users (default)
workingDir: /home/agent
env: {}
envFrom: []
Expand Down
1 change: 1 addition & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allowed_channels = ["1234567890"]
# allowed_users = ["<YOUR_DISCORD_USER_ID>"] # empty or omitted = allow all users

[agent]
command = "kiro-cli"
Expand Down
24 changes: 22 additions & 2 deletions docs/discord-bot-howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ Step-by-step guide to create and configure a Discord bot for openab.
3. Click **Copy Channel ID**
4. Use this ID in `allowed_channels` in your config

## 7. Configure openab
## 7. Get Your User ID (optional)

1. Make sure **Developer Mode** is enabled (see step 6)
2. Right-click your own username (in a message or the member list)
3. Click **Copy User ID**
4. Use this ID in `allowed_users` to restrict who can interact with the bot

## 8. Configure openab

Set the bot token and channel ID:

Expand All @@ -61,15 +68,28 @@ In `config.toml`:
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allowed_channels = ["your-channel-id-from-step-6"]
# allowed_users = ["your-user-id-from-step-7"] # optional: restrict who can use the bot
```

### Access control behavior

| `allowed_channels` | `allowed_users` | Result |
|---|---|---|
| empty | empty | All users, all channels (default) |
| set | empty | Only these channels, all users |
| empty | set | All channels, only these users |
| set | set | **AND** — must be in allowed channel AND allowed user |

- Empty `allowed_users` (default) = no user filtering, fully backward compatible
- Denied users get a 🚫 reaction and no reply

For Kubernetes:
```bash
kubectl create secret generic openab-secret \
--from-literal=discord-bot-token="your-token-from-step-3"
```

## 8. Test
## 9. Test

In the allowed channel, mention the bot:

Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub struct DiscordConfig {
pub bot_token: String,
#[serde(default)]
pub allowed_channels: Vec<String>,
#[serde(default)]
pub allowed_users: Vec<String>,
}

#[derive(Debug, Deserialize)]
Expand Down
11 changes: 10 additions & 1 deletion src/discord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::config::ReactionsConfig;
use crate::format;
use crate::reactions::StatusReactionController;
use serenity::async_trait;
use serenity::model::channel::Message;
use serenity::model::channel::{Message, ReactionType};
use serenity::model::gateway::Ready;
use serenity::model::id::{ChannelId, MessageId};
use serenity::prelude::*;
Expand All @@ -15,6 +15,7 @@ use tracing::{error, info};
pub struct Handler {
pub pool: Arc<SessionPool>,
pub allowed_channels: HashSet<u64>,
pub allowed_users: HashSet<u64>,
pub reactions_config: ReactionsConfig,
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (non-blocking): let _ = msg.react(...) 失敗會被靜默吞掉。考慮到是 denied user 情境,靜默處理合理,但加個 tracing::warn 在失敗時會更方便排查問題。例如:

if let Err(e) = msg.react(&ctx.http, ReactionType::Unicode("🚫".into())).await {
    tracing::warn!(error = %e, "failed to react with 🚫");
}

Expand Down Expand Up @@ -64,6 +65,14 @@ impl EventHandler for Handler {
return;
}

if !self.allowed_users.is_empty() && !self.allowed_users.contains(&msg.author.id.get()) {
tracing::info!(user_id = %msg.author.id, "denied user, ignoring");
if let Err(e) = msg.react(&ctx.http, ReactionType::Unicode("🚫".into())).await {
tracing::warn!(error = %e, "failed to react with 🚫");
}
return;
}

let prompt = if is_mentioned {
strip_mention(&msg.content)
} else {
Expand Down
28 changes: 22 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,22 @@ async fn main() -> anyhow::Result<()> {
agent_cmd = %cfg.agent.command,
pool_max = cfg.pool.max_sessions,
channels = ?cfg.discord.allowed_channels,
users = ?cfg.discord.allowed_users,
reactions = cfg.reactions.enabled,
"config loaded"
);

let pool = Arc::new(acp::SessionPool::new(cfg.agent, cfg.pool.max_sessions));
let ttl_secs = cfg.pool.session_ttl_hours * 3600;

let allowed_channels: HashSet<u64> = cfg
.discord
.allowed_channels
.iter()
.filter_map(|s| s.parse().ok())
.collect();
let allowed_channels = parse_id_set(&cfg.discord.allowed_channels, "allowed_channels")?;
let allowed_users = parse_id_set(&cfg.discord.allowed_users, "allowed_users")?;
info!(channels = allowed_channels.len(), users = allowed_users.len(), "parsed allowlists");

let handler = discord::Handler {
pool: pool.clone(),
allowed_channels,
allowed_users,
reactions_config: cfg.reactions,
};

Expand Down Expand Up @@ -84,3 +83,20 @@ async fn main() -> anyhow::Result<()> {
info!("openab shut down");
Ok(())
}

fn parse_id_set(raw: &[String], label: &str) -> anyhow::Result<HashSet<u64>> {
let set: HashSet<u64> = raw
.iter()
.filter_map(|s| match s.parse() {
Ok(id) => Some(id),
Err(_) => {
tracing::warn!(value = %s, label = label, "ignoring invalid entry");
None
}
})
.collect();
if !raw.is_empty() && set.is_empty() {
anyhow::bail!("all {label} entries failed to parse — refusing to start with an empty allowlist");
}
Ok(set)
}