diff --git a/base/JaBa.js b/base/JaBa.js index a6264872..a51ed6ca 100644 --- a/base/JaBa.js +++ b/base/JaBa.js @@ -1,6 +1,5 @@ const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder } = require("discord.js"), { Player } = require("discord-player-play-dl"), - { DiscordTogether } = require("../helpers/discordTogether"), { GiveawaysManager } = require("discord-giveaways"), { REST } = require("@discordjs/rest"), { Routes } = require("discord-api-types/v10"); @@ -44,8 +43,6 @@ class JaBa extends Client { this.databaseCache.usersReminds = new Collection(); this.databaseCache.mutedUsers = new Collection(); - this.discordTogether = new DiscordTogether(this); - this.player = new Player(this); playdl.getFreeClientID().then(id => playdl.setToken({ diff --git a/commands/General/activity.js b/commands/General/activity.js deleted file mode 100644 index 52370be7..00000000 --- a/commands/General/activity.js +++ /dev/null @@ -1,98 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder, PermissionsBitField } = require("discord.js"), - { defaultApplications } = require("../../helpers/discordTogether"); -const BaseCommand = require("../../base/BaseCommand"); - -class Activity extends BaseCommand { - /** - * - * @param {import("../base/JaBa")} client - */ - constructor(client) { - super({ - command: new SlashCommandBuilder() - .setName("activity") - .setDescription(client.translate("general/activity:DESCRIPTION")) - .setDMPermission(false), - aliases: [], - dirname: __dirname, - ownerOnly: false, - }); - } - /** - * - * @param {import("../../base/JaBa")} client - */ - async onLoad() { - //... - } - /** - * - * @param {import("../../base/JaBa")} client - * @param {import("discord.js").ChatInputCommandInteraction} interaction - * @param {Object} data - */ - async execute(client, interaction) { - await interaction.deferReply(); - - const voice = interaction.member.voice.channel; - if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL"); - - const perms = voice.permissionsFor(client.user); - if (!perms.has(PermissionsBitField.Flags.Connect) || !perms.has(PermissionsBitField.Flags.Speak)) return interaction.error("music/play:VOICE_CHANNEL_CONNECT"); - - const activities = defaultApplications.map(a => { - return { - label: `${a.name} ${a.premium_tier_level ? `(${interaction.translate("general/activity:BOOST_NEEDED")})` : ""}`, - value: a.id, - }; - }); - - const row = new ActionRowBuilder() - .addComponents( - new StringSelectMenuBuilder() - .setCustomId("activity_select") - .setPlaceholder(client.translate("common:NOTHING_SELECTED")) - .addOptions(activities), - ); - - await interaction.editReply({ - content: interaction.translate("general/activity:AVAILABLE_ACTIVITIES"), - components: [row], - }); - - const filter = i => i.user.id === interaction.user.id; - const collector = interaction.channel.createMessageComponentCollector({ filter, idle: (15 * 1000) }); - - collector.on("collect", async i => { - if (i.isStringSelectMenu() && i.customId === "activity_select") { - const activity = i?.values[0]; - - const invite = await client.discordTogether.createTogetherCode(voice.id, activity); - const embed = new EmbedBuilder() - .setTitle(defaultApplications.find(a => a.id === activity).name) - .setColor(client.config.embed.color) - .setDescription(`**[${interaction.translate("general/activity:CLICK_HERE", { - activity: defaultApplications.find(a => a.id === activity).name, - channel: voice.name, - })}](${invite.code})**`) - .setFooter({ - text: client.config.embed.footer, - }) - .setTimestamp(); - - await interaction.editReply({ - embeds: [embed], - components: [], - }); - } - }); - - collector.on("end", () => { - return interaction.editReply({ - components: [], - }); - }); - } -} - -module.exports = Activity; \ No newline at end of file diff --git a/commands/General/boosters.js b/commands/General/boosters.js new file mode 100644 index 00000000..ab17a269 --- /dev/null +++ b/commands/General/boosters.js @@ -0,0 +1,174 @@ +const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); +const BaseCommand = require("../../base/BaseCommand"); + +class Boosters extends BaseCommand { + /** + * + * @param {import("../base/JaBa")} client + */ + constructor(client) { + super({ + command: new SlashCommandBuilder() + .setName("boosters") + .setDescription(client.translate("general/boosters:DESCRIPTION")) + .setDMPermission(false), + aliases: [], + dirname: __dirname, + ownerOnly: false, + }); + } + /** + * + * @param {import("../../base/JaBa")} client + */ + async onLoad() { + //... + } + /** + * + * @param {import("../../base/JaBa")} client + * @param {import("discord.js").ChatInputCommandInteraction} interaction + * @param {Object} data + */ + async execute(client, interaction) { + await interaction.deferReply(); + + let currentPage = 0; + const boosters = (await interaction.guild.members.fetch()).filter(m => m.premiumSince); + const embeds = generateBoostersEmbeds(interaction, boosters); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("boosters_prev_page") + .setStyle(ButtonStyle.Primary) + .setEmoji("⬅️"), + new ButtonBuilder() + .setCustomId("boosters_next_page") + .setStyle(ButtonStyle.Primary) + .setEmoji("➡️"), + new ButtonBuilder() + .setCustomId("boosters_jump_page") + .setStyle(ButtonStyle.Secondary) + .setEmoji("↗️"), + new ButtonBuilder() + .setCustomId("boosters_stop") + .setStyle(ButtonStyle.Danger) + .setEmoji("⏹️"), + ); + + await interaction.editReply({ + content: `${interaction.translate("common:PAGE")}: **${currentPage + 1}**/**${embeds.length}**`, + embeds: [embeds[currentPage]], + components: [row], + }); + + const filter = i => i.user.id === interaction.user.id; + const collector = interaction.guild === null ? (await interaction.user.createDM()).createMessageComponentCollector({ filter, idle: (20 * 1000) }) : interaction.channel.createMessageComponentCollector({ filter, idle: (20 * 1000) }); + + collector.on("collect", async i => { + if (i.isButton()) { + if (i.customId === "boosters_prev_page") { + i.deferUpdate(); + + if (currentPage !== 0) { + --currentPage; + interaction.editReply({ + content: `${interaction.translate("common:PAGE")}: **${currentPage + 1}**/**${embeds.length}**`, + embeds: [embeds[currentPage]], + components: [row], + }); + } + } else if (i.customId === "boosters_next_page") { + i.deferUpdate(); + + if (currentPage < embeds.length - 1) { + currentPage++; + interaction.editReply({ + content: `${interaction.translate("common:PAGE")}: **${currentPage + 1}**/**${embeds.length}**`, + embeds: [embeds[currentPage]], + components: [row], + }); + } + } else if (i.customId === "boosters_jump_page") { + i.deferUpdate(); + + const msg = await interaction.followUp({ + content: interaction.translate("misc:JUMP_TO_PAGE", { + length: embeds.length, + }), + fetchReply: true, + }); + + const filter = res => { + return res.author.id === interaction.user.id && !isNaN(res.content); + }; + + interaction.channel.awaitMessages({ filter, max: 1, time: (10 * 1000) }).then(collected => { + if (embeds[collected.first().content - 1]) { + currentPage = collected.first().content - 1; + interaction.editReply({ + content: `${interaction.translate("common:PAGE")}: **${currentPage + 1}**/**${embeds.length}**`, + embeds: [embeds[currentPage]], + components: [row], + }); + + if (collected.first().deletable) collected.first().delete(); + if (msg.deletable) msg.delete(); + } else { + if (collected.first().deletable) collected.first().delete(); + if (msg.deletable) msg.delete(); + return; + } + }); + } else if (i.customId === "boosters_stop") { + i.deferUpdate(); + collector.stop(); + } + } + }); + + collector.on("end", () => { + row.components.forEach(component => { + component.setDisabled(true); + }); + + return interaction.editReply({ + components: [row], + }); + }); + } +} + +/** + * + * @param {import("discord.js").ChatInputCommandInteraction} interaction + * @param {Array} boosters + * @returns + */ +function generateBoostersEmbeds(interaction, boosters) { + const embeds = []; + let k = 10; + + for (let i = 0; i < boosters.size; i += 10) { + const current = boosters.sort((a, b) => b.premiumSinceTimestamp - a.premiumSinceTimestamp).map(g => g).slice(i, k); + let j = i; + k += 10; + + const info = current.map(member => `${++j}. ${member.toString()} | ${interaction.translate("general/boosters:BOOSTER_SINCE")}: **${interaction.client.printDate(member.premiumSince, null, interaction.guild.data.locale)}**`).join("\n"); + + const embed = new EmbedBuilder() + .setColor(interaction.client.config.embed.color) + .setFooter({ + text: interaction.client.config.embed.footer, + }) + .setTitle(interaction.translate("general/boosters:BOOSTERS_LIST")) + .setDescription(info) + .setTimestamp(); + embeds.push(embed); + } + + return embeds; +} + +module.exports = Boosters; \ No newline at end of file diff --git a/dashboard/public/docs/updates.md b/dashboard/public/docs/updates.md index 3d9ebe6b..508f8d57 100644 --- a/dashboard/public/docs/updates.md +++ b/dashboard/public/docs/updates.md @@ -1,3 +1,10 @@ +### JaBa v4.1.20 +* Добавлено + * Команда *boosters* - Список бустеров с сортировкой по дате буста. + +* Удалено + * Команда *activity* - Её функционал официально добавлен в Discord. + ### JaBa v4.1.19 * Изменения * Мелкие внутренние изменения. diff --git a/helpers/discordTogether.js b/helpers/discordTogether.js deleted file mode 100644 index a52cc329..00000000 --- a/helpers/discordTogether.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - Thanks to discord-together =) - List of IDs from here: https://gist.github.com/GeneralSadaf/42d91a2b6a93a7db7a39208f2d8b53ad -*/ -const fetch = require("node-fetch"); - -const defaultApplications = [ - { id: "880218394199220334", name: "Watch Together", nitro_requirement: false, premium_tier_level: 0, max_participants: -1, use: true }, - { id: "902271654783242291", name: "Sketch Heads", nitro_requirement: false, premium_tier_level: 0, max_participants: 8, use: true }, - { id: "879863976006127627", name: "Word Snacks", nitro_requirement: false, premium_tier_level: 0, max_participants: 8, use: true }, - { id: "878067389634314250", name: "Doodle Crew", nitro_requirement: false, premium_tier_level: 0, max_participants: 16, use: true }, // not in Discord Games Lab guild - { id: "755827207812677713", name: "Poker Night", nitro_requirement: false, premium_tier_level: 1, max_participants: 7, use: true }, - { id: "832012774040141894", name: "Chess In The Park", nitro_requirement: false, premium_tier_level: 1, max_participants: -1, use: true }, - { id: "879863686565621790", name: "Letter League", nitro_requirement: false, premium_tier_level: 1, max_participants: 8, use: true }, - { id: "852509694341283871", name: "SpellCast", nitro_requirement: false, premium_tier_level: 1, max_participants: 6, use: true }, - { id: "832013003968348200", name: "Checkers In The Park", nitro_requirement: false, premium_tier_level: 1, max_participants: -1, use: true }, - { id: "832025144389533716", name: "Blazing 8s", nitro_requirement: false, premium_tier_level: 1, max_participants: 8, use: true }, - { id: "945737671223947305", name: "Putt Party", nitro_requirement: false, premium_tier_level: 1, max_participants: 8, use: true }, - { id: "903769130790969345", name: "Land-io", nitro_requirement: false, premium_tier_level: 1, max_participants: 16, use: true }, - { id: "947957217959759964", name: "Bobble League", nitro_requirement: false, premium_tier_level: 1, max_participants: 8, use: true }, - { id: "976052223358406656", name: "Ask Away", nitro_requirement: false, premium_tier_level: 1, max_participants: 10, use: true }, - { id: "950505761862189096", name: "Know What I Meme", nitro_requirement: false, premium_tier_level: 1, max_participants: 8, use: true }, - - // not public - /* - { id: "773336526917861400", name: "Betrayal.io", nitro_requirement: false, premium_tier_level: 0, max_participants: null, use: false }, - { id: "814288819477020702", name: "Fishington.io", nitro_requirement: false, premium_tier_level: 0, max_participants: null, use: false }, - { id: "879864070101172255", name: "Sketchy Artist", nitro_requirement: false, premium_tier_level: 0, max_participants: 12, use: false }, - { id: "879863881349087252", name: "Awkword", nitro_requirement: false, premium_tier_level: 0, max_participants: 12, use: false }, - */ -]; - -/** - * Class symbolizing a DiscordTogether - * @template {Object.} T - */ -class DiscordTogether { - /** - * Create a new DiscordTogether - * @param {import("../base/JaBa")} client - * @param {T} applications - * @example - * const Discord = require("discord.js"); - * const client = new Discord.Client({ intents: [Discord.Intents.FLAGS.GUILDS, Discord.Intents.FLAGS.GUILD_MESSAGES] }); - * const { DiscordTogether } = require("discord-together"); - * - * client.discordTogether = new DiscordTogether(client); - * - * client.on("message", async message => { - * if (message.content === "start") { - * client.discordTogether.createTogetherCode(message.member.voice.channelID, "puttparty").then(async invite => { - * return message.channel.send(`${invite.code}`); - * }); - * }; - * }); - * - * client.login("your token"); - */ - constructor(client) { - if (!client) throw new SyntaxError("Invalid Discord.Client !"); - - /** - * Discord.Client - */ - this.client = client; - - /** - * Discord Together applications - */ - this.applications = defaultApplications; - } - - /** - * Create a Discord Together invite code (note: send the invite using markdown link) - * @param {String} voiceChannelId - * @param {keyof (defaultApplications & T)} option - * @example - * client.on("message", async message => { - * if (message.content === "start") { - * client.discordTogether.createTogetherCode(message.member.voice.channelID, "youtube").then(async invite => { - * return message.channel.send(`${invite.code}`); // Click the blue link - * }); - * }; - * }); - * @returns {Promise<{ code: String; }>} - */ - async createTogetherCode(voiceChannelId, option) { - /** - * @param {String} code The invite link (only use the blue link) - */ - const returnData = { code: "none" }; - - if (option && this.applications.find(apps => apps.id === option).id) { - const applicationID = this.applications.find(apps => apps.id === option).id; - try { - await fetch(`https://discord.com/api/v10/channels/${voiceChannelId}/invites`, { - method: "POST", - body: JSON.stringify({ - max_age: 86400, - max_uses: 0, - temporary: false, - target_type: 2, - target_application_id: applicationID, - }), - headers: { - Authorization: `Bot ${this.client.config.token}`, - "Content-Type": "application/json", - }, - }).then(res => res.json()) - .then(invite => { - if (invite.error || !invite.code) throw new Error("An error occured while retrieving data!"); - if (Number(invite.code) === 50013) console.warn("Your bot lacks permissions to perform that action"); - returnData.code = `https://discord.com/invite/${invite.code}`; - }); - } catch (err) { - throw new Error("An error occured while starting Youtube together !"); - } - return returnData; - } else { - throw new SyntaxError("Invalid option!"); - } - } -} - -module.exports = { DiscordTogether, defaultApplications }; \ No newline at end of file diff --git a/languages/en-US/README.txt b/languages/en-US/README.md similarity index 100% rename from languages/en-US/README.txt rename to languages/en-US/README.md diff --git a/languages/ru-RU/general/activity.json b/languages/ru-RU/general/activity.json deleted file mode 100644 index 179696f8..00000000 --- a/languages/ru-RU/general/activity.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "DESCRIPTION": "Создать активность в голосовом канале", - "USAGE": "", - "EXAMPLES": "activity", - "AVAILABLE_ACTIVITIES": "Доступные активности:", - "CLICK_HERE": "Нажмите сюда, чтобы начать {{activity}} в {{channel}}", - "BOOST_NEEDED": "Необходим первый уровень буста или выше!" -} \ No newline at end of file diff --git a/languages/ru-RU/general/boosters.json b/languages/ru-RU/general/boosters.json new file mode 100644 index 00000000..bde2f20c --- /dev/null +++ b/languages/ru-RU/general/boosters.json @@ -0,0 +1,7 @@ +{ + "DESCRIPTION": "Список участников, которые дали буст серверу", + "USAGE": "", + "EXAMPLES": "boosters", + "BOOSTERS_LIST": "Список бустеров", + "BOOSTER_SINCE": "Буст с" +} \ No newline at end of file diff --git a/languages/uk-UA/general/activity.json b/languages/uk-UA/general/activity.json deleted file mode 100644 index 35a3597c..00000000 --- a/languages/uk-UA/general/activity.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "DESCRIPTION": "Створити активність у голосовому каналі", - "USAGE": "", - "EXAMPLES": "activity", - "AVAILABLE_ACTIVITIES": "Доступні активності:", - "CLICK_HERE": "Натисніть сюди, щоб почати {{activity}} в {{channel}}", - "BOOST_NEEDED": "Необхідний перший рівень бусту або вище!" -} \ No newline at end of file diff --git a/languages/uk-UA/general/boosters.json b/languages/uk-UA/general/boosters.json new file mode 100644 index 00000000..bde2f20c --- /dev/null +++ b/languages/uk-UA/general/boosters.json @@ -0,0 +1,7 @@ +{ + "DESCRIPTION": "Список участников, которые дали буст серверу", + "USAGE": "", + "EXAMPLES": "boosters", + "BOOSTERS_LIST": "Список бустеров", + "BOOSTER_SINCE": "Буст с" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 04356d0f..2b7b6870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jaba", - "version": "4.1.19", + "version": "4.1.20", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "jaba", - "version": "4.1.19", + "version": "4.1.20", "license": "ISC", "dependencies": { "@discord-player/extractor": "^3.0.2", diff --git a/package.json b/package.json index 22455882..23814528 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jaba", - "version": "4.1.19", + "version": "4.1.20", "description": "My Discord Bot", "main": "index.js", "private": true,