Merge branch 'feature/docs' into develop
This commit is contained in:
commit
7371d99e0e
18 changed files with 165 additions and 33 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
63
src/lib.rs
63
src/lib.rs
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use serde::Serialize;
|
||||
|
||||
/// List of Activity Flags to send to Discord Client.
|
||||
#[derive(Serialize, Debug)]
|
||||
pub enum ActivityFlag {
|
||||
Instance = 1,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
/// List of Commands to send through IPC to Discord Client.
|
||||
/// Currently only supports SET_ACTIVITY.
|
||||
pub enum Commands {
|
||||
SetActivity,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
20
src/models/error.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod activity;
|
||||
pub mod activity_data;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue