Skip to content
This repository has been archived by the owner on Nov 15, 2021. It is now read-only.

Commit

Permalink
Merge branch 'master' of github.com-brasstax:brasstax/ffrkbot
Browse files Browse the repository at this point in the history
  • Loading branch information
brasstax committed Nov 28, 2018
2 parents 2226733 + 654f959 commit a4ef9be
Show file tree
Hide file tree
Showing 2 changed files with 373 additions and 0 deletions.
62 changes: 62 additions & 0 deletions commands/misc/speedrank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const {Command} = require('discord.js-commando');
const path = require('path');
speedrankPath = path.join(__dirname,
'..', '..', 'utilities', 'speedrank-utils.js');
const speedrankUtils = require('../../utilities/speedrank-utils.js');

module.exports = class ReplyCommand extends Command {
/** constructor for looking up soulbreaks.
* @param {Object} client: discord.js-commando client.
**/
constructor(client) {
super(client, {
name: 'rank',
group: 'misc',
memberName: 'speedrank',
description: 'Looks up all the entries for a given speedrunner in a specified leaderboard' +
' from the FFRecordKeeper Discord Speedrun challenge leaderboard' +
' (https://docs.google.com/spreadsheets/d/11gTjAkpm4D3uoxnYCN7ZfbiVnKyi7tmm9Vp9HvTkGpw).',
examples: ['rank EverythingIsGravy 5star', 'rank dragoonic 4star no-csb', 'rank littlefiredragon'],
args: [
{
key: 'name',
prompt: 'Enter the speedrunner you want to query.' +
' Make sure you use the name as it appears in the spreadsheet.',
type: 'string',
default: 'EverythingIsGravy',
},
{
key: 'category',
prompt: 'Enter the name of the category you want to look up.' +
' (Can be "5star", "4star", "3star", or "Torment" ' +
' on the chart. The default is "4star")',
type: 'string',
default: '4star',
},
{
key: 'secondaryCategory',
prompt: 'Specify if you want the overall or no-csb ranking' +
' no-csb does not apply to Torment' +
' (defaults to "overall".)',
type: 'string',
default: 'overall',
},
],
aliases: ['speedrank'],
});
}

/** trigger to run upon invocation.
* @param {Object} msg: discord.js-commando message.
* @param {Array} args: args from the user input.
* @return {Method} msg.say: string
**/
run(msg, args) {
console.log('Im in ur msg');
const {name, category, secondaryCategory} = args;
return speedrankUtils.speedrank(msg, name, category, secondaryCategory)
.catch( (err) => {
console.log('error in speedrank command:', err);
});
};
};
311 changes: 311 additions & 0 deletions utilities/speedrank-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
const {google} = require('googleapis');
const util = require('util');
const fs = require('fs');
const OAuth2Client = google.auth.OAuth2;
fs.readFileAsync = util.promisify(fs.readFile);
const titlecase = require('titlecase');
const escapeStringRegexp = require('escape-string-regexp');
const pad = require('pad');

const SPREADSHEET_ID = '11gTjAkpm4D3uoxnYCN7ZfbiVnKyi7tmm9Vp9HvTkGpw';
const TOKEN_PATH = 'secrets/credentials.json';
const SECRETS_PATH = 'secrets/client_secret.json';

/** authorize():
* Authorizes Google credentials.
* @return {google.auth.OAuth2} oAuth2Client
**/
async function authorize() {
let secrets;
let token;
try {
secrets = await fs.readFileAsync(SECRETS_PATH);
token = await fs.readFileAsync(TOKEN_PATH);
const credentials = JSON.parse(secrets);
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new OAuth2Client(
client_id, client_secret, redirect_uris[0]);
oAuth2Client.setCredentials(JSON.parse(token));
return oAuth2Client;
} catch (err) {
console.log('Unable to open SECRETS_PATH or TOKEN_PATH', err);
}
}

exports.speedrank = function lookupspeedrank(
msg, player, category, secondaryCategory) {
console.log(util.format('.top caller: %s#%s for top %s %s (%s)',
msg.author.username, msg.author.discriminator,
player, category, secondaryCategory));
return new Promise((resolve, reject) => {
const sheets = google.sheets({version: 'v4'});
authorize()
.then((oAuth2Client) => {
category = escapeStringRegexp(category);
secondaryCategory = escapeStringRegexp(secondaryCategory);
category = category.toLowerCase();
const categorySheet = getSheet(category, secondaryCategory);
player = escapeStringRegexp(player);

const request = {
spreadsheetId: SPREADSHEET_ID,
range: categorySheet,
auth: oAuth2Client,
};
sheets.spreadsheets.values.get(request, (err, {data}) => {
if (err) {
if (err.code === 400) {
console.log(err.code);
msg.channel.send(`Invalid speedrun category "${category}".`)
.then((res) => {
resolve(res);
return;
});
} else {
console.log(err);
msg.channel.send(
'The bot user has not set up valid Google API credentials yet.')
.then((res) => {
resolve(res);
return;
});
}
} else {
// CategoryNames (headers) are fixed for the rank request
let categoryNames = ['Boss', 'Rank', 'Time'];

//Fight names are always in Row 2
const fightRow = data.values[2];

let fightNames = [];
for(let i = 1; i < fightRow.length; i++) {
if (fightRow[i] === undefined ||
fightRow[i] === '' ||
fightRow[i] === null) {
continue;
}
else {
fightNames.push(fightRow[i]);
}
}


let contestants = [];
let padLength = 0;
let playerActualName = player;

// Now that we know the fights, let's search each fight column for the player in question
for (let catName of fightNames) {
let categoryRange = find(catName, data.values);

//TODO: Get max rows of table
for (let i = 0; i < 500; i++) {
// categoryRange.row + 2 will give us the starting row of
// contestants.
let row = categoryRange.row + 2 + i;
//console.log("row = " + row);
// Stop processing if we've hit the end of the list.
if (data.values[row] === undefined) {
break;
}
let contestant = [];
// entryStartPos gives us the starting cell
// with the contestant name.
const entryStartPos = categoryRange.columnNum - 1;
let cell = checkCell(data.values[row][entryStartPos]);
if (catName.length > padLength) {
padLength = catName.length + 1;
console.log(padLength);
}
if (cell.toLowerCase() === player.toLowerCase()) {

//Grab the spreadsheet formatted version of the player's name
playerActualName = cell;

//Grab the name of the boss the player has an entry for
contestant.push(catName);

// Grab the actual rank of this row
contestant.push(checkCell(data.values[row][0]));

// The Overall category and Torment sheets places avg time in the third column. Others have them in the second
if (catName === "Overall" || categorySheet === 'Torment'){
contestant.push(checkCell(data.values[row][entryStartPos+2]));
}
else {
contestant.push(checkCell(data.values[row][entryStartPos+1]));
}

contestants.push(contestant);
break;
}
}
}
const rankTable =
outputRankTable(categorySheet, playerActualName,
categoryNames, contestants, padLength);
msg.channel.send(rankTable)
.then( (res) => {
resolve(res);
});
}
});
});
});
};

/** getSheet():
* returns a sheet based on the shorthand input the user provides.
* @param {String} category: the category the user inputs.
* @param {String} secondaryCategory: the secondaryCategory the user inputs.
* @return {String} sheet
**/
function getSheet(category, secondaryCategory) {
let version = "Overall";
if (secondaryCategory === 'no-csb'){
version = 'No CSB';
}
switch (category) {
case '3star':
category = `GL 3* ${version} rankings`;
break;
case '4star':
category = `GL 4* ${version} rankings`;
break;
case '5star':
//category = 'GL 5* '+version+ ' rankings';
category = `GL 5* ${version} rankings`;
break;
case 'torment':
category = 'Torment';
break;
default:
category = 'Error';
}
return category;
}
/**
* Finds a value within a given range.
* @see https://stackoverflow.com/questions/10807936/how-do-i-search-google-spreadsheets/10823543#10823543
* @param {String} value The value to find.
* @param {String} data The range to search in using Google's A1 format.
* @return {Object} A range pointing to the first cell containing the value,
* or null if not found.
*/
function find(value, data) {
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].length; j++) {
//console.log('Searching:'+data[i][j]);
if (data[i][j] == value) {
const columnName = columnToName(j + 1);
return {row: i + 1, column: columnName, columnNum: j + 1};
}
}
}
return null;
}
/**
* Returns the Google Spreadsheet column name equivalent of a number.
* @param {Integer} columnNumber The column number to look for
* @return {String} columnName
*/
function columnToName(columnNumber) {
let columnName = '';
let modulo;
const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
while (columnNumber > 0) {
modulo = (columnNumber - 1) % 26;
columnName = alpha.charAt(modulo) + columnName;
columnNumber = Math.floor((columnNumber - modulo)/26);
}
return columnName;
}

/**
* Formats a ranking table for output.
* @param {String} category
* @param {String} player
* @param {Array} categoryNames
* @param {Array} contestants
* @param {Integer} namePadLength
* @return {String} table
*/
function outputRankTable(category, player,
categoryNames, contestants, namePadLength) {
let table = '';
table += outputTitle(category, player);
table += outputCategoryHeader(categoryNames, namePadLength);
table += outputContestants(contestants, namePadLength);
table = util.format('```%s```', table);
return table;
}
/**
* Outputs the title of the table.
* @param {String} category
* @param {String} player
* @return {String} title
*/
function outputTitle(category, player) {
let title = '';
title = `${player} - ${category}\n`;
return title;
}
/**
* Outputs category headers for table.
* @param {Array} categoryNames
* @param {Integer} namePadLength
* @return {String} categoryHeader
*/
function outputCategoryHeader(categoryNames, namePadLength) {
let categoryHeader = '';
categoryHeader += pad(categoryNames[0], namePadLength);
categoryHeader += ' | ';
categoryHeader += pad(categoryNames[1], 4);
categoryHeader += ' | ';
categoryHeader += pad(categoryNames[2], 5);
categoryHeader += ' | ';

const repeatLength = categoryHeader.length - 1;
categoryHeader += '\n';
// create a series of dashes to indicate header
categoryHeader += '-'.repeat(repeatLength);
categoryHeader += '\n';
return categoryHeader;
}
/**
* Outputs contestants.
* @param {Array} contestants
* @param {Integer} namePadLength
* @return {String} formattedContestants
*/
function outputContestants(contestants, namePadLength) {
let formattedContestants = '';
for (let i = 0; i < contestants.length; i++) {

//boss output
formattedContestants += pad(contestants[i][0], namePadLength);
formattedContestants += ' | ';
//rank output
formattedContestants += pad(contestants[i][1], 4);
formattedContestants += ' | ';
//time output
formattedContestants += pad(contestants[i][2], 5);
formattedContestants += ' | ';

formattedContestants += '\n';
}
return formattedContestants;
}
/**
* Ensures null cells don't mess with anything.
* @param cell
* @return {String} cell or ''
*/
function checkCell(cell) {
if (cell === undefined || cell === '' || cell === null) {
return '';
}
else{
return cell;
}
}

0 comments on commit a4ef9be

Please sign in to comment.