Skip to content
Open
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
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "5.0.20"
edition = "2018"

[dependencies]
discord-rich-presence = "1"
once_cell = "1.19"
native-windows-gui = { git = "https://github.com/Stremio/native-windows-gui", features = [
"high-dpi",
Expand Down
61 changes: 61 additions & 0 deletions src/stremio_app/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::stremio_app::{
PipeServer,
};

use super::discord::DiscordRpc;
use super::stremio_server::StremioServer;

#[derive(Default, NwgUi)]
Expand Down Expand Up @@ -247,6 +248,10 @@ impl MainWindow {
let hide_splash_sender = self.hide_splash_notice.sender();
let focus_sender = self.focus_notice.sender();
let autoupdater_setup_mutex = self.autoupdater_setup_file.clone();

let discord_rpc: Arc<Mutex<Option<DiscordRpc>>> = Arc::new(Mutex::new(None));
let discord_rpc_clone = discord_rpc.clone();

thread::spawn(move || loop {
if let Some(msg) = web_rx
.recv()
Expand Down Expand Up @@ -336,6 +341,62 @@ impl MainWindow {
}
}
}
Some("discord-connect") => {
let mut discord_guard = discord_rpc_clone.lock().unwrap();
if discord_guard.is_none() {
let discord = DiscordRpc::new();
match discord.connect() {
Ok(()) => {
*discord_guard = Some(discord);
web_tx_web.send(RPCResponse::discord_status(true)).ok();
}
Err(e) => {
eprintln!("Discord connect error: {}", e);
web_tx_web.send(RPCResponse::discord_status(false)).ok();
}
}
} else {
// Already connected
web_tx_web.send(RPCResponse::discord_status(true)).ok();
}
}
Some("discord-disconnect") => {
let mut discord_guard = discord_rpc_clone.lock().unwrap();
if let Some(ref discord) = *discord_guard {
if let Err(e) = discord.disconnect() {
eprintln!("Discord disconnect error: {}", e);
}
}
*discord_guard = None;
web_tx_web.send(RPCResponse::discord_status(false)).ok();
}
Some("discord-set-activity") => {
if let Some(params) = msg.get_params() {
let state = params.get("state").and_then(|v| v.as_str()).unwrap_or("");
let details =
params.get("details").and_then(|v| v.as_str()).unwrap_or("");
let image = params.get("image").and_then(|v| v.as_str());
let start_timestamp =
params.get("startTimestamp").and_then(|v| v.as_i64());

let discord_guard = discord_rpc_clone.lock().unwrap();
if let Some(ref discord) = *discord_guard {
if let Err(e) =
discord.set_activity(state, details, image, start_timestamp)
{
eprintln!("Discord set activity error: {}", e);
}
}
}
}
Some("discord-clear-activity") => {
let discord_guard = discord_rpc_clone.lock().unwrap();
if let Some(ref discord) = *discord_guard {
if let Err(e) = discord.clear_activity() {
eprintln!("Discord clear activity error: {}", e);
}
}
}
Some(player_command) if player_command.starts_with("mpv-") => {
let resp_json = serde_json::to_string(
&msg.args.expect("Cannot have method without args"),
Expand Down
122 changes: 122 additions & 0 deletions src/stremio_app/discord.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient};
use std::sync::{Arc, Mutex};

const DISCORD_APP_ID: &str = "1452620752263319665";

/// Discord Rich Presence for Stremio
/// Handles connection to Discord and activity updates
pub struct DiscordRpc {
client: Arc<Mutex<Option<DiscordIpcClient>>>,
}

impl Default for DiscordRpc {
fn default() -> Self {
Self::new()
}
}

impl DiscordRpc {
pub fn new() -> Self {
Self {
client: Arc::new(Mutex::new(None)),
}
}

pub fn connect(&self) -> Result<(), String> {
let mut client_guard = self.client.lock().map_err(|e| e.to_string())?;

if client_guard.is_some() {
return Ok(());
}

let mut client = DiscordIpcClient::new(DISCORD_APP_ID);

client
.connect()
.map_err(|e| format!("Failed to connect to Discord: {}", e))?;

*client_guard = Some(client);

println!("Discord RPC connected");
Ok(())
}

pub fn disconnect(&self) -> Result<(), String> {
let mut client_guard = self.client.lock().map_err(|e| e.to_string())?;

if let Some(mut client) = client_guard.take() {
client
.close()
.map_err(|e| format!("Failed to close Discord connection: {}", e))?;
}

println!("Discord RPC disconnected");
Ok(())
}

pub fn set_activity(
&self,
state: &str,
details: &str,
large_image: Option<&str>,
start_timestamp: Option<i64>,
) -> Result<(), String> {
let mut client_guard = self.client.lock().map_err(|e| e.to_string())?;

if let Some(ref mut client) = *client_guard {
let mut payload = activity::Activity::new().state(state).details(details);

// add assets
if let Some(image) = large_image {
payload = payload.assets(
activity::Assets::new()
.large_image(image)
.large_text("Stremio"),
);
} else {
// Default Stremio logo
payload = payload.assets(
activity::Assets::new()
.large_image("stremio_logo")
.large_text("Stremio"),
);
}

// add timestamps
if let Some(start) = start_timestamp {
payload = payload.timestamps(activity::Timestamps::new().start(start));
}

client
.set_activity(payload)
.map_err(|e| format!("Failed to set activity: {}", e))?;

println!("Discord activity set: {} - {}", state, details);
} else {
return Err("Discord not connected".to_string());
}

Ok(())
}

/// Clear activity
pub fn clear_activity(&self) -> Result<(), String> {
let mut client_guard = self.client.lock().map_err(|e| e.to_string())?;

if let Some(ref mut client) = *client_guard {
client
.clear_activity()
.map_err(|e| format!("Failed to clear activity: {}", e))?;

println!("Discord activity cleared");
}

Ok(())
}
}

impl Drop for DiscordRpc {
fn drop(&mut self) {
let _ = self.disconnect();
}
}
5 changes: 5 additions & 0 deletions src/stremio_app/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,9 @@ impl RPCResponse {
pub fn update_available() -> String {
Self::response_message(Some(json!(["autoupdater-show-notif"])))
}
pub fn discord_status(connected: bool) -> String {
Self::response_message(Some(json!(["discord-status", {
"connected": connected,
}])))
}
}
1 change: 1 addition & 0 deletions src/stremio_app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod app;
pub use app::MainWindow;
pub mod discord;
pub mod ipc;
pub mod stremio_player;
pub mod stremio_server;
Expand Down