From 3876e12ec98dbca1e3b09c728c043913ba396631 Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 10 Mar 2026 21:54:25 -0400 Subject: [PATCH] feat: add Owncast API client for sending messages and checking status Made-with: Cursor --- src/main.rs | 1 + src/owncast_api.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/owncast_api.rs diff --git a/src/main.rs b/src/main.rs index b8695e8..3a164cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod config; mod events; mod html; +mod owncast_api; fn main() { println!("owncast-irc-bridge"); diff --git a/src/owncast_api.rs b/src/owncast_api.rs new file mode 100644 index 0000000..f4b5c83 --- /dev/null +++ b/src/owncast_api.rs @@ -0,0 +1,78 @@ +use reqwest::Client; +use tracing::{error, warn}; + +pub struct OwncastApiClient { + client: Client, + base_url: String, + access_token: String, +} + +impl OwncastApiClient { + pub fn new(base_url: String, access_token: String) -> Self { + Self { + client: Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + access_token, + } + } + + pub async fn send_chat_message(&self, body: &str) -> anyhow::Result<()> { + let url = format!("{}/api/integrations/chat/send", self.base_url); + let resp = self + .client + .post(&url) + .bearer_auth(&self.access_token) + .json(&serde_json::json!({ "body": body })) + .send() + .await?; + + let status = resp.status(); + if status.is_success() { + return Ok(()); + } + + let resp_body = resp.text().await.unwrap_or_default(); + if status.is_client_error() { + error!(status = %status, body = %resp_body, "Owncast API client error (not retrying)"); + anyhow::bail!("Owncast API returned {status}"); + } + + warn!(status = %status, body = %resp_body, "Owncast API server error, retrying once"); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + let retry_resp = self + .client + .post(&url) + .bearer_auth(&self.access_token) + .json(&serde_json::json!({ "body": body })) + .send() + .await?; + + let retry_status = retry_resp.status(); + if retry_status.is_success() { + Ok(()) + } else { + let retry_body = retry_resp.text().await.unwrap_or_default(); + error!(status = %retry_status, body = %retry_body, "Owncast API retry failed"); + anyhow::bail!("Owncast API retry returned {}", retry_status) + } + } + + pub async fn get_status(&self) -> anyhow::Result { + let url = format!("{}/api/status", self.base_url); + let resp = self.client.get(&url).send().await?; + let status: OwncastStatus = resp.json().await?; + Ok(status) + } +} + +#[derive(Debug, serde::Deserialize)] +pub struct OwncastStatus { + pub online: bool, + #[serde(default)] + #[serde(rename = "streamTitle")] + pub stream_title: Option, + #[serde(default)] + #[serde(rename = "viewerCount")] + pub viewer_count: Option, +}