Compare commits

..

47 Commits

Author SHA1 Message Date
4ad4f69f9f updated package info 2022-02-01 11:59:34 -06:00
dc0cc6685d typo 2022-01-31 21:20:12 -06:00
c04cd2ef12 i hate windows 2022-01-31 21:18:43 -06:00
763892443d there were some extra newlines 2022-01-31 19:46:09 -06:00
9646d9f941 updated version number and dependencies 2022-01-31 19:35:28 -06:00
fe866050d5 improved messages and general cleanup 2022-01-31 19:33:39 -06:00
00d04c787d added inactivity timeout 2022-01-31 17:47:43 -06:00
f0283145dc added cancel command 2022-01-31 17:39:06 -06:00
50c297bfea removed dev comments 2022-01-31 17:07:59 -06:00
c2c59e53ca updated comments 2022-01-31 17:06:40 -06:00
85d0e833dd cleaned up queue code 2022-01-31 17:06:34 -06:00
1e990abdd5 updated comments 2022-01-31 16:56:47 -06:00
5ce68086b8 updated to es2020 2022-01-31 15:53:23 -06:00
5ee97cfd08 fixed special character bug 2022-01-31 15:50:25 -06:00
5167a25626 added comments 2022-01-31 14:24:29 -06:00
a3a7acf9a8 updated readme 2022-01-31 14:08:33 -06:00
7ae2cb8db0 updated install script 2022-01-31 13:56:39 -06:00
d7adc4afde updated version number 2022-01-31 13:45:40 -06:00
ae7093ae1d cleaned up queue file 2022-01-31 13:18:58 -06:00
e719eaef95 updated readme 2022-01-31 12:52:33 -06:00
c3855c31bc updated readme 2022-01-31 12:51:07 -06:00
f43c6ab4a6 changed version number 2022-01-31 12:49:48 -06:00
aa0fffdc94 functional queue 2022-01-31 12:46:44 -06:00
588365e558 minor windows script uudate 2022-01-27 22:00:15 -06:00
fcfb3e290f minor update to bash script 2022-01-27 21:58:23 -06:00
1bf5e9d64f updated windows scripts 2022-01-27 21:58:13 -06:00
219d3e55e7 added screen/tmux support 2022-01-27 21:04:48 -06:00
4f696e8b21 updated start scripts 2022-01-27 20:43:51 -06:00
f6adafe735 updated readme 2022-01-27 20:35:30 -06:00
c9f9ba366a updated readme 2022-01-27 20:34:10 -06:00
9067a67bd5 updated unix scripts 2022-01-27 20:05:27 -06:00
8d2ec0ce02 updated bash scripts 2022-01-27 19:57:59 -06:00
92f2409768 added unix scripts 2022-01-27 19:42:26 -06:00
66818a40be Merge branch 'main' of git.zomo.dev:zomo/1800queue 2022-01-27 19:40:54 -06:00
53db0f47b9 update license 2022-01-27 19:40:52 -06:00
adc15f0555 renamed unix scripts folder 2022-01-27 19:40:39 -06:00
e2d3097829 added unix scripts 2022-01-27 19:40:28 -06:00
72ddf229ec added unix installation scripts 2022-01-27 19:39:14 -06:00
93b7aea301 added install/run instructions 2022-01-27 19:29:09 -06:00
ce7efb08fd added install/run instructions 2022-01-27 19:28:18 -06:00
428de6a74e added install/run instructions 2022-01-27 19:26:04 -06:00
f4d14b2507 update license 2022-01-27 19:20:34 -06:00
4b14e5e050 queue create/join 2022-01-27 18:49:52 -06:00
2a3e1e3efa link commands 2022-01-27 18:49:08 -06:00
83d1af3bef add commands 2022-01-27 18:48:51 -06:00
a461fad0c1 function to receive interaction 2022-01-27 18:48:37 -06:00
0a9918a508 dev packages 2022-01-27 18:46:54 -06:00
23 changed files with 4666 additions and 3318 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
node_modules
token
.DS_Store
log

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Ashley Rosch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
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 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.

99
README.md Normal file
View File

@@ -0,0 +1,99 @@
<!-- markdownlint-disable MD033 -->
# 1800queue alt
This version does not run continuously, and requires each tourney to be started with a command
## Prerequirements
Download and install [Node.js](https://nodejs.org/en/)
<details>
<summary><b>Your bot's token</b></summary>
<br>
<p>Your bot's token can be found in your <a href="https://discord.com/developers/applications">Discord dev portal</a></p>
<ol>
<li>
Create or choose your application
</li>
<li>
Go to the <code>Bot</code> section
<ul>
<li>
click <code>Add Bot</code> if you haven't already
</li>
</ul>
</li>
<li>
Copy your token
</li>
</ol>
</details>
## Automatic Installation/Updating
1. Download and extract this repository
2. Open the `scripts` folder
3. Open the folder that corresponds with your operating system
4. Run the `install` file
## Manual Installation/Updating
1. Download and extract this repository
2. Open the directory that contains `package.json` in your command prompt or terminal
3. Run the command `npm install`
4. Create a file called `token` in the same directory and enter your bot client's token
## Starting
1. Open the `scripts` folder
2. Open the folder that corresponds with your operating system
3. Run the `start` file
## Other Start Scripts
<details>
<summary><b>Mac/Linux Screen</b></summary>
<br>
<p>Screen allows the bot to run in the background without a terminal present</p>
<p><a href="https://linuxize.com/post/how-to-use-linux-screen/">Tutorial for usage</a>, check if it's installed already before installing</p>
<p><b>Installing</b></p>
<ul>
<li>
<p>Mac</p>
<ol>
<li>Install <a href="https://formulae.brew.sh/"><code>Homebrew</code></a></li>
<li>Open the terminal and run the command <code>brew install screen</code></li>
</ol>
</li>
<li>
<p>Linux</p>
<ul>
<li>Use your distribution's package manager to find <code>screen</code></li>
</ul>
</li>
</ul>
</details>
<details>
<summary><b>Mac/Linux Tmux</b></summary>
<br>
<p>Tmux allows the bot to run in the background without a terminal present</p>
<p><a href="https://linuxize.com/post/getting-started-with-tmux/">Tutorial for usage</a>, check if it's installed already before installing</p>
<p><b>Installing</b></p>
<ul>
<li>
<p>Mac</p>
<ol>
<li>Install <a href="https://formulae.brew.sh/"><code>Homebrew</code></a></li>
<li>Open the terminal and run the command <code>brew install tmux</code></li>
</ol>
</li>
<li>
<p>Linux</p>
<ul>
<li>Use your distribution's package manager to find <code>tmux</code></li>
</ul>
</li>
</ul>
</details>

157
dist/api.js vendored
View File

@@ -18,47 +18,31 @@ var __importStar = (this && this.__importStar) || function (mod) {
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPlayer = void 0;
exports.getPlayerInteraction = exports.getPlayer = void 0;
const cheerio = __importStar(require("cheerio"));
const discord_js_1 = require("discord.js");
const https_1 = __importDefault(require("https"));
//making long regex rather than splitting the string at ":" because regex would be easier to debug than logic in the case the website changes
//while names cant have spaces, the name slot in ogtitle could be shown as "No Player Name" which has spaces
const util_1 = require("./util");
const uniteApiRegex = {
//$1 = name, $2 = id
ogtitle: /Unite API - ([\w\d ]+) \((.*)\)/,
//$1 = level, $2 = rank, $3 = elo, $4 = battles, $5 = wins, $6 = win rate
ogdescription: /Pokémon Unite : Lv.(\d+) (\w+) \((\d+)\)\n Battles : (\d+)\n Wins : (\d+)\n Win Rate : (\d+)%/,
ogtitle: /unite api - (.+) \((.*)\)/i,
//$1 = level, $2 = rank, $3 = elo/class (rest is found by splitting each line)
ogdescription: [
/lv\.(\d+) (\w+) \((\d+)\)/i,
/lv\.(\d+) (\w+): class (\d+)/i //other
]
};
/*
og:title
Unite API - IanWhysp (X188GF7)
og:description
Pokémon Unite : Lv.40 Master (1741)
Battles : 1089
Wins : 576
Win Rate : 52%
*/
/**
* gets the html of the uniteApi page for the player
* @param name name of player
* @returns the html of the page through a promise (rejects if the page status is not 200)
*/
function getHTML(name) {
name = name.replace(/[^\w\d]/g, '');
//name = name.replace(/[^\w\d]/g, '');
return new Promise((resolve, reject) => {
const init = {
host: 'uniteapi.dev',
@@ -71,11 +55,10 @@ function getHTML(name) {
return;
}
let result = Buffer.alloc(0);
response.on('data', function (chunk) {
response.on('data', chunk => {
result = Buffer.concat([result, chunk]);
});
response.on('end', function () {
// result has response body buffer
response.on('end', () => {
resolve(result.toString());
});
};
@@ -87,20 +70,24 @@ function getHTML(name) {
* interprets the html from getHTML()
* @param name name of player
* @returns player data from site
* @throws errorMessage class if the request fails
*/
function readHTML(html) {
let metaElems = cheerio.load(html)('meta').toArray(), foundData = {
let $ = cheerio.load(html);
let metaElems = $('meta').toArray(), foundData = {
name: "",
id: "",
avatar: "",
level: "",
rank: "",
elo: "",
elo: null,
class: null,
battles: "",
wins: "",
winrate: ""
};
//filter down to just ones named "og:..."
metaElems = metaElems.filter(el => { var _a; return (_a = el.attribs.property) === null || _a === void 0 ? void 0 : _a.startsWith('og:'); });
metaElems = metaElems.filter(el => el.attribs.property?.startsWith('og:'));
metaElems.forEach(el => {
let attr = el.attribs;
if (attr.property === 'og:title') {
@@ -111,17 +98,63 @@ function readHTML(html) {
}
}
else if (attr.property === 'og:description') {
let data = uniteApiRegex.ogdescription.exec(attr.content);
if (data !== null && data.length >= 7) {
foundData.level = data[1];
foundData.rank = data[2];
foundData.elo = data[3];
foundData.battles = data[4];
foundData.wins = data[5];
foundData.winrate = data[6];
//all lines
let lines = attr.content.split('\n').map(l => l.trim()), extraLines = [];
//ensure first line is correct
while (lines.length && !/pok.mon unite/i.test(lines[0])) {
let line = lines.shift();
if (line)
extraLines.push(line);
}
if (!lines.length)
throw (0, util_1.emsg)('Unable to read data, please try again');
//bring the first lines removed back into the data
lines = [
...lines,
...extraLines.filter(d => d)
];
//first line
{
//will be only text after "pokemon unite:"
let line = lines[0].split(':').slice(1).join(':').trim();
let regex = uniteApiRegex.ogdescription;
if (regex[0].test(line)) { //is master/has elo
let regexData = line.match(regex[0]);
if (!regexData || regexData.length < 4)
throw (0, util_1.emsg)('Unable to read data, please try again');
foundData.level = regexData[1];
foundData.rank = regexData[2];
foundData.elo = regexData[3];
}
else { //is not master/has a class
let regexData = line.match(regex[1]);
if (!regexData || regexData.length < 4)
throw (0, util_1.emsg)('Unable to read data, please try again');
foundData.level = regexData[1];
foundData.rank = regexData[2];
foundData.class = regexData[3];
}
}
lines.shift();
//rest of lines
lines.forEach(line => {
let split = line.split(':').map(l => l.trim()), key = split[0].toLowerCase().replace(/[^\w]/g, ''), value = split[1];
switch (key) {
case 'battles':
foundData.battles = value;
break;
case 'wins':
foundData.wins = value;
break;
case 'winrate':
foundData.winrate = value;
break;
}
});
}
});
foundData.avatar = $('.player-card-image img').attr('src') || "";
foundData.avatar = foundData.avatar.replace('../', 'https://uniteapi.dev/');
return foundData;
}
/**
@@ -139,14 +172,40 @@ function verifyData(data) {
* @param name name of player
* @returns player data
*/
function getPlayer(name) {
return __awaiter(this, void 0, void 0, function* () {
let html = yield getHTML(name);
let data = readHTML(html);
if (verifyData(data))
return data;
return null;
});
async function getPlayer(name) {
let html = await getHTML(name);
let data = readHTML(html);
if (verifyData(data))
return data;
return null;
}
exports.getPlayer = getPlayer;
//await getPlayer('IanWhysp')
async function sendPlayerEmbed(interaction, data) {
let embed = new discord_js_1.MessageEmbed()
.setTitle(`${data.name} (${data.id})`)
.setURL(`https://uniteapi.dev/p/${encodeURIComponent(data.name)}`)
.setTimestamp()
.setThumbnail(data.avatar)
.setDescription(`Level ${data.level}
${data.rank} ${data.elo ? `(${data.elo})` : `Class ${data.class}`}
**Battles** ${data.battles}
**Wins** ${data.wins}
**Win Rate** ${data.winrate}`);
await interaction.editReply({ embeds: [embed] });
}
/**
* calls getPlayer() with the name from the interaction
* @param interaction discord interaction
* @throws errorMessage class if the user cannot be found
*/
async function getPlayerInteraction(interaction) {
let username = interaction.options.getString('username', true);
await interaction.deferReply();
let data = await getPlayer(username);
if (data === null)
throw (0, util_1.emsg)('Unable to find user');
else
sendPlayerEmbed(interaction, data);
}
exports.getPlayerInteraction = getPlayerInteraction;

81
dist/discord.js vendored
View File

@@ -1,41 +1,70 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerCommands = void 0;
const rest_1 = require("@discordjs/rest");
const v9_1 = require("discord-api-types/v9");
const commands = [{
// list of commands to register with discord
const commands = [
{
name: 'queue',
description: 'creates a queue'
}];
description: 'create a queue',
options: [
{
type: 4,
name: 'teamsize',
description: 'size of each team',
required: true,
min_value: 1
}
]
},
{
name: 'join',
description: 'join the active queue'
},
{
name: 'leave',
description: 'leave the active queue'
},
{
name: 'ready',
description: 'ready the queue and display team info'
},
{
name: 'cancel',
description: 'cancels the current queue (must have the Manage Messages permission)'
},
{
name: 'player',
description: 'display player information',
options: [
{
type: 3,
name: 'username',
description: 'in game name or UniteApi short link',
required: true
}
]
}
];
/**
* register/reload commands on guild(s)
* @param token discord bot token
* @param clientId discord bot client id
* @param guildIds discord guild id(s)
*/
function registerCommands(token, clientId, guildIds) {
return __awaiter(this, void 0, void 0, function* () {
const rest = new rest_1.REST({ version: '9' }).setToken(token);
if (typeof guildIds === 'string')
guildIds = [guildIds];
for (let i = 0; i < guildIds.length; i++) {
try {
yield rest.put(v9_1.Routes.applicationGuildCommands(clientId, guildIds[i]), { body: commands });
console.log(`[${guildIds[i]}] registered command`);
}
catch (error) {
console.error(error);
}
async function registerCommands(token, clientId, guildIds) {
const rest = new rest_1.REST({ version: '9' }).setToken(token);
if (typeof guildIds === 'string')
guildIds = [guildIds];
for (let i = 0; i < guildIds.length; i++) {
try {
await rest.put(v9_1.Routes.applicationGuildCommands(clientId, guildIds[i]), { body: commands });
console.log(`[${guildIds[i]}] registered command`);
}
});
catch (error) {
console.error(error);
}
}
}
exports.registerCommands = registerCommands;

48
dist/index.js vendored
View File

@@ -18,35 +18,57 @@ var __importStar = (this && this.__importStar) || function (mod) {
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const discord_js_1 = require("discord.js");
const fs = __importStar(require("fs"));
const api_1 = require("./api");
const discord_1 = require("./discord");
const queue_1 = require("./queue");
const util_1 = require("./util");
const CLIENT = new discord_js_1.Client({ intents: [discord_js_1.Intents.FLAGS.GUILDS] });
//init logs with a timestamp
console.log(new Date().toISOString() + '\n\n');
//get token
if (!fs.existsSync('./token')) {
fs.writeFileSync('./token', '');
console.error('Missing Discord Token, please enter the bot token into the token file');
process.exit(1);
}
const TOKEN = fs.readFileSync('./token').toString();
//discord connections
CLIENT.on('ready', client => {
console.log(`Logged in as ${client.user.tag}`);
client.guilds.fetch().then(guilds => (0, discord_1.registerCommands)(TOKEN, client.user.id, guilds.map(g => g.id)));
});
CLIENT.on('interactionCreate', (interaction) => __awaiter(void 0, void 0, void 0, function* () {
CLIENT.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand())
return;
if (interaction.commandName === 'queue')
(0, queue_1.createQueue)(interaction);
}));
try {
if (interaction.commandName === 'queue')
await (0, queue_1.createQueue)(interaction);
else if (interaction.commandName === 'join')
await (0, queue_1.joinQueue)(interaction);
else if (interaction.commandName === 'leave')
await (0, queue_1.leaveQueue)(interaction);
else if (interaction.commandName === 'ready')
await (0, queue_1.readyQueue)(interaction);
else if (interaction.commandName === 'cancel')
await (0, queue_1.cancelQueue)(interaction);
else if (interaction.commandName === 'player')
await (0, api_1.getPlayerInteraction)(interaction);
}
catch (e) {
if (e instanceof util_1.errorMessage) {
if (interaction.deferred || interaction.replied)
interaction.editReply(e.msg);
else
interaction.reply({
content: e.msg,
ephemeral: e.ephemeral
});
}
else
console.error(e);
}
});
CLIENT.login(TOKEN);

187
dist/queue.js vendored
View File

@@ -1,6 +1,189 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createQueue = void 0;
function createQueue(interaction) {
exports.cancelQueue = exports.readyQueue = exports.leaveQueue = exports.joinQueue = exports.createQueue = exports.queueContains = exports.getAll = exports.getInfo = void 0;
const discord_js_1 = require("discord.js");
const util_1 = require("./util");
//maps ChannelID to QueueInfo
const QUEUE = new Map();
/**
* get the queueInfo of an interaction
* @param interaction
* @throws errorMessage class if it does not exist
* @returns queue info
*/
function getInfo(interaction) {
let info = QUEUE.get(interaction.channelId);
if (!info)
throw (0, util_1.emsg)('There is not an active queue in this channel, type `/queue` to create one');
return info;
}
exports.getInfo = getInfo;
/**
* compiles all the get functions above
* @param interaction
* @throws if another get function throws
* @returns object containing each
*/
const getAll = (interaction) => ({
member: (0, util_1.getMember)(interaction),
channel: (0, util_1.getChannel)(interaction),
info: getInfo(interaction)
});
exports.getAll = getAll;
/**
* checks if the interaction data is already in the queue
* @param interaction
* @returns boolean
*/
function queueContains(interaction) {
let { member, info } = (0, exports.getAll)(interaction);
if (info.players.map(m => m.id).includes(member.id))
return true;
return false;
}
exports.queueContains = queueContains;
/**
* creates the timeout for the queue
* @param interaction
* @returns time timeout identifier
*/
function setQueueTimeout(interaction) {
let channel = (0, util_1.getChannel)(interaction);
return setTimeout(() => {
clearQueue(interaction);
channel.send('Queue has been reset due to inactivity');
}, 5 * 60 * 1000); //5 minutes
}
/**
* updates the rich embed for the current queue
* @param interaction
*/
async function sendQueueEmbed(interaction, closed = false) {
let info = getInfo(interaction), origInteraction = info.initiator.interaction;
let embed = new discord_js_1.MessageEmbed()
.setTitle('Queue')
.setAuthor({
name: info.initiator.member.displayName,
iconURL: info.initiator.member.displayAvatarURL({ dynamic: true })
})
.addField('Team Size', info.teamsize.toString(), true)
.addField('Players Joined', info.players.length.toString(), true)
.setFooter({ text: closed ? 'queue is finished' : 'type /join' });
if (origInteraction.deferred || origInteraction.replied)
await origInteraction.editReply({ embeds: [embed] });
else
await origInteraction.reply({ embeds: [embed] });
}
/**
* sends the list of teams
* @param interaction
*/
async function sendTeamsEmbed(interaction, teams) {
let embed = new discord_js_1.MessageEmbed()
.setTitle('Teams');
teams.forEach((team, i) => {
team.map(m => m.user.tag);
embed.addField(`Title ${i + 1}`, team.join('\n'));
});
interaction.reply({ embeds: [embed] });
}
/**
* sends the list of teams
* @param interaction
*/
async function clearQueue(interaction) {
let info = getInfo(interaction);
sendQueueEmbed(interaction, true);
clearTimeout(info.timeout);
QUEUE.delete(interaction.channelId);
}
/**
* creates a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be created
*/
async function createQueue(interaction) {
let member = (0, util_1.getMember)(interaction), { channelId } = interaction, teamsize = interaction.options.getInteger('teamsize', true);
if (QUEUE.has(channelId))
throw (0, util_1.emsg)('There is already an active queue in this channel, ' + (queueContains(interaction) ? 'and you are already in it' : 'type `/join` to join'));
QUEUE.set(channelId, {
players: [
member
],
initiator: {
interaction,
member
},
teamsize: teamsize,
timeout: setQueueTimeout(interaction)
});
sendQueueEmbed(interaction);
}
exports.createQueue = createQueue;
/**
* joins a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be joined
*/
async function joinQueue(interaction) {
let { member, info } = (0, exports.getAll)(interaction);
if (queueContains(interaction))
throw (0, util_1.emsg)('You are already in the active queue');
info.players.push(member);
clearTimeout(info.timeout);
info.timeout = setQueueTimeout(interaction);
QUEUE.set(interaction.channelId, info);
sendQueueEmbed(interaction);
await interaction.reply('Joined the queue');
}
exports.joinQueue = joinQueue;
/**
* leaves a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be left
*/
async function leaveQueue(interaction) {
let { member, info } = (0, exports.getAll)(interaction);
if (!queueContains(interaction))
throw (0, util_1.emsg)('You aren\'t in the active queue');
info.players.splice(info.players.indexOf(member), 1);
clearTimeout(info.timeout);
info.timeout = setQueueTimeout(interaction);
QUEUE.set(interaction.channelId, info);
sendQueueEmbed(interaction);
await interaction.reply('Left the queue');
}
exports.leaveQueue = leaveQueue;
/**
* readys a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be readied
*/
async function readyQueue(interaction) {
let { member, info } = (0, exports.getAll)(interaction), { initiator } = info;
if (member.id !== initiator.member.id)
throw (0, util_1.emsg)('Only the queue initiator can ready the queue');
clearQueue(interaction);
if (info.players.filter(m => m.id !== initiator.member.id).length === 0)
throw (0, util_1.emsg)('Nobody signed up for the queue, the queue has been reset');
//team data
let playerlist = (0, util_1.shuffle)(info.players), teams = [];
//fill team data
for (let i = 0; i < playerlist.length; i += info.teamsize)
teams.push(playerlist.slice(i, i + info.teamsize));
sendTeamsEmbed(interaction, teams);
}
exports.readyQueue = readyQueue;
/**
* readys a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be reset
*/
async function cancelQueue(interaction) {
let { info, member, channel } = (0, exports.getAll)(interaction);
if (!member.permissionsIn(channel).has('MANAGE_MESSAGES'))
throw (0, util_1.emsg)('You do not have permission to run this command');
clearQueue(interaction);
await interaction.reply('Queue has been reset');
}
exports.cancelQueue = cancelQueue;

66
dist/util.js vendored Normal file
View File

@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getChannel = exports.getMember = exports.emsg = exports.errorMessage = exports.shuffle = void 0;
const discord_js_1 = require("discord.js");
/**
* shuffles an array
* https://stackoverflow.com/a/2450976/2856416
* @param array an array
* @returns an array but shuffled
*/
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle...
while (currentIndex != 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]
];
}
return array;
}
exports.shuffle = shuffle;
class errorMessage {
constructor(msg, ephemeral = true) {
this.msg = msg;
this.ephemeral = ephemeral;
}
}
exports.errorMessage = errorMessage;
/**
* a simple class to contain an error message and related data
* @param msg error message
* @param ephemeral (default=true)
* @returns new errorMessage
*/
const emsg = (msg, ephemeral = true) => new errorMessage(msg, ephemeral);
exports.emsg = emsg;
/**
* get the GuildMember of an interaction
* @param interaction
* @throws errorMessage class if it cannot be read
* @returns member
*/
function getMember(interaction) {
let member = interaction.member;
if (!(member instanceof discord_js_1.GuildMember))
throw (0, exports.emsg)('Unable to retrieve guild member information, please try again');
return member;
}
exports.getMember = getMember;
/**
* get the TextChannel of an interaction
* @param interaction
* @throws errorMessage class if it cannot be read
* @returns member
*/
function getChannel(interaction) {
let channel = interaction.channel;
if (!(channel instanceof discord_js_1.TextChannel))
throw (0, exports.emsg)('Unable to retrieve text channel information, please try again');
return channel;
}
exports.getChannel = getChannel;

6533
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +1,44 @@
{
"name": "1800queue",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node .",
"watch": "npm-watch",
"dev": "npm-watch"
},
"watch": {
"build": {
"patterns": [
"src"
],
"extensions": "ts",
"legacyWatch": true
"name": "1800queue-alt",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node .",
"watch": "npm-watch",
"dev": "npm-watch",
"getname": "echo $npm_package_name"
},
"start": {
"patterns": [
"dist"
],
"extensions": "js",
"legacyWatch": true,
"runOnChangeOnly": true
"watch": {
"build": {
"patterns": [
"src"
],
"extensions": "ts",
"legacyWatch": true
},
"start": {
"patterns": [
"dist"
],
"extensions": "js",
"legacyWatch": true,
"runOnChangeOnly": true
}
},
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^17.0.13",
"npm-watch": "^0.11.0",
"ts-node": "^10.4.0",
"typescript": "^4.5.5"
},
"dependencies": {
"@discordjs/rest": "^0.3.0",
"cheerio": "^1.0.0-rc.10",
"discord-api-types": "^0.26.1",
"discord.js": "^13.6.0"
}
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^17.0.13",
"npm-watch": "^0.11.0",
"typescript": "^4.5.5"
},
"dependencies": {
"@discordjs/rest": "^0.3.0",
"cheerio": "^1.0.0-rc.10",
"discord-api-types": "^0.26.1",
"discord.js": "^13.6.0"
}
}

View File

@@ -1 +0,0 @@
# 1800queue

18
scripts/mac-linux/install Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
#change to ccurrent directory
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
cd ../..
#run
npm install
#discord token
[ ! -f "token" ] && touch "token"
if [ ! -s "token" ]
then
printf "Enter your discord bot token: "
read token
printf $token > "token"
fi

42
scripts/mac-linux/start Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/bash
#change to current directory
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
cd ../..
#colors
C1='\033[1;37m'
C2='\033[1;33m'
NC='\033[0m'
#make log folder
if [[ ! -d log ]]
then
mkdir log
fi
#current file number
num=0
if [[ -f log/number ]]
then
num=$(cat log/number)
fi
echo $num > log/number
#watchdog
while true
do
#increment log file number
num=$(( num + 1 ))
echo $num > log/number
#run
npm start 2> log/$num.err | tee log/$num.out
#give chance to close program
printf "\n\n\n${C1}press ${C2}Control+C${C1} to close${NC}\n\n\n"
sleep 5
done

12
scripts/mac-linux/start_screen Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
#change to ccurrent directory
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
#start
if [[ $(screen -ls | grep $(npm run getname -s)) ]]; then
echo "Session already exists"
else
screen -S $(npm run getname -s) -d -m ./start
echo "Created session"
fi

12
scripts/mac-linux/start_tmux Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
#change to ccurrent directory
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
#start
if [[ $(tmux ls | grep $(npm run getname -s)) ]]; then
echo "Session already exists"
else
tmux new-session -d -s $(npm run getname -s) ./start
echo "Created session"
fi

View File

@@ -0,0 +1,16 @@
@echo off
@REM change to ccurrent directory
cd %~f0\..\..\..\
@REM discord token
if not exist "token" copy NUL "token"
for /f %%i in ("token") do set size=%%~zi
if %size% equ 0 (
set /p token="Enter your discord bot token: "
echo | set /p=%id% > "token"
)
@REM run
npm install

28
scripts/windows/start.bat Normal file
View File

@@ -0,0 +1,28 @@
@echo off
@Rem change to current directory
cd %~f0\..\..\..\
@Rem make log folder
if not exist log mkdir log
@Rem current file number
set /A num=0
if exist log\number set /p num=<log\number
echo %num > log\number
@Rem watchdog
:watchdog
@Rem increment log file number
set /A num=num+1
echo %num > log\number
@Rem run
powershell "npm start 2> log\%num%.err | tee log\%num%.out"
@Rem give chance to close program
timeout /t 5 /nobreak
goto watchdog

View File

@@ -1,37 +1,32 @@
import * as cheerio from 'cheerio';
import { CommandInteraction, MessageEmbed } from 'discord.js';
import { IncomingMessage } from 'http';
import http from 'https';
import { emsg } from './util';
//making long regex rather than splitting the string at ":" because regex would be easier to debug than logic in the case the website changes
//while names cant have spaces, the name slot in ogtitle could be shown as "No Player Name" which has spaces
const uniteApiRegex = {
//$1 = name, $2 = id
ogtitle: /Unite API - ([\w\d ]+) \((.*)\)/,
//$1 = level, $2 = rank, $3 = elo, $4 = battles, $5 = wins, $6 = win rate
ogdescription: /Pokémon Unite : Lv.(\d+) (\w+) \((\d+)\)\n Battles : (\d+)\n Wins : (\d+)\n Win Rate : (\d+)%/,
ogtitle: /unite api - (.+) \((.*)\)/i,
//$1 = level, $2 = rank, $3 = elo/class (rest is found by splitting each line)
ogdescription: [ //just the first line
/lv\.(\d+) (\w+) \((\d+)\)/i, //master
/lv\.(\d+) (\w+): class (\d+)/i //other
]
}
type uniteApiData = {
name: string,
id: string,
avatar: string,
level: string,
rank: string,
elo: string,
class: string|null,
elo: string|null,
battles: string,
wins: string,
winrate: string
}
/*
og:title
Unite API - IanWhysp (X188GF7)
og:description
Pokémon Unite : Lv.40 Master (1741)
Battles : 1089
Wins : 576
Win Rate : 52%
*/
/**
* gets the html of the uniteApi page for the player
@@ -40,7 +35,7 @@ Pokémon Unite : Lv.40 Master (1741)
*/
function getHTML(name: string): Promise<string> {
name = name.replace(/[^\w\d]/g, '');
//name = name.replace(/[^\w\d]/g, '');
return new Promise((resolve, reject) => {
@@ -58,12 +53,11 @@ function getHTML(name: string): Promise<string> {
}
let result = Buffer.alloc(0);
response.on('data', function(chunk) {
response.on('data', chunk => {
result = Buffer.concat([result, chunk]);
});
response.on('end', function() {
// result has response body buffer
response.on('end', () => {
resolve(result.toString());
});
};
@@ -79,16 +73,20 @@ function getHTML(name: string): Promise<string> {
* interprets the html from getHTML()
* @param name name of player
* @returns player data from site
* @throws errorMessage class if the request fails
*/
function readHTML(html: string): uniteApiData {
let metaElems = cheerio.load(html)('meta').toArray(),
let $ = cheerio.load(html)
let metaElems = $('meta').toArray(),
foundData: uniteApiData = {
name: "",
id: "",
avatar: "",
level: "",
rank: "",
elo: "",
elo: null,
class: null,
battles: "",
wins: "",
winrate: ""
@@ -107,18 +105,89 @@ function readHTML(html: string): uniteApiData {
foundData.id = data[2];
}
} else if (attr.property === 'og:description') {
let data = uniteApiRegex.ogdescription.exec(attr.content);
if (data !== null && data.length >= 7) {
foundData.level = data[1];
foundData.rank = data[2];
foundData.elo = data[3];
foundData.battles = data[4];
foundData.wins = data[5];
foundData.winrate = data[6];
//all lines
let lines = attr.content.split('\n').map(l => l.trim()),
extraLines: string[] = [];
//ensure first line is correct
while (lines.length && !/pok.mon unite/i.test(lines[0])) {
let line = lines.shift();
if (line)
extraLines.push(line);
}
if (!lines.length)
throw emsg('Unable to read data, please try again');
//bring the first lines removed back into the data
lines = [
...lines,
...extraLines.filter(d => d)
];
//first line
{
//will be only text after "pokemon unite:"
let line = lines[0].split(':').slice(1).join(':').trim();
let regex = uniteApiRegex.ogdescription;
if (regex[0].test(line)) { //is master/has elo
let regexData = line.match(regex[0]);
if (!regexData || regexData.length < 4)
throw emsg('Unable to read data, please try again');
foundData.level = regexData[1];
foundData.rank = regexData[2];
foundData.elo = regexData[3];
} else { //is not master/has a class
let regexData = line.match(regex[1]);
if (!regexData || regexData.length < 4)
throw emsg('Unable to read data, please try again');
foundData.level = regexData[1];
foundData.rank = regexData[2];
foundData.class = regexData[3];
}
}
lines.shift();
//rest of lines
lines.forEach(line => {
let split = line.split(':').map(l => l.trim()),
key = split[0].toLowerCase().replace(/[^\w]/g, ''),
value = split[1];
switch(key) {
case 'battles':
foundData.battles = value;
break;
case 'wins':
foundData.wins = value;
break;
case 'winrate':
foundData.winrate = value;
break;
}
});
}
})
});
foundData.avatar = $('.player-card-image img').attr('src') || "";
foundData.avatar = foundData.avatar.replace('../', 'https://uniteapi.dev/');
return foundData;
@@ -148,4 +217,35 @@ export async function getPlayer(name: string): Promise<uniteApiData|null> {
return null;
}
//await getPlayer('IanWhysp')
async function sendPlayerEmbed(interaction: CommandInteraction, data: uniteApiData) {
let embed = new MessageEmbed()
.setTitle(`${data.name} (${data.id})`)
.setURL(`https://uniteapi.dev/p/${encodeURIComponent(data.name)}`)
.setTimestamp()
.setThumbnail(data.avatar)
.setDescription(`Level ${data.level}
${data.rank} ${data.elo ? `(${data.elo})` : `Class ${data.class}`}
**Battles** ${data.battles}
**Wins** ${data.wins}
**Win Rate** ${data.winrate}`);
await interaction.editReply({embeds: [embed]});
}
/**
* calls getPlayer() with the name from the interaction
* @param interaction discord interaction
* @throws errorMessage class if the user cannot be found
*/
export async function getPlayerInteraction(interaction: CommandInteraction) {
let username = interaction.options.getString('username', true);
await interaction.deferReply();
let data = await getPlayer(username);
if (data === null)
throw emsg('Unable to find user');
else
sendPlayerEmbed(interaction, data);
}

View File

@@ -1,10 +1,50 @@
import { REST } from '@discordjs/rest';
import { Routes } from 'discord-api-types/v9';
const commands = [{
name: 'queue',
description: 'creates a queue'
}];
// list of commands to register with discord
const commands = [
{
name: 'queue',
description: 'create a queue',
options: [
{
type: 4, //INTEGER
name: 'teamsize',
description: 'size of each team',
required: true,
min_value: 1
}
]
},
{
name: 'join',
description: 'join the active queue'
},
{
name: 'leave',
description: 'leave the active queue'
},
{
name: 'ready',
description: 'ready the queue and display team info'
},
{
name: 'cancel',
description: 'cancels the current queue (must have the Manage Messages permission)'
},
{
name: 'player',
description: 'display player information',
options: [
{
type: 3, //STRING
name: 'username',
description: 'in game name or UniteApi short link',
required: true
}
]
}
];
/**
* register/reload commands on guild(s)

View File

@@ -1,10 +1,16 @@
import { Client, Intents } from 'discord.js';
import * as fs from 'fs';
import { getPlayerInteraction } from './api';
import { registerCommands } from './discord';
import { createQueue } from './queue';
import { cancelQueue, createQueue, joinQueue, leaveQueue, readyQueue } from './queue';
import { errorMessage } from './util';
const CLIENT = new Client({ intents: [Intents.FLAGS.GUILDS] });
//init logs with a timestamp
console.log(new Date().toISOString()+'\n\n');
//get token
if (!fs.existsSync('./token')) {
fs.writeFileSync('./token', '');
console.error('Missing Discord Token, please enter the bot token into the token file');
@@ -12,6 +18,7 @@ if (!fs.existsSync('./token')) {
}
const TOKEN = fs.readFileSync('./token').toString();
//discord connections
CLIENT.on('ready', client => {
console.log(`Logged in as ${client.user.tag}`);
client.guilds.fetch().then(guilds =>
@@ -21,8 +28,36 @@ CLIENT.on('ready', client => {
CLIENT.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return;
if (interaction.commandName === 'queue')
createQueue(interaction);
try {
if (interaction.commandName === 'queue')
await createQueue(interaction);
else if (interaction.commandName === 'join')
await joinQueue(interaction);
else if (interaction.commandName === 'leave')
await leaveQueue(interaction);
else if (interaction.commandName === 'ready')
await readyQueue(interaction);
else if (interaction.commandName === 'cancel')
await cancelQueue(interaction);
else if (interaction.commandName === 'player')
await getPlayerInteraction(interaction);
} catch (e) {
if (e instanceof errorMessage) {
if (interaction.deferred || interaction.replied)
interaction.editReply(e.msg);
else
interaction.reply({
content: e.msg,
ephemeral: e.ephemeral
});
} else console.error(e);
}
});
CLIENT.login(TOKEN);

View File

@@ -1,5 +1,236 @@
import { CommandInteraction } from "discord.js";
import { CommandInteraction, GuildMember, MessageEmbed } from "discord.js";
import { emsg, getChannel, getMember, queueInfo, shuffle } from "./util";
export function createQueue(interaction: CommandInteraction) {
//maps ChannelID to QueueInfo
const QUEUE = new Map<string, queueInfo>();
/**
* get the queueInfo of an interaction
* @param interaction
* @throws errorMessage class if it does not exist
* @returns queue info
*/
export function getInfo(interaction: CommandInteraction): queueInfo {
let info = QUEUE.get(interaction.channelId);
if (!info)
throw emsg('There is not an active queue in this channel, type `/queue` to create one');
return info;
}
/**
* compiles all the get functions above
* @param interaction
* @throws if another get function throws
* @returns object containing each
*/
export const getAll = (interaction: CommandInteraction) => ({
member: getMember(interaction),
channel: getChannel(interaction),
info: getInfo(interaction)
});
/**
* checks if the interaction data is already in the queue
* @param interaction
* @returns boolean
*/
export function queueContains(interaction: CommandInteraction): boolean {
let {member, info} = getAll(interaction);
if (info.players.map(m=>m.id).includes(member.id))
return true;
return false;
}
/**
* creates the timeout for the queue
* @param interaction
* @returns time timeout identifier
*/
function setQueueTimeout(interaction: CommandInteraction) {
let channel = getChannel(interaction);
return setTimeout(() => {
clearQueue(interaction);
channel.send('Queue has been reset due to inactivity');
}, 5*60*1000) //5 minutes
}
/**
* updates the rich embed for the current queue
* @param interaction
*/
async function sendQueueEmbed(interaction: CommandInteraction, closed: boolean = false) {
let info = getInfo(interaction),
origInteraction = info.initiator.interaction;
let embed = new MessageEmbed()
.setTitle('Queue')
.setAuthor({
name: info.initiator.member.displayName,
iconURL: info.initiator.member.displayAvatarURL({dynamic: true})
})
.addField('Team Size', info.teamsize.toString(), true)
.addField('Players Joined', info.players.length.toString(), true)
.setFooter({text: closed ? 'queue is finished' : 'type /join'});
if (origInteraction.deferred || origInteraction.replied)
await origInteraction.editReply({embeds: [embed]});
else
await origInteraction.reply({embeds: [embed]});
}
/**
* sends the list of teams
* @param interaction
*/
async function sendTeamsEmbed(interaction: CommandInteraction, teams: GuildMember[][]) {
let embed = new MessageEmbed()
.setTitle('Teams');
teams.forEach((team, i) => {
team.map(m => m.user.tag);
embed.addField(`Team ${i+1}`, team.join('\n'))
});
interaction.reply({embeds: [embed]});
}
/**
* sends the list of teams
* @param interaction
*/
async function clearQueue(interaction: CommandInteraction) {
let info = getInfo(interaction);
sendQueueEmbed(interaction, true);
clearTimeout(info.timeout);
QUEUE.delete(interaction.channelId);
}
/**
* creates a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be created
*/
export async function createQueue(interaction: CommandInteraction) {
let member = getMember(interaction),
{channelId} = interaction,
teamsize = interaction.options.getInteger('teamsize', true);
if (QUEUE.has(channelId))
throw emsg('There is already an active queue in this channel, ' + (queueContains(interaction) ? 'and you are already in it' : 'type `/join` to join'));
QUEUE.set(channelId, {
players: [
member
],
initiator: {
interaction,
member
},
teamsize: teamsize,
timeout: setQueueTimeout(interaction)
});
sendQueueEmbed(interaction);
}
/**
* joins a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be joined
*/
export async function joinQueue(interaction: CommandInteraction) {
let {member, info} = getAll(interaction);
if (queueContains(interaction))
throw emsg('You are already in the active queue');
info.players.push(member);
clearTimeout(info.timeout);
info.timeout = setQueueTimeout(interaction)
QUEUE.set(interaction.channelId, info);
sendQueueEmbed(interaction);
await interaction.reply('Joined the queue');
}
/**
* leaves a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be left
*/
export async function leaveQueue(interaction: CommandInteraction) {
let {member, info} = getAll(interaction);
if (!queueContains(interaction))
throw emsg('You aren\'t in the active queue');
info.players.splice(info.players.indexOf(member), 1);
clearTimeout(info.timeout);
info.timeout = setQueueTimeout(interaction)
QUEUE.set(interaction.channelId, info);
sendQueueEmbed(interaction);
await interaction.reply('Left the queue');
}
/**
* readys a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be readied
*/
export async function readyQueue(interaction: CommandInteraction) {
let {member, info} = getAll(interaction),
{initiator} = info;
if (member.id !== initiator.member.id)
throw emsg('Only the queue initiator can ready the queue');
clearQueue(interaction);
if (info.players.filter(m => m.id !== initiator.member.id).length === 0)
throw emsg('Nobody signed up for the queue, the queue has been reset');
//team data
let playerlist: GuildMember[] = shuffle(info.players),
teams: GuildMember[][] = [];
//fill team data
for (let i = 0; i < playerlist.length; i+= info.teamsize)
teams.push(playerlist.slice(i, i+info.teamsize));
sendTeamsEmbed(interaction, teams);
}
/**
* readys a queue from an interaction
* @param interaction
* @throws errorMessage class if it cannot be reset
*/
export async function cancelQueue(interaction: CommandInteraction) {
let {info, member, channel} = getAll(interaction);
if (!member.permissionsIn(channel).has('MANAGE_MESSAGES'))
throw emsg('You do not have permission to run this command');
clearQueue(interaction);
await interaction.reply('Queue has been reset');
}

87
src/util.ts Normal file
View File

@@ -0,0 +1,87 @@
import { CommandInteraction, GuildMember, TextChannel } from "discord.js";
/**
* shuffles an array
* https://stackoverflow.com/a/2450976/2856416
* @param array an array
* @returns an array but shuffled
*/
export function shuffle(array: any[]) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle...
while (currentIndex != 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
export class errorMessage {
public msg: string;
public ephemeral: boolean;
constructor(msg: string, ephemeral: boolean = true) {
this.msg = msg;
this.ephemeral = ephemeral;
}
}
/**
* a simple class to contain an error message and related data
* @param msg error message
* @param ephemeral (default=true)
* @returns new errorMessage
*/
export const emsg = (msg: string, ephemeral: boolean = true) => new errorMessage(msg, ephemeral);
export type queueInfo = {
players: GuildMember[],
initiator: {
interaction: CommandInteraction,
member: GuildMember
},
teamsize: number,
timeout: NodeJS.Timeout
}
/**
* get the GuildMember of an interaction
* @param interaction
* @throws errorMessage class if it cannot be read
* @returns member
*/
export function getMember(interaction: CommandInteraction): GuildMember {
let member = interaction.member;
if (!(member instanceof GuildMember))
throw emsg('Unable to retrieve guild member information, please try again');
return member;
}
/**
* get the TextChannel of an interaction
* @param interaction
* @throws errorMessage class if it cannot be read
* @returns member
*/
export function getChannel(interaction: CommandInteraction): TextChannel {
let channel = interaction.channel;
if (!(channel instanceof TextChannel))
throw emsg('Unable to retrieve text channel information, please try again');
return channel;
}

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2016",
"target": "es2020",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",