15 stable releases

1.4.2 Aug 2, 2024
1.4.1 Mar 27, 2024
1.3.1 Jan 8, 2024
1.2.1 Dec 17, 2023
1.0.4 Apr 26, 2023

#51 in HTTP client

21 downloads per month
Used in natsuki

MIT license

76KB
1.5K SLoC

topgg crates.io crates.io downloads

The official Rust SDK for the Top.gg API.

Getting Started

Make sure to have a Top.gg API token handy. If not, then view this tutorial on how to retrieve yours. After that, add the following line to the dependencies section of your Cargo.toml:

topgg = "1.4"

For more information, please read the documentation!

Features

This library provides several feature flags that can be enabled/disabled in Cargo.toml. Such as:

  • api: Interacting with the Top.gg API and accessing the top.gg/api/* endpoints. (enabled by default)
    • autoposter: Automating the process of periodically posting bot statistics to the Top.gg API.
  • webhook: Accessing the serde deserializable topgg::Vote struct.
    • actix-web: Wrapper for working with the actix-web web framework.
    • axum: Wrapper for working with the axum web framework.
    • rocket: Wrapper for working with the rocket web framework.
    • warp: Wrapper for working with the warp web framework.
  • serenity: Extra helpers for working with serenity library (with bot caching disabled).
    • serenity-cached: Extra helpers for working with serenity library (with bot caching enabled).
  • twilight: Extra helpers for working with twilight library (with bot caching disabled).
    • twilight-cached: Extra helpers for working with twilight library (with bot caching enabled).

Examples

Fetching a user from its Discord ID

use topgg::Client;

#[tokio::main]
async fn main() {
  let client = Client::new(env!("TOPGG_TOKEN").to_string());
  let user = client.get_user(661200758510977084).await.unwrap();
  
  assert_eq!(user.username, "null");
  assert_eq!(user.id, 661200758510977084);
  
  println!("{:?}", user);
}

Posting your bot's statistics

use topgg::{Client, Stats};

#[tokio::main]
async fn main() {
  let client = Client::new(env!("TOPGG_TOKEN").to_string());

  let server_count = 12345;
  client
    .post_stats(Stats::from(server_count))
    .await
    .unwrap();
}

Checking if a user has voted your bot

use topgg::Client;

#[tokio::main]
async fn main() {
  let client = Client::new(env!("TOPGG_TOKEN").to_string());

  if client.has_voted(661200758510977084).await.unwrap() {
    println!("checks out");
  }
}

Autoposting with serenity

In your Cargo.toml:

[dependencies]
# using serenity with guild caching disabled
topgg = { version = "1.4", features = ["autoposter", "serenity"] }

# using serenity with guild caching enabled
topgg = { version = "1.4", features = ["autoposter", "serenity-cached"] }

In your code:

use core::time::Duration;
use serenity::{client::{Client, Context, EventHandler}, model::{channel::Message, gateway::Ready}};
use topgg::Autoposter;

struct Handler;

#[serenity::async_trait]
impl EventHandler for Handler {
  async fn message(&self, ctx: Context, msg: Message) {
    if msg.content == "!ping" {
      if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
        println!("Error sending message: {why:?}");
      }
    }
  }

  async fn ready(&self, _: Context, ready: Ready) {
    println!("{} is connected!", ready.user.name);
  }
}

#[tokio::main]
async fn main() {
  let topgg_client = topgg::Client::new(env!("TOPGG_TOKEN").to_string());
  let autoposter = Autoposter::serenity(&topgg_client, Duration::from_secs(1800));
  
  let bot_token = env!("DISCORD_TOKEN").to_string();
  let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT;

  let mut client = Client::builder(&bot_token, intents)
    .event_handler(Handler)
    .event_handler_arc(autoposter.handler())
    .await
    .unwrap();

  if let Err(why) = client.start().await {
    println!("Client error: {why:?}");
  }
}

Autoposting with twilight

In your Cargo.toml:

[dependencies]
# using twilight with guild caching disabled
topgg = { version = "1.4", features = ["autoposter", "twilight"] }

# using twilight with guild caching enabled
topgg = { version = "1.4", features = ["autoposter", "twilight-cached"] }

In your code:

use core::time::Duration;
use topgg::Autoposter;
use twilight_gateway::{Event, Intents, Shard, ShardId};

#[tokio::main]
async fn main() {
  let client = topgg::Client::new(env!("TOPGG_TOKEN").to_string());
  let autoposter = Autoposter::twilight(&client, Duration::from_secs(1800));

  let mut shard = Shard::new(
    ShardId::ONE,
    env!("DISCORD_TOKEN").to_string(),
    Intents::GUILD_MEMBERS | Intents::GUILDS,
  );

  loop {
    let event = match shard.next_event().await {
      Ok(event) => event,
      Err(source) => {
        if source.is_fatal() {
          break;
        }

        continue;
      }
    };
    
    autoposter.handle(&event).await;
    
    match event {
      Event::Ready(_) => {
        println!("Bot is ready!");
      },

      _ => {}
    }
  }
}

Writing an actix-web webhook for listening to votes

In your Cargo.toml:

[dependencies]
topgg = { version = "1.4", default-features = false, features = ["actix-web"] }

In your code:

use actix_web::{
  error::{Error, ErrorUnauthorized},
  get, post, App, HttpServer,
};
use std::io;
use topgg::IncomingVote;

#[get("/")]
async fn index() -> &'static str {
  "Hello, World!"
}

#[post("/webhook")]
async fn webhook(vote: IncomingVote) -> Result<&'static str, Error> {
  match vote.authenticate(env!("TOPGG_WEBHOOK_PASSWORD")) {
    Some(vote) => {
      println!("{:?}", vote);

      Ok("ok")
    }
    _ => Err(ErrorUnauthorized("401")),
  }
}

#[actix_web::main]
async fn main() -> io::Result<()> {
  HttpServer::new(|| App::new().service(index).service(webhook))
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Writing an axum webhook for listening to votes

In your Cargo.toml:

[dependencies]
topgg = { version = "1.4", default-features = false, features = ["axum"] }

In your code:

use axum::{routing::get, Router, Server};
use std::{net::SocketAddr, sync::Arc};
use topgg::{Vote, VoteHandler};

struct MyVoteHandler {}

#[axum::async_trait]
impl VoteHandler for MyVoteHandler {
  async fn voted(&self, vote: Vote) {
    println!("{:?}", vote);
  }
}

async fn index() -> &'static str {
  "Hello, World!"
}

#[tokio::main]
async fn main() {
  let state = Arc::new(MyVoteHandler {});

  let app = Router::new().route("/", get(index)).nest(
    "/webhook",
    topgg::axum::webhook(env!("TOPGG_WEBHOOK_PASSWORD").to_string(), Arc::clone(&state)),
  );

  let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();

  Server::bind(&addr)
    .serve(app.into_make_service())
    .await
    .unwrap();
}

Writing a rocket webhook for listening to votes

In your Cargo.toml:

[dependencies]
topgg = { version = "1.4", default-features = false, features = ["rocket"] }

In your code:

#![feature(decl_macro)]

use rocket::{get, http::Status, post, routes};
use topgg::IncomingVote;

#[get("/")]
fn index() -> &'static str {
  "Hello, World!"
}

#[post("/webhook", data = "<vote>")]
fn webhook(vote: IncomingVote) -> Status {
  match vote.authenticate(env!("TOPGG_WEBHOOK_PASSWORD")) {
    Some(vote) => {
      println!("{:?}", vote);

      Status::Ok
    },
    _ => {
      println!("found an unauthorized attacker.");

      Status::Unauthorized
    }
  }
}

fn main() {
  rocket::ignite()
    .mount("/", routes![index, webhook])
    .launch();
}

Writing a warp webhook for listening to votes

In your Cargo.toml:

[dependencies]
topgg = { version = "1.4", default-features = false, features = ["warp"] }

In your code:

use std::{net::SocketAddr, sync::Arc};
use topgg::{Vote, VoteHandler};
use warp::Filter;

struct MyVoteHandler {}

#[async_trait::async_trait]
impl VoteHandler for MyVoteHandler {
  async fn voted(&self, vote: Vote) {
    println!("{:?}", vote);
  }
}

#[tokio::main]
async fn main() {
  let state = Arc::new(MyVoteHandler {});

  // POST /webhook
  let webhook = topgg::warp::webhook(
    "webhook",
    env!("TOPGG_WEBHOOK_PASSWORD").to_string(),
    Arc::clone(&state),
  );

  let routes = warp::get().map(|| "Hello, World!").or(webhook);

  let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();

  warp::serve(routes).run(addr).await
}

Dependencies

~0.3–36MB
~558K SLoC