Packages

A comprehensive Elixir client for the Top.gg API, allowing you to interact with Discord bot statistics, user votes, and bot information with full type safety and excellent documentation.

Current section

Files

Jump to
topgg_ex README.md
Raw

README.md

# TopggEx

[![Hex.pm](https://img.shields.io/hexpm/v/topgg_ex.svg)](https://hex.pm/packages/topgg_ex)
[![Documentation](https://img.shields.io/badge/docs-hexdocs-blue.svg)](https://hexdocs.pm/topgg_ex/)
[![License](https://img.shields.io/hexpm/l/topgg_ex.svg)](https://github.com/jilv220/topgg_ex/blob/master/LICENSE)

A community Elixir SDK for the [Top.gg](https://top.gg) API, allowing you to interact with Discord bot statistics, user votes, and bot information.

## Features

- 🚀 **Complete API Coverage**: All Top.gg API endpoints supported
- 🎯 **Webhook Support**: Built-in webhook handler for vote notifications
- 🔒 **Type Safety**: Full typespecs and structured data
- ⚡ **HTTP/2 Support**: Built on Finch for modern HTTP performance
- 🧪 **Well Tested**: Comprehensive test suite with 95%+ coverage
- 📚 **Excellent Documentation**: Detailed docs with examples
- 🏗️ **Clean Architecture**: Separated HTTP client for maintainability

## Installation

Add `topgg_ex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:topgg_ex, "~> 0.1.0"},
    {:finch, "~> 0.19"}  # Required HTTP client
  ]
end
```

Then run:

```bash
mix deps.get
```

## Quick Start

### 1. Setup

First, add Finch to your application's supervision tree:

```elixir
# In your application.ex
children = [
  {Finch, name: :topgg_finch}
]
```

### 2. Create API Client

```elixir
# Get your token from https://top.gg/api/docs#mybots
{:ok, api} = TopggEx.Api.new("your_topgg_token_here")
```

### 3. Post Bot Statistics

```elixir
# Update your bot's server count
{:ok, stats} = TopggEx.Api.post_stats(api, %{server_count: 1250})
```

## Usage Examples

### Bot Statistics

```elixir
# Post bot stats
{:ok, _} = TopggEx.Api.post_stats(api, %{
  server_count: 1250,
  shard_count: 2,
  shards: [625, 625]
})

# Get your bot's current stats
{:ok, stats} = TopggEx.Api.get_stats(api)
# => %{server_count: 1250, shard_count: 2, shards: [625, 625]}
```

### Bot Information

```elixir
# Get information about any bot
{:ok, bot} = TopggEx.Api.get_bot(api, "461521980492087297")
# => %{"id" => "461521980492087297", "username" => "Shiro", ...}

# Search for bots
{:ok, results} = TopggEx.Api.get_bots(api, %{
  search: %{username: "music"},
  limit: 10,
  fields: ["id", "username", "short_description"]
})
```

### Vote Checking

```elixir
# Check if a user has voted
{:ok, has_voted?} = TopggEx.Api.has_voted(api, "205680187394752512")
# => true or false

# Get recent voters
{:ok, voters} = TopggEx.Api.get_votes(api)
# => [%{"username" => "Example", "id" => "123...", "avatar" => "https://..."}, ...]

# Check weekend multiplier status
{:ok, is_weekend?} = TopggEx.Api.is_weekend(api)
# => true or false
```

### Webhook Handling

TopggEx includes a built-in webhook handler for receiving vote notifications from Top.gg:

```elixir
# In your Phoenix router
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :webhook do
    plug :accepts, ["json"]
    plug TopggEx.Webhook, authorization: "your_webhook_auth_token"
  end

  scope "/webhooks" do
    pipe_through :webhook
    post "/topgg", MyAppWeb.WebhookController, :handle_vote
  end
end

# In your controller
defmodule MyAppWeb.WebhookController do
  use MyAppWeb, :controller

  def handle_vote(conn, _params) do
    case conn.assigns.topgg_payload do
      %{"user" => user_id, "type" => "upvote", "bot" => bot_id} ->
        # Handle the vote
        MyApp.handle_user_vote(user_id, bot_id)
        send_resp(conn, 204, "")

      %{"user" => user_id, "type" => "test"} ->
        # Handle test webhook
        IO.puts("Test webhook from user: #{user_id}")
        send_resp(conn, 204, "")
    end
  end
end
```

#### Functional Webhook API

For more control, you can use the functional API:

```elixir
def handle_webhook(conn) do
  case TopggEx.Webhook.verify_and_parse(conn, "your_auth_token") do
    {:ok, payload} ->
      process_vote(payload)
      send_resp(conn, 204, "")

    {:error, :unauthorized} ->
      send_resp(conn, 403, Jason.encode!(%{error: "Unauthorized"}))

    {:error, :invalid_body} ->
      send_resp(conn, 400, Jason.encode!(%{error: "Invalid body"}))
  end
end
```

#### Webhook Listener

You can also create custom webhook handlers:

```elixir
webhook_handler = TopggEx.Webhook.listener(fn payload, conn ->
  %{"user" => user_id, "type" => vote_type} = payload

  case vote_type do
    "upvote" ->
      MyApp.record_vote(user_id)
      MyApp.send_thank_you(user_id)
    "test" ->
      IO.puts("Test webhook received!")
  end

  send_resp(conn, 204, "")
end, authorization: "your_webhook_auth_token")

# Use in router
post "/webhook", webhook_handler
```

### Advanced Usage

```elixir
# Custom Finch instance
{:ok, api} = TopggEx.Api.new("your_token", %{
  finch_name: :my_custom_finch,
  base_url: "https://top.gg/api"  # Optional custom base URL
})

# Complex bot search
{:ok, results} = TopggEx.Api.get_bots(api, %{
  search: %{
    username: "music bot",
    tags: "music"
  },
  sort: "server_count",
  limit: 50,
  fields: ["id", "username", "short_description", "server_count"]
})
```

## API Reference

### Core Functions

#### API Client (`TopggEx.Api`)

| Function       | Description              | Parameters         |
| -------------- | ------------------------ | ------------------ |
| `new/2`        | Create API client        | `token`, `options` |
| `post_stats/2` | Update bot statistics    | `api`, `stats`     |
| `get_stats/1`  | Get bot statistics       | `api`              |
| `get_bot/2`    | Get bot information      | `api`, `bot_id`    |
| `get_bots/2`   | Search bots              | `api`, `query`     |
| `get_votes/2`  | Get recent voters        | `api`, `page`      |
| `has_voted/2`  | Check user vote status   | `api`, `user_id`   |
| `is_weekend/1` | Check weekend multiplier | `api`              |

#### Webhook Handler (`TopggEx.Webhook`)

| Function             | Description                    | Parameters              |
| -------------------- | ------------------------------ | ----------------------- |
| `verify_and_parse/2` | Parse webhook payload          | `conn`, `auth_token`    |
| `listener/2`         | Create functional handler      | `handler_fun`, `opts`   |
| Plug behavior        | Use as Phoenix/Plug middleware | `authorization`, `opts` |

### Error Handling

All functions return `{:ok, result}` on success or `{:error, reason}` on failure:

```elixir
case TopggEx.Api.post_stats(api, %{server_count: 100}) do
  {:ok, stats} ->
    IO.puts("Stats updated successfully!")
  {:error, %{status: 401}} ->
    IO.puts("Invalid API token")
  {:error, %{status: 429}} ->
    IO.puts("Rate limited - try again later")
  {:error, reason} ->
    IO.puts("Network error: #{inspect(reason)}")
end
```

## Configuration

### Environment Variables

You can set your Top.gg token via environment variables:

```elixir
# config/runtime.exs
config :my_app, :topgg_token, System.get_env("TOPGG_TOKEN")

# In your application
token = Application.get_env(:my_app, :topgg_token)
{:ok, api} = TopggEx.Api.new(token)
```

### Custom HTTP Client Options

```elixir
{:ok, api} = TopggEx.Api.new("your_token", %{
  finch_name: :my_finch,      # Custom Finch instance name
  base_url: "https://top.gg/api"  # Custom API base URL
})
```

## Rate Limiting

Top.gg API has rate limits. The library will return appropriate errors:

- **429 Too Many Requests**: You've hit the rate limit
- **403 Forbidden**: Invalid token or insufficient permissions

Implement exponential backoff for production applications:

```elixir
defmodule MyBot.Stats do
  def update_stats_with_retry(api, stats, retries \\ 3) do
    case TopggEx.Api.post_stats(api, stats) do
      {:ok, result} -> {:ok, result}
      {:error, %{status: 429}} when retries > 0 ->
        Process.sleep(1000 * (4 - retries))  # Exponential backoff
        update_stats_with_retry(api, stats, retries - 1)
      error -> error
    end
  end
end
```

## Testing

The library includes comprehensive tests. Run them with:

```bash
mix test
```

For testing your own applications, you can mock the HTTP client:

```elixir
# In your tests
setup do
  bypass = Bypass.open()

  {:ok, api} = TopggEx.Api.new("test_token", %{
    base_url: "http://localhost:#{bypass.port}/api"
  })

  {:ok, %{bypass: bypass, api: api}}
end
```

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

### Development

```bash
# Get dependencies
mix deps.get

# Run tests
mix test

# Generate documentation
mix docs

# Check formatting
mix format --check-formatted

# Run static analysis
mix dialyzer
```

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Links

- [Top.gg API Documentation](https://docs.top.gg)
- [Hex Package](https://hex.pm/packages/topgg_ex)
- [Documentation](https://hexdocs.pm/topgg_ex/)
- [GitHub Repository](https://github.com/jilv220/topgg_ex)

## Acknowledgments

- Thanks to the [Top.gg](https://top.gg) team for providing the API
- Built with [Finch](https://github.com/sneako/finch) for modern HTTP performance
- Inspired by the JavaScript [topgg.js](https://github.com/top-gg/node-sdk) library