wayclip-daemon 0.2.0

Recording and management of Wayclip daemon
use crate::{
    common::misc::discord::custom::CustomDiscordPresenceManager,
    linux::session::{CurrentSession, LastClipInfo, UserSessionInfo},
};
use discord_rich_presence::{
    DiscordIpc, DiscordIpcClient,
    activity::{Activity, ActivityType, Assets, Button, Timestamps},
};
use std::sync::mpsc;

pub mod custom;

const WAYCLIP_DISCORD_CLIENT_ID: &str = "1416659610416316466";

#[derive(Clone, Debug)]
enum PresenceCommand {
    RecordingEvent { current_session: CurrentSession },
    SavingEvent { current_session: CurrentSession },
}

#[derive(Clone)]
pub enum DiscordPresenceState {
    Custom(CustomDiscordPresenceManager),
    Default,
}

///  States:
///  (default)
///  1. Recording
///  2. Just saved clip
///
///  (custom)
///  3. Custom
///
///  Recording:
///  - Show how long
///  - Have counter & increment on save
///  - If logged in: Link user profile
///  - Button: Get Wayclip (promo button that user can disable)
///
///  Just saved a clip:
///  - Show length
///  - Get timestamp & display for ~3 min
///  - If uploaded: Link clip URL
///
///  Custom:
///  - State
///  - Details (with parsing)
///  - ActivityType
pub struct DiscordPresenceManager {
    tx: mpsc::Sender<PresenceCommand>,
}

impl DiscordPresenceManager {
    fn set_activity(client: &mut Option<DiscordIpcClient>, activity: Activity) {
        if client.is_none() {
            let mut c = DiscordIpcClient::new(WAYCLIP_DISCORD_CLIENT_ID);
            match c.connect() {
                Ok(()) => {
                    log::debug!("Connected to Discord IPC");
                    *client = Some(c);
                }
                Err(e) => {
                    log::debug!("Discord not available: {e}");
                    return;
                }
            }
        }

        let set_activity_failed = if let Some(discord) = client.as_mut() {
            discord.set_activity(activity).is_err()
        } else {
            true
        };

        if set_activity_failed {
            log::warn!("Failed to set Discord activity");
            *client = None;
        }
    }

    fn get_saved_buttons(last_clip_info: Option<LastClipInfo>) -> Vec<Button<'static>> {
        let mut buttons = Vec::new();

        if let Some(clip_info) = last_clip_info
            && let Some(url) = clip_info.url
        {
            buttons.push(Button::new(clip_info.name, url.to_string()));
        }

        buttons
    }

    fn get_recording_buttons(
        promo_enabled: bool,
        user_session_info: Option<UserSessionInfo>,
    ) -> Vec<Button<'static>> {
        let mut buttons = Vec::new();

        if promo_enabled {
            buttons.push(
                // TODO: Fix hardcoded URL
                Button::new("Get Wayclip", "https://wayclip.com"),
            );
        }

        if let Some(user_session) = user_session_info {
            buttons.push(Button::new(
                format!("{}'s profile", user_session.username),
                user_session.url.to_string(),
            ));
        }

        buttons
    }

    pub fn new(state: DiscordPresenceState) -> Self {
        let (tx, rx) = mpsc::channel::<PresenceCommand>();

        let state_clone = state.clone();
        std::thread::Builder::new()
            .name("discord-presence".into())
            .spawn(move || {
                let mut client: Option<DiscordIpcClient> = None;
                for command in rx {
                    let activity = match command {
                        PresenceCommand::RecordingEvent { current_session } => match state_clone {
                            DiscordPresenceState::Default => {
                                let mut activity = Activity::new().state("Recording Desktop");

                                if let Some(g) = current_session.game.clone() {
                                    let mut assets = Assets::new();
                                    if let Some(icon) = g.icon_url.clone() {
                                        assets = assets
                                            .large_image(icon)
                                            .small_image("wayclip-logo")
                                            .large_text(g.name.clone())
                                            .small_text("Clipping with Wayclip")
                                    }
                                    activity = activity
                                        .assets(assets)
                                        .name(g.name)
                                        .state("Clipping with Wayclip");
                                }

                                activity = activity
                                    .activity_type(ActivityType::Playing)
                                    // TODO: A way to disable promo
                                    .buttons(Self::get_recording_buttons(
                                        true,
                                        current_session.user_session,
                                    ))
                                    .timestamps(
                                        Timestamps::new()
                                            .start(current_session.started_at.timestamp()),
                                    );
                                activity
                            }
                            DiscordPresenceState::Custom(ref custom_config) => {
                                custom_config.get_activity(&current_session)
                            }
                        },
                        PresenceCommand::SavingEvent { current_session } => match state_clone {
                            DiscordPresenceState::Default => Activity::new()
                                .state("Saved clip")
                                .details("Just saved a new clip using Wayclip!")
                                .activity_type(ActivityType::Playing)
                                .buttons(Self::get_saved_buttons(current_session.last_clip)),
                            DiscordPresenceState::Custom(ref custom_config) => {
                                custom_config.get_activity(&current_session)
                            }
                        },
                    };
                    Self::set_activity(&mut client, activity);
                }

                if let Some(mut c) = client.take() {
                    let _ = c.close();
                }
            })
            .expect("spawn discord thread");

        Self { tx }
    }

    pub fn set_recording(&self, current_session: CurrentSession) {
        let _ = self
            .tx
            .send(PresenceCommand::RecordingEvent { current_session });
    }

    pub fn set_saving(&self, current_session: CurrentSession) {
        let _ = self
            .tx
            .send(PresenceCommand::SavingEvent { current_session });
    }
}