extension prefixes

This commit is contained in:
legop3 2026-06-14 02:12:27 -04:00
parent efb07ace5d
commit 6e416573e5
3 changed files with 37 additions and 6 deletions

View file

@ -7,6 +7,7 @@ RTP_PUBLIC_IP=192.168.0.100
# DISCORD_OUTBOUND_SIP_HOST=192.168.0.25
# DISCORD_OUTBOUND_SIP_PORT=5060
# DISCORD_OUTBOUND_SIP_TRANSPORT=udp
# DISCORD_OUTBOUND_EXTENSION_PREFIX=*80
# DATA_DIR=/var/lib/sipcord
# CONFIG_PATH=./config.toml
# SOUNDS_DIR=./wav

View file

@ -73,6 +73,7 @@ RTP_PUBLIC_IP=192.168.0.100
DISCORD_OUTBOUND_SIP_HOST=192.168.0.25
DISCORD_OUTBOUND_SIP_PORT=5060
DISCORD_OUTBOUND_SIP_TRANSPORT=udp
DISCORD_OUTBOUND_EXTENSION_PREFIX=*80
```
Set both IPs to the address other SIP devices use to reach the bridge. For
@ -85,6 +86,11 @@ Set `DISCORD_OUTBOUND_SIP_HOST` to the PBX or SIP server that should receive
Discord-originated extension calls. For a FreePBX box at `192.168.0.25`, that
means `DISCORD_OUTBOUND_SIP_HOST=192.168.0.25`.
By default, Discord-originated calls prepend `*80` to the requested extension so
FreePBX intercom/auto-answer targets work out of the box. Set
`DISCORD_OUTBOUND_EXTENSION_PREFIX=` to an empty string if you want plain
extension dialing instead.
Create a `docker-compose.yml`:
```yaml
@ -205,8 +211,11 @@ Usage:
Behavior:
- The user running `/call` must already be in a Discord voice channel.
- The bot uses that voice channel as the bridge destination.
- The bridge dials the requested extension through the configured PBX target, for
example `sip:1101@192.168.0.25:5060;transport=udp`.
- The bridge dials the requested extension through the configured PBX target.
- By default it prepends `*80`, so `/call extension:1101` becomes something like
`sip:*801101@192.168.0.25:5060;transport=udp`.
- Set `DISCORD_OUTBOUND_EXTENSION_PREFIX=` to empty if you want `/call
extension:1101` to dial `1101` directly instead.
- When the SIP side answers, the phone call is connected to the Discord voice
channel where the command was run.
@ -252,6 +261,7 @@ Dial `1000` (or whatever you put in `dialplan.toml`) and you should hear the bot
| `DISCORD_OUTBOUND_SIP_HOST` | *(disabled if unset)* | PBX/SIP host used by Discord `/call` |
| `DISCORD_OUTBOUND_SIP_PORT` | `5060` | Port for Discord-originated outbound SIP calls |
| `DISCORD_OUTBOUND_SIP_TRANSPORT` | `udp` | Transport for Discord-originated outbound SIP calls: `udp`, `tcp`, or `tls` |
| `DISCORD_OUTBOUND_EXTENSION_PREFIX` | `*80` | Prefix prepended before the requested extension for Discord-originated calls; set empty for direct dialing |
| `CONFIG_PATH` | `./config.toml` | Path to config.toml |
| `DIALPLAN_PATH` | `./dialplan.toml` | Path to dialplan.toml |
| `SOUNDS_DIR` | `./wav` | Path to sound files directory |

View file

@ -71,6 +71,9 @@ fn default_discord_outbound_sip_port() -> u16 {
fn default_discord_outbound_sip_transport() -> String {
"udp".to_string()
}
fn default_discord_outbound_extension_prefix() -> String {
"*80".to_string()
}
/// All environment variables consumed by the bridge, deserialized once at startup.
#[derive(Debug, Clone, serde::Deserialize)]
@ -117,6 +120,8 @@ pub struct EnvConfig {
pub discord_outbound_sip_port: u16,
#[serde(default = "default_discord_outbound_sip_transport")]
pub discord_outbound_sip_transport: String,
#[serde(default = "default_discord_outbound_extension_prefix")]
pub discord_outbound_extension_prefix: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -142,18 +147,28 @@ pub struct DiscordOutboundSipConfig {
pub host: String,
pub port: u16,
pub transport: OutboundSipTransport,
pub extension_prefix: String,
}
impl DiscordOutboundSipConfig {
pub fn build_sip_uri(&self, extension: &str) -> String {
let dialed_extension = format!("{}{}", self.extension_prefix, extension);
match self.transport {
OutboundSipTransport::Udp => {
format!("sip:{}@{}:{};transport=udp", extension, self.host, self.port)
format!(
"sip:{}@{}:{};transport=udp",
dialed_extension, self.host, self.port
)
}
OutboundSipTransport::Tcp => {
format!("sip:{}@{}:{};transport=tcp", extension, self.host, self.port)
format!(
"sip:{}@{}:{};transport=tcp",
dialed_extension, self.host, self.port
)
}
OutboundSipTransport::Tls => {
format!("sips:{}@{}:{}", dialed_extension, self.host, self.port)
}
OutboundSipTransport::Tls => format!("sips:{}@{}:{}", extension, self.host, self.port),
}
}
}
@ -230,6 +245,7 @@ impl EnvConfig {
host,
port: self.discord_outbound_sip_port,
transport,
extension_prefix: self.discord_outbound_extension_prefix.clone(),
})
}
@ -530,6 +546,7 @@ mod tests {
discord_outbound_sip_host: None,
discord_outbound_sip_port: 5060,
discord_outbound_sip_transport: "udp".to_string(),
discord_outbound_extension_prefix: "*80".to_string(),
};
assert_eq!(env.resolved_data_dir(), ".");
}
@ -557,6 +574,7 @@ mod tests {
discord_outbound_sip_host: None,
discord_outbound_sip_port: 5060,
discord_outbound_sip_transport: "udp".to_string(),
discord_outbound_extension_prefix: "*80".to_string(),
};
assert_eq!(env.resolved_data_dir(), "/tmp");
}
@ -584,6 +602,7 @@ mod tests {
discord_outbound_sip_host: None,
discord_outbound_sip_port: 5060,
discord_outbound_sip_transport: "udp".to_string(),
discord_outbound_extension_prefix: "*80".to_string(),
};
let tls = env.to_tls_config();
assert_eq!(tls.cert_dir, PathBuf::from("/data/certs"));
@ -627,12 +646,13 @@ mod tests {
discord_outbound_sip_host: Some("192.168.0.25".to_string()),
discord_outbound_sip_port: 5060,
discord_outbound_sip_transport: "udp".to_string(),
discord_outbound_extension_prefix: "*80".to_string(),
};
let outbound = env.discord_outbound_sip_config().unwrap();
assert_eq!(
outbound.build_sip_uri("1101"),
"sip:1101@192.168.0.25:5060;transport=udp"
"sip:*801101@192.168.0.25:5060;transport=udp"
);
}