Skip to content

Conversation

@ThePotato97
Copy link

@ThePotato97 ThePotato97 commented Apr 25, 2025

closes #186

Add Discord Rich Presence (RPC) Support

This adds basic support for Discord Rich Presence to psst.

If you enable it and provide a valid Discord Application ID, the app will connect to your running Discord client and show the currently playing track in your profile.


Changes

  • Added a new discord_rpc.rs module under psst-core to manage the Discord RPC connection in a background thread.
  • Added discord-presence as a new dependency.
  • Extended the user configuration with two new fields:
    • discord_rpc_enable (bool)
    • discord_rpc_app_id (string)
  • Updated the Preferences UI with:
    • A checkbox to enable/disable Discord RPC.
    • A text field for entering your Discord App ID.
    • A button linking to Discord Developer Portal if you need to create one.
  • Hooked the RPC updates into the PlaybackController:
    • Starts the RPC client when enabled (and app ID is valid).
    • Updates it live if the App ID changes.
    • Shuts it down cleanly when disabled or when quitting.

Behavior

  • Nothing happens unless the user explicitly enables the feature.
  • Invalid App IDs are rejected cleanly with warnings.
  • If playback changes (play, pause, seek, next track), the Discord status updates automatically.
  • Disconnects and clears the Discord status when pausing or stopping playback.

Notes

  • Only one new real dependency was added: discord-presence.
  • Defaults are safe: Discord RPC is disabled by default.

Screenshots

image
image


Why

Because Discord status is cool
and now people can see what garbage music you're listening to in real time.

Copy link
Collaborator

@jacksongoode jacksongoode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice - thank you for contributing! but I'd like to do a proper re-review after the unrelated linting changes were resolved.

@SO9010
Copy link
Collaborator

SO9010 commented May 6, 2025

I had a little skim over this PR! It's looking good so far!

Also I think the taplo.toml shouldn't be included to the PR.

@SO9010
Copy link
Collaborator

SO9010 commented Jun 17, 2025

Just a question, do we have to make it so that the user makes up their ID or are we able to say generate it for them so its more automatic?

@Cleboost
Copy link
Contributor

Cleboost commented Jun 30, 2025

Link #186 to close

@Cleboost
Copy link
Contributor

Any news with this pr?

@SO9010
Copy link
Collaborator

SO9010 commented Dec 3, 2025

Just a question, do we have to make it so that the user makes up their ID or are we able to say generate it for them so its more automatic?

@jacksongoode
Copy link
Collaborator

Just a question, do we have to make it so that the user makes up their ID or are we able to say generate it for them so its more automatic?

Also curious about this. And we should maybe hide the ID once the rpc is connected?

Copy link
Collaborator

@jacksongoode jacksongoode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some more comments, but it looks good, just some note around following Last.fm's scrobbler pattern.

Comment on lines +66 to +68
while !discord_presence::Client::is_ready() {
std::thread::sleep(Duration::from_millis(10));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 10ms a little too fast for this? Also could there be a case where it never becomes ready? We should have some fallback to kill the loop in that case.

Comment on lines +95 to +110
DiscordRpcCmd::UpdateAppId(new_id) => {
// take the old client out
if let Some(old) = rpc.client.take() {
if let Err(e) = old.shutdown() {
log::warn!("shutdown failed: {}", e);
}
}
// create replacement
match Self::create_client(new_id) {
Ok(new_cli) => rpc.client = Some(new_cli),
Err(e) => log::warn!("failed to create new client: {}", e),
}
}
}
}
// when tx is dropped everywhere, rx returns Err -> loop ends, rpc is dropped
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, these comments can be capitalized.

None
}

fn parse_valid_app_id(id_str: &str) -> Option<u64> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a link to some docs as to what is a valid app id?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +324 to +328
fn update_discord_rpc(&mut self, playback: &Playback) {
if let Some(now_playing) = playback.now_playing.as_ref() {
if let Some(discord_rpc_sender) = &mut self.discord_rpc_sender {
let (title, artist, album_name, images, duration, progress) =
match &now_playing.item {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is quite nested, maybe you can opt for some early returns to keep it a little cleaner here.

Comment on lines 591 to +592
self.report_now_playing(&data.playback);
self.update_discord_rpc(&data.playback);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a look at the Last.fm reporting it is inside report_now_playing. We should put the Discord rpc scrobbler there as well and generally follow Last.fm's pattern unless there's some reason not to.

pub lastfm_api_key: Option<String>,
pub lastfm_api_secret: Option<String>,
pub lastfm_enable: bool,
pub discord_rpc_app_id: String,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be an Option?

.with_spacer(theme::grid(1.0))
.with_child(
Label::new(
"Connects to a running Discord client to display currently playing music",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Connects to a running Discord client to display currently playing music",
"Connects to a running Discord client to display what's currently playing",

))
.with_spacer(theme::grid(1.0))
.with_child(
Button::new("Create a Discord App →")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the Last.fm button, I don't think we need the arrow - for consistency sake.

Comment on lines 945 to 950
self.for_all_pages(request, |page: Page<Option<EpisodeLink>>| {
if !page.items.is_empty() {
let ids = page
.items
.into_iter()
.filter_map(|link| link.map(|link| link.id));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated change?

@@ -0,0 +1,2 @@
[formatting]
indent_string = " "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unrelated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discord Rich Presence is not showing

4 participants