diff --git a/.gitignore b/.gitignore index 06de90a..b81217d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.bundle \ No newline at end of file +.bundle +*.gem \ No newline at end of file diff --git a/Gemfile b/Gemfile index f97a909..ed050f9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,11 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" # Specify your gem's dependencies in topgg.gemspec gemspec -gem 'rake', '~> 13.0' +gem "rack" +gem "rake" +gem "ostruct" +gem "cgi" diff --git a/Gemfile.lock b/Gemfile.lock index f90d67a..0d073fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,27 @@ PATH remote: . specs: - topgg (1.1.0) + topgg (1.2.0) + rack (~> 3.0.9.1) GEM remote: https://rubygems.org/ specs: + cgi (0.5.0) + ostruct (0.6.1) + rack (3.0.9.1) rake (13.0.6) PLATFORMS + x64-mingw-ucrt x86_64-linux DEPENDENCIES - rake (~> 13.0) + cgi (~> 0.5.0) + ostruct + rack + rake topgg! BUNDLED WITH - 2.2.22 + 2.6.9 diff --git a/LICENSE b/LICENSE index 0b78562..68561e0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 rhydderchc +Copyright (c) 2021-2025 rhydderchc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE.txt b/LICENSE.txt index 420b77e..68561e0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2021 TODO: Write your name +Copyright (c) 2021-2025 rhydderchc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 26f3cce..411ebcf 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,220 @@ -# [Top.gg](https://top.gg) Ruby +# Top.gg Ruby SDK + +The community-maintained Ruby library for Top.gg. + +## Chapters + +- [Installation](#installation) +- [Setting up](#setting-up) +- [Usage](#usage) + - [API v1](#api-v1-1) + - [Getting your project's vote information of a user](#getting-your-projects-vote-information-of-a-user) + - [Posting your bot's application commands list](#posting-your-bots-application-commands-list) + - [API v0](#api-v0-1) + - [Getting a bot](#getting-a-bot) + - [Getting several bots](#getting-several-bots) + - [Getting your project's voters](#getting-your-projects-voters) + - [Check if a user has voted for your project](#check-if-a-user-has-voted-for-your-project) + - [Getting your bot's statistics](#getting-your-bots-statistics) + - [Posting your bot's statistics](#posting-your-bots-statistics) + - [Automatically posting your bot's statistics every few minutes](#automatically-posting-your-bots-statistics-every-few-minutes) + - [Checking if the weekend vote multiplier is active](#checking-if-the-weekend-vote-multiplier-is-active) + - [Generating widget URLs](#generating-widget-urls) + - [Webhooks](#webhooks) + - [Being notified whenever someone voted for your project](#being-notified-whenever-someone-voted-for-your-project) -The Top.gg Ruby SDK is a lighweight package, that allows you -to fetch data from the top.gg api and post data such as statistics to the website. +## Installation -It provides you with numerous methods to interact with the API. -## Dependencies +```sh +$ gem install topgg +``` -* Ruby +## Setting up -## Installation +### API v1 + +> **NOTE**: API v1 also includes API v0. + +```rb +require "topgg" + +client = V1Topgg.new(ENV["TOPGG_TOKEN"]) +``` + +### API v0 + +```rb +require "topgg" + +client = Topgg.new(ENV["TOPGG_TOKEN"]) +``` + +## Usage + +### API v1 + +#### Getting your project's vote information of a user + +```rb +vote = client.vote("661200758510977084") +``` + +#### Posting your bot's application commands list + +##### Discordrb + +```rb +commands = bot.rest.api.get_global_application_commands(bot.application_id).to_json + +client.post_commands(commands) +``` + +##### Raw + +```rb +commands = "[{\"options\":[],\"name\":\"test\",\"name_localizations\":null,\"description\":\"command description\",\"description_localizations\":null,\"contexts\":[],\"default_permission\":null,\"default_member_permissions\":null,\"dm_permission\":false,\"integration_types\":[],\"nsfw\":false}]" + +client.post_commands(commands) +``` + +### API v0 + +#### Getting a bot + +```rb +bot = client.get_bot("264811613708746752") +``` + +#### Getting several bots + +```rb +bots = client.search_bot({ sort: "id", limit: 50, offset: 0 }) + +for bot in bots.results do + puts bot.username +end +``` + +#### Getting your project's voters -``` bash +##### First page -gem install topgg +```rb +voters = client.votes +for voter in voters.results do + puts voter.username +end ``` -## Note -You require a Token to interact with the Api. -The token can be found at `https://top.gg/bot/[YOUR_BOT_ID]/webhooks` +##### Subsequent pages -## Example +```rb +voters = client.votes(2) -Here's a straightforward example of how to request data with the wrapper. +for voter in voters.results do + puts voter.username +end +``` -```ruby -require 'topgg' +#### Check if a user has voted for your project -client = Topgg.new("AUTH_TOKEN", "BOTID") +```rb +has_voted = client.voted?("8226924471638491136") +``` -client.get_bot("1234").defAvatar -# returns -# "6debd47ed13483642cf09e832ed0bc1b" +#### Getting your bot's statistics +```rb +stats = client.get_stats ``` -### Auto Posting -The library provides you with autoposting functionality, and autoposts at an interval of 30 minutes. -Here's how you can use it +#### Posting your bot's statistics -```ruby -require 'topgg' -require 'discordrb' +```rb +client.post_stats(bot.server_count) +``` -bot = Discordrb::Bot.new token: "TOKEN" +#### Automatically posting your bot's statistics every few minutes -client = Topgg.new("AUTH_TOKEN", "BOTID") - bot.ready do |event| - client.auto_post_stats(bot) # The discordrb bot client. +With Discordrb: + +```rb +require "discordrb" + +bot = Discordrb::Bot.new(token: env["BOT_TOKEN"], intents: [:servers]) + +bot.ready do |event| + client.auto_post_stats(bot) + + puts("Bot is now ready!") end + bot.run ``` -# Documentation +#### Checking if the weekend vote multiplier is active + +```rb +is_weekend = client.is_weekend? +``` + +#### Generating widget URLs + +##### Large + +```rb +widget_url = Dbl::Widget.large(:discord_bot, "574652751745777665") +``` + +##### Votes + +```rb +widget_url = Dbl::Widget.votes(:discord_bot, "574652751745777665") +``` -Check out the api reference [here](https://rubydoc.info/gems/topgg/) +##### Owner + +```rb +widget_url = Dbl::Widget.owner(:discord_bot, "574652751745777665") +``` + +##### Social + +```rb +widget_url = Dbl::Widget.social(:discord_bot, "574652751745777665") +``` + +### Webhooks + +#### Being notified whenever someone voted for your project + +##### Ruby on Rails + +In your `config/application.rb`: + +```rb +module MyServer + class Application < Rails::Application + # ... + + config.middleware.use Dbl::Webhook, + type: Dbl::Webhook::VOTE, + path: "/votes", + auth: ENV["MY_TOPGG_WEBHOOK_SECRET"] do |vote| + Rails.logger.info "A user with the ID of #{vote.voter_id} has voted us on Top.gg!" + end + end +end +``` + +##### Sinatra + +```rb +use Dbl::Webhook, +type: Dbl::Webhook::VOTE, +path: "/votes", +auth: ENV["MY_TOPGG_WEBHOOK_SECRET"] do |vote| + puts "A user with the ID of #{vote.voter_id} has voted us on Top.gg!" +end +``` diff --git a/lib/lib.rb b/lib/lib.rb index 4bf0263..f98778c 100644 --- a/lib/lib.rb +++ b/lib/lib.rb @@ -1,101 +1,120 @@ # frozen_string_literal: true -require_relative 'topgg/utils/request' -require 'json' -require_relative 'topgg/bot' -require_relative 'topgg/botSearch' -require_relative 'topgg/user' -require_relative 'topgg/stats' -require_relative 'topgg/votes' +require "base64" +require "json" + +require_relative "topgg/bot" +require_relative "topgg/botSearch" +require_relative "topgg/stats" +require_relative "topgg/user" +require_relative "topgg/vote" +require_relative "topgg/votes" +require_relative "topgg/webhooks" +require_relative "topgg/widget" +require_relative "topgg/utils/request" + # Class Topgg -# The class instantiates all the methods for api requests and posts. +# Top.gg API v0 client class Topgg - # initializes the class attributes. - # @param token [String] The authorization token from top.gg - # @param id [String] The client id of the bot. - def initialize(token, id) - @id = id + # Initializes the client + # @param token [String] Your Top.gg API token + def initialize(token) + begin + token_section = token.split('.')[1] + padding = '=' * ((4 - token_section.length % 4) % 4) + token_section += padding + + token_data = JSON.parse(Base64.decode64(token_section)) + + @id = token_data['id'] + rescue StandardError + raise ArgumentError, 'Got a malformed API token.' + end + @token = token @request = Dbl::Utils::Request.new(token) end - # The method fetches bot statistics from top.gg - # @param id [String] The id of the bot you want to fetch statistics from. + # Get Discord bot statistics from top.gg + # @param id [String] The bot's ID # @return [Dbl::Bot] The Bot Object def get_bot(id) - resp = @request.get("bots/#{id}") - - Dbl::Bot.new(resp) + Dbl::Bot.new(@request.get("bots/#{id}")) end - # The method searches bots from top.gg using a keyword query. + # Searches Discord bots from Top.gg using a keyword query. # @param [Object] params The parameters that can be used to query a search # To know what the parameters are check it out here # @return [Dbl::BotSearch] The BotSearch Object def search_bot(params) - resp = @request.get('bots/search', params) - Dbl::BotSearch.new(resp) + Dbl::BotSearch.new(@request.get("bots", params)) end - # Search for a user on top.gg with id - # @param id [String] The id of the user to search for. + # [Deprecated] Fetch a user on Top.gg based on an ID + # @param id [String] The user's ID # @return [Dbl::User] def user(id) - resp = @request.get("users/#{id}") - - Dbl::User.new(resp) + nil end - # Get Bot statistics. - # @param id [String] Id of the bot you want to get statistics of + # Get your Discord bot's posted statistics + # @param id [String] The bot's ID. Unused, no longer has an effect. # @return [Dbl::Stats] - def get_stats(id) - resp = @request.get("bots/#{id}/stats") - - Dbl::Stats.new(resp) + def get_stats(id = nil) + Dbl::Stats.new(@request.get("bots/stats")) end - # Mini-method to query if the bot(self) was voted by the user. - # @param userid [String] The user id. + # Mini-method to query if your project was voted by the user in the past 12 hours. + # @param userid [String] The user's ID. # @return [Boolean] def voted?(userid) - resp = @request.get("bots/#{@id}/check", { userId: userid }) + resp = @request.get("bots/check", { userId: userid }) + resp["voted"] == 1 + end - ret = resp - ret['voted'] == 1 + # Checks if the weekend multiplier is active, where a single vote counts as two. + # @return [Boolean] + def is_weekend? + resp = @request.get("weekend") + resp["is_weekend"] end - # Get the Last 1000 votes of the bot(self) + # Get the unique votes of your project + # @param page [Integer] The page to use. Defaults to 1. # @return [Dbl::Votes] - def votes - resp = @request.get("bots/#{@id}/votes") + def votes(page = 1) + page = page.to_i + page = 1 if page < 1 - Dbl::Votes.new(resp) + Dbl::Votes.new(@request.get("bots/#{@id}/votes", { page: page })) end - # Post Bot Statistics to the website - # @param server_count [Integer] The amount of servers the bot is in. - # @param shards [Integer] The amount of servers the bot is in per shard - # @param shard_count [Integer] The total number of shards. - def post_stats(server_count, shards: nil, shard_count: nil) - json_post = { - server_count: server_count, - shards: shards, - shard_count: shard_count - }.to_json - @request.post("bots/#{@id}/stats", json_post, { 'Content-Type' => 'application/json' }) + # Posts your Discord bot's statistics to the API. This will update the statistics in your Discord bot's Top.gg page. + # @param server_count [Integer] The amount of servers the bot is in. Must not be less than 1. + # @param shards [Integer] The amount of servers the bot is in per shard. Unused, no longer has an effect. + # @param shard_count [Integer] The total number of shards. Unused, no longer has an effect. + def post_stats(server_count, shards = nil, shard_count = nil) + raise ArgumentError, "server_count cannot be less than 1" unless server_count > 0 + + json_post = { server_count: server_count }.to_json + @request.post( + "bots/stats", + json_post + ) end - # Auto-posts stats on to the top.gg website + # Auto-posts your Discord bot's stats to the Top.gg website # @param client [Discordrb::Bot] instanceof discordrb client. def auto_post_stats(client) semaphore = Mutex.new Thread.new do semaphore.synchronize do - interval 1800 do + interval 900 do server_len = client.servers.length post_stats(server_len) - print("[TOPGG] : \033[31;1;4m Bot statistics has been successfully posted!\033[0m") + puts( + "[TOPGG] : \033[31;1;4m Bot statistics has been successfully posted!\033[0m" + ) end end end @@ -110,11 +129,31 @@ def self def interval(seconds) loop do yield - sleep seconds + sleep seconds end end end - - - +# Top.gg API v1 client +class V1Topgg < Topgg + # Updates the application commands list in your Discord bot's Top.gg page. + # @param commands [String] A list of application commands in raw Discord API JSON objects. This cannot be empty. + def post_commands(commands) + @request.post("v1/projects/@me/commands", commands) + end + + # Get the latest vote information of a Top.gg user on your project. + # @param id [String] The user's ID. + # @param source [String] The ID type to use. Defaults to "discord". + # @return [Dbl::Vote] + def vote(id, source = "discord") + raise ArgumentError, "source must be either \"discord\" or \"topgg\"" unless source == "discord" or source == "topgg" + + begin + Dbl::Vote.new(@request.get("v1/projects/@me/votes/#{id}", { source: source })) + rescue Net::HTTPError => err + return nil if err.response.code == "404" + raise + end + end +end \ No newline at end of file diff --git a/lib/topgg/bot.rb b/lib/topgg/bot.rb index f0bad3d..cb1ff18 100644 --- a/lib/topgg/bot.rb +++ b/lib/topgg/bot.rb @@ -1,178 +1,193 @@ +require "date" + module Dbl - # The Bot class spreads the json parsed hash into different methods + # A Discord bot listed on Top.gg class Bot # Initializes the Bot class # @param obj [Object] def initialize(obj) @obj = obj end - # Returns raw hash of the parsed json object + + # The raw hash of the parsed JSON object # @return [Hash] attr_reader :obj - alias :raw :obj - + alias raw obj alias data obj - # Returns error message, if there's an error + + # The error message if there's an error, otherwise nil # @return [String] def error - @obj['error'].to_str + @obj["error"] end - # Returns the default Avatar of the client + # The bot's default avatar # @return [String] def defAvatar - @obj['defAvatar'].to_str + "" end - # Returns the invite link of the bot + # The bot's invite URL # @return [String] def invite - @obj['invite'].to_str + @obj["invite"] end - # Returns the bot website, if configured + # The bot's website URL, if configured # @return [String] def website - @obj['website'].to_str + @obj["website"] end - # Returns support server link + # The bot's support server URL # @return [String] def support - "https://discord.gg/#{@obj['support']}" + @obj["support"] end - # Returns github repository link, if any + # The bot's github repository URL, if any # @return [String] def github - @obj['github'].to_str + @obj["github"] end - # Returns the long Description of the bot + # The bot's long description # @return [String] def longdesc - @obj['longdesc'].to_str + @obj["longdesc"] end - # Returns the short description of the bot + # The bot's short description # @return [String] def shortdesc - @obj['shortdesc'].to_str + @obj["shortdesc"] end - # Returns the default prefix of the bot + # The bot's default prefix # @return [String] def prefix - @obj['prefix'].to_str + @obj["prefix"] end - # Returns the bot library + # The bot's Discord library # @return [String] def lib - @obj['lib'].to_str + "" end - # Returns the bot client id + # The bot's client ID # @return [String] def clientid - @obj['clientid'].to_str + @obj["clientid"] end - # Returns the avatar link of the bot + # The bot's avatar URL # @return [String] def avatar - "https://cdn.discordapp.com/avatars/#{@obj['id']}/#{@obj['avatar']}.webp?size=1024" + @obj["avatar"] end - # Returns the bot id + # The bot's ID # @return [String] def id - @obj['id'].to_str + @obj["id"] end - # Returns the bot descriminator + # The bot's discriminator # @return [String] - def descriminator - @obj['descriminator'].to_str + def discriminator + "0" end - # Returns the bot username + # The bot's username # @return [String] def username - @obj['username'].to_str + @obj["username"] end - # Returns the date on which the bot was submitted + # The bot's submission date # @return [Date] def date - Date.parse(@obj['date']) + Date.parse(@obj["date"]) end - # Returns the server count of the bot + # The bot's posted server count # @return [Integer] def server_count - @obj['server_count'].to_i + @obj["server_count"].to_i end - # Returns the amount of shards - # @return [String] + # The bot's posted shard count + # @return [Integer] def shard_count - @obj['shard_count'].to_str + 0 end - # Returns configured guilds in which the bot is in - # @return [String] + # The configured servers in which the bot is in + # @return [Array] def guilds - @obj['guilds'].to_str - end + nil + end - # Returns the amount of guilds per shard of the bot - # @return [String] + # The server count for each shard of the bot + # @return [Array] def shards - @obj['shards'].to_str + nil end - # Returns the monthyPoints of the bot - # @return [String] + # The bot's monthly votes + # @return [Integer] def monthlyPoints - @obj['monthlyPoints'].to_str + @obj["monthlyPoints"].to_i end - # Returns the total points of the bot - # @return [String] + # The bot's total votes + # @return [Integer] def points - @obj['points'].to_str + @obj["points"].to_i end - # Returns true/false depending on if the bot is certified or not + # Whether the bot is certified # @return [Boolean] def certifiedBot - @obj['certifiedBot'].to_str + false end - # Returns the owner ids + # The bot's vanity code, can be nil + # @return [String] + def vanity + @obj["vanity"] + end + + # The bot's owner IDs # @return [Array] def owners - @obj['owners'].to_str + @obj["owners"] end - # Return the bot tags + # The bot's tags # @return [Array] def tags - @obj['tags'].to_str + @obj["tags"] end - # Returns the bot banner url + # The bot's banner URL # @return [String] def bannerUrl - @obj['bannerUrl'].to_str + "" end - # Returns the donate bot guild ID + # The bot's donatebot server ID # @return [String] def donatebotguildid - @obj['donatebotguildid'].to_str + "" + end + + # The bot's reviews + # @return [Object] + def reviews + @obj["reviews"] end end end diff --git a/lib/topgg/botSearch.rb b/lib/topgg/botSearch.rb index 97b3ce8..3526dd9 100644 --- a/lib/topgg/botSearch.rb +++ b/lib/topgg/botSearch.rb @@ -1,54 +1,66 @@ # frozen_string_literal: true module Dbl - # This class spreads the BotSearch Response body into different methods + # A Discord bot search query class BotSearch # Initalizes the BotSearch class # @param obj [Object] Response Hash def initialize(obj) @obj = obj end - # Get raw hash response + + # The raw hash of the parsed JSON object # @return [Hash] attr_reader :obj - - alias raw obj + alias raw obj alias data obj # The Total number of results # @return [Integer] def total - @obj['total'].to_i + @obj["total"].to_i end - # The first result - # @return [Spreader::Bot] + # The first result, can be nil + # @return [Bot] def first - Spreader::Bot.new(@obj['results'][0]) + results = @obj["results"] + + if results.empty? + nil + else + Bot.new(results[0]) + end end # The number of bots shown in the first page # @return [Integer] def count - @obj['count'].to_i + @obj["count"].to_i end - # Iterates through the results - # @return [Array] + # The results array + # @return [Array] def results - arr = [] - flag = 0 # iteration flag - @obj['results'].each do |data| - arr[flag] = Spreader::Bot.new(data) - flag += 1 + @obj["results"].map { |b| Bot.new(b) } + end + + # The number of bots skipped, can be nil + # @return [Integer] + def offset + offset = @obj["offset"] + + if offset.nil? + nil + else + offset.to_i end - arr end - # Length of the results. + # The length of the results # @return [Integer] def size - @obj['results'].length + @obj["results"].length end end end diff --git a/lib/topgg/stats.rb b/lib/topgg/stats.rb index 7504821..418da20 100644 --- a/lib/topgg/stats.rb +++ b/lib/topgg/stats.rb @@ -1,5 +1,5 @@ module Dbl - # The Stats class spreads the json response into different methods + # A Discord bot's statistics class Stats # Initializes the Stats class # @param obj [Object] Response Hash @@ -7,28 +7,29 @@ def initialize(obj) @obj = obj end - # Returns raw Hash of the response + # The raw hash of the parsed JSON object + # @return [Hash] attr_reader :obj - + alias raw obj alias data obj - # Returns the server Count of the bot + # The bot's server count # @return [Integer] def server_count @obj['server_count'] end - # The amount of servers per shard + # The bot's server count in each shard # @return [Integer] def shards @obj['shards'] end - # Returns the total number of shards + # The bot's shard count # @return [Integer] def shard_count @obj['shard_count'] end end -end +end \ No newline at end of file diff --git a/lib/topgg/user.rb b/lib/topgg/user.rb index a5bf680..35a69fa 100644 --- a/lib/topgg/user.rb +++ b/lib/topgg/user.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module Dbl - # The User class, used for Spreading the response body into different methods + # A Top.gg user class User # Instantiates the class variables # @param obj [Object] Response Data Object @@ -8,72 +8,74 @@ def initialize(obj) @obj = obj end + # The raw hash of the parsed JSON object + # @return [Hash] attr_reader :obj alias raw obj - alias data obj + # Check for errors, if any def error - @obj['error'] + @obj["error"] end - # The Id of the user + # The user's ID def id - @obj['id'] + @obj["id"] end - # The username of the user + # The user's username def username - @obj['username'] + @obj["username"] end - # The avatar of the user + # The user's avatar URL # @return [String] def avatar - "https://cdn.discordapp.com/avatars/#{@obj['id']}/#{@obj['avatar']}.webp?size=1024" + @obj["avatar"] end - # The default avatar of the user + # The user's default avatar # @return [String] def defAvatar - @obj['defAvatar'] + "" end - # Returns true/false depending upon if the user is a moderator or not. + # Whether the user is a moderator. # @return [Boolean] def mod - @obj['mod'] + false end - # Returns true/false depending upon if the user is a supporter or not. + # Whether the user is a Top.gg supporter. # @return [Boolean] def supporter - @obj['supporter'] + false end - # Returns true/false depending upon if the user is a certified developer or not. + # Whether the user is a certified developer. # @return [Boolean] def certifiedDev - @obj['certifiedDev'] + false end - # Returns an object containing all user social integrations. + # The user's socials # @return [Object] def social - @obj['social'] + nil end - # Returns true/false depending on weather the user is an admin or not. + # Whether the user is an administrator. # @return [Boolean] def admin - @obj['admin'] + false end - # Returns true/false depending on weather the user is a website Moderator or not. + # Whether the user is a web moderator. # @return [Boolean] def webMod - @obj['webMod'] + false end end end diff --git a/lib/topgg/utils/request.rb b/lib/topgg/utils/request.rb index 916910f..7da131a 100644 --- a/lib/topgg/utils/request.rb +++ b/lib/topgg/utils/request.rb @@ -1,43 +1,53 @@ -require 'net/http' -require 'json' - -module Dbl - module Utils - class Request - def initialize(token) - @token = token - @url = "https://top.gg/api" - end - - - def get(params) - - uri = URI.parse(@url+"/#{params}") +require "net/http" +require "json" + +module Dbl + module Utils + class Request + def initialize(token) + @token = token + @url = "https://top.gg/api" + end + + def get(params, query=nil) + uri = URI.parse(@url + "/#{params}") + + if not query.nil? + uri.query = URI.encode_www_form(query) + end + http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(uri.request_uri) - request.add_field 'Authorization', @token + request.add_field "Authorization", "Bearer #{@token}" response = http.request(request) - JSON.parse(response.body) - end - - def post(params, data) + raise Net::HTTPError.new("Top.gg HTTP error: #{response.code}", response) unless response.code.start_with?('2') + JSON.parse(response.body) + end - uri = URI.parse(@url+"/#{params}") - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE + def post(params, data) + uri = URI.parse(@url + "/#{params}") + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE - request = Net::HTTP::Post.new(uri.request_uri, initheader = {'Content-Type' =>'application/json', 'Authorization' => @token}) - request.body = data.to_json + request = + Net::HTTP::Post.new( + uri.request_uri, + initheader = { + "Content-Type" => "application/json", + "Authorization" => "Bearer #{@token}" + } + ) + request.body = data.to_json - response = http.request(request) - JSON.parse(response.body) - end -end + response = http.request(request) + JSON.parse(response.body) + end + end + end end -end \ No newline at end of file diff --git a/lib/topgg/vote.rb b/lib/topgg/vote.rb new file mode 100644 index 0000000..a6546b9 --- /dev/null +++ b/lib/topgg/vote.rb @@ -0,0 +1,33 @@ +module Dbl + # A Top.gg vote information + class Vote + def initialize(obj) + @obj = obj + end + + # The raw hash of the parsed JSON object + # @return [Hash] + attr_reader :obj + + alias raw obj + alias data obj + + # When the vote was cast + # @return [Date] + def voted_at + Date.parse(@obj["created_at"]) + end + + # When the vote expires + # @return [Date] + def expires_at + Date.parse(@obj["expires_at"]) + end + + # The vote's weight + # @return [Integer] + def weight + @obj["weight"] + end + end +end \ No newline at end of file diff --git a/lib/topgg/voteEvent.rb b/lib/topgg/voteEvent.rb new file mode 100644 index 0000000..e6b23e3 --- /dev/null +++ b/lib/topgg/voteEvent.rb @@ -0,0 +1,47 @@ +module Dbl + # A dispatched Top.gg vote event + class VoteEvent + def initialize(obj) + @obj = obj + end + + # The raw hash of the parsed JSON object + # @return [Hash] + attr_reader :obj + + alias raw obj + alias data obj + + # The ID of the project that received a vote. + # @return [String] + def receiver_id + !@obj["bot"].nil? ? @obj["guild"] : @obj["bot"] + end + + # The ID of the Top.gg user who voted. + # @return [String] + def voter_id + @obj["user"] + end + + # Whether this vote is just a test done from the page settings. + # @return [Boolean] + def is_test + @obj["type"] == "test" + end + + # Whether the weekend multiplier is active, where a single vote counts as two. + # @return [Boolean] + def is_weekend + @obj["isWeekend"] == true + end + + # The query strings found on the vote page. + # @return [Hash] + def query + return nil if @obj["query"].nil? + + CGI.parse(@obj["query"]).transform_values(&:first) + end + end +end \ No newline at end of file diff --git a/lib/topgg/votes.rb b/lib/topgg/votes.rb index c44c0cd..3e04f15 100644 --- a/lib/topgg/votes.rb +++ b/lib/topgg/votes.rb @@ -1,5 +1,5 @@ module Dbl - # This class Spreads the Vote response body into different methods. + # A collection of Top.gg votes class Votes # Initializes the votes class # @param obj [Object] JSON parsed object @@ -7,17 +7,27 @@ def initialize(obj) @obj = obj end - # Get raw hash return of the object + # The raw hash of the parsed JSON object # @return [Hash] attr_reader :obj - + alias raw obj alias data obj - # Get the first vote amongst all the other votes. - # @return [Spreader::User] + # Get the first vote amongst all the other votes, can be nil + # @return [User] def first - Spreader::User.new(@obj[0]) + if @obj.empty? + nil + else + User.new(@obj[0]) + end + end + + # The results array + # @return [Array] + def results + @obj.map { |u| User.new(u) } end # Get the total number of votes @@ -26,4 +36,4 @@ def total @obj.length end end -end \ No newline at end of file +end diff --git a/lib/topgg/webhooks.rb b/lib/topgg/webhooks.rb new file mode 100644 index 0000000..09b5963 --- /dev/null +++ b/lib/topgg/webhooks.rb @@ -0,0 +1,46 @@ +require "rack" +require "cgi" + +require_relative "voteEvent" + +module Dbl + # A wrapper for directly receiving events from Top.gg's servers + class Webhook + VOTE = ->(json) { VoteEvent.new(json) } + + def initialize(app, type:, path:, auth:, &callback) + raise ArgumentError, "A callback must be provided" unless callback + raise ArgumentError, "A type must be provided" unless type.respond_to?(:call) + + @app = app + @deserializer = type + @path = path + @auth = auth + @callback = callback + end + + def call(env) + req = Rack::Request.new(env) + + if req.post? && req.path.start_with?(@path) + if req.get_header('HTTP_AUTHORIZATION') != @auth + return [401, { 'Content-Type' => 'text/plain' }, ['Unauthorized']] + end + + body = req.body.read + + begin + data = JSON.parse(body) + + @callback.call(@deserializer.call(data)) if data + + return [204, { 'Content-Type' => 'text/plain' }, ['']] + rescue JSON::ParserError + return [400, { 'Content-Type' => 'text/plain' }, ['Bad request']] + end + end + + @app.call(env) + end + end +end \ No newline at end of file diff --git a/lib/topgg/widget.rb b/lib/topgg/widget.rb new file mode 100644 index 0000000..830e8c5 --- /dev/null +++ b/lib/topgg/widget.rb @@ -0,0 +1,52 @@ +module Dbl + # Widget generator functions. + class Widget + @base_url = "https://top.gg/api/v1" + + class << self + attr_accessor :base_url + + TYPES = [:discord_bot, :discord_server] + + # Generates a large widget URL. + # @param ty [Symbol] The widget type. + # @param id [String] The ID. + def large(ty, id) + raise ArgumentError, "Invalid widget type" unless TYPES.include?(ty) + type = ty.to_s.gsub('_', '/') + + "#{@base_url}/widgets/large/#{type}/#{id}" + end + + # Generates a small widget URL for displaying votes. + # @param ty [Symbol] The widget type. + # @param id [String] The ID. + def votes(ty, id) + raise ArgumentError, "Invalid widget type" unless TYPES.include?(ty) + type = ty.to_s.gsub('_', '/') + + "#{@base_url}/widgets/small/votes/#{type}/#{id}" + end + + # Generates a small widget URL for displaying a project's owner. + # @param ty [Symbol] The widget type. + # @param id [String] The ID. + def owner(ty, id) + raise ArgumentError, "Invalid widget type" unless TYPES.include?(ty) + type = ty.to_s.gsub('_', '/') + + "#{@base_url}/widgets/small/owner/#{type}/#{id}" + end + + # Generates a small widget URL for displaying social stats. + # @param ty [Symbol] The widget type. + # @param id [String] The ID. + def social(ty, id) + raise ArgumentError, "Invalid widget type" unless TYPES.include?(ty) + type = ty.to_s.gsub('_', '/') + + "#{@base_url}/widgets/small/social/#{type}/#{id}" + end + end + end +end \ No newline at end of file diff --git a/topgg.gemspec b/topgg.gemspec index 795f39d..e35fd59 100644 --- a/topgg.gemspec +++ b/topgg.gemspec @@ -1,31 +1,24 @@ # frozen_string_literal: true Gem::Specification.new do |spec| - spec.name = 'topgg' - spec.version = '1.1.0' - spec.authors = ['Adonis Tremblay'] - spec.email = ['rhydderchc@gmail.com'] + spec.name = "topgg" + spec.version = "1.2.0" + spec.authors = ["Adonis Tremblay"] + spec.email = ["rhydderchc@gmail.com"] - spec.summary = 'A top.gg api wrapper for ruby.' - spec.description = 'This is a ruby library of the top.gg API.' - spec.homepage = 'https://github.com/rhydderchc/topgg-ruby' - spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0') + spec.summary = "A Top.gg API wrapper for ruby." + spec.description = "This is a ruby library of the Top.gg API." + spec.homepage = "https://github.com/Top-gg-Community/ruby-sdk" + spec.license = "MIT" + spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0") - spec.metadata['allowed_push_host'] = "https://rubygems.org" + spec.metadata["allowed_push_host"] = "https://rubygems.org" + spec.metadata["homepage_uri"] = spec.homepage - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/rhydderchc/topgg-ruby' - spec.metadata['changelog_uri'] = 'https://github.com/rhydderchc/CHANGELOG.md' - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path(__dir__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } - end - spec.bindir = 'exe' - spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ['lib'] + spec.files = Dir.glob("lib/**/*") + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] # Uncomment to register a new dependency of your gem # spec.add_dependency "example-gem", "~> 1.0"