discord_toggle_rpc/
lib.rs

1extern crate dotenv;
2mod utils;
3
4use dotenv::dotenv;
5use serde_json::from_str;
6use std::{collections::HashMap, env, error::Error, io};
7use utils::handle_status_error;
8
9const SETTING_ENDPOINTS: &str = "https://discord.com/api/v9/users/@me/settings";
10const CONNECTION_ENDPOINTS: &str = "https://discord.com/api/v9/users/@me/connections";
11
12pub async fn run(mut args: env::Args) {
13    dotenv().ok();
14    let discord_token = env::var("DISCORD_TOKEN").expect("Discord Token is not found");
15
16    // skip the first index (name of binary)
17    args.next();
18
19    match args.next() {
20        Some(arg) => {
21            match_arg(arg, discord_token).await;
22        }
23        None => {
24            eprintln!("Please include arguments.");
25        }
26    };
27}
28
29pub async fn match_arg(arg: String, token: String) {
30    if arg == "game" {
31        match toggle_game_rpc(token).await {
32            Ok(status) => {
33                println!("Show playing game: {}", status);
34            }
35            Err(err) => {
36                eprintln!("An error occured: {}", err);
37            }
38        }
39    } else if arg == "spotify" {
40        match toggle_spotify_rpc(token).await {
41            Ok(status) => {
42                println!("Show listening Spotify: {}", status);
43            }
44            Err(err) => {
45                eprintln!("An error occured: {}", err);
46            }
47        }
48    }
49}
50
51pub async fn toggle_spotify_rpc(token: String) -> Result<serde_json::Value, Box<dyn Error>> {
52    let client = reqwest::Client::new();
53
54    let res = client
55        .get(CONNECTION_ENDPOINTS)
56        .header("authorization", &token)
57        .send()
58        .await?;
59
60    handle_status_error(res.status().as_u16())?;
61
62    let connections: serde_json::Value = from_str(&res.text().await?)?;
63
64    let mut spotify_id: Option<&str> = None;
65    let mut show_activity_spotify: Option<bool> = None;
66
67    for connection in connections.as_array().unwrap().iter() {
68        if connection["type"].as_str().unwrap() == "spotify" {
69            spotify_id = connection["id"].as_str();
70            show_activity_spotify = connection["show_activity"].as_bool();
71            break;
72        }
73    }
74
75    // Check whether spotify_id is None or not
76    if spotify_id.is_none() {
77        let connection_error =
78            io::Error::new(io::ErrorKind::Other, "Account is not connected to Spotify");
79
80        return Err(Box::new(connection_error));
81    };
82
83    let spotify_id = spotify_id.unwrap();
84    let show_activity_spotify = show_activity_spotify.unwrap();
85
86    let mut body = HashMap::new();
87
88    body.insert("show_activity", !show_activity_spotify);
89
90    let res = client
91        .patch(format!("{}/spotify/{}", CONNECTION_ENDPOINTS, spotify_id))
92        .header("authorization", &token)
93        .json(&body)
94        .send()
95        .await?;
96
97    if let Err(err) = handle_status_error(res.status().as_u16()) {
98        Err(err)
99    } else {
100        let current_settings: serde_json::Value = serde_json::from_str(&res.text().await?)?;
101
102        Ok(current_settings["show_activity"].clone())
103    }
104}
105
106pub async fn toggle_game_rpc(token: String) -> Result<serde_json::Value, Box<dyn Error>> {
107    let client = reqwest::Client::new();
108
109    let res = client
110        .get(SETTING_ENDPOINTS)
111        .header("authorization", &token)
112        .send()
113        .await?;
114
115    handle_status_error(res.status().as_u16())?;
116
117    // parse from json
118    let settings: serde_json::Value = serde_json::from_str(&res.text().await?)?;
119
120    let mut body = HashMap::new();
121    let show_current_game = &settings["show_current_game"];
122
123    body.insert("show_current_game", !show_current_game.as_bool().unwrap());
124
125    let res = client
126        .patch(SETTING_ENDPOINTS)
127        .header("authorization", &token)
128        .json(&body)
129        .send()
130        .await?;
131
132    if let Err(err) = handle_status_error(res.status().as_u16()) {
133        Err(err)
134    } else {
135        let current_settings: serde_json::Value = serde_json::from_str(&res.text().await?)?;
136
137        Ok(current_settings["show_current_game"].clone())
138    }
139}
140
141#[tokio::test]
142#[should_panic(expected = "Unauthorized request")]
143async fn unauthorized_request_game() {
144    let discord_token = String::from("Invalid token peko");
145
146    match toggle_game_rpc(discord_token).await {
147        Ok(_) => {
148            // Should not run
149            panic!("Oh no.");
150        }
151        Err(_) => {
152            panic!("Unauthorized request");
153        }
154    }
155}
156
157#[tokio::test]
158#[should_panic(expected = "Unauthorized request")]
159async fn unauthorized_request_spotify() {
160    let discord_token = String::from("Invalid token peko");
161
162    match toggle_spotify_rpc(discord_token).await {
163        Ok(_) => {
164            // Should not run
165            panic!("Oh no.");
166        }
167        Err(_) => {
168            panic!("Unauthorized request");
169        }
170    }
171}