master
Jan 2021-03-06 15:39:00 +01:00
parent b6b9eb66ab
commit 32ef944a50
13 changed files with 289 additions and 40 deletions

80
commands/blacklist.js Normal file
View File

@ -0,0 +1,80 @@
const Revolt = require('revolt.js');
const { client, logger, config, db } = require('..');
const getUser = require('../util/get_user');
module.exports.meta = {
name: 'blacklist',
aliases: [ 'block' ],
description: 'Blacklist or unblacklist users.',
devLevel: 1
}
/**
*
* @param { Revolt.Message } message
* @param { string[] } args
*/
module.exports.run = async (message, args) => new Promise(async (resolve, reject) => {
if (!args[0]) return client.channels.sendMessage(message.channel, ':x: No user specified');
let user = await getUser(args.shift());
if (!user) return client.channels.sendMessage(message.channel, ':x: User not found');
let blacklistStatus = db.blacklist.get(user._id);
if (!blacklistStatus) blacklistStatus = {
blacklisted: false,
silent: false,
nextWarning: Date.now() - 1,
lastBlacklistedBy: null
}
let removeFriend = () => client.users.removeFriend(user._id)
.catch(reject);
let block = () => client.users.blockUser(user._id)
.catch(reject);
let unblock = () => client.users.unblockUser(user._id)
.catch(reject);
if (args[0]) {
switch (args[0]?.toLowerCase()) {
case 'yes':
case 'true':
case 'on':
case 'normal':
blacklistStatus.blacklisted = true;
blacklistStatus.silent = false;
blacklistStatus.lastBlacklistedBy = message.author;
await removeFriend();
await block();
break;
case 'no':
case 'false':
case 'off':
blacklistStatus.blacklisted = false;
blacklistStatus.silent = false;
await unblock();
break;
case 'silent':
blacklistStatus.blacklisted = true;
blacklistStatus.silent = true;
blacklistStatus.lastBlacklistedBy = message.author;
await removeFriend();
await block();
break;
default:
return client.channels.sendMessage(message.channel, `Unknown argument, must be either \`true\`, \`false\` or \`silent\``);
}
}
await client.channels.sendMessage(message.channel,
`>##${args[0] ? ' Blacklist updated:' : ''} <@${user._id}>\n>\u200b\n` +
`>Blacklisted: \`${blacklistStatus.blacklisted}\`\n` +
`>Silent: \`${blacklistStatus.silent}\`\n` +
`>Last blacklisted by: ${blacklistStatus.lastBlacklistedBy != null ? `<@${blacklistStatus.lastBlacklistedBy}>` : '`None`'}`);
db.blacklist.set(user._id, blacklistStatus);
});

53
commands/devs.js Normal file
View File

@ -0,0 +1,53 @@
const Revolt = require('revolt.js');
const { client, logger, config, db } = require('..');
const getUser = require('../util/get_user');
module.exports.meta = {
name: 'devs',
aliases: [ 'dev', 'setdev' ],
description: 'Set user\'s developer status.',
devLevel: 1
}
/**
*
* @param { Revolt.Message } message
* @param { string[] } args
*/
module.exports.run = async (message, args) => new Promise(async (resolve, reject) => {
if (!args[0]) return client.channels.sendMessage(message.channel, ':x: No user specified');
let user = await getUser(args.shift());
if (!user) return client.channels.sendMessage(message.channel, ':x: User not found');
let devStatus = db.devs.get(user._id);
if (!devStatus) devStatus = {
level: 0,
lastModifiedBy: null
}
if (args[0]) {
if (user._id === message.author)
return client.channels.sendMessage(message.channel, 'You can\'t modify yourself.');
if (user._id === client.user._id)
return client.channels.sendMessage(message.channel, 'You can\'t modify me, you cunt.');
let newLevel = Number(args[0]);
if (isNaN(newLevel))
return client.channels.sendMessage(message.channel, `That is not a valid number.`);
let authorLevel = require('../util/dev').getDevLevel(message.author);
if (newLevel >= authorLevel)
return client.channels.sendMessage(message.channel, `You can't assign a dev level greater than or `
+ `equal to your own level. (Your level: ${authorLevel})`);
devStatus.level = newLevel;
devStatus.lastModifiedBy = message.author;
}
await client.channels.sendMessage(message.channel, `>## ${args[0] ? 'Updated ' : ''}<@${user._id}>'s dev status\n>\u200b\n`
+ `>Dev level: \`${devStatus.level}\`\n`
+ `>Last modified by: ${devStatus.lastModifiedBy ? `<@${devStatus.lastModifiedBy}>` : '`Nobody`'}`);
db.devs.set(user._id, devStatus);
});

View File

@ -3,7 +3,7 @@ const { client, logger, config } = require('..');
module.exports.meta = {
name: 'help',
aliases: [],
aliases: [ 'commands' ],
description: 'List all commands.'
}
@ -16,10 +16,13 @@ module.exports.run = async (message, args) => new Promise(async (resolve, reject
let msgContent = `My current prefix is \`${config.prefix}\`. To add me to a group, hit me up with a friend request first.\n\u200b\n* * *\n\u200b\n`
+ `| Name | Aliases | Description |\n`
+ `| ---- | ------- | ----------- |\n`;
let userDevLvl = require('../util/dev').getDevLevel(message.author);
require('../util/command_loader').commands
.forEach(command => {
msgContent += `| ${config.prefix}${command.meta.name} | ${command.meta.aliases?.join(', ') || 'None'} | ${command.meta.description || 'None'}\n`;
if (!command.meta.devLevel || command.meta.devLevel <= userDevLvl) // Ensure normal users don't see dev commands
msgContent += `| ${userDevLvl > 0 ? `\`[${command.meta.devLevel || 0}]\` ` : ''}${config.prefix}${command.meta.name}`
+ `| ${command.meta.aliases?.join(', ') || 'None'} | ${command.meta.description || 'None'}\n`;
});
client.channels.sendMessage(message.channel, msgContent);
await client.channels.sendMessage(message.channel, msgContent);
});

37
commands/leaderboard.js Normal file
View File

@ -0,0 +1,37 @@
const Revolt = require('revolt.js');
const { client, logger, config } = require('..');
const { levels, levelups } = require('../util/levels');
module.exports.meta = {
name: 'leaderboard',
aliases: [ 'top' ],
description: 'View leaderboards for XP and tic tac toe.'
}
/**
*
* @param { Revolt.Message } message
* @param { string[] } args
*/
module.exports.run = async (message, args) => new Promise(async (resolve, reject) => {
let xp = Array.from(levels)
.sort((a, b) => b[1].xp - a[1].xp);
let top = xp.slice(0, 5);
let out = `># Top 5 - XP\n>\u200b\n`;
let p = [];
top.forEach(item => p.push(client.users.fetch(item[0])));
p = await Promise.allSettled(p);
p.forEach((pr, i) => top[i].push(pr?.value));
top.forEach((item, i) => out += `><@${item[0]}>: `
+ `${item[1]?.xp} XP, Level ${item[1]?.level}}\n`);
let combinedXP = xp.reduce((total, cur) => total + cur[1]?.xp, 0);
out += `\n#### Combined XP of all users: \`${combinedXP}\``
await client.channels.sendMessage(message.channel, out);
});

View File

@ -1,6 +1,7 @@
const Revolt = require('revolt.js');
const { client, logger, config } = require('..');
const { levels, levelups } = require('../util/levels');
const getUser = require('../util/get_user');
module.exports.meta = {
name: 'rank',
@ -16,12 +17,16 @@ module.exports.meta = {
module.exports.run = async (message, args) => new Promise(async (resolve, reject) => {
const channel = await client.channels.fetch(message.channel);
if (channel.channel_type !== "Group") return client.channels.sendMessage(message.channel, `You can't earn XP in this channel.`);
let groupLevels = levels.get(message.channel);
const { xp, level, enabled } = (groupLevels?.[message.author] || {});
const response = `You are level **${level}** with **${xp}** XP. `
+ `XP required for level up: **${levelups.find(l => l > xp) - xp}**.\n`
+ (enabled === false ? '**Note: Leveling up is currently disabled in this channel.**' : '');
let target = await getUser(args.join(' '));
const { xp, level, enabled } = (levels.get(target?._id ?? message.author) || {});
if (xp === undefined || level === undefined)
return client.channels.sendMessage(message.channel,
`${target ? `${target.username} doesn't` : `You don't`} have any XP yet. Check back later!`);
const response = `${target ? `${target.username} is` : 'You are'} level **${level}** with **${xp}** XP. `
+ `XP required for level up: **${levelups.find(l => l > xp) - xp}**.`;
client.channels.sendMessage(message.channel, response)
.catch(console.warn);

View File

@ -1,6 +1,4 @@
const Revolt = require('revolt.js');
const { client, logger, config } = require('..');
const { levels, levelups } = require('../util/levels');
const { client, config } = require('..');
const getUser = require('../util/get_user');
/**
@ -23,7 +21,7 @@ module.exports.run = async (message, args) => new Promise(async (resolve, reject
if ((await client.channels.fetch(message.channel)).channel_type !== 'Group')
return client.channels.sendMessage(message.channel, ':x: You can\'t play here.');
if (args[0]?.toLowerCase() === 'stop') {
if (args[0]?.toLowerCase() === 'stop' || args[0]?.toLowerCase() === 'end') {
if (!sessions.get(message.channel)?.messageID)
return client.channels.sendMessage(message.channel, `:x: No ongoing match found.`);
@ -66,12 +64,12 @@ module.exports.run = async (message, args) => new Promise(async (resolve, reject
const updateMsg = async () => {
text =
`**X**: <@${x}> \u200b **O**: <@${o}>\n\u200b\n` +
`|${turn}| \u200b \u200b **1** \u200b \u200b | \u200b \u200b **2** \u200b \u200b | \u200b \u200b **3** \u200b \u200b |\n` +
`|${winner === false ? '/' : turn}| \u200b \u200b **1** \u200b \u200b | \u200b \u200b **2** \u200b \u200b | \u200b \u200b **3** \u200b \u200b |\n` +
`|-|:-:|:-:|:-:|\n` +
`|**A**|${field[0][0]}|${field[0][1]}|${field[0][2]}|\n` +
`|**B**|${field[1][0]}|${field[1][1]}|${field[1][2]}|\n` +
`|**C**|${field[2][0]}|${field[2][1]}|${field[2][2]}|\n` +
(winner ? `\n\u200b\n# Winner: <@${winner === 'X' ? x : o}>\n\u200b` : '');
(winner ? `\n\u200b\n# Winner: <@${winner === 'X' ? x : o}>\n\u200b` : winner === false ? '\n\u200b\n# Draw' :'');
if (!msg) {
msg = await client.channels.sendMessage(message.channel, text);
@ -107,8 +105,14 @@ module.exports.run = async (message, args) => new Promise(async (resolve, reject
turn = turn === 'X' ? 'O' : 'X';
let w = getWinner(field);
console.log(w);
if (w) {
if (w === false) {
// Draw
end();
winner = false;
updateMsg();
sessions.delete(message.channel);
client.channels.sendMessage(message.channel, `Draw: Nobody wins`);
} else if (w) {
end();
winner = w;
updateMsg();
@ -146,10 +150,12 @@ function awaitMessages(channel, callback) {
/**
* @param {('X'|'O'|' ')[][]} field
* @returns {null|'X'|'O'|boolean}
*/
const getWinner = field => {
let winner = null;
['X', 'O'].forEach(player => {
['X', 'O'].forEach(player => {
// Horizontal lines
for (let i = 0; i < 3; i++) {
if (field[i][0] === player &&
@ -165,10 +171,20 @@ const getWinner = field => {
}
// Diagonal lines
if ((field[0][0] === player || field[0][2] === player) &&
(field[2][0] === player || field[2][2] === player) &&
if(((field[0][0] === player && field[2][2] === player) ||
(field[2][0] === player && field[0][2] === player)) &&
(field[1][1] === player)) if (!winner) winner = player;
});
// Draw
if (!winner) {
let draw = true;
for (let i = 0; i < 3; i++)
for (let j = 0; j < 3; j++) {
if (field[i][j] === ' ') draw = false;
}
if (draw) return false;
}
return winner;
}

View File

@ -6,7 +6,7 @@ const { levels, levelups } = require('../util/levels');
module.exports.meta = {
name: 'whois',
aliases: [ 'userinfo', 'who' ],
description: 'Shows user info in JSON.'
description: 'Shows user info.'
}
/**
@ -23,7 +23,7 @@ module.exports.run = async (message, args) => new Promise(async (resolve, reject
return client.channels.sendMessage(message.channel, ':x: I can\'t find that user. Type their name or ID.');
const avatarURL = `https://api.revolt.chat/users/${target._id}/avatar`;
const { xp, level, enabled } = (levels.get(message.channel)?.[target._id] || {});
const { xp, level } = (levels.get(target._id) || {});
let msgContent = `> ## \u200b ${target.username}\n` +
`> \u200b\n` +

View File

@ -1,7 +1,12 @@
require('dotenv').config();
const Revolt = require('revolt.js');
const Enmap = require('enmap');
const { default: Logger, LogLevel } = require('log75');
const db = {
blacklist: new Enmap({ name: 'userBlacklist' }),
devs: new Enmap({ name: 'devList' })
}
const config = {
revoltClient: {
apiURL: process.env.API_URL || 'https://api.revolt.chat',
@ -10,6 +15,7 @@ const config = {
},
prefix: process.env.BOT_PREFIX || '/',
debug: process.env.NODE_ENV !== 'production',
botOwner: process.env.BOT_OWNER || null,
creds: {
// chrome debug -> Application -> IndexedDB -> localforage -> state -> auth.accounts[user ID].session
session_token: process.env.SESSION_TOKEN,
@ -40,6 +46,22 @@ client.on("message", async message => {
let command = commands.find(cmd => cmd.meta.name === commandName || cmd.meta.aliases?.indexOf(commandName) > -1);
if (!command) return;
let userDevLvl = require('./util/dev').getDevLevel(message.author);
let blacklistStatus = db.blacklist.get(message.author);
if (blacklistStatus?.blacklisted && userDevLvl <= 0) {
logger.warn(`Rejected blocked user`);
if (!blacklistStatus.silent && blacklistStatus.nextWarning < Date.now()) {
await client.channels.sendMessage(message.channel, `<@${message.author}>, you're currently not allowed to use this bot.`);
}
blacklistStatus.nextWarning = Date.now() + 1000 * 60 * 2;
db.blacklist.set(message.author, blacklistStatus);
return;
}
if (userDevLvl < (command.meta.devLevel ?? 0)) return logger.warn(`Rejected dev command`);
logger.info(`${logPrefix} -> COMMAND ${config.prefix}${commandName} ${args?.join(' ')}`)
try {
@ -65,8 +87,6 @@ client.useExistingSession(config.creds)
require('./util/levels'); // Level ups
});
module.exports.client = client;
module.exports.logger = logger;
module.exports.config = config;
module.exports = { client, logger, config, db }
const { commands } = require('./util/command_loader');

View File

@ -17,6 +17,6 @@ const { logger } = require('../index');
/**
*
* @type {Array<{ meta: { name: String, aliases: String[], description: String? }, run(import(revolt.js).Message, args: String[]): Promise }>}
* @type {Array<{ meta: { name: String, aliases: String[], description: String?, devLevel: number? }, run(import(revolt.js).Message, args: String[]): Promise }>}
*/
module.exports.commands = [];

8
util/dev.js Normal file
View File

@ -0,0 +1,8 @@
const { db, config } = require('..');
module.exports = {
getDevLevel: (user) => {
if (user === config.botOwner) return Number.POSITIVE_INFINITY;
return db.devs.get(user)?.level ?? 0
}
}

View File

@ -1,18 +1,26 @@
/**if () {
} else
/**
* Automatically accepts friend requests and
* sends requests to users messaging the bot.
* Required for users to add it to groups.
*/
const { client, logger } = require('..');
const isBlocked = require('../util/is_blocked');
client.on("packet", async packet => {
try {
if (packet.type !== 'UserRelationship') return;
if (packet.status === 'Incoming') {
// Incoming friend request
// Ignore blocked
if (isBlocked(packet.user)) {
logger.info(`Rejecting friend request from blocked user ${packet.user}`);
client.users.removeFriend(packet.user)
.catch(console.error);
return;
}
const user = await client.users.fetch(packet.user)
.catch(e => {
logger.error(`Failed to fetch author of friend request:\n${e}`);
@ -38,6 +46,12 @@ client.user.relations
.filter(r => r.status === "Incoming")
.forEach(async relationship => {
try {
if (isBlocked(relationship._id)) {
logger.info(`Rejecting friend request from blocked user ${relationship._id}`);
client.users.removeFriend(relationship._id)
.catch(console.error);
return;
}
const user = await client.users.fetch(relationship._id);
await client.users.addFriend(user.username);
logger.info(`Accepted pending friend request from ${user.username}`);

10
util/is_blocked.js Normal file
View File

@ -0,0 +1,10 @@
const { db } = require('..');
/**
*
* @param {String} user
* @returns Boolean
*/
module.exports = (user) => {
return !!db.blacklist.get(user)?.blacklisted;
}

View File

@ -1,5 +1,5 @@
const Enmap = require('enmap');
const { client, logger, config } = require('..');
const { client, logger, config, db } = require('..');
const levels = new Enmap({ name: "levels" });
let cooldowns = {}
@ -7,32 +7,35 @@ let cooldowns = {}
client.on('message', async message => {
if (message.author === client.user._id) return;
try {
if (db.blacklist.get(message.author)?.blacklisted) return;
const channel = await client.channels.fetch(message.channel);
if (channel.channel_type !== "Group") return;
let groupLevels = levels.get(channel._id);
if (!groupLevels) groupLevels = {}
if (!groupLevels[message.author]) groupLevels[message.author] = { xp: 0, level: 0, enabled: true }
let userLevels = levels.get(message.author);
if (!userLevels) userLevels = { xp: 0, level: 0 }
if (groupLevels[message.author].enabled === false) return;
if (userLevels.enabled === false) return;
// Ensure users only get XP once a minute
if (cooldowns[message.author] > Date.now()) return;
cooldowns[message.author] = Date.now() + 1000 * 60;
// Give a random amount of XP between 15 and 25
groupLevels[message.author].xp += 15 + Math.round(Math.random() * 10);
userLevels.xp += 15 + Math.round(Math.random() * 10);
let newLevel = 0;
for (const i of levelups) groupLevels[message.author].xp >= i && newLevel++;
for (const i of levelups) userLevels.xp >= i && newLevel++;
if (newLevel > groupLevels[message.author].level) {
if (newLevel > userLevels.level) {
client.channels.sendMessage(message.channel, `>GG <@${message.author}>, you just leveled up! New level: **${newLevel}**`)
.catch(console.error);
.catch(console.error)
.then(msg => setTimeout(() =>
client.channels.deleteMessage(channel._id, msg?._id).catch(console.warn), 15000));
groupLevels[message.author].level = newLevel;
userLevels.level = newLevel;
}
levels.set(channel._id, groupLevels);
levels.set(message.author, userLevels);
} catch (e) {
console.error(e);
}
@ -43,7 +46,7 @@ client.on('message', async message => {
const levelups = [ 30 ];
while (levelups.length < 250) {
let i = levelups[levelups.length - 1];
levelups.push(Math.round(i * (i > 50000 ? 0.02 : (i < 100 ? 1.1 : 1.05))));
levelups.push(Math.round(i * (i > 50000 ? 0.05 : (i < 100 ? 1.3 : 1.1))));
}
for (const i in levelups) levelups[i] += levelups[i - 1] || 0;