├── db └── page.txt ├── .gitignore ├── helpers ├── utils.js ├── config.js ├── eventSystem.js ├── commandSystem.js └── database.js ├── package.json ├── events ├── ready.js └── interactionCreate.js ├── components ├── selectMenus.js ├── modals.js ├── buttons.js ├── textInputs.js ├── rows.js └── embeds.js ├── index.js ├── LICENSE ├── commands ├── destek-sistemi-sıfırla.js ├── destek-sistemi.js └── destek-ayarla.js └── README.md /db/page.txt: -------------------------------------------------------------------------------- 1 | Klasör boş kalmasın diye bir dosya :)) 2 | Dosyayı güvenle silebilirsiniz! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Removing personal files 2 | 3 | config.json 4 | db/servers.json 5 | 6 | # Removing extra files 7 | 8 | node_modules/ 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /helpers/utils.js: -------------------------------------------------------------------------------- 1 | function helperError(error) { 2 | console.error("[Önemli] Bir hata oluştu! Yetkililere ulaşırsanız yardımcı olabilirler:", error); 3 | } 4 | 5 | module.exports = { 6 | helperError, 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ticket-form-bot", 3 | "version": "1.0.0", 4 | "description": "Discord.js v14 ile tasarlanan formlu destek botu.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node ." 8 | }, 9 | "author": "Wyntine", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@types/node": "^18.15.10", 13 | "discord.js": "^14.8.0", 14 | "wio.db": "^4.0.22" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /events/ready.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { Client } = require("discord.js"); 4 | const { getCommands } = require("../helpers/commandSystem"); 5 | 6 | /** @param {Client} client */ 7 | module.exports = async (client) => { 8 | try { 9 | await client.application.commands.set(getCommands("slash")); 10 | } catch (error) { 11 | console.error(error); 12 | } 13 | console.log(`${client.user.tag} Aktif!`); 14 | }; 15 | -------------------------------------------------------------------------------- /helpers/config.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, writeFileSync } = require("fs"); 2 | 3 | function checkConfig() { 4 | const configFile = readFileSync("./config.json", { encoding: "utf-8", flag: "a+" }); 5 | if (!configFile.length) { 6 | writeFileSync( 7 | "./config.json", 8 | JSON.stringify({ TOKEN: "Botunuzun tokenini buraya girin!" }, undefined, 2) 9 | ); 10 | console.warn("[CONFIG] Config dosyası sıfırdan oluşturuldu. Lütfen ayarları doldurunuz."); 11 | return false; 12 | } 13 | return true; 14 | } 15 | 16 | module.exports = { 17 | checkConfig, 18 | }; 19 | -------------------------------------------------------------------------------- /helpers/eventSystem.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { Client } = require("discord.js"); 4 | const { readdirSync } = require("fs"); 5 | 6 | let events = []; 7 | 8 | /** 9 | * @param {Client} client 10 | */ 11 | function loadEvents(client) { 12 | events = []; 13 | readdirSync("./events").forEach((e) => { 14 | const eve = require(`../events/${e}`); 15 | const name = e.split(".")[0]; 16 | client.on(name, (...args) => eve(...args)); 17 | console.log(`[EVENT] ${name} eventi yüklendi.`); 18 | }); 19 | } 20 | 21 | module.exports = { 22 | events, 23 | loadEvents, 24 | }; 25 | -------------------------------------------------------------------------------- /components/selectMenus.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { StringSelectMenuBuilder } = require("discord.js"); 4 | 5 | const destekFormSelect = new StringSelectMenuBuilder() 6 | .setCustomId("del") 7 | .setPlaceholder("Bilet Menüsü!") 8 | .addOptions([ 9 | { 10 | label: "Destek Sil", 11 | description: "Destek Kanalını silersin", 12 | value: "delete", 13 | }, 14 | { 15 | label: "Üye Ekle/Üye Çıkar", 16 | description: "Destek talebine üye ekleyip çıkarırsın.", 17 | value: "panel", 18 | }, 19 | ]); 20 | 21 | module.exports = { 22 | destekFormSelect, 23 | }; 24 | -------------------------------------------------------------------------------- /components/modals.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { ModalBuilder } = require("@discordjs/builders"); 4 | const { üyeÇıkarRow, üyeEkleRow, destekSebepRow } = require("./rows"); 5 | 6 | const destekModal = new ModalBuilder() 7 | .setCustomId("form") 8 | .setTitle("Destek Sistemi!") 9 | .addComponents(destekSebepRow); 10 | const üyeEkleModal = new ModalBuilder() 11 | .setCustomId("eklemenu") 12 | .setTitle("Destek Sistemi") 13 | .addComponents(üyeEkleRow); 14 | const üyeÇıkarModal = new ModalBuilder() 15 | .setCustomId("eklemenu2") 16 | .setTitle("Destek Sistemi") 17 | .addComponents(üyeÇıkarRow); 18 | 19 | module.exports = { 20 | destekModal, 21 | üyeEkleModal, 22 | üyeÇıkarModal, 23 | }; 24 | -------------------------------------------------------------------------------- /components/buttons.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { ButtonBuilder, ButtonStyle } = require("discord.js"); 4 | 5 | /** 6 | * @param {string} label 7 | */ 8 | function ticketButton(label) { 9 | return new ButtonBuilder().setLabel(label).setStyle(ButtonStyle.Primary).setCustomId("ticket"); 10 | } 11 | 12 | const destekEkleButon = new ButtonBuilder() 13 | .setLabel("Ekle") 14 | .setStyle(ButtonStyle.Success) 15 | .setCustomId("ekle"); 16 | const destekÇıkarButon = new ButtonBuilder() 17 | .setLabel("Çıkar") 18 | .setStyle(ButtonStyle.Danger) 19 | .setCustomId("çıkar"); 20 | const destekSilButon = new ButtonBuilder() 21 | .setLabel("Sil") 22 | .setStyle(ButtonStyle.Secondary) 23 | .setCustomId("sil"); 24 | 25 | module.exports = { 26 | ticketButton, 27 | destekEkleButon, 28 | destekÇıkarButon, 29 | destekSilButon, 30 | }; 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { Client, GatewayIntentBits, Partials } = require("discord.js"); 4 | const { helperError } = require("./helpers/utils"); 5 | const { checkConfig } = require("./helpers/config"); 6 | 7 | process.on("uncaughtException", helperError).on("unhandledRejection", helperError); 8 | 9 | const checked = checkConfig(); 10 | if (!checked) process.exit(0); 11 | 12 | const INTENTS = Object.values(GatewayIntentBits); 13 | const PARTIALS = Object.values(Partials); 14 | 15 | const { TOKEN } = require("./config.json"); 16 | const { loadCommands } = require("./helpers/commandSystem"); 17 | const { loadEvents } = require("./helpers/eventSystem"); 18 | 19 | const client = new Client({ 20 | intents: INTENTS, 21 | allowedMentions: { parse: ["users"] }, 22 | partials: PARTIALS, 23 | retryLimit: 3, 24 | }); 25 | 26 | loadCommands(); 27 | loadEvents(client); 28 | 29 | client.login(TOKEN); 30 | -------------------------------------------------------------------------------- /components/textInputs.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { TextInputBuilder, TextInputStyle } = require("discord.js"); 4 | 5 | const destekSebepInput = new TextInputBuilder() 6 | .setCustomId("sebep") 7 | .setLabel("Destek açma sebebiniz nedir?") 8 | .setStyle(TextInputStyle.Paragraph) 9 | .setMinLength(2) 10 | .setPlaceholder("Bir sebep gir.") 11 | .setRequired(true); 12 | 13 | const üyeEkleInput = new TextInputBuilder() 14 | .setCustomId("uyeid") 15 | .setLabel("Kullanıcı ID") 16 | .setStyle(TextInputStyle.Paragraph) 17 | .setMinLength(10) 18 | .setPlaceholder("Eklemek istediğiniz kullanıcının id girin.") 19 | .setRequired(true); 20 | 21 | const üyeÇıkarInput = new TextInputBuilder() 22 | .setCustomId("cikarid") 23 | .setLabel("Kullanıcı ID") 24 | .setStyle(TextInputStyle.Paragraph) 25 | .setMinLength(10) 26 | .setPlaceholder("Çıkarmak istediğiniz kullanıcı ID girin.") 27 | .setRequired(true); 28 | 29 | module.exports = { 30 | destekSebepInput, 31 | üyeEkleInput, 32 | üyeÇıkarInput, 33 | }; 34 | -------------------------------------------------------------------------------- /components/rows.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { ActionRowBuilder } = require("discord.js"); 4 | const { ticketButton, destekEkleButon, destekÇıkarButon, destekSilButon } = require("./buttons"); 5 | const { destekFormSelect } = require("./selectMenus"); 6 | const { destekSebepInput, üyeÇıkarInput, üyeEkleInput } = require("./textInputs"); 7 | 8 | const destekSebepRow = new ActionRowBuilder().addComponents(destekSebepInput); 9 | const üyeEkleRow = new ActionRowBuilder().addComponents(üyeEkleInput); 10 | const üyeÇıkarRow = new ActionRowBuilder().addComponents(üyeÇıkarInput); 11 | const destekPanelRow = new ActionRowBuilder().addComponents( 12 | destekEkleButon, 13 | destekÇıkarButon, 14 | destekSilButon 15 | ); 16 | const destekFormRow = new ActionRowBuilder().addComponents(destekFormSelect); 17 | 18 | /** 19 | * @param {string} label 20 | */ 21 | function ticketRow(label) { 22 | return new ActionRowBuilder().addComponents(ticketButton(label)); 23 | } 24 | 25 | module.exports = { 26 | destekSebepRow, 27 | üyeEkleRow, 28 | üyeÇıkarRow, 29 | destekPanelRow, 30 | destekFormRow, 31 | ticketRow, 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Wyntine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commands/destek-sistemi-sıfırla.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { PermissionsBitField, CommandInteraction } = require("discord.js"); 4 | const { 5 | yetkiYokEmbed, 6 | destekSıfırlamaBaşarı, 7 | destekRolEksik, 8 | destekKanalEksik, 9 | } = require("../components/embeds"); 10 | const { deleteTicketSystem, getTicketSystem } = require("../helpers/database"); 11 | 12 | module.exports = { 13 | name: "destek-sistemi-sıfırla", 14 | description: "Destek Sistemi sıfırlarsın.", 15 | options: [], 16 | /** 17 | * @param {CommandInteraction} interaction 18 | */ 19 | run: async (interaction) => { 20 | if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) { 21 | return interaction.reply({ embeds: [yetkiYokEmbed], ephemeral: true }); 22 | } 23 | 24 | const { log, yetkili } = getTicketSystem(interaction.guild?.id); 25 | if (!log) return interaction.reply({ embeds: [destekRolEksik], ephemeral: true }); 26 | if (!yetkili) return interaction.reply({ embeds: [destekKanalEksik], ephemeral: true }); 27 | 28 | deleteTicketSystem(interaction.guild?.id); 29 | return interaction.reply({ embeds: [destekSıfırlamaBaşarı] }); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /commands/destek-sistemi.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { PermissionsBitField, CommandInteraction } = require("discord.js"); 4 | const { 5 | destekRolEksik, 6 | destekKanalEksik, 7 | destekMesajEmbed, 8 | yetkiYokEmbed, 9 | } = require("../components/embeds"); 10 | const { ticketRow } = require("../components/rows"); 11 | const { getTicketSystem } = require("../helpers/database"); 12 | 13 | module.exports = { 14 | name: "destek-sistemi", 15 | description: "Destek Sistemi ayarlarsın.", 16 | options: [ 17 | { name: "embed-mesaj", description: "Bir embed mesaj gir.", type: 3, required: true }, 18 | { name: "buton-mesaj", description: "Bir buton mesaj gir.", type: 3, required: true }, 19 | ], 20 | /** 21 | * @param {CommandInteraction} interaction 22 | */ 23 | run: async (interaction) => { 24 | if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) 25 | return interaction.reply({ embeds: [yetkiYokEmbed], ephemeral: true }); 26 | 27 | const { log, yetkili } = getTicketSystem(interaction.guild?.id); 28 | const mesaj = interaction.options.getString("embed-mesaj"); 29 | const buton = interaction.options.getString("buton-mesaj"); 30 | 31 | if (!yetkili) return interaction.reply({ embeds: [destekRolEksik], ephemeral: true }); 32 | if (!log) return interaction.reply({ embeds: [destekKanalEksik], ephemeral: true }); 33 | 34 | return interaction.reply({ embeds: [destekMesajEmbed(mesaj)], components: [ticketRow(buton)] }); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /commands/destek-ayarla.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { PermissionsBitField, CommandInteraction } = require("discord.js"); 4 | const { 5 | yetkiYokEmbed, 6 | destekRolAyarlı, 7 | destekKanalAyarlı, 8 | destekBaşarıEmbed, 9 | } = require("../components/embeds"); 10 | const { getTicketSystem, createTicketSystem } = require("../helpers/database"); 11 | 12 | module.exports = { 13 | name: "destek-ayarla", 14 | description: "Destek Sistemi ayarlarsın.", 15 | options: [ 16 | { name: "yetkili-rol", description: "Bir yetkili rolü seç.", type: 8, required: true }, 17 | { name: "log-kanalı", description: "Bir log kanalı seç.", type: 7, required: true }, 18 | ], 19 | /** 20 | * @param {CommandInteraction} interaction 21 | */ 22 | run: async (interaction) => { 23 | if (!interaction.member?.permissions.has(PermissionsBitField.Flags.Administrator)) 24 | return interaction.reply({ embeds: [yetkiYokEmbed], ephemeral: true }); 25 | 26 | const { log, yetkili } = getTicketSystem(interaction.guild?.id); 27 | const rol = interaction.options.getRole("yetkili-rol"); 28 | const kanal = interaction.options.getChannel("log-kanalı"); 29 | 30 | if (yetkili) return interaction.reply({ embeds: [destekRolAyarlı], ephemeral: true }); 31 | if (log) return interaction.reply({ embeds: [destekKanalAyarlı], ephemeral: true }); 32 | 33 | createTicketSystem(interaction.guild?.id, rol.id, kanal.id); 34 | return interaction.reply({ embeds: [destekBaşarıEmbed(kanal, rol)] }); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /helpers/commandSystem.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { readdirSync } = require("fs"); 4 | 5 | let commands = []; 6 | 7 | function loadCommands() { 8 | commands = []; 9 | readdirSync("./commands").forEach((f) => { 10 | if (!f.endsWith(".js")) return; 11 | const props = require(`../commands/${f}`); 12 | commands.push({ 13 | command: props, 14 | slashCommand: { 15 | name: props.name.toLowerCase(), 16 | description: props.description, 17 | options: props.options, 18 | dm_permission: props.dm_permission, 19 | type: 1, 20 | }, 21 | }); 22 | console.log(`[COMMAND] ${props.name} komutu yüklendi.`); 23 | }); 24 | } 25 | 26 | /** 27 | * @param {string} name 28 | * @param {"slash" | "command" | undefined} slashOrCommand 29 | */ 30 | function getCommand(name, slashOrCommand) { 31 | const found = commands.find(({ slashCommand }) => slashCommand.name === name); 32 | if (!found) return; 33 | return slashOrCommand === "command" 34 | ? found.command 35 | : slashOrCommand === "slash" 36 | ? found.slashCommand 37 | : found; 38 | } 39 | 40 | /** 41 | * @param {"slash" | "command" | undefined} slashOrCommand 42 | */ 43 | function getCommands(slashOrCommand = undefined) { 44 | return commands.map((cmd) => 45 | slashOrCommand === "command" ? cmd.command : slashOrCommand === "slash" ? cmd.slashCommand : cmd 46 | ); 47 | } 48 | 49 | module.exports = { 50 | commands, 51 | loadCommands, 52 | getCommand, 53 | getCommands, 54 | }; 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nasıl kurulur ve çalıştırılır? 2 | 3 | - Eğer projeyi hiç çalıştırmadıysanız ilk önce **npm i** ile paketleri kurunuz. 4 | - Sonra projeyi **npm start** ile çalıştırın ve **config.json** ayar dosyası oluşacaktır. 5 | - Oluşan ayar dosyasını tamamlayıp botunuzu **npm start** ile yeniden başlatınız. 6 | 7 | # Dosya sistemi bilgisi 8 | 9 | - Bottaki komut/etkinlik yapısı karşılaştığınız altyapılardan biraz daha farklı olabilir. 10 | - Eğer komut/etkinlik yapısı aynı değilse dosyalarımız sizin altyapınızda çalışmayacaktır. 11 | - Örnek komut: 12 | 13 | ```js 14 | const { CommandInteraction } = require("discord.js"); 15 | 16 | module.exports = { 17 | name: "ping", 18 | description: "Botun gecikmesini gösterir.", 19 | options: [], 20 | /** @param {CommandInteraction} interaction */ 21 | run: async (interaction) => { 22 | return interaction.reply(`Gecikmem ${client.ping.ws} milisaniye!`); 23 | }, 24 | }; 25 | ``` 26 | 27 | - Örnek etkinlik: 28 | 29 | ```js 30 | const { Client } = require("discord.js"); 31 | 32 | /** @param {Client} client */ 33 | module.exports = async (client) => { 34 | console.log(`${client.user.tag} aktif!`); 35 | }; 36 | ``` 37 | 38 | # Küçük notlar 39 | 40 | - Eğer kodu düzenlerken hata verirse sebebi `// @ts-check` satırıdır. 41 | 42 | > 🗝️ Bu satır, koddaki olası çakışmaları ve hataları kod çalışmadan öngörmemizi sağlar ve bot geliştirme sırasında hata riskimizi azaltır.
⚠️ Ne yaptığınızı biliyorsanız satırı silebilirsiniz! 43 | 44 | - `/* @param {...} ... */` ve `/* @type {...} */` satırları da neyin nesi? 45 | > 🗝️ Bu tür fonksiyon parametre türü (**@param**) ve değişken türü (**@type**) belirten satırlar kod yazmamızı kolaylaştırır ve yazdığımız koddan emin olmamızı sağlar.
⚠️ Gereksiz görüyorsanız satırı silebilirsiniz! 46 | 47 | # Bir hata buldum! 48 | 49 | - 🐜 Eğer bir hata bulduysanız ve çözümünü biliyorsanız yeni istek ([pull request](https://github.com/Wyntine/TicketFormBot/compare)) açabilirsiniz! 50 | - 📱 Bana ulaşmak istiyorsanız [discord](https://discord.com/users/920360120469311578) üzerinden ulaşabilirsiniz! 51 | - 👑 Ek olarak, altyapının geliştirilmesinde büyük katkıda bulunan [sunucumuza](https://discord.gg/altyapilar) da gelebilirsiniz! 52 | 53 | ```ts 54 | const author = "Wyntine"; 55 | const goodLuck = "İyi kodlamalar!"; 56 | 57 | console.log(`✨ ${author}: ${goodLuck} ✨`); 58 | ``` 59 | -------------------------------------------------------------------------------- /helpers/database.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { JsonDatabase } = require("wio.db"); 4 | const db = new JsonDatabase({ databasePath: "./db/servers.json" }); 5 | 6 | const { 7 | TextChannel, 8 | Message, 9 | ChannelType, 10 | GuildMemberRoleManager, 11 | PermissionsBitField, 12 | } = require("discord.js"); 13 | 14 | /** 15 | * @param {string | undefined} guildId 16 | * @param {string} adminId 17 | * @param {string} logChannelId 18 | */ 19 | function createTicketSystem(guildId, adminId, logChannelId) { 20 | if (typeof guildId !== "string") return; 21 | db.set(`${guildId}.destek`, { yetkili: adminId, log: logChannelId }); 22 | } 23 | 24 | /** 25 | * @param {string | undefined} guildId 26 | * @returns {{ 27 | * log: string | undefined 28 | * yetkili: string | undefined 29 | * }} 30 | */ 31 | function getTicketSystem(guildId) { 32 | const defaultValue = { log: undefined, yetkili: undefined }; 33 | if (typeof guildId !== "string") return defaultValue; 34 | return db.get(`${guildId}.destek`) ?? defaultValue; 35 | } 36 | 37 | /** 38 | * @param {string | undefined} guildId 39 | * @returns {string | undefined} 40 | */ 41 | function getTicketSystemAdmin(guildId) { 42 | if (typeof guildId !== "string") return undefined; 43 | return db.get(`${guildId}.destek.yetkili`) ?? undefined; 44 | } 45 | 46 | /** 47 | * @param {string | undefined} guildId 48 | * @returns {string | undefined} 49 | */ 50 | function getTicketSystemLogChannel(guildId) { 51 | if (typeof guildId !== "string") return undefined; 52 | return db.get(`${guildId}.destek.log`) ?? undefined; 53 | } 54 | 55 | /** 56 | * @param {string | undefined} guildId 57 | */ 58 | function deleteTicketSystem(guildId) { 59 | if (typeof guildId !== "string") return; 60 | db.delete(`${guildId}.destek`); 61 | } 62 | 63 | /** 64 | * @param {import("discord.js").Interaction} interaction 65 | * @param {string} userId 66 | * @return {TextChannel | undefined} 67 | */ 68 | function getTicketChannel(interaction, userId) { 69 | const guild = interaction.guild; 70 | if (!guild) return; 71 | const channel = guild.channels.cache.find( 72 | (channel) => "topic" in channel && channel.topic === userId 73 | ); 74 | if (!channel || channel.type !== ChannelType.GuildText) return; 75 | return channel; 76 | } 77 | 78 | /** 79 | * @param {import("discord.js").Interaction} interaction 80 | * @returns {boolean} 81 | */ 82 | function checkTicketAdmin(interaction) { 83 | const member = interaction.member; 84 | const guild = interaction.guild; 85 | const channel = interaction.channel; 86 | 87 | if (!channel || !("topic" in channel) || !guild || !member) return false; 88 | const adminRole = getTicketSystemAdmin(guild.id); 89 | if (!adminRole) return false; 90 | 91 | /** @type {GuildMemberRoleManager} */ 92 | const memberRoles = member.roles; 93 | /** @type {Readonly} */ 94 | const memberPermissions = member.permissions; 95 | 96 | const topicCheck = channel.topic === interaction.user.id; 97 | const permissionCheck = memberPermissions.has("Administrator", true); 98 | const roleCheck = memberRoles.cache.has(adminRole); 99 | 100 | return topicCheck || permissionCheck || roleCheck; 101 | } 102 | 103 | module.exports = { 104 | db, 105 | createTicketSystem, 106 | getTicketSystem, 107 | deleteTicketSystem, 108 | getTicketSystemAdmin, 109 | getTicketSystemLogChannel, 110 | getTicketChannel, 111 | checkTicketAdmin, 112 | }; 113 | -------------------------------------------------------------------------------- /components/embeds.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { EmbedBuilder, Colors } = require("discord.js"); 4 | 5 | const yetkiYokEmbed = new EmbedBuilder() 6 | .setColor(Colors.Red) 7 | .setDescription("Bu komutu kullanabilmek için `Yönetici` yetkisine sahip olmalısın."); 8 | 9 | const destekSıfırlamaBaşarı = new EmbedBuilder() 10 | .setDescription(`Destek sistemi başarıyla sıfırlandı.`) 11 | .setColor(Colors.Green); 12 | 13 | const destekRolEksik = new EmbedBuilder() 14 | .setColor(Colors.Red) 15 | .setDescription("Destek yetkili rolü ayarlanmamış."); 16 | const destekKanalEksik = new EmbedBuilder() 17 | .setColor(Colors.Red) 18 | .setDescription("Destek log kanalı ayarlanmamış."); 19 | 20 | const destekRolAyarlı = new EmbedBuilder() 21 | .setColor(Colors.Red) 22 | .setDescription("Destek yetkili rolü zaten ayarlanmış."); 23 | const destekKanalAyarlı = new EmbedBuilder() 24 | .setColor(Colors.Red) 25 | .setDescription("Destek log kanalı zaten ayarlanmış."); 26 | 27 | /** 28 | * @param {any} kanal 29 | * @param {any} rol 30 | */ 31 | function destekBaşarıEmbed(kanal, rol) { 32 | return new EmbedBuilder() 33 | .setDescription( 34 | `Destek Sistemi başarıyla ayarlandı.\n\nDestek Log: ${kanal}\nDestek Yetkili: ${rol}` 35 | ) 36 | .setColor(Colors.Green); 37 | } 38 | 39 | /** 40 | * @param {string | null} mesaj 41 | */ 42 | function destekMesajEmbed(mesaj) { 43 | return new EmbedBuilder().setTitle("Destek Sistemi").setDescription(mesaj).setColor(Colors.Green); 44 | } 45 | 46 | const açıkTalepVar = new EmbedBuilder() 47 | .setColor(Colors.Red) 48 | .setDescription("Zaten açık bir talebiniz var."); 49 | 50 | /** 51 | * @param {string} userId 52 | * @param {string} channelName 53 | */ 54 | function talepSilindiLogEmbed(userId, channelName) { 55 | return new EmbedBuilder() 56 | .setTitle("Destek Sistemi") 57 | .setDescription(`Bir destek talebi silindi.`) 58 | .addFields( 59 | { name: `Talep Kapatan:`, value: `<@${userId}>`, inline: true }, 60 | { name: `Kapatılan Talep:`, value: `**${channelName}**`, inline: true } 61 | ); 62 | } 63 | 64 | /** 65 | * @param {string} userTag 66 | * @param {string} reason 67 | */ 68 | function talepOluşturulduLogEmbed(userTag, reason) { 69 | return new EmbedBuilder() 70 | .setTitle("Destek Sistemi") 71 | .setDescription( 72 | `**${userTag}** adlı kullanıcı **${reason}** nedeniyle bir destek talebi oluşturdu.` 73 | ) 74 | .setColor(Colors.Green); 75 | } 76 | 77 | /** 78 | * @param {string} userId 79 | */ 80 | function talepEklendiEmbed(userId) { 81 | return new EmbedBuilder() 82 | .setTitle("Destek Sistemi") 83 | .setDescription(`<@${userId}> adlı kullanıcı destek talebine eklendi.`); 84 | } 85 | 86 | /** 87 | * @param {string} userId 88 | */ 89 | function talepAtıldıEmbed(userId) { 90 | return new EmbedBuilder() 91 | .setTitle("Destek Sistemi") 92 | .setDescription(`<@${userId}> adlı kullanıcı destek talebinden atıldı.`); 93 | } 94 | 95 | const destekPanelEmbed = new EmbedBuilder() 96 | .setTitle("Kullanıcı Paneli") 97 | .setDescription("Aşağıdaki butonlardan üye ekleyebilir veya çıkarabilirsin!") 98 | .setColor("Random"); 99 | 100 | const sahipDeğilEmbed = new EmbedBuilder() 101 | .setColor(Colors.Red) 102 | .setDescription(`🛡️ Bu talebin sahibi veya talep yetkilisi değilsiniz.`); 103 | 104 | module.exports = { 105 | yetkiYokEmbed, 106 | destekSıfırlamaBaşarı, 107 | destekRolEksik, 108 | destekKanalEksik, 109 | destekRolAyarlı, 110 | destekKanalAyarlı, 111 | destekBaşarıEmbed, 112 | destekMesajEmbed, 113 | açıkTalepVar, 114 | talepSilindiLogEmbed, 115 | talepOluşturulduLogEmbed, 116 | talepEklendiEmbed, 117 | talepAtıldıEmbed, 118 | destekPanelEmbed, 119 | sahipDeğilEmbed, 120 | }; 121 | -------------------------------------------------------------------------------- /events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { PermissionsBitField, ChannelType } = require("discord.js"); 4 | const { 5 | açıkTalepVar, 6 | talepSilindiLogEmbed, 7 | talepOluşturulduLogEmbed, 8 | talepEklendiEmbed, 9 | talepAtıldıEmbed, 10 | destekPanelEmbed, 11 | sahipDeğilEmbed, 12 | } = require("../components/embeds"); 13 | const { destekModal, üyeEkleModal, üyeÇıkarModal } = require("../components/modals"); 14 | const { destekPanelRow, destekFormRow } = require("../components/rows"); 15 | const { getCommand } = require("../helpers/commandSystem"); 16 | const { 17 | getTicketSystemLogChannel, 18 | getTicketSystemAdmin, 19 | checkTicketAdmin, 20 | } = require("../helpers/database"); 21 | 22 | /** 23 | * @param {import("discord.js").Interaction} interaction 24 | */ 25 | module.exports = async (interaction) => { 26 | const channel = interaction.channel; 27 | const guild = interaction.guild; 28 | const user = interaction.user; 29 | const client = interaction.client; 30 | 31 | if (!guild || !channel || channel.isDMBased() || channel.isVoiceBased() || channel.isThread()) 32 | return; 33 | 34 | if (interaction.isChatInputCommand()) { 35 | const command = getCommand(interaction.commandName, "command"); 36 | if (command) command.run(interaction); 37 | } else if (interaction.isButton()) { 38 | switch (interaction.customId) { 39 | case "ticket": { 40 | return await interaction.showModal(destekModal); 41 | } 42 | case "ekle": { 43 | if (!checkTicketAdmin(interaction)) 44 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 45 | return await interaction.showModal(üyeEkleModal); 46 | } 47 | case "çıkar": { 48 | if (!checkTicketAdmin(interaction)) 49 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 50 | return await interaction.showModal(üyeÇıkarModal); 51 | } 52 | case "sil": { 53 | if (!checkTicketAdmin(interaction)) 54 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 55 | const log = getTicketSystemLogChannel(guild.id); 56 | channel.delete(); 57 | return client.channels.cache 58 | .get(`${log}`) 59 | ?.send({ embeds: [talepSilindiLogEmbed(user.id, channel.name)] }); 60 | } 61 | } 62 | } else if (interaction.isModalSubmit()) { 63 | switch (interaction.customId) { 64 | case "form": { 65 | const sebep = interaction.fields.getTextInputValue("sebep"); 66 | const yetkili = getTicketSystemAdmin(guild.id); 67 | const roleStaff = guild.roles.cache.get(yetkili); 68 | const talepKanal = guild.channels.cache.find((c) => "topic" in c && c.topic === user.id); 69 | if (talepKanal) return interaction.reply({ embeds: [açıkTalepVar], ephemeral: true }); 70 | 71 | const overwrites = [ 72 | { id: guild.id, deny: [PermissionsBitField.Flags.ViewChannel] }, 73 | { id: user.id, allow: [PermissionsBitField.Flags.ViewChannel] }, 74 | ]; 75 | 76 | if (roleStaff) 77 | overwrites.push({ id: roleStaff.id, allow: [PermissionsBitField.Flags.ViewChannel] }); 78 | const yeniKanal = await guild.channels.create({ 79 | name: `destek-${user.username}`, 80 | type: ChannelType.GuildText, 81 | permissionOverwrites: overwrites, 82 | topic: user.id, 83 | }); 84 | 85 | await yeniKanal.send({ 86 | embeds: [talepOluşturulduLogEmbed(user.tag, sebep)], 87 | content: roleStaff ? `${roleStaff.toString()} | ${user.toString()}` : user.toString(), 88 | components: [destekFormRow], 89 | }); 90 | return interaction.reply({ 91 | content: `Biletiniz başarıyla açıldı. <#${yeniKanal.id}>`, 92 | ephemeral: true, 93 | }); 94 | } 95 | case "eklemenu": { 96 | if (!checkTicketAdmin(interaction)) 97 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 98 | const id = interaction.fields.getTextInputValue("uyeid"); 99 | channel.permissionOverwrites.create(id, { ViewChannel: true }); 100 | return interaction.reply({ embeds: [talepEklendiEmbed(id)] }); 101 | } 102 | case "eklemenu2": { 103 | if (!checkTicketAdmin(interaction)) 104 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 105 | const id = interaction.fields.getTextInputValue("cikarid"); 106 | channel.permissionOverwrites.create(id, { ViewChannel: false }); 107 | return interaction.reply({ embeds: [talepAtıldıEmbed(id)] }); 108 | } 109 | } 110 | } else if (interaction.isAnySelectMenu()) { 111 | switch (interaction.customId) { 112 | case "del": { 113 | switch (interaction.values[0]) { 114 | case "delete": { 115 | if (!checkTicketAdmin(interaction)) 116 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 117 | const log = getTicketSystemLogChannel(guild.id); 118 | channel.delete(); 119 | return client.channels.cache 120 | .get(`${log}`) 121 | ?.send({ embeds: [talepSilindiLogEmbed(user.id, channel.name)] }); 122 | } 123 | case "panel": { 124 | if (!checkTicketAdmin(interaction)) 125 | return interaction.reply({ embeds: [sahipDeğilEmbed], ephemeral: true }); 126 | await interaction.deferUpdate(); 127 | const message = await channel.messages.fetch(interaction.message.id); 128 | return message.edit({ embeds: [destekPanelEmbed], components: [destekPanelRow] }); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | }; 135 | --------------------------------------------------------------------------------