Skip to content

Commit f67edc9

Browse files
authored
feat: use separate Discord apps for movies and TV shows (#159)
1 parent f33483f commit f67edc9

13 files changed

Lines changed: 168 additions & 160 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,22 @@ A simple app that acts as a bridge between [Discord](https://discord.com/) and [
1515

1616
<p align="center"><img src="./docs/demo/profile-status.png" width="260px" alt="Profile Status"><p>
1717

18+
<p align="center"><img src="./docs/demo/tray.png" width="260px" alt="Tray"><p>
19+
1820
**Protip**: If you are already using Plex, and would like to link it with Trakt, you can use the [Plex-Trakt-Scrobbler](https://github.com/trakt/Plex-Trakt-Scrobbler) plugin.
1921

2022
If you already know **Trakt** and how awesome it is this is definitely the best option, as it works **more reliably** and with some extra benefits over the other implementations, such as registering your watch status **wherever you are watching** (TV, phone, across the world, etc.), **in whatever app you are watching on** (Netflix, Roku, Plex, HBO Max), as long as you have a single device running **Discord** and **Discrakt**.
2123

24+
## Features
25+
26+
- Separate Discord Rich Presence apps for Movies and TV Shows
27+
- Movie posters and show artwork displayed via TMDB
28+
- Direct links to IMDB and Trakt pages
29+
- Progress bar showing watch percentage
30+
- System tray integration with pause/resume functionality
31+
- Start at login option
32+
- Browser-based setup wizard with OAuth device flow
33+
2234
Plex Rich Presence alternatives:
2335

2436
- [discord-rich-presence-plex](https://github.com/Phineas05/discord-rich-presence-plex)

docs/demo/console.png

63.1 KB
Loading

docs/demo/discrakt.png

-196 KB
Binary file not shown.

docs/demo/discrakt.psd

-1.36 MB
Binary file not shown.

docs/demo/member-list.png

3.92 KB
Loading

docs/demo/profile-status.png

-33.8 KB
Loading

docs/demo/tray.png

33.4 KB
Loading

src/autostart.rs

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const LAUNCHAGENT_LABEL: &str = "com.afonsojramos.discrakt";
33

44
#[cfg(target_os = "macos")]
55
mod macos {
6-
use super::*;
6+
use super::LAUNCHAGENT_LABEL;
77
use std::fs;
88
use std::path::PathBuf;
99
use std::process::Command;
@@ -13,7 +13,7 @@ mod macos {
1313
}
1414

1515
fn plist_path() -> Option<PathBuf> {
16-
launch_agents_dir().map(|d| d.join(format!("{}.plist", LAUNCHAGENT_LABEL)))
16+
launch_agents_dir().map(|d| d.join(format!("{LAUNCHAGENT_LABEL}.plist")))
1717
}
1818

1919
fn app_path() -> Option<PathBuf> {
@@ -35,7 +35,7 @@ mod macos {
3535
}
3636

3737
pub fn is_enabled() -> bool {
38-
plist_path().map(|p| p.exists()).unwrap_or(false)
38+
plist_path().is_some_and(|p| p.exists())
3939
}
4040

4141
pub fn enable() -> Result<(), String> {
@@ -45,24 +45,23 @@ mod macos {
4545
// Ensure LaunchAgents directory exists
4646
if let Some(dir) = plist_path.parent() {
4747
fs::create_dir_all(dir)
48-
.map_err(|e| format!("Failed to create LaunchAgents dir: {}", e))?;
48+
.map_err(|e| format!("Failed to create LaunchAgents dir: {e}"))?;
4949
}
5050

5151
// Determine if we're launching an app bundle or binary
52-
let (program_path, program_args) =
53-
if app_path.extension().map(|e| e == "app").unwrap_or(false) {
54-
// App bundle - use open command
55-
(
56-
"/usr/bin/open".to_string(),
57-
format!(
58-
"<string>-a</string>\n <string>{}</string>",
59-
app_path.display()
60-
),
61-
)
62-
} else {
63-
// Direct binary
64-
(app_path.display().to_string(), String::new())
65-
};
52+
let (program_path, program_args) = if app_path.extension().is_some_and(|e| e == "app") {
53+
// App bundle - use open command
54+
(
55+
"/usr/bin/open".to_string(),
56+
format!(
57+
"<string>-a</string>\n <string>{}</string>",
58+
app_path.display()
59+
),
60+
)
61+
} else {
62+
// Direct binary
63+
(app_path.display().to_string(), String::new())
64+
};
6665

6766
let plist_content = if program_args.is_empty() {
6867
format!(
@@ -71,10 +70,10 @@ mod macos {
7170
<plist version="1.0">
7271
<dict>
7372
<key>Label</key>
74-
<string>{}</string>
73+
<string>{LAUNCHAGENT_LABEL}</string>
7574
<key>ProgramArguments</key>
7675
<array>
77-
<string>{}</string>
76+
<string>{program_path}</string>
7877
</array>
7978
<key>RunAtLoad</key>
8079
<true/>
@@ -84,8 +83,7 @@ mod macos {
8483
<string>Interactive</string>
8584
</dict>
8685
</plist>
87-
"#,
88-
LAUNCHAGENT_LABEL, program_path
86+
"#
8987
)
9088
} else {
9189
format!(
@@ -94,11 +92,11 @@ mod macos {
9492
<plist version="1.0">
9593
<dict>
9694
<key>Label</key>
97-
<string>{}</string>
95+
<string>{LAUNCHAGENT_LABEL}</string>
9896
<key>ProgramArguments</key>
9997
<array>
100-
<string>{}</string>
101-
{}
98+
<string>{program_path}</string>
99+
{program_args}
102100
</array>
103101
<key>RunAtLoad</key>
104102
<true/>
@@ -108,13 +106,11 @@ mod macos {
108106
<string>Interactive</string>
109107
</dict>
110108
</plist>
111-
"#,
112-
LAUNCHAGENT_LABEL, program_path, program_args
109+
"#
113110
)
114111
};
115112

116-
fs::write(&plist_path, plist_content)
117-
.map_err(|e| format!("Failed to write plist: {}", e))?;
113+
fs::write(&plist_path, plist_content).map_err(|e| format!("Failed to write plist: {e}"))?;
118114

119115
// Load the launch agent
120116
let plist_path_str = plist_path
@@ -139,7 +135,7 @@ mod macos {
139135
.output();
140136
}
141137

142-
fs::remove_file(&plist_path).map_err(|e| format!("Failed to remove plist: {}", e))?;
138+
fs::remove_file(&plist_path).map_err(|e| format!("Failed to remove plist: {e}"))?;
143139

144140
tracing::info!("Autostart disabled");
145141
}

src/discord.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ use std::{thread::sleep, time::Duration};
66

77
use crate::{
88
trakt::{Trakt, TraktWatchingResponse},
9-
utils::{get_watch_stats, MediaType},
9+
utils::{
10+
get_watch_stats, MediaType, DEFAULT_DISCORD_APP_ID_MOVIE, DEFAULT_DISCORD_APP_ID_SHOW,
11+
},
1012
};
1113

1214
pub struct Discord {
1315
client: DiscordIpcClient,
16+
current_app_id: String,
1417
}
1518

1619
#[derive(Default)]
@@ -30,17 +33,50 @@ impl Discord {
3033
tracing::error!("Couldn't create Discord client: {e}");
3134
e
3235
})?;
33-
Ok(Discord { client })
36+
Ok(Discord {
37+
client,
38+
current_app_id: discord_client_id,
39+
})
40+
}
41+
42+
/// Switch to a different Discord application ID if needed.
43+
/// Returns true if a switch occurred.
44+
fn switch_app_id(&mut self, new_app_id: &str) -> bool {
45+
if self.current_app_id == new_app_id {
46+
return false;
47+
}
48+
49+
tracing::info!(
50+
"Switching Discord app ID from {} to {}",
51+
self.current_app_id,
52+
new_app_id
53+
);
54+
55+
// Close existing connection
56+
let _ = self.client.close();
57+
58+
// Create new client with new app ID
59+
match DiscordIpcClient::new(new_app_id) {
60+
Ok(new_client) => {
61+
self.client = new_client;
62+
self.current_app_id = new_app_id.to_string();
63+
self.connect();
64+
true
65+
}
66+
Err(e) => {
67+
tracing::error!("Failed to create Discord client with new app ID: {e}");
68+
false
69+
}
70+
}
3471
}
3572

3673
pub fn connect(&mut self) {
3774
loop {
3875
if self.client.connect().is_ok() {
3976
break;
40-
} else {
41-
tracing::warn!("Failed to connect to Discord, retrying in 15 seconds");
42-
sleep(Duration::from_secs(15));
4377
}
78+
tracing::warn!("Failed to connect to Discord, retrying in 15 seconds");
79+
sleep(Duration::from_secs(15));
4480
}
4581
}
4682

@@ -56,6 +92,13 @@ impl Discord {
5692
) {
5793
let mut payload_data = Payload::default();
5894

95+
// Switch to appropriate Discord app ID based on media type
96+
let target_app_id = match trakt_response.r#type.as_str() {
97+
"episode" => DEFAULT_DISCORD_APP_ID_SHOW,
98+
_ => DEFAULT_DISCORD_APP_ID_MOVIE, // Default to movie for unknown types (including "movie")
99+
};
100+
self.switch_app_id(target_app_id);
101+
59102
let img_url = match trakt_response.r#type.as_str() {
60103
"movie" => {
61104
let movie = trakt_response.movie.as_ref().unwrap();

src/main.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use discrakt::{
99
state::AppState,
1010
trakt::Trakt,
1111
tray::{Tray, TrayCommand},
12-
utils::{get_watch_stats, load_config},
12+
utils::{get_watch_stats, load_config, DEFAULT_DISCORD_APP_ID},
1313
};
1414
use std::{
1515
sync::{
@@ -114,15 +114,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
114114
let app_state_clone = Arc::clone(&app_state);
115115
let should_quit_clone = Arc::clone(&should_quit);
116116

117-
let discord_client_id = cfg.discord_client_id.clone();
118117
let trakt_client_id = cfg.trakt_client_id.clone();
119118
let trakt_username = cfg.trakt_username.clone();
120119
let trakt_access_token = cfg.trakt_access_token.clone();
121120
let tmdb_token = cfg.tmdb_token.clone();
122121

123122
// Spawn background polling thread
124123
let polling_handle = thread::spawn(move || {
125-
let mut discord = match Discord::new(discord_client_id) {
124+
let mut discord = match Discord::new(DEFAULT_DISCORD_APP_ID.to_string()) {
126125
Ok(d) => d,
127126
Err(e) => {
128127
tracing::error!("Failed to create Discord client: {e}");

0 commit comments

Comments
 (0)