From 46c6907f598254f6dd518116a0d64f96fa2d2ebd Mon Sep 17 00:00:00 2001 From: legop3 Date: Sun, 14 Jun 2026 02:27:47 -0400 Subject: [PATCH] trying to get intercom working --- .env.example | 2 +- README.md | 21 +++++++++-------- sipcord-bridge/src/call/mod.rs | 2 ++ sipcord-bridge/src/config.rs | 12 +++++----- sipcord-bridge/src/transport/sip/mod.rs | 31 ++++++++++++++++++++++--- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 632908f..96ab09c 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +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 +# DISCORD_OUTBOUND_EXTENSION_PREFIX= # DATA_DIR=/var/lib/sipcord # CONFIG_PATH=./config.toml # SOUNDS_DIR=./wav diff --git a/README.md b/README.md index c44c68b..126cbb5 100644 --- a/README.md +++ b/README.md @@ -73,7 +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 +DISCORD_OUTBOUND_EXTENSION_PREFIX= ``` Set both IPs to the address other SIP devices use to reach the bridge. For @@ -86,10 +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. +Discord-originated calls dial the requested extension directly by default. The +bridge also attaches common auto-answer headers (`Call-Info: +;answer-after=0` and `Alert-Info: Ring Answer`) to Discord-originated +outbound calls so auto-answer phones can behave more like FreePBX intercom +targets. Create a `docker-compose.yml`: @@ -212,10 +213,10 @@ 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. -- 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. +- It dials the requested extension directly, for example + `sip:1101@192.168.0.25:5060;transport=udp`. +- Discord-originated outbound calls also include common auto-answer headers so + phones configured for that behavior can answer immediately. - When the SIP side answers, the phone call is connected to the Discord voice channel where the command was run. @@ -261,7 +262,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 | +| `DISCORD_OUTBOUND_EXTENSION_PREFIX` | `""` | Optional prefix prepended before the requested extension for Discord-originated calls | | `CONFIG_PATH` | `./config.toml` | Path to config.toml | | `DIALPLAN_PATH` | `./dialplan.toml` | Path to dialplan.toml | | `SOUNDS_DIR` | `./wav` | Path to sound files directory | diff --git a/sipcord-bridge/src/call/mod.rs b/sipcord-bridge/src/call/mod.rs index 2de3f72..78536fb 100644 --- a/sipcord-bridge/src/call/mod.rs +++ b/sipcord-bridge/src/call/mod.rs @@ -553,6 +553,7 @@ impl BridgeCoordinator { tracking_id: req.call_id.clone(), sip_uri, caller_display_name: Some(req.caller_username.clone()), + auto_answer: true, fork_total, }); outbound_backend.report_call_status(&req.call_id, "ringing"); @@ -585,6 +586,7 @@ impl BridgeCoordinator { tracking_id: req.call_id.clone(), sip_uri, caller_display_name: Some(req.caller_username.clone()), + auto_answer: false, fork_total, }); } diff --git a/sipcord-bridge/src/config.rs b/sipcord-bridge/src/config.rs index 7a6cdf0..273ab40 100644 --- a/sipcord-bridge/src/config.rs +++ b/sipcord-bridge/src/config.rs @@ -72,7 +72,7 @@ fn default_discord_outbound_sip_transport() -> String { "udp".to_string() } fn default_discord_outbound_extension_prefix() -> String { - "*80".to_string() + String::new() } /// All environment variables consumed by the bridge, deserialized once at startup. @@ -546,7 +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(), + discord_outbound_extension_prefix: String::new(), }; assert_eq!(env.resolved_data_dir(), "."); } @@ -574,7 +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(), + discord_outbound_extension_prefix: String::new(), }; assert_eq!(env.resolved_data_dir(), "/tmp"); } @@ -602,7 +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(), + discord_outbound_extension_prefix: String::new(), }; let tls = env.to_tls_config(); assert_eq!(tls.cert_dir, PathBuf::from("/data/certs")); @@ -646,13 +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(), + discord_outbound_extension_prefix: String::new(), }; let outbound = env.discord_outbound_sip_config().unwrap(); assert_eq!( outbound.build_sip_uri("1101"), - "sip:*801101@192.168.0.25:5060;transport=udp" + "sip:1101@192.168.0.25:5060;transport=udp" ); } diff --git a/sipcord-bridge/src/transport/sip/mod.rs b/sipcord-bridge/src/transport/sip/mod.rs index 77bbc17..824cab5 100644 --- a/sipcord-bridge/src/transport/sip/mod.rs +++ b/sipcord-bridge/src/transport/sip/mod.rs @@ -117,6 +117,7 @@ pub enum SipCommand { tracking_id: String, sip_uri: String, caller_display_name: Option, + auto_answer: bool, /// Total number of fork legs for this tracking_id (for multi-contact forking) fork_total: usize, }, @@ -407,13 +408,14 @@ fn process_sip_command(cmd: SipCommand, calls: &Arc>) tracking_id, sip_uri, caller_display_name, + auto_answer, fork_total, } => { info!( - "Making outbound call: tracking_id={}, uri={}, caller={:?}, fork={}/{}", - tracking_id, sip_uri, caller_display_name, fork_total, fork_total + "Making outbound call: tracking_id={}, uri={}, caller={:?}, auto_answer={}, fork={}/{}", + tracking_id, sip_uri, caller_display_name, auto_answer, fork_total, fork_total ); - match make_outbound_call(&sip_uri, caller_display_name.as_deref()) { + match make_outbound_call(&sip_uri, caller_display_name.as_deref(), auto_answer) { Ok(call_id) => { // Store tracking_id -> call_id mapping let outbound_calls = OUTBOUND_CALL_TRACKING.get_or_init(DashMap::new); @@ -524,8 +526,10 @@ pub fn remove_outbound_tracking(call_id: CallId) -> Option { fn make_outbound_call( sip_uri: &str, caller_display_name: Option<&str>, + auto_answer: bool, ) -> Result { unsafe { + use self::ffi::pj_str::make_string_hdr; let uri = std::ffi::CString::new(sip_uri).map_err(|source| SipCallError::InvalidString { field: "sip_uri", @@ -548,6 +552,27 @@ fn make_outbound_call( ::pjsua::pjsua_msg_data_init(msg_data.as_mut_ptr()); let msg_data_ptr = msg_data.assume_init_mut(); + if auto_answer { + let pool = ::pjsua::pjsua_pool_create(c"outbound_call".as_ptr(), 512, 512); + if pool.is_null() { + return Err(SipCallError::MakeCall(-1)); + } + + let call_info = make_string_hdr(pool, c"Call-Info", ";answer-after=0") + .map_err(|_| SipCallError::MakeCall(-1))?; + ::pjsua::pj_list_insert_before( + &mut msg_data_ptr.hdr_list as *mut _ as *mut ::pjsua::pj_list_type, + call_info as *mut ::pjsua::pj_list_type, + ); + + let alert_info = make_string_hdr(pool, c"Alert-Info", "Ring Answer") + .map_err(|_| SipCallError::MakeCall(-1))?; + ::pjsua::pj_list_insert_before( + &mut msg_data_ptr.hdr_list as *mut _ as *mut ::pjsua::pj_list_type, + alert_info as *mut ::pjsua::pj_list_type, + ); + } + // Build the From URI with display name: "name" // The local_uri field overrides the From header in the outgoing INVITE let from_uri_cstring;