Skip to content
This repository was archived by the owner on Oct 4, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ futures = "0.3.5"
isahc = { version = "0.9.3", features = ["json"] }

flate2 = { version = "1.0.14", features = ["zlib"], default-features = false }
url = "2.1.1"
url = { version = "2.1.1", features = ["serde"] }
log = "0.4.8"

[dependencies.tokio]
Expand Down
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ pub enum PandaError {
/// tungstenite
TungsteniteError(TungsteniteError),

/// An invalid webhook id was given
InvalidWebhook,

RuntimeError,
}

Expand All @@ -102,6 +105,7 @@ impl fmt::Display for PandaError {
Self::TungsteniteError(e) => write!(f, "Tungstenite Error: {}", e),
Self::UnknownOpcodeSent => write!(f, "panda sent an invalid Opcode, please report the bug"),
Self::InvalidDecodeSent => write!(f, "panda sent an invalid payload, please report the bug"),
Self::InvalidWebhook => write!(f, "invalid webhook id"),
Self::RuntimeError => write!(f, "runtime error")
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/gateway/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl GatewayConnection {
log::error!("Disconnected from the gateway, starting reconnect...");
match GatewayConnection::new().await {
Ok(g) => {
std::mem::replace(self, g);
*self = g;
log::info!("Connected succesfully");
break;
}
Expand Down
55 changes: 53 additions & 2 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
models::{
channel::{Channel, Embed, Message},
user::User,
webhook::{Webhook, ExecuteWebhook},
},
};

Expand Down Expand Up @@ -427,8 +428,6 @@ impl HttpClient {
let route = Route::get_channel_invite(channel_id);
let res = self._make_request(route).await?;

dbg!(res);

Ok(())
}
// // pub async fn create_channel_invite() {}
Expand Down Expand Up @@ -488,4 +487,56 @@ impl HttpClient {
// PUT/channels/{channel.id}/recipients/{user.id}

// DELETE/channels/{channel.id}/recipients/{user.id}

/// Creates a new webook, and returns the [`Webhook`]. For the format of the avatar data, see
/// [the Discord API docs](https://discord.com/developers/docs/reference#image-data).
///
/// [`Webhook`]: ../../panda/models/webhook/struct.Webhook.html
pub async fn create_webhook(
&self,
channel_id: impl AsRef<str>,
name: impl AsRef<str>,
avatar_data: Option<impl AsRef<str>>
) -> Result<Webhook> {
// Create body
let body = match avatar_data {
Some(data) => serde_json::json!({ "name": name.as_ref(), "avatar": data.as_ref() }),
None => serde_json::json!({ "name": name.as_ref() }),
};

// Parse to a valid Body, isahc::Body
let body = serde_json::to_string(&body)?;

// Create route
let route = Route::create_webhook(channel_id, body);
let mut res = self._make_request(route).await?;
Ok(res.json()?)
}

/// Deletes a webhook.
pub async fn delete_webhook(&self, webhook_id: impl AsRef<str>) -> Result<()> {
// Create route
let route = Route::delete_webhook(webhook_id);
if self._make_request(route).await?.status() == StatusCode::NO_CONTENT {
Ok(())
} else {
Err(PandaError::InvalidWebhook)
}
}

/// Executes a webhook with the given parameters.
pub async fn execute_webhook(
&self,
webhook_id: impl AsRef<str>,
token: impl AsRef<str>,
params: ExecuteWebhook
) -> Result<()> {
// Parse to a valid Body, isahc::Body
let body = serde_json::to_string(&params)?;

// Create route
let route = Route::execute_webhook(webhook_id, token, body);
let _ = self._make_request(route).await?;
Ok(())
}
}
49 changes: 49 additions & 0 deletions src/http/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ macro_rules! bucket_key {
(emoji: $id: expr) => {
format!("emoji:{}", $id.as_ref());
};
(webhook: $id: expr) => {
format!("webhooks:{}", $id.as_ref());
};
}

macro_rules! api_request {
Expand Down Expand Up @@ -322,6 +325,20 @@ impl Route<()> {
body: (),
}
}

pub(crate) fn delete_webhook(webhook_id: impl AsRef<str>) -> Self {
let method = Method::DELETE;
let uri = api_request!("/webhooks/{}", webhook_id.as_ref());

let bucket_key = bucket_key!(webhook: webhook_id);

Route {
method,
uri,
bucket_key,
body: (),
}
}
}

// Routes with body
Expand Down Expand Up @@ -446,6 +463,38 @@ impl<B: Into<Body>> Route<B> {
// body,
// }
// }

pub(crate) fn create_webhook(channel_id: impl AsRef<str>, body: B) -> Self {
let method = Method::POST;
let uri = api_request!("/channels/{}/webhooks", channel_id.as_ref());

let bucket_key = bucket_key!(channel: channel_id);

Route {
method,
uri,
bucket_key,
body,
}
}

pub(crate) fn execute_webhook(
webhook_id: impl AsRef<str>,
token: impl AsRef<str>,
body: B
) -> Self {
let method = Method::POST;
let uri = api_request!("/webhooks/{}/{}", webhook_id.as_ref(), token.as_ref());

let bucket_key = bucket_key!(webhook: webhook_id);

Route {
method,
uri,
bucket_key,
body,
}
}
}

/// Used to encode emoji as a valid char in URL
Expand Down
3 changes: 3 additions & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod user;
pub mod voice;
#[doc(inline)]
pub mod invite;
#[doc(inline)]
pub mod webhook;

// Re-export all models
pub use channel::*;
Expand All @@ -25,3 +27,4 @@ pub use guild::*;
pub use user::*;
pub use voice::*;
pub use invite::*;
pub use webhook::*;
48 changes: 48 additions & 0 deletions src/models/webhook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::models::user::User;
use crate::models::channel::Embed;
use serde::{Deserialize, Serialize};
use serde_repr::*;

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Webhook {
pub id: String,
#[serde(rename = "type")]
pub kind: WebhookKind,
pub guild_id: Option<String>,
pub channel_id: String,
/// The user this webhook was created by. `None` when getting a webhook by its token.
pub user: Option<User>,
pub name: Option<String>,
pub avatar: Option<String>,
/// The secure token of the webhook. Only `Some` for incoming webhooks ([`WebhookKind::Incoming`])
///
/// [`WebhookKind::Incoming`]: enum.WebhookKind.html#variant.Incoming
pub token: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct ExecuteWebhook {
#[serde(flatten)]
pub payload: WebhookPayload,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar_url: Option<url::Url>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tts: Option<bool>,
}

#[derive(Debug, Serialize)]
pub enum WebhookPayload {
#[serde(rename = "content")]
MessageContent(String),
#[serde(rename = "embeds")]
Embeds(Vec<Embed>),
}

#[derive(Clone, Debug, Deserialize_repr, Serialize_repr, PartialEq)]
#[repr(u8)]
pub enum WebhookKind {
Incoming = 1,
ChannelFollower = 2,
}