Merge branch 'feature/docs' into develop

This commit is contained in:
DylanCa 2024-01-13 12:41:54 +01:00
commit 7371d99e0e
18 changed files with 165 additions and 33 deletions

View file

@ -1,13 +1,14 @@
[package]
name = "rust-discord-activity"
description = "A lightweight library to control Discord Rich Presence"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
authors = ["DylanCa <dylan.cattelan@gmail.com"]
authors = ["Dylan Cattelan <dylan.cattelan@gmail.com"]
license = "MIT"
keywords = ["discord", "activity", "rich", "presence"]
documentation = "https://docs.rs/Rust-Discord-Activity/latest"
repository = "https://github.com/DylanCa/Rust-Discord-Activity"
homepage = "https://github.com/DylanCa/Rust-Discord-Activity"
categories = ["api-bindings", "gui", "multimedia"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -6,17 +6,19 @@ Rust Discord Activity is available directly on [crates.io](https://crates.io/cra
`cargo add rust-discord-activity`
## How to use
Three steps:
1. Create your Activity and set desired data using provided structs
2. Create a new Payload with your Activity
3. Instantiate a new DiscordClient and send your Payload through it
1. Instantiate a new DiscordClient
2. Create your Activity and set desired data using provided structs
3. Create a new Payload with your Activity
4. Send your Payload through the DiscordClient
Et voilà !
## Example
```rust
let mut client = DiscordClient::new("<application_id>");
let limg = Some(String::from("https://placehold.co/600x400/png"));
let simg = Some(String::from("https://placehold.co/600x400/png"));
let simg = Some(String::from("https://placehold.co/200x100/png"));
let asset = Asset::new(limg, None, simg, None);
let now_in_millis = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
let timestamp = Timestamp::new(Some(now_in_millis - 10000), None);
@ -40,7 +42,6 @@ activity
let payload = Payload::new(EventName::Activity, EventData::Activity(activity));
let mut client = DiscordClient::new("<application_id>");
let _ = client.send_payload(payload);
```
@ -49,6 +50,9 @@ And voilà! This sets-up a new Activity for the current Discord user:
<img alt="Discord Rich Presence" src="https://imgur.com/gf9pOen.png" width="300"/>
## Limitations
For the moment, the library only works with MacOS and local Discord application.
## Next Steps
- Write proper documentation for this library
- Write unit tests

View file

@ -8,23 +8,37 @@ use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use crate::models::client::{commands::Commands, payload::OpCode, payload::Payload};
use crate::models::error::Error as ErrorMsg;
use crate::models::error::Error::DiscordNotFound;
/// Client used to communicate with Discord through IPC.
pub struct DiscordClient {
/// ID of Discord Application, see <https://discord.com/developers> for more info
pub id: String,
socket: Option<UnixStream>,
}
impl DiscordClient {
/// Used to instantiate a new Discord Client.
pub fn new(id: &str) -> Self {
let mut client = Self {
Self {
id: id.to_string(),
socket: None,
};
}
}
client
.connect()
.expect("Could not connect to client. Is Discord running ?");
client
/// Tries to enable a connection to the Discord Application.
pub fn connect(&mut self) -> Result<(), ErrorMsg> {
let path = self.fetch_process_pathbuf().join("discord-ipc-0");
match UnixStream::connect(&path) {
Ok(socket) => {
self.socket = Some(socket);
self.handshake().expect("Could not handshake.");
Ok(())
}
Err(_) => Err(DiscordNotFound),
}
}
pub fn send_payload(&mut self, payload: Payload) -> Result<(u32, Value), Box<dyn Error>> {
@ -44,21 +58,6 @@ impl DiscordClient {
self.socket.as_mut().unwrap()
}
fn connect(&mut self) -> Result<(), Box<dyn Error>> {
let path = self.fetch_process_pathbuf().join("discord-ipc-0");
match UnixStream::connect(&path) {
Ok(socket) => {
self.socket = Some(socket);
self.handshake().expect("Could not handshake.");
}
Err(_) => panic!("Could not connect to client. Is Discord running ?"),
}
Ok(())
}
fn fetch_process_pathbuf(&mut self) -> PathBuf {
let mut path = String::new();

View file

@ -1,5 +1,64 @@
pub mod client;
pub mod models;
//! # Rust Discord Activity
//! _A lightweight Rust library to control Discord Rich Presence_
//!
//! ## Installation
//! Rust Discord Activity is available directly on [crates.io](https://crates.io/crates/rust-discord-activity):
//! `cargo add rust-discord-activity`
//!
//! ## How to use
//! 1. Instantiate a new DiscordClient
//! 2. Create your Activity and set desired data using provided structs
//! 3. Create a new Payload with your Activity
//! 4. Send your Payload through the DiscordClient
//!
//! Et voilà !
//!
//! ## Example
//! ```rust
//! let mut client = DiscordClient::new("<application_id>");
//!
//! let limg = Some(String::from("https://placehold.co/600x400/png"));
//! let simg = Some(String::from("https://placehold.co/200x100/png"));
//! let asset = Asset::new(limg, None, simg, None);
//! let now_in_millis = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
//! let timestamp = Timestamp::new(Some(now_in_millis - 10000), None);
//!
//! let party = Party::new(None, Some((2, 4)));
//! let mut button_vec = vec![];
//! button_vec.push(Button::new("First Button".into(), "https://google.com".into()));
//! button_vec.push(Button::new("Second Button".into(), "https://yahoo.com".into()));
//!
//! let mut activity = Activity::new();
//!
//! activity
//! .set_state(Some("This is State".into()))
//! .set_activity_type(Some(ActivityType::LISTENING))
//! .set_details(Some("This is Details".parse().unwrap()))
//! .set_timestamps(Some(timestamp))
//! .set_assets(Some(asset))
//! .set_party(Some(party))
//! .set_instance(Some(true))
//! .set_buttons(Some(button_vec));
//!
//! let payload = Payload::new(EventName::Activity, EventData::Activity(activity));
//!
//! let _ = client.send_payload(payload);
//!
//! ```
//!
//! And voilà! This sets-up a new Activity for the current Discord user:
//!
//! <img alt="Discord Rich Presence" src="https://imgur.com/gf9pOen.png" width="300"/>
//!
//! ## Limitations
//! For the moment, the library only works with MacOS and local Discord application.
//!
//! ## Next Steps
//! - Write proper documentation for this library
//! - Write unit tests
mod client;
mod models;
pub use client::ipc::DiscordClient;
pub use models::activity::Activity;

View file

@ -4,57 +4,73 @@ use crate::models::activity_data::{
};
use serde::Serialize;
/// Test Doc
/// Represents a Discord Activity object to be send to Discord application.
/// See <https://discord.com/developers/docs/game-sdk/activities#data-models> for more information.
#[derive(Serialize, Debug)]
pub struct Activity {
/// Name of the Discord Application - Read Only.
#[serde(skip_serializing)]
name: String,
/// Type of Activity - Will be discarded by Discord App.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
activity_type: Option<i8>,
/// Livestream URL, accepts only Twitch and Youtube links - Will be discarded by Discord App.
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
/// Time of creation of the Activity.
#[serde(skip_serializing_if = "Option::is_none")]
created_at: Option<u128>,
/// Timestamps for the Activity. Used to set the "elapsed / remaining" countdown on Discord Activity.
#[serde(skip_serializing_if = "Option::is_none")]
timestamps: Option<Timestamp>,
/// ID of Discord Application provided when instantiating DiscordClient.
#[serde(skip_serializing_if = "Option::is_none")]
application_id: Option<i32>,
/// First line of Discord Activity.
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<String>,
/// Second line of Discord Activity.
#[serde(skip_serializing_if = "Option::is_none")]
state: Option<String>,
/// Sets a custom Emoji on the Discord Activity - Will be discarded by Discord App.
#[serde(skip_serializing_if = "Option::is_none")]
emoji: Option<Emoji>,
/// Adds a player count after the State.
#[serde(skip_serializing_if = "Option::is_none")]
party: Option<Party>,
/// Activity Large image and Small image.
#[serde(skip_serializing_if = "Option::is_none")]
assets: Option<Asset>,
/// Adds a Secret URI to the activity to enable Chat Join messages.
#[serde(skip_serializing_if = "Option::is_none")]
secrets: Option<Secret>,
/// Whether activity is in an Instance context, like an ongoing match.
#[serde(skip_serializing_if = "Option::is_none")]
instance: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
flags: Option<i8>,
/// List of buttons added at the bottom of the Activity. Up to 2 buttons are supported by Discord.
#[serde(skip_serializing_if = "Option::is_none")]
buttons: Option<Vec<Button>>,
}
impl Activity {
/// Instantiates a new Activity.
pub fn new() -> Activity {
Self {
name: "".to_string(),
@ -75,11 +91,13 @@ impl Activity {
}
}
/// Sets a name for the Activity - Will be discarded by Discord App.
pub fn set_name(&mut self, name: String) -> &mut Self {
self.name = name;
self
}
/// Sets a new ActivityType - Will be discarded by Discord App.
pub fn set_activity_type(&mut self, activity_type: Option<ActivityType>) -> &mut Self {
match activity_type {
Some(val) => self.activity_type = Some(val as i8),
@ -89,61 +107,73 @@ impl Activity {
self
}
/// Sets a streaming URL.
pub fn set_url(&mut self, url: Option<String>) -> &mut Self {
self.url = url;
self
}
/// Sets a created_at for the Activity.
pub fn set_created_at(&mut self, created_at: Option<u128>) -> &mut Self {
self.created_at = created_at;
self
}
/// Sets new Timestamps for the Activity.
pub fn set_timestamps(&mut self, timestamps: Option<Timestamp>) -> &mut Self {
self.timestamps = timestamps;
self
}
/// Sets an Application ID for the Activity - Will be discarded by Discord App.
pub fn set_application_id(&mut self, application_id: Option<i32>) -> &mut Self {
self.application_id = application_id;
self
}
/// Sets the details for the current Activity.
pub fn set_details(&mut self, details: Option<String>) -> &mut Self {
self.details = details;
self
}
/// Sets a state for the current Activity.
pub fn set_state(&mut self, state: Option<String>) -> &mut Self {
self.state = state;
self
}
/// Sets an emoji for the current Activity.
pub fn set_emoji(&mut self, emoji: Option<Emoji>) -> &mut Self {
self.emoji = emoji;
self
}
/// Sets the party count for the current Activity.
pub fn set_party(&mut self, party: Option<Party>) -> &mut Self {
self.party = party;
self
}
/// Sets the Image Assets for the current Activity.
pub fn set_assets(&mut self, assets: Option<Asset>) -> &mut Self {
self.assets = assets;
self
}
/// Sets a Secret for the current Activity.
pub fn set_secrets(&mut self, secrets: Option<Secret>) -> &mut Self {
self.secrets = secrets;
self
}
/// Sets the instance boolean for the current Activity.
pub fn set_instance(&mut self, instance: Option<bool>) -> &mut Self {
self.instance = instance;
self
}
/// Sets the flags for the current Activity.
pub fn set_flags(&mut self, flag: Option<ActivityFlag>) -> &mut Self {
match flag {
Some(val) => self.flags = Some(val as i8),
@ -153,6 +183,7 @@ impl Activity {
self
}
/// Sets the Buttons for the current Activity. Up to 2 buttons are supported by Discord.
pub fn set_buttons(&mut self, buttons: Option<Vec<Button>>) -> &mut Self {
self.buttons = buttons;
self

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// List of Activity Flags to send to Discord Client.
#[derive(Serialize, Debug)]
pub enum ActivityFlag {
Instance = 1,

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// List of Activity Type - Only Game is supported for the moment.
#[derive(Serialize, Debug)]
pub enum ActivityType {
GAME = 0,

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// Contains Large and Small images of an Activity.
#[derive(Serialize, Debug)]
pub struct Asset {
#[serde(skip_serializing_if = "Option::is_none")]

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// Simple structure containing a label and an URL to form a Discord Activity button.
#[derive(Serialize, Debug)]
pub struct Button {
label: String,

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// Contains data of an Emoji. ID must be a Discord Emoji ID.
#[derive(Serialize, Debug)]
pub struct Emoji {
name: String,

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// Contains data of a Party.
#[derive(Serialize, Debug)]
pub struct Party {
#[serde(skip_serializing_if = "Option::is_none")]

View file

@ -1,5 +1,6 @@
use serde::Serialize;
/// Contains Secrets URIs to join, spectate or instantiate a match through Discord Chat.
#[derive(Serialize, Debug)]
pub struct Secret {
#[serde(skip_serializing_if = "Option::is_none")]

View file

@ -1,5 +1,9 @@
use serde::Serialize;
/// Contains start and end time for an Activity.
/// Must be in Milliseconds since UNIX_EPOCH time.
/// If only Start is set and is in the past, it will display "xx:xx elapsed"
/// Otherwise if End is set, it will display "xx:xx remaining"
#[derive(Serialize, Debug)]
pub struct Timestamp {
#[serde(skip_serializing_if = "Option::is_none")]

View file

@ -1,3 +1,5 @@
/// List of Commands to send through IPC to Discord Client.
/// Currently only supports SET_ACTIVITY.
pub enum Commands {
SetActivity,
}

View file

@ -2,12 +2,14 @@ use serde::Serialize;
use crate::models::activity::Activity;
/// List of EventData to send to Discord - Currently only supports Activity.
#[derive(Serialize, Debug)]
#[serde(untagged)]
pub enum EventData {
Activity(Activity),
}
/// List of EventName to send to Discord - Currently only supports Activity.
pub enum EventName {
Activity,
}

View file

@ -1,11 +1,13 @@
use crate::models::client::event::{EventData, EventName};
use serde::Serialize;
/// List of OpCode to send to Discord App through IPC.
pub enum OpCode {
HANDSHAKE,
MESSAGE,
}
/// Payload object used to encapsulate data to send to Discord Client.
#[derive(Serialize, Debug)]
pub struct Payload {
pub event_name: String,

20
src/models/error.rs Normal file
View file

@ -0,0 +1,20 @@
use std::borrow::Cow;
use std::fmt;
use std::fmt::{Display, Formatter};
/// Custom Error list for the library.
pub enum Error {
DiscordNotFound,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let msg = match self {
Error::DiscordNotFound => {
Cow::Borrowed("Could not connect to client. Is Discord running ?")
}
};
f.write_str(&msg)
}
}

View file

@ -1,3 +1,4 @@
pub mod activity;
pub mod activity_data;
pub mod client;
pub mod error;