Skip to content

Commit

Permalink
feat: add more methods from the undocumented api
Browse files Browse the repository at this point in the history
  • Loading branch information
doinkythederp committed Sep 2, 2023
1 parent eb950fa commit 54847ce
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 10 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ repository = "https://github.com/doinkythederp/discordlist-rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["rustls-tls"]
default = ["rustls-tls", "undocumented"]
rustls-tls = ["reqwest/rustls-tls"]
native-tls = ["reqwest/native-tls"]
undocumented = []

[dependencies]
bitflags = "2.4.0"
const_format = "0.2.31"
reqwest = { version = "0.11.20", features = ["json"], default-features = false }
serde = { version = "1.0.188", features = ["derive"] }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ Discordlist-rs uses `reqwest` to make HTTPS requests, which has two options for
- `rustls-tls` (default): Use the `rustls` crate to make HTTPS requests.
- `native-tls`: Use the platform-specific TLS implementation to make HTTPS requests.

Discordlist also has some undocumented API endpoints which can be enabled with a feature:
Discordlist also has some undocumented API endpoints which can be controlled with a feature:

- `undocumented`: Enable making API calls that are not documented by Discordlist. Considered less stable.
- `undocumented` (default): Enable making API calls that are not documented by Discordlist. Considered less stable.

## Usage

Expand Down
70 changes: 70 additions & 0 deletions src/bitflags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! A set of macros for easily working with internals.
//!
//! ISC License (ISC)
//!
//! Copyright (c) 2016, Serenity Contributors
//!
//! Permission to use, copy, modify, and/or distribute this software for any purpose
//! with or without fee is hereby granted, provided that the above copyright notice
//! and this permission notice appear in all copies.
//!
//! THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
//! REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
//! FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
//! INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
//! OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
//! TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
//! THIS SOFTWARE.

/// The macro forwards the generation to the `bitflags::bitflags!` macro and implements
/// the default (de)serialization for Discord's bitmask values.
///
/// The flags are created with `T::from_bits_truncate` for the deserialized integer value.
///
/// Use the `bitflags::bitflags! macro directly if a different serde implementation is required.

macro_rules! bitflags {
(
$(#[$outer:meta])*
$vis:vis struct $BitFlags:ident: $T:ty {
$(
$(#[$inner:ident $($args:tt)*])*
const $Flag:ident = $value:expr;
)*
}

$($t:tt)*
) => {
bitflags::bitflags! {
$(#[$outer])*
$vis struct $BitFlags: $T {
$(
$(#[$inner $($args)*])*
const $Flag = $value;
)*
}
}

bitflags!(__impl_serde $BitFlags: $T);

bitflags! {
$($t)*
}
};
(__impl_serde $BitFlags:ident: $T:tt) => {
impl<'de> serde::de::Deserialize<'de> for $BitFlags {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
Ok(Self::from_bits_truncate(<$T>::deserialize(deserializer)?))
}
}

impl serde::ser::Serialize for $BitFlags {
fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
self.bits().serialize(serializer)
}
}
};
() => {};
}

pub(crate) use bitflags;
153 changes: 146 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use std::time::Duration;

#[cfg(feature = "undocumented")]
use crate::search::SearchOptions;
use const_format::formatcp;
use reqwest::{Client, Method, RequestBuilder, Url};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;

#[cfg(feature = "undocumented")]
mod bitflags;
#[cfg(feature = "undocumented")]
pub mod search;

const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
const CRATE_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
Expand Down Expand Up @@ -59,7 +66,7 @@ impl DiscordlistClient {
.bearer_auth(&self.token)
}

/// Set the guild count displayed on your bot's discordlist.gg listing.
/// Set the guild count displayed on your bot's listing page.
pub async fn set_guild_count(&self, guild_count: u64) -> reqwest::Result<()> {
let mut endpoint = Self::endpoint(&format!("/bots/{}/guilds", self.bot_id));
endpoint
Expand All @@ -74,7 +81,7 @@ impl DiscordlistClient {
Ok(())
}

/// Add a command to your discordlist.gg bot listing page.x
/// Add a command to your bot listing page.
#[cfg(feature = "undocumented")]
pub async fn add_bot_command(&self, command: Command) -> reqwest::Result<()> {
self.build_request(
Expand All @@ -84,13 +91,55 @@ impl DiscordlistClient {
.json(&command)
.send()
.await?
.error_for_status()?;
.error_for_status()?
.json()
.await
}

/// Fetch a bot listing page
#[cfg(feature = "undocumented")]
pub async fn get_bot(&self, id: u64) -> reqwest::Result<Bot> {
self.build_request(Method::POST, Self::endpoint(&format!("/bots/{id}")))
.send()
.await?
.error_for_status()?
.json()
.await
}

todo!()
/// Fetch a user on discordlist.gg
#[cfg(feature = "undocumented")]
pub async fn get_user(&self, id: u64) -> reqwest::Result<User> {
self.build_request(Method::POST, Self::endpoint(&format!("/users/{id}")))
.send()
.await?
.error_for_status()?
.json()
.await
}

/// Search for Discord bots listed on discordlist.gg
#[cfg(feature = "undocumented")]
pub async fn search(&self, mut options: SearchOptions) -> reqwest::Result<SearchResults> {
const SEARCH_HOST: &str = "https://search.discordlist.gg";
let mut endpoint = Self::endpoint("/bots/search");
endpoint.set_host(Some(SEARCH_HOST)).unwrap();

if options.query.is_none() {
options.query = Some("*".to_string());
}

self.build_request(Method::POST, endpoint)
.json(&options)
.send()
.await?
.error_for_status()?
.json()
.await
}
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
/// A command shown on a discordlist.gg bot listing page.
pub struct Command {
Expand All @@ -101,3 +150,93 @@ pub struct Command {
syntax: String,
categories: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
/// A bot's listing page on discordlist.gg.
pub struct Bot {
flags: u64,
bot_id: Option<String>,
features: u64,
id: String,
username: String,
avatar: String,
discriminator: u64,
prefix: String,
is_packable: bool,
is_hidden: bool,
is_forced_into_hiding: bool,
invite_url: String,
webhook_url: Option<String>,
webhook_auth: Option<String>,
website_url: String,
repo_url: String,
twitter_url: String,
instagram_url: String,
support_server_url: String,
slug: String,
tags: Vec<String>,
created_on: String,
owner_id: String,
co_owner_ids: Vec<String>,
brief_description: String,
long_description: String,
guild_count: u64,
votes: u64,
all_time_votes: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
/// A user on discordlist.gg.
pub struct User {
avatar: Option<String>,
banner: Option<String>,
bio: Option<String>,
bots: Vec<String>,
claps: u64,
co_owned_bots: Vec<String>,
co_owned_guilds: Vec<String>,
created_on: String,
#[deprecated = "user discriminators are being phased out from Discord"]
discriminator: u64,
display_name: Option<String>,
flags: u64,
guilds: Vec<String>,
id: String,
packs: Vec<String>,
slug: Option<String>,
username: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
/// A search on discordlist.gg.
pub struct SearchResults {
hits: Vec<SearchHit>,
limit: u64,
nb_hits: u64,
offset: u64,
query: String,
tag_distribution: HashMap<String, u64>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
/// A search hit on discordlist.gg.
pub struct SearchHit {
avatar: String,
brief_description: String,
co_owner_ids: Vec<String>,
created_on: String,
discriminator: u64,
features: String,
flags: String,
guild_count: u64,
id: String,
invite_url: String,
owner_id: String,
prefix: String,
tags: String,
username: String,
votes: String,
}
Loading

0 comments on commit 54847ce

Please sign in to comment.