This repository was archived by the owner on Jul 19, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
New leaderboard command #89
Open
MrOwnership
wants to merge
3
commits into
RetroAchievements:master
Choose a base branch
from
MrOwnership:add_leaderboard_command
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| /** | ||
| * Runs when a `lb` or `leaderboard` command is executed. Sends an embed containing | ||
| * the top 10 leaderboards entries for the leaderboard ID passed in with the command. | ||
| */ | ||
|
|
||
| const { RichEmbed } = require('discord.js'); | ||
| const fetch = require('node-fetch'); | ||
|
|
||
| const Command = require('../../structures/Command.js'); | ||
|
|
||
| const raUrl = 'https://retroachievements.org/'; | ||
|
|
||
| module.exports = class LeaderboardCommand extends Command { | ||
| constructor(client) { | ||
| super(client, { | ||
| name: 'leaderboard', | ||
| aliases: ['leaderboard', 'lb'], | ||
| group: 'rautil', | ||
| memberName: 'leaderboard', | ||
| description: 'Return leaderboard top 10.', | ||
| examples: [ | ||
| '`lb 2` Shows the top 10 users for leaderboard ID 2.', | ||
| ], | ||
| throttling: { | ||
| usages: 5, | ||
| duration: 60, | ||
| }, | ||
| args: [ | ||
| { | ||
| key: 'id', | ||
| prompt: 'Leaderboard ID to get top 10 for.', | ||
| type: 'string', | ||
| default: '' | ||
| } | ||
| ], | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Converts score to the correct displayable format. | ||
| * | ||
| * @param {String} type Leaderboard score type | ||
| * @param {Number} score Score value to convert | ||
| * @returns {String} Score formatted to a human readable output string | ||
| */ | ||
| convertScore(type, score) { | ||
| switch (type) { | ||
| case 'TIME': // Number of frames | ||
| var hours = Math.trunc(score / 216000); | ||
| var minutes = Math.trunc((score / 3600) - (hours * 60)); | ||
| var seconds = Math.trunc((score % 3600) / 60); | ||
| var milliseconds = Math.trunc(((score % 3600) % 60) * (100.0 / 60.0)); | ||
|
|
||
| return `**Time:** ` + this.formatValues(hours, minutes, seconds) + "." + | ||
| String(milliseconds).padStart(2, '0'); | ||
| case 'TIMESECS': // Number of seconds | ||
| var hours = Math.trunc(score / 3600); | ||
| var minutes = Math.trunc((score / 60) - (hours * 60)); | ||
| var seconds = Math.trunc(score % 60); | ||
|
|
||
| return `**Time:** ` + this.formatValues(hours, minutes, seconds); | ||
| case 'MILLISECS': // Hundredths of seconds | ||
| var hours = Math.trunc(score / 360000); | ||
| var minutes = Math.trunc((score / 6000) - (hours * 60)); | ||
| var seconds = Math.trunc((score % 6000) / 100); | ||
| var milliseconds = Math.trunc((score % 100)); | ||
|
Comment on lines
+63
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similar thing here...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. except MILLISECS is poorly named and is really hundreths of a second. https://docs.retroachievements.org/Leaderboards/#value-format
|
||
|
|
||
| return `**Time:** ` + this.formatValues(hours, minutes, seconds) + "." + | ||
| String(milliseconds).padStart(2, '0'); | ||
| case 'MINUTES': // Number of minutes | ||
| var hours = Math.trunc(score / 60); | ||
| var minutes = Math.trunc(score % 60); | ||
|
|
||
| return `**Time:** ` + hours + "h" + String(minutes).padStart(2, '0'); | ||
| case 'SCORE': // Number padded to six digits | ||
| return `**Score:** ` + String(score).padStart(6, '0'); | ||
| default: // Raw number | ||
| return `**Score:** ` + score; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Formats leaderboard time depending on the number of hours. | ||
| * | ||
| * @param {Number} hours Hours value | ||
| * @param {Number} minutes Minutes value | ||
| * @param {Number} seconds Seconds value | ||
| * @returns {String} Hour/Minute/Second score formatted to a human readable output string | ||
| */ | ||
| formatValues(hours, minutes, seconds) { | ||
| if (hours == 0) { | ||
| return String(minutes).padStart(2, '0') + ":" + | ||
| String(seconds).padStart(2, '0'); | ||
| } | ||
|
|
||
| return String(hours).padStart(2, '0') + "h" + | ||
| String(minutes).padStart(2, '0') + ":" + | ||
| String(seconds).padStart(2, '0'); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a unix time to a human readable date/time format. | ||
| * For example: `20 Mar 2021, 12:00 PM)`. | ||
| * | ||
| * @param {Number} timestamp Unix time | ||
| * @returns {String} Unix time formatted to a human readable output string | ||
| */ | ||
| getDate(timestamp) { | ||
| let date = new Date(timestamp); | ||
|
|
||
| let year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date); | ||
| let month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date); | ||
| let day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date); | ||
| let time = new Intl.DateTimeFormat('en-GB', { timeStyle: 'short', timeZone: 'UTC' }).format(date); | ||
| return `${day} ${month} ${year}, ${time}`; | ||
| } | ||
|
|
||
| /** | ||
| * Fetch the JSON from the dorequest call. | ||
| * | ||
| * @param {String} dorequestLeaderboardUrl Leaderboard dorequest URL | ||
| * @returns {String} JSON output from dorequest call | ||
| */ | ||
| async getDoRequestJson(dorequestLeaderboardUrl) { | ||
| return fetch(dorequestLeaderboardUrl) | ||
| .then((res) => res.json()) | ||
| .then((res) => res.LeaderboardData) | ||
| .catch(() => null); | ||
| } | ||
|
|
||
| /** | ||
| * Generate the embed for the leaderboard data. | ||
| * | ||
| * @param {Number} leaderboardID Leaderboard ID to fetch data for | ||
| * @returns {RichEmbed} Embed filled with leaderboard data | ||
| */ | ||
| async getLbEmbed(leaderboardID) { | ||
| // Check the input leaderboard ID | ||
| const id = Number.parseInt(leaderboardID, 10); | ||
| if (Number.isNaN(id) || id <= 0) { | ||
| return '**ERROR**: Missing or invalid leaderboard ID.'; | ||
| } | ||
|
|
||
| const dorequestLeaderboardUrl = `${raUrl}dorequest.php?r=lbinfo&i=${leaderboardID}`; | ||
| const json = await this.getDoRequestJson(dorequestLeaderboardUrl); | ||
|
|
||
| // Check if leaderboard exists | ||
| if (json.LBID == 0) { | ||
| return '**ERROR**: Leaderboard ID does not exist.'; | ||
| } | ||
|
|
||
| // Create embed header | ||
| const embed = new RichEmbed() | ||
| .setColor('#00ff00') | ||
| .setTitle(`${json.LBTitle} (${json.GameTitle})`) | ||
| .setDescription(`${json.LBDesc}`) | ||
| .setURL(raUrl + 'leaderboardinfo.php?i=' + leaderboardID) | ||
| .setThumbnail('https://s3-eu-west-1.amazonaws.com/i.retroachievements.org' + json.GameIcon) | ||
| .setFooter(`Created ` + (json.LBAuthor == null ? `` : `by ${json.LBAuthor} `) + `on ${json.LBCreated}.`); | ||
|
|
||
| // Loop through leaderboard entries and fill the embed | ||
| const lbEntries = json.Entries; | ||
| for (let i = 0; i < lbEntries.length; i += 1) { | ||
| embed | ||
| .addField( | ||
| lbEntries[i].Rank + `: ` + lbEntries[i].User, | ||
| this.convertScore(json.LBFormat, lbEntries[i].Score) + `\n` + | ||
| `**Date:** ` + this.getDate(lbEntries[i].DateSubmitted * 1000) | ||
| ); | ||
| } | ||
|
|
||
| return embed; | ||
| } | ||
|
|
||
| /** | ||
| * Runs the leaderboard command. | ||
| * | ||
| * @param {Object} msg Message to respond to | ||
| * @param {Number} id Leaderboard ID to fetch data for | ||
| * @returns {Promise} Promise completion | ||
| */ | ||
| async run(msg, { id }) { | ||
| const sentMsg = await msg.reply(':hourglass: Getting info, please wait...'); | ||
| let response = `${id}`; | ||
| response = await this.getLbEmbed(id); | ||
|
|
||
| return sentMsg.edit(response); | ||
| } | ||
| }; | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this last part of the formula be 1 thousand divided by sixty?
(since it's milliseconds)