From 32ef944a50f4bdd1a44398c7a89ed7562e9e5695 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 6 Mar 2021 15:39:00 +0100 Subject: [PATCH] pee --- commands/blacklist.js | 80 +++++++++++++++++++++++++++++++++++++++++ commands/devs.js | 53 +++++++++++++++++++++++++++ commands/help.js | 9 +++-- commands/leaderboard.js | 37 +++++++++++++++++++ commands/rank.js | 15 +++++--- commands/tictactoe.js | 38 ++++++++++++++------ commands/whois.js | 4 +-- index.js | 26 ++++++++++++-- util/command_loader.js | 2 +- util/dev.js | 8 +++++ util/friendships.js | 20 +++++++++-- util/is_blocked.js | 10 ++++++ util/levels.js | 27 +++++++------- 13 files changed, 289 insertions(+), 40 deletions(-) create mode 100644 commands/blacklist.js create mode 100644 commands/devs.js create mode 100644 commands/leaderboard.js create mode 100644 util/dev.js create mode 100644 util/is_blocked.js diff --git a/commands/blacklist.js b/commands/blacklist.js new file mode 100644 index 0000000..2c022e8 --- /dev/null +++ b/commands/blacklist.js @@ -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); +}); \ No newline at end of file diff --git a/commands/devs.js b/commands/devs.js new file mode 100644 index 0000000..27bb5ac --- /dev/null +++ b/commands/devs.js @@ -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); +}); \ No newline at end of file diff --git a/commands/help.js b/commands/help.js index fdb1321..9e7bed3 100644 --- a/commands/help.js +++ b/commands/help.js @@ -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); }); \ No newline at end of file diff --git a/commands/leaderboard.js b/commands/leaderboard.js new file mode 100644 index 0000000..64be387 --- /dev/null +++ b/commands/leaderboard.js @@ -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); +}); \ No newline at end of file diff --git a/commands/rank.js b/commands/rank.js index 8bf1f38..81aeeb5 100644 --- a/commands/rank.js +++ b/commands/rank.js @@ -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); diff --git a/commands/tictactoe.js b/commands/tictactoe.js index 1713960..c9d39ad 100644 --- a/commands/tictactoe.js +++ b/commands/tictactoe.js @@ -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; } diff --git a/commands/whois.js b/commands/whois.js index 36f961b..e0a825d 100644 --- a/commands/whois.js +++ b/commands/whois.js @@ -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` + diff --git a/index.js b/index.js index 4746981..a8ac964 100644 --- a/index.js +++ b/index.js @@ -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'); diff --git a/util/command_loader.js b/util/command_loader.js index 8dcb48c..4da86fc 100644 --- a/util/command_loader.js +++ b/util/command_loader.js @@ -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 = []; \ No newline at end of file diff --git a/util/dev.js b/util/dev.js new file mode 100644 index 0000000..1447cee --- /dev/null +++ b/util/dev.js @@ -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 + } +} \ No newline at end of file diff --git a/util/friendships.js b/util/friendships.js index b3a02ae..8c3795a 100644 --- a/util/friendships.js +++ b/util/friendships.js @@ -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}`); diff --git a/util/is_blocked.js b/util/is_blocked.js new file mode 100644 index 0000000..865ae9e --- /dev/null +++ b/util/is_blocked.js @@ -0,0 +1,10 @@ +const { db } = require('..'); + +/** + * + * @param {String} user + * @returns Boolean + */ +module.exports = (user) => { + return !!db.blacklist.get(user)?.blacklisted; +} \ No newline at end of file diff --git a/util/levels.js b/util/levels.js index a7997ac..8552e5e 100644 --- a/util/levels.js +++ b/util/levels.js @@ -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;