auto nickname based on phone call

This commit is contained in:
legop3 2026-06-14 12:17:11 -04:00
parent c5ce9d7dac
commit 025d5ffd88
2 changed files with 93 additions and 13 deletions

View file

@ -47,7 +47,9 @@ Use this URL format, replacing `YOUR_CLIENT_ID`:
```
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=36700160
```
The bot needs Connect + Speak permissions in voice channels.
The bot needs Connect + Speak permissions in voice channels. If you want the
bot nickname to change to the phone/person being called, also grant Change
Nickname.
### 2. Get Discord channel IDs
@ -263,6 +265,9 @@ Behavior:
command was run.
- `/directory` opens the configured phone directory as Discord buttons. Clicking
a phone button behaves like `/call` for that extension.
- When a Discord-originated call starts, the bot tries to set its server
nickname to the matching phone directory label. If the extension is not in the
directory, it uses the dialed extension.
Current scope:
- `/call` is implemented for the static self-host backend.

View file

@ -678,10 +678,13 @@ async fn handle_call_command(
Ok(req) => {
let extension = req.discord_username.clone();
match cfg.request_tx.send(req) {
Ok(()) => format!(
Ok(()) => {
set_call_nickname_for_extension(command.guild_id, cfg, &extension).await;
format!(
"Dialing extension `{}` from your current voice channel.",
extension
),
)
}
Err(_) => "Outbound call queue is unavailable right now.".to_string(),
}
}
@ -793,10 +796,13 @@ async fn handle_directory_button(
"/directory",
) {
Ok(req) => match cfg.request_tx.send(req) {
Ok(()) => format!(
Ok(()) => {
set_call_nickname(component.guild_id, cfg, &entry.label).await;
format!(
"Dialing `{}` (`{}`) from your current voice channel.",
entry.label, entry.extension
),
)
}
Err(_) => "Outbound call queue is unavailable right now.".to_string(),
},
Err(msg) => msg,
@ -805,6 +811,76 @@ async fn handle_directory_button(
respond_to_component(ctx, component, &response).await;
}
async fn set_call_nickname_for_extension(
guild_id: Option<GuildId>,
cfg: &DiscordOutboundCallConfig,
extension: &str,
) {
let display_name = cfg
.phone_directory
.iter()
.find(|entry| entry.extension == extension)
.map(|entry| entry.label.as_str())
.unwrap_or(extension);
set_call_nickname(guild_id, cfg, display_name).await;
}
async fn set_call_nickname(
guild_id: Option<GuildId>,
cfg: &DiscordOutboundCallConfig,
display_name: &str,
) {
let Some(guild_id) = guild_id else {
return;
};
let nickname = call_nickname(display_name);
let url = format!(
"https://discord.com/api/v10/guilds/{}/members/@me",
guild_id.get()
);
let result = reqwest::Client::new()
.patch(url)
.header("Authorization", format!("Bot {}", cfg.bot_token))
.json(&serde_json::json!({ "nick": nickname }))
.send()
.await;
match result {
Ok(response) if response.status().is_success() => {
debug!(
"Set bot nickname in guild {} while calling {}",
guild_id, display_name
);
}
Ok(response) => {
warn!(
"Failed to set bot nickname in guild {}: HTTP {}",
guild_id,
response.status()
);
}
Err(e) => {
warn!("Failed to set bot nickname in guild {}: {}", guild_id, e);
}
}
}
fn call_nickname(display_name: &str) -> String {
const MAX_NICKNAME_CHARS: usize = 32;
let trimmed = display_name.trim();
let name = if trimmed.is_empty() { "Unknown" } else { trimmed };
if name.chars().count() <= MAX_NICKNAME_CHARS {
return name.to_string();
}
let mut out: String = name.chars().take(MAX_NICKNAME_CHARS - 3).collect();
out.push_str("...");
out
}
fn build_directory_response(entries: &[PhoneDirectoryEntry]) -> CreateInteractionResponseMessage {
let visible_entries: Vec<&PhoneDirectoryEntry> = entries
.iter()
@ -941,10 +1017,9 @@ fn build_hangup_request(
let requested_by = command
.member
.as_ref()
.and_then(|member| member.nick.clone())
.or_else(|| command.user.global_name.clone())
.unwrap_or_else(|| command.user.name.clone())
.to_string();
.and_then(|member| member.nick.as_ref().map(ToString::to_string))
.or_else(|| command.user.global_name.as_ref().map(ToString::to_string))
.unwrap_or_else(|| command.user.name.to_string());
Ok(HangupCallRequest {
request_id: format!("hangup-{}", command.id),