├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── workflows │ └── node.js.yml ├── LICENSE ├── README.md ├── commands ├── create.js ├── drop.js ├── edit.js ├── end.js ├── help.js ├── invite.js ├── list.js ├── ping.js ├── reroll.js └── start.js ├── config.json ├── events ├── discord │ ├── interactionCreate.js │ ├── messageCreate.js │ └── ready.js └── giveaways │ ├── endedGiveawayReactionAdded.js │ ├── giveawayEnded.js │ ├── giveawayReactionAdded.js │ ├── giveawayReactionRemoved.js │ └── giveawayRerolled.js ├── index.js ├── package.json ├── slash ├── drop.js ├── edit.js ├── end.js ├── help.js ├── invite.js ├── list.js ├── pause.js ├── ping.js ├── reroll.js ├── resume.js └── start.js ├── storage └── giveaways.json └── utils ├── message.js └── slashsync.js /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [Discord](https://discord.gg/ARu4hr6hJw). 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 119 | 120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 121 | enforcement ladder](https://github.com/mozilla/diversity). 122 | 123 | [homepage]: https://www.contributor-covenant.org 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | https://www.contributor-covenant.org/faq. Translations are available at 127 | https://www.contributor-covenant.org/translations. 128 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Workflow 4 | 5 | 1. Fork and clone this repository. 6 | 2. Create a new branch in your fork based off the **main** branch. 7 | 3. Make your changes. 8 | 4. Commit your changes, and push them. 9 | 5. Submit a Pull Request [here]! 10 | 11 | ## Contributing to the code 12 | 13 | **The issue tracker is only for issue reporting or proposals/suggestions. If you have a question, you can ask the team in our [Discord Server][discord server]**. 14 | 15 | To contribute to this repository, feel free to create a new fork of the repository and 16 | submit a pull request. 17 | 18 | ### Commit Guidelines 19 | 20 | - Follow the [Commit Convention][commit convention]. 21 | - Download and use the VSCode Extension [Commitizen](commitizen) 22 | 23 | ### SloshDB Project Concept Guidelines 24 | 25 | There are a number of guidelines considered when reviewing Pull Requests to be merged. _This is by no means an exhaustive list, but here are some things to consider before/while submitting your ideas._ 26 | 27 | - Everything in SloshDB Project should be generally useful for the majority of users. Don't let that stop you if you've got a good concept though, as your idea still might be a great addition. 28 | - Everything should follow [OOP paradigms][oop paradigms] and generally rely on behaviour over state where possible. This generally helps methods be predictable, keeps the codebase simple and understandable, reduces code duplication through abstraction, and leads to efficiency and therefore scalability. 29 | 30 | 31 | 32 | 33 | 34 | [vscode]: https://code.visualstudio.com 35 | 36 | 37 | [commit convention]: https://www.conventionalcommits.org/en/v1.0.0/ 38 | [discord server]: https://discord.gg/ARu4hr6hJw 39 | [node.js]: https://nodejs.org/en/download/ 40 | [here]: https://github.com/ZeroDiscord/Giveaway/pulls 41 | [oop paradigms]: https://en.wikipedia.org/wiki/Object-oriented_programming 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the bot 4 | title: "[ BUG ]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Do the '...' command. 16 | 2. Under '....' condition. 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Discord Build [e.g. Ptb 103871 (e0f6e26) Host 1.0.1010 Windows 11 64-Bit ] 35 | - Browser [e.g. stock browser, safari] 36 | - Version [e.g. 22] 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this bot 4 | title: "[ Feature Request ] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Please describe the changes this PR makes and why it should be merged:** 2 | 3 | 10 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm install 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GiveawayBot™ 2 | [![forthebadge](https://forthebadge.com/images/badges/made-with-javascript.svg)](https://forthebadge.com) 3 | 4 | ### A Discord Giveaway bot written in Discord.js to create & enjoy Feature rich and Seamless Giveaways within your very own Discord guild! 5 | 6 | ## Links 7 | - ### This Giveaway Bot Was Created by [ZeroSync](https://youtube.com/c/ZeroSync/) 8 | - [Youtube Channel](https://www.youtube.com/c/ZeroSync) 9 | - [Support Server Link](https://discord.gg/ARu4hr6hJw) 10 | 11 | ## Licensed Under 12 | ### Creative Commons Zero v1.0 Universal 13 | [View the license here](https://github.com/ZeroDiscord/Giveaway/blob/master/LICENSE) 14 | #### Copyright 2021 © All Rights are Reserved 15 | 16 | # Contributions 17 | 18 | All contributions are welcomed, it is recommended to create an issue or reply in a comment of an existing issue to let us know what you are working on first, that way we do not overwrite each other. 19 | 20 | - Please read [contributing guide](.github/CONTRIBUTING.md) for details on this project. 21 | - Please respect the [pull request template](.github/PULL_REQUEST_TEMPLATE/pull_request_template.md) while submiting a pull request. 22 | 23 | # Code of Conduct 24 | 25 | Please read [code of conduct](.github/CODE_OF_CONDUCT.md) for details on our code of conduct. 26 | 27 | [![forthebadge](https://forthebadge.com/images/badges/it-works-why.svg)](https://forthebadge.com) 28 | 29 | # You can run the bot in just a few steps! Let me show you how: 30 | ## Hosting 31 | > ⚠ This bot needs a [Node.js v16.9+](https://nodejs.org/en/blog/release/v16.9.0/) runtime to function since discord.js version 14 requires said node version to function. 32 | 33 | ### [Host On Repl.it](https://repl.it/github/ZeroDiscord/Giveaway) 34 | ### [Remix On Glitch](https://glitch.com/edit/#!/import/github/ZeroDiscord/Giveaway) 35 | 36 | **Aliter** 37 | 38 | ### Step 1: Install the Dependencies: 39 | Linux 40 | ```sh 41 | wget https://nodejs.org/dist/v16.18.0/node-v16.18.0-linux-x64.tar.xz 42 | unxz node-v16.18.0-linux-x64.tar.xz 43 | tar xvf node-v16.18.0-linux-x64.tar 44 | mv node-v16.18.0-linux-x64 /usr/local/node 45 | 46 | ln /usr/local/node/bin/node /usr/bin 47 | ln /usr/local/node/bin/corepack /usr/bin 48 | ln /usr/local/node/bin/npm /usr/bin 49 | 50 | corepack enable 51 | ``` 52 | Windows 53 | ```sh 54 | # https://nodejs.org/en/blog/release/v16.9.1/ get node.js 55 | npm install 56 | ``` 57 | 58 | ### Step 2: Obtain a Bot Token From [Here](https://discord.com/developers)

59 | ### Step 3 : Replace the Token in [config.json](https://github.com/ZeroDiscord/Giveaway/blob/master/config.json)
60 | #### That's all! We Are Done! Now Simply host the Bot! 61 | 62 | ### Run with node 63 | ```sh 64 | node index.js 65 | ``` 66 | ### Run with pm2 67 | ```sh 68 | npm install -g pm2@latest 69 | pm2 start --name "Giveaway" index.js --watch 70 | ``` 71 | 72 | # Features 73 | ## Featuring | Slash Commands 74 | ### Interactive Giveaway Creation 75 | ### Featured ✨ Bonus Entries 76 | ### And Lots More! 77 | - Direct message when the server mentioned for joining is not joined 78 | - Direct message when the server mentioned for joining is joined 79 | - Direct Message When User Reacts on an ended giveaway 80 | - Direct Message User On Removing Reaction 81 | - Direct Message Winner On Winning 82 | -------------------------------------------------------------------------------- /commands/create.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js'), 2 | { EmbedBuilder } = Discord, 3 | parsec = require('parsec'), 4 | messages = require('../utils/message'); 5 | 6 | module.exports.run = async (client, message) => { 7 | // If the member doesn't have enough permissions 8 | if ( 9 | !message.member.permissions.has("ManageMessages") && 10 | !message.member.roles.cache.some(r => r.name === "Giveaways") 11 | ) { 12 | return message.reply( 13 | ":x: You need to have the manage messages permissions to start giveaways." 14 | ); 15 | } 16 | 17 | const collector = message.channel.createMessageCollector({ 18 | filter: (m) => m.author.id === message.author.id, 19 | time: 60000, 20 | }); 21 | 22 | let xembed = new EmbedBuilder() 23 | .setTitle("Oops! Looks Like We Met A Timeout! 🕖") 24 | .setColor("#FF0000") 25 | .setDescription('💥 Snap our luck!\nYou took too much time to decide!\nUse ``create`` again to start a new giveaway!\nTry to respond within **30 seconds** this time!') 26 | .setFooter({ 27 | text: `${client.user.username}`, 28 | iconURL: client.user.displayAvatarURL() 29 | }) 30 | .setTimestamp() 31 | 32 | 33 | function waitingEmbed(title, desc) { 34 | return message.channel.send({ 35 | embeds: [ 36 | new EmbedBuilder() 37 | .setAuthor({ 38 | name: `${message.author.tag} + ' | Giveaway Setup'`, 39 | iconURL: message.member.displayAvatarURL() 40 | }) 41 | .setTitle('Giveaway ' + title) 42 | .setDescription(desc + ' within the next 60 seconds.') 43 | .setFooter({ 44 | text: "Type 'cancel' to exit this process.", 45 | iconURL: client.user.displayAvatarURL() 46 | }) 47 | .setTimestamp() 48 | .setColor('#2F3136'), 49 | ], 50 | 51 | }); 52 | } 53 | 54 | let winnerCount, channel, duration, prize, cancelled; 55 | 56 | await waitingEmbed('Prize', 'Please send the giveaway prize'); 57 | 58 | collector.on('collect', async (m) => { 59 | if (cancelled) return; 60 | 61 | async function failed(options, ...cancel) { 62 | if (typeof cancel[0] === 'boolean') 63 | (cancelled = true) && (await m.reply(options)); 64 | else { 65 | await m.reply( 66 | options instanceof EmbedBuilder ? { embeds: [options] } : options 67 | ); 68 | return await waitingEmbed(...cancel); 69 | } 70 | } 71 | 72 | if (m.content === 'cancel'){ 73 | collector.stop() 74 | return await failed('Cancelled Giveaway Creation.', true) 75 | } 76 | 77 | switch (true) { 78 | case !prize: { 79 | if (m.content.length > 256) 80 | return await failed( 81 | 'The prize can not be more than 256 characters.', 82 | 'Prize', 83 | 'Please send the giveaway prize' 84 | ); 85 | else { 86 | prize = m.content; 87 | await waitingEmbed('Channel', 'Please send the giveaway channel'); 88 | } 89 | 90 | break; 91 | } 92 | 93 | case !channel: { 94 | if (!(_channel = m.mentions.channels.first() || m.guild.channels.cache.get(m.content))) 95 | return await failed( 96 | 'Please send a valid channel / channel ID.', 97 | 'Channel', 98 | 'Please send the giveaway channel' 99 | ); 100 | else if (!_channel.isTextBased()) 101 | return await failed( 102 | 'The channel must be a text channel.', 103 | 'Channel', 104 | 'Please send the giveaway channel' 105 | ); 106 | else { 107 | channel = _channel; 108 | await waitingEmbed( 109 | 'Winner Count', 110 | 'Please send the giveaway winner count.' 111 | ); 112 | } 113 | 114 | break; 115 | } 116 | 117 | case !winnerCount: { 118 | if (!(_w = parseInt(m.content))) 119 | return await failed( 120 | 'The number of winners must be an integer.', 121 | 122 | 'Winner Count', 123 | 'Please send the giveaway winner count.' 124 | ); 125 | if (_w < 1) 126 | return await failed( 127 | 'Winner count must be more than 1.', 128 | 'Winner Count', 129 | 'Please send the giveaway winner count.' 130 | ); 131 | else if (_w > 15) 132 | return await failed( 133 | 'Winner count must be less than 15.', 134 | 'Winner Count', 135 | 'Please send the giveaway winner count.' 136 | ); 137 | else { 138 | winnerCount = _w; 139 | await waitingEmbed('Duration', 'Please send the giveaway duration'); 140 | } 141 | 142 | break; 143 | } 144 | 145 | case !duration: { 146 | if (!(_d = parsec(m.content).duration)) 147 | return await failed( 148 | 'Please provide a valid duration.', 149 | 'Duration', 150 | 'Please send the giveaway duration' 151 | ); 152 | if (_d > parsec('21d').duration) 153 | return await failed( 154 | 'Duration must be less than 21 days!', 155 | 'Duration', 156 | 'Please send the giveaway duration' 157 | ); 158 | else { 159 | duration = _d; 160 | } 161 | 162 | return client.giveawaysManager.start(channel, { 163 | prize, 164 | duration, 165 | winnerCount, 166 | hostedBy: client.config.hostedBy ? message.author : null, 167 | messages, 168 | }); 169 | } 170 | } 171 | }); 172 | collector.on('end', (collected, reason) => { 173 | if (reason == 'time') { 174 | message.reply({ embeds: [xembed]}) 175 | } 176 | }) 177 | }; 178 | -------------------------------------------------------------------------------- /commands/drop.js: -------------------------------------------------------------------------------- 1 | const messages = require("../utils/message"); 2 | module.exports.run = async (client, message, args) => { 3 | // If the member doesn't have enough permissions 4 | if ( 5 | !message.member.permissions.has("ManageMessages") && 6 | !message.member.roles.cache.some(r => r.name === "Giveaways") 7 | ) { 8 | return message.reply( 9 | ":x: You need to have the manage messages permissions to start giveaways." 10 | ); 11 | } 12 | 13 | // Giveaway channel 14 | let giveawayChannel = message.mentions.channels.first(); 15 | // If no channel is mentionned 16 | if (!giveawayChannel) { 17 | return message.reply(":x: You have to mention a valid channel!"); 18 | } 19 | 20 | // Number of winners 21 | let giveawayNumberWinners = parseInt(args[1]); 22 | // If the specified number of winners is not a number 23 | if (isNaN(giveawayNumberWinners) || parseInt(giveawayNumberWinners) <= 0) { 24 | return message.reply( 25 | ":x: You have to specify a valid number of winners!" 26 | ); 27 | } 28 | 29 | // Giveaway prize 30 | let giveawayPrize = args.slice(2).join(" "); 31 | // If no prize is specified 32 | if (!giveawayPrize) { 33 | return message.reply(":x: You have to specify a valid prize!"); 34 | } 35 | // Start the giveaway 36 | await client.giveawaysManager.start(giveawayChannel, { 37 | // The giveaway prize 38 | prize: giveawayPrize, 39 | // The giveaway winner count 40 | winnerCount: parseInt(giveawayNumberWinners), 41 | // Who hosts this giveaway 42 | hostedBy: client.config.hostedBy ? message.author : null, 43 | // specify drop 44 | isDrop: true, 45 | // Messages 46 | messages 47 | }); 48 | message.reply(`Giveaway started in ${giveawayChannel}!`); 49 | } 50 | -------------------------------------------------------------------------------- /commands/edit.js: -------------------------------------------------------------------------------- 1 | module.exports.run = async (client, message) => { 2 | const Discord = require("discord.js"); 3 | const ms = require("ms"); 4 | 5 | // If the member doesn't have enough permissions 6 | if ( 7 | !message.member.permissions.has("ManageMessages") && 8 | !message.member.roles.cache.some(r => r.name === "Giveaways") 9 | ) { 10 | return message.reply( 11 | ":x: You need to have the manage messages permissions to start giveaways." 12 | ); 13 | } 14 | 15 | let time = ""; 16 | let winnersCount; 17 | let prize = ""; 18 | let giveawayx = ""; 19 | let embed = new Discord.EmbedBuilder() 20 | .setTitle("Edit A Giveaway!") 21 | .setColor('#2F3136') 22 | .setFooter({ 23 | text: `${client.user.username}`, 24 | iconURL: client.user.displayAvatarURL() 25 | }) 26 | .setTimestamp(); 27 | const msg = await message.reply({ 28 | embeds: 29 | [embed.setDescription( 30 | "Which Giveaway Would You Like To Edit?\nProvide The Giveaway Message's ID\n **Must Reply within 30 seconds!**" 31 | )] 32 | } 33 | ); 34 | let xembed = new Discord.EmbedBuilder() 35 | .setTitle("Oops! Looks Like We Met A Timeout! 🕖") 36 | .setColor("#FF0000") 37 | .setDescription('💥 Snap our luck!\nYou took too much time to decide!\nUse ``edit`` again to edit a giveaway!\nTry to respond within **30 seconds** this time!') 38 | .setFooter({ 39 | text: `${client.user.username}`, 40 | iconURL: client.user.displayAvatarURL() 41 | }) 42 | .setTimestamp(); 43 | 44 | const filter = m => m.author.id === message.author.id && !m.author.bot; 45 | const collector = await message.channel.createMessageCollector(filter, { 46 | max: 3, 47 | time: 30000 48 | }); 49 | 50 | collector.on("collect", async collect => { 51 | 52 | const response = collect.content; 53 | let gid = response; 54 | // check if the ID is valid 55 | 56 | 57 | await collect.delete() 58 | if (!client.giveawaysManager.giveaways.find((g) => g.messageID === gid)) { 59 | return msg.edit({ 60 | embeds: [ 61 | embed.setDescription( 62 | "Uh-Oh! Looks like you provided an Invalid Message ID!\n**Try Again?**\n Example: ``677813783523098627``" 63 | )] 64 | } 65 | ); 66 | } 67 | else { 68 | collector.stop( 69 | msg.edit({ 70 | embeds: [ 71 | embed.setDescription( 72 | `Alright! Next, What Would be our new time for the giveaway to be ended \n** Must Reply within 30 seconds!**` 73 | )] 74 | } 75 | ) 76 | ); 77 | } 78 | const collector2 = await message.channel.createMessageCollector(filter, { 79 | max: 3, 80 | time: 30000 81 | }); 82 | collector2.on("collect", async collect2 => { 83 | 84 | let mss = ms(collect2.content); 85 | await collect2.delete() 86 | if (!mss) { 87 | return msg.edit({ 88 | embeds: [ 89 | embed.setDescription( 90 | "Aw Snap! Looks Like You Provided Me With An Invalid Duration\n**Try Again?**\n Example: ``-10 minutes``,``-10m``,``-10``\n **Note: - (minus) Inidicates you want to reduce the time!**" 91 | )] 92 | } 93 | ); 94 | } else { 95 | time = mss; 96 | collector2.stop( 97 | msg.edit({ 98 | embeds: [ 99 | embed.setDescription( 100 | `Alright! Next, How may winners should I roll for the giveaway now?\n**Must Reply within 30 seconds.**` 101 | )] 102 | } 103 | ) 104 | ); 105 | } 106 | const collector3 = await message.channel.createMessageCollector(filter, { 107 | max: 3, 108 | time: 30000, 109 | errors: ['time'] 110 | }); 111 | collector3.on("collect", async collect3 => { 112 | 113 | const response3 = collect3.content.toLowerCase(); 114 | await collect3.delete() 115 | if (parseInt(response3) < 1 || isNaN(parseInt(response3))) { 116 | return msg.edit({ 117 | embeds: [ 118 | embed.setDescription( 119 | "Boi! Winners Must Be A Number or greater than equal to one!\n**Try Again?**\n Example ``1``,``10``, etcetra." 120 | )] 121 | } 122 | ); 123 | } else { 124 | winnersCount = parseInt(response3); 125 | collector3.stop( 126 | msg.edit({ 127 | embeds: [ 128 | embed.setDescription( 129 | `Alright, Generous Human! Next, What should be the new prize for the giveaway?\n**Must Reply within 30 seconds!**` 130 | )] 131 | }) 132 | ) 133 | } 134 | const collector4 = await message.channel.createMessageCollector(filter, { 135 | max: 3, 136 | time: 30000, 137 | errors: ['time'] 138 | }); 139 | collector4.on("collect", async collect4 => { 140 | 141 | const response4 = collect4.content.toLowerCase(); 142 | prize = response4; 143 | await collect4.delete() 144 | collector4.stop( 145 | console.log(giveawayx), 146 | msg.edit({ 147 | embeds: [ 148 | embed.setDescription( 149 | `Edited` 150 | )] 151 | } 152 | ) 153 | ); 154 | client.giveawaysManager.edit(gid, { 155 | newWinnerCount: winnersCount, 156 | newPrize: prize, 157 | addTime: time 158 | }) 159 | }); 160 | }); 161 | }); 162 | }); 163 | collector.on('end', (collected, reason) => { 164 | if (reason == 'time') { 165 | message.reply({ embeds: [xembed] }); 166 | } 167 | }) 168 | try { 169 | collector2.on('end', (collected, reason) => { 170 | if (reason == 'time') { 171 | 172 | message.reply({ embeds: [xembed] }); 173 | } 174 | }); 175 | collector3.on('end', (collected, reason) => { 176 | if (reason == 'time') { 177 | message.reply({ embeds: [xembed] }); 178 | 179 | } 180 | }) 181 | collector4.on('end', (collected, reason) => { 182 | if (reason == 'time') { 183 | 184 | message.reply({ embeds: [xembed] }); 185 | } 186 | }) 187 | } catch (e) { } 188 | } 189 | -------------------------------------------------------------------------------- /commands/end.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, message, args) => { 2 | 3 | // If the member doesn't have enough permissions 4 | if(!message.member.permissions.has('ManageMessages') && !message.member.roles.cache.some((r) => r.name === "Giveaways")){ 5 | return message.reply(':x: You need to have the manage messages permissions to reroll giveaways.'); 6 | } 7 | 8 | // If no message ID or giveaway name is specified 9 | if(!args[0]){ 10 | return message.reply(':x: You have to specify a valid message ID!'); 11 | } 12 | 13 | // try to found the giveaway with prize then with ID 14 | let giveaway = 15 | // Search with giveaway prize 16 | client.giveawaysManager.giveaways.find((g) => g.prize === args.join(' ')) || 17 | // Search with giveaway ID 18 | client.giveawaysManager.giveaways.find((g) => g.messageId == args[0]); 19 | 20 | // If no giveaway was found 21 | if(!giveaway){ 22 | return message.reply('Unable to find a giveaway for `'+ args.join(' ') + '`.'); 23 | } 24 | 25 | // Edit the giveaway 26 | client.giveawaysManager.end(giveaway.messageId) 27 | // Success message 28 | .then(() => { 29 | // Success message 30 | message.reply('Giveaway Ended.'); 31 | }).catch((e) => { 32 | message.reply({ 33 | content: e 34 | }); 35 | }) 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /commands/help.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder, ActionRowBuilder, SelectMenuBuilder, ComponentType } = require("discord.js"); 2 | const config = require('../config.json'); 3 | 4 | module.exports.run = async (client, message, args) => { 5 | 6 | const embed = new EmbedBuilder() 7 | .setTitle(`Commands of ${client.user.username}`) 8 | .setColor('#2F3136') 9 | .setDescription('**Please Select a category to view all its commands**') 10 | .addFields({ name: `Links:`, value: `- [Youtube Channel](https://youtube.com/c/Zerosync)\n- [Discord Server](https://discord.gg/ARu4hr6hJw)\n- [GitHub](https://github.com/ZeroDiscord/Giveaway)`, inline: true }) 11 | .setTimestamp() 12 | .setFooter({ 13 | text: `Requested by ${message.author.username} | ` + config.copyright, 14 | iconURL: message.author.displayAvatarURL() 15 | }); 16 | 17 | const giveaway = new EmbedBuilder() 18 | .setTitle("Categories » Giveaway") 19 | .setColor('#2F3136') 20 | .setDescription("```yaml\nHere are the giveaway commands:```") 21 | .addFields( 22 | { name: 'Create / Start' , value: `Start a giveaway in your guild!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 23 | { name: 'Drop' , value: `Start a drop giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 24 | { name: 'Edit' , value: `Edit an already running giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 25 | { name: 'End' , value: `End an already running giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 26 | { name: 'List' , value: `List all the giveaways running within this guild!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 27 | { name: 'Pause' , value: `Pause an already running giveaway!\n > **Type: __\`slash\`__**`, inline: true }, 28 | { name: 'Reroll' , value: `Reroll an ended giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 29 | { name: 'Resume' , value: `Resume a paused giveaway!\n > **Type: __\`slash\`__**`, inline: true }, 30 | ) 31 | .setTimestamp() 32 | .setFooter({ 33 | text: `Requested by ${message.author.username} | ` + config.copyright, 34 | iconURL: message.author.displayAvatarURL() 35 | }); 36 | 37 | const general = new EmbedBuilder() 38 | .setTitle("Categories » General") 39 | .setColor('#2F3136') 40 | .setDescription("```yaml\nHere are the general bot commands:```") 41 | .addFields( 42 | { name: 'Help' , value: `Shows all available commands to this bot!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 43 | { name: 'Invite' , value: `Get the bot's invite link!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 44 | { name: 'Ping' , value: `Check the bot's websocket latency!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 45 | ) 46 | .setTimestamp() 47 | .setFooter({ 48 | text: `Requested by ${message.author.username} | ` + config.copyright, 49 | iconURL: message.author.displayAvatarURL() 50 | }); 51 | 52 | const components = (state) => [ 53 | new ActionRowBuilder().addComponents( 54 | new SelectMenuBuilder() 55 | .setCustomId("help-menu") 56 | .setPlaceholder("Please Select a Category") 57 | .setDisabled(state) 58 | .addOptions([{ 59 | label: `Giveaways`, 60 | value: `giveaway`, 61 | description: `View all the giveaway based commands!`, 62 | emoji: `🎉` 63 | }, 64 | { 65 | label: `General`, 66 | value: `general`, 67 | description: `View all the general bot commands!`, 68 | emoji: `⚙` 69 | } 70 | ]) 71 | ), 72 | ]; 73 | 74 | const initialMessage = await message.reply({ embeds: [embed], components: components(false) }); 75 | 76 | const filter = (interaction) => interaction.user.id === message.author.id; 77 | 78 | const collector = message.channel.createMessageComponentCollector( 79 | { 80 | filter, 81 | componentType: ComponentType.SelectMenu, 82 | idle: 300000, 83 | dispose: true, 84 | }); 85 | 86 | collector.on('collect', (interaction) => { 87 | if (interaction.values[0] === "giveaway") { 88 | interaction.update({ embeds: [giveaway], components: components(false) }).catch((e) => {}); 89 | } else if (interaction.values[0] === "general") { 90 | interaction.update({ embeds: [general], components: components(false) }).catch((e) => {}); 91 | } 92 | }); 93 | collector.on("end", (collected, reason) => { 94 | if (reason == "time") { 95 | initialMessage.edit({ 96 | content: "Collector Destroyed, Try Again!", 97 | components: [], 98 | }); 99 | } 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /commands/invite.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); 2 | const config = require('../config.json'); 3 | 4 | module.exports.run = async (client, message, args) => { 5 | const row = new ActionRowBuilder() 6 | .addComponents( 7 | new ButtonBuilder() 8 | .setLabel(`Invite ${client.user.username}`) 9 | .setStyle(ButtonStyle.Link) 10 | .setURL(`https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=applications.commands%20bot`), 11 | new ButtonBuilder() 12 | .setLabel('Support Server') 13 | .setStyle(ButtonStyle.Link) 14 | .setURL("https://discord.gg/ARu4hr6hJw"), 15 | ) 16 | let invite = new EmbedBuilder() 17 | .setAuthor({ 18 | name: `Invite ${client.user.username}`, 19 | iconURL: client.user.displayAvatarURL() 20 | }) 21 | .setTitle("Invite & Support Link!") 22 | .setDescription(`Invite ${client.user} to your server today & enjoy seamless giveaways with advanced features!`) 23 | .setColor('#2F3136') 24 | .setTimestamp() 25 | .setFooter({ 26 | text: `Requested by ${message.author.username} | ` + config.copyright, 27 | iconURL: message.author.displayAvatarURL() 28 | }); 29 | message.reply({ embeds: [invite], components: [row]}); 30 | } 31 | -------------------------------------------------------------------------------- /commands/list.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js'); 2 | const config = require('../config.json'); 3 | 4 | module.exports.run = async (client, message, args) => { 5 | const select = new Discord.SelectMenuBuilder().setCustomId("select").setPlaceholder("Choose a type of giveaway to view!").addOptions([ 6 | { 7 | label: '🎉 Normal Giveaways', 8 | description: 'Check the normal giveaways currently running in your server!', 9 | value: 'normal', 10 | }, 11 | ]) 12 | const row = new Discord.ActionRowBuilder().addComponents([select]) 13 | let giveaways = client.giveawaysManager.giveaways.filter(g => g.guildId === `${message.guild.id}` && !g.ended); 14 | if (!giveaways.some(e => e.messageId)) { 15 | return message.reply('💥 No Giveaways To Be Displayed') 16 | } 17 | const msg = await message.reply({ embeds: [new Discord.EmbedBuilder().setDescription("Choose an option in the select menu to get started!").setColor("#2F3136").setTimestamp()], components: [row] }) 18 | let embed = new Discord.EmbedBuilder() 19 | .setTitle("Currently Active Giveaways") 20 | .setColor("#2F3136") 21 | .setFooter({ 22 | text: `${client.user.username}`, 23 | iconURL: client.user.displayAvatarURL() 24 | }) 25 | .setTimestamp() 26 | 27 | const filter = x => x.customId == "select" && x.user.id == message.author.id 28 | const collector = await message.channel.createMessageComponentCollector({ filter, time: 60000, max: 1 }) 29 | collector.on("collect", async (i) => { 30 | i.update({ components: [] }); 31 | const val = i.values[0] 32 | if (val == "normal") { 33 | await Promise.all(giveaways.map(async (x) => { 34 | embed.addFields({ name: 35 | `Normal Giveaway:`, value: `**Prize:** **[${x.prize}](https://discord.com/channels/${x.guildId}/${x.channelId}/${x.messageId})\nStarted:** ()\n**Ends:** ()` 36 | }); 37 | })); 38 | msg.edit({ embeds: [embed] }) 39 | } 40 | 41 | }) 42 | collector.on("end",(collected, reason) => { 43 | if(reason == "time") 44 | msg.edit({ content: "👀 Collector Destroyed, Try Again!", components: [] }) 45 | }) 46 | } 47 | 48 | -------------------------------------------------------------------------------- /commands/ping.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js'); 2 | const config = require('../config.json'); 3 | module.exports.run = async (client, message, args) => { 4 | let m = await message.reply("Sending request to websocket...") 5 | let pong = new Discord.EmbedBuilder() 6 | .setAuthor({ 7 | name: `🏓 Pong!`, 8 | iconURL: message.author.displayAvatarURL() 9 | }) 10 | .setTitle("Client's Ping") 11 | .setColor('#2F3136') 12 | .setTimestamp() 13 | 14 | .addFields([ 15 | { name: '**Latency**', value: `\`${Date.now() - message.createdTimestamp}ms\`` }, 16 | { name: '**API Latency**', value: `\`${Math.round(client.ws.ping)}ms\`` }, 17 | ]) 18 | .setFooter({ 19 | text: `Requested by ${message.author.tag}`, 20 | iconURL: message.author.displayAvatarURL() 21 | }); 22 | 23 | m.delete() 24 | message.reply({ content: " ", embeds: [pong] }) 25 | } 26 | -------------------------------------------------------------------------------- /commands/reroll.js: -------------------------------------------------------------------------------- 1 | const ms = require('ms'); 2 | module.exports.run = async (client, message, args) => { 3 | 4 | // If the member doesn't have enough permissions 5 | if(!message.member.permissions.has('ManageMessages') && !message.member.roles.cache.some((r) => r.name === "Giveaways")){ 6 | return message.reply(':x: You need to have the manage messages permissions to reroll giveaways.'); 7 | } 8 | 9 | // If no message ID or giveaway name is specified 10 | if(!args[0]){ 11 | return message.reply(':x: You have to specify a valid message ID!'); 12 | } 13 | 14 | // try to found the giveaway with prize then with ID 15 | let giveaway = 16 | // Search with giveaway prize 17 | client.giveawaysManager.giveaways.find((g) => g.prize === args.join(' ')) || 18 | // Search with giveaway ID 19 | client.giveawaysManager.giveaways.find((g) => g.messageID === args[0]); 20 | 21 | // If no giveaway was found 22 | if(!giveaway){ 23 | return message.reply('Unable to find a giveaway for `'+ args.join(' ') +'`.'); 24 | } 25 | 26 | // Reroll the giveaway 27 | client.giveawaysManager.reroll(giveaway.messageID) 28 | .then(() => { 29 | // Success message 30 | message.reply('Giveaway rerolled!'); 31 | }) 32 | .catch((e) => { 33 | if(e.startsWith(`Giveaway with message ID ${giveaway.messageID} is not ended.`)){ 34 | message.reply('This giveaway is not ended!'); 35 | } else { 36 | console.error(e); 37 | message.reply(e); 38 | } 39 | }); 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /commands/start.js: -------------------------------------------------------------------------------- 1 | const ms = require("ms"); 2 | const messages = require("../utils/message"); 3 | module.exports.run = async (client, message, args) => { 4 | // If the member doesn't have enough permissions 5 | if ( 6 | !message.member.permissions.has("ManageMessages") && 7 | !message.member.roles.cache.some(r => r.name === "Giveaways") 8 | ) { 9 | return message.reply( 10 | ":x: You need to have the manage messages permissions to start giveaways." 11 | ); 12 | } 13 | 14 | // Giveaway channel 15 | let giveawayChannel = message.mentions.channels.first(); 16 | // If no channel is mentionned 17 | if (!giveawayChannel) { 18 | return message.reply(":x: You have to mention a valid channel!"); 19 | } 20 | 21 | // Giveaway duration 22 | let giveawayDuration = args[1]; 23 | // If the duration isn't valid 24 | if (!giveawayDuration || isNaN(ms(giveawayDuration))) { 25 | return message.reply(":x: You have to specify a valid duration!"); 26 | } 27 | 28 | // Number of winners 29 | let giveawayNumberWinners = parseInt(args[2]); 30 | // If the specified number of winners is not a number 31 | if (isNaN(giveawayNumberWinners) || parseInt(giveawayNumberWinners) <= 0) { 32 | return message.reply( 33 | ":x: You have to specify a valid number of winners!" 34 | ); 35 | } 36 | 37 | // Giveaway prize 38 | let giveawayPrize = args.slice(3).join(" "); 39 | // If no prize is specified 40 | if (!giveawayPrize) { 41 | return message.reply(":x: You have to specify a valid prize!"); 42 | } 43 | // Start the giveaway 44 | await client.giveawaysManager.start(giveawayChannel, { 45 | // The giveaway duration 46 | duration: ms(giveawayDuration), 47 | // The giveaway prize 48 | prize: giveawayPrize, 49 | // The giveaway winner count 50 | winnerCount: parseInt(giveawayNumberWinners), 51 | // Who hosts this giveaway 52 | hostedBy: client.config.hostedBy ? message.author : null, 53 | // Messages 54 | messages 55 | }); 56 | message.reply(`Giveaway started in ${giveawayChannel}!`); 57 | } 58 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "BotTokenHere", 3 | "prefix": "PrefixHere", 4 | "everyoneMention": false, 5 | "copyright": "GiveawayBot™ v3 By ZeroSync", 6 | "hostedBy": true 7 | } 8 | -------------------------------------------------------------------------------- /events/discord/interactionCreate.js: -------------------------------------------------------------------------------- 1 | module.exports = (client, interaction) => { 2 | // Check if our interaction is a slash command 3 | if (interaction.isCommand()) { 4 | 5 | // Get the command from our slash command collection 6 | const command = client.interactions.get(interaction.commandName); 7 | 8 | // If command does not exist return an error message 9 | if (!command) return interaction.reply({ 10 | content: "Something Went Wrong | Perhaps command not registered?", 11 | ephemeral: true 12 | }); 13 | 14 | command.run(client, interaction); 15 | } 16 | } -------------------------------------------------------------------------------- /events/discord/messageCreate.js: -------------------------------------------------------------------------------- 1 | module.exports = (client, message) => { 2 | // return if author is a bot 3 | if (message.author.bot) return; 4 | 5 | // return if message does not match prefix (in command) 6 | if (message.content.indexOf(client.config.prefix) !== 0) return; 7 | 8 | // Defining what are arguments and commands 9 | const args = message.content.slice(client.config.prefix.length).trim().split(/ +/g); 10 | const command = args.shift().toLowerCase(); 11 | 12 | // Get the command data from the client.commands Enmap 13 | const cmd = client.commands.get(command); 14 | 15 | // If command does not exist return 16 | if (!cmd) return; 17 | 18 | // Run the command 19 | cmd.run(client, message, args); 20 | }; 21 | -------------------------------------------------------------------------------- /events/discord/ready.js: -------------------------------------------------------------------------------- 1 | const register = require('../../utils/slashsync'); 2 | const { ActivityType } = require('discord.js'); 3 | 4 | module.exports = async (client) => { 5 | await register(client, client.register_arr.map((command) => ({ 6 | name: command.name, 7 | description: command.description, 8 | options: command.options, 9 | type: '1' 10 | })), { 11 | debug: true 12 | }); 13 | // Register slash commands - ( If you are one of those people who read the codes I highly suggest ignoring this because I am very bad at what I am doing, thanks LMAO ) 14 | console.log(`[ / | Slash Command ] - ✅ Loaded all slash commands!`) 15 | let invite = `https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=applications.commands%20bot`; 16 | console.log(`[STATUS] ${client.user.tag} is now online!\n[INFO] Bot by ZeroSync https://www.youtube.com/c/ZeroSync\n[Invite Link] ${invite}`); 17 | client.user.setPresence({ 18 | activities: [{ name: `ZeroSync on YouTube`, type: ActivityType.Watching }], 19 | status: 'online', 20 | }); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /events/giveaways/endedGiveawayReactionAdded.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js'); 2 | module.exports = { 3 | async execute(giveaway, member, reaction) { 4 | reaction.users.remove(member.user); 5 | member.send({ 6 | embeds: [ 7 | new Discord.EmbedBuilder() 8 | .setTitle(`Giveaway ended already!`) 9 | .setColor('#b50505') 10 | .setDescription( 11 | `Hey ${member.user} **[[This Giveaway]](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})** that you reacted has already ended :sob:\nBe quick next time!` 12 | ) 13 | .setTimestamp(), 14 | ], 15 | }) 16 | .catch((e) => {}); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /events/giveaways/giveawayEnded.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | module.exports = { 3 | async execute(giveaway, winners) { 4 | winners.forEach((member) => { 5 | member.send({ 6 | embeds: [new Discord.EmbedBuilder() 7 | .setTitle(`🎁 Let's goo!`) 8 | .setColor("#2F3136") 9 | .setDescription(`Hello there ${member.user}\n I heard that you have won **[[This Giveaway]](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})**\n Good Job On Winning **${giveaway.prize}!**\nDirect Message the host to claim your prize!!`) 10 | .setTimestamp() 11 | .setFooter({ 12 | text: `${member.user.username}`, 13 | iconURL: member.user.displayAvatarURL() 14 | }) 15 | ] 16 | }).catch(e => {}) 17 | }); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /events/giveaways/giveawayReactionAdded.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | module.exports = { 3 | async execute(giveaway, reactor, messageReaction) { 4 | let approved = new Discord.EmbedBuilder() 5 | .setTimestamp() 6 | .setColor("#2F3136") 7 | .setTitle("Entry Approved! | You have a chance to win!!") 8 | .setDescription( 9 | `Your entry to [This Giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId}) has been approved!` 10 | ) 11 | .setFooter({ text: "Subscribe to ZeroSync on YT!" }) 12 | .setTimestamp() 13 | let denied = new Discord.EmbedBuilder() 14 | .setTimestamp() 15 | .setColor("#2F3136") 16 | .setTitle(":x: Entry Denied | Database Entry Not Found & Returned!") 17 | .setDescription( 18 | `Your entry to [This Giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId}) has been denied, please review the requirements to the giveaway properly.` 19 | ) 20 | .setFooter({ text: "Subscribe to ZeroSync on YT!" }) 21 | 22 | let client = messageReaction.message.client 23 | if (reactor.user.bot) return; 24 | if(giveaway.extraData) { 25 | if (giveaway.extraData.server !== "null") { 26 | try { 27 | await client.guilds.cache.get(giveaway.extraData.server).members.fetch(reactor.id) 28 | return reactor.send({ 29 | embeds: [approved] 30 | }); 31 | } catch(e) { 32 | messageReaction.users.remove(reactor.user); 33 | return reactor.send({ 34 | embeds: [denied] 35 | }).catch(e => {}) 36 | } 37 | } 38 | if (giveaway.extraData.role !== "null" && !reactor.roles.cache.get(giveaway.extraData.role)){ 39 | messageReaction.users.remove(reactor.user); 40 | return reactor.send({ 41 | embeds: [denied] 42 | }).catch(e => {}) 43 | } 44 | 45 | return reactor.send({ 46 | embeds: [approved] 47 | }).catch(e => {}) 48 | } else { 49 | return reactor.send({ 50 | embeds: [approved] 51 | }).catch(e => {}) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /events/giveaways/giveawayReactionRemoved.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | module.exports = { 3 | async execute(giveaway, member) { 4 | return member.send({ 5 | embeds: [new Discord.EmbedBuilder() 6 | .setTimestamp() 7 | .setTitle('❓ Hold Up Did You Just Remove a Reaction From A Giveaway?') 8 | .setColor("#2F3136") 9 | .setDescription( 10 | `Your entry to [This Giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId}) was recorded but you un-reacted, since you don't need **${giveaway.prize}** I would have to choose someone else 😭` 11 | ) 12 | .setFooter({ text: "Think It was a mistake? Go react again!" }) 13 | ] 14 | }).catch(e => {}) 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /events/giveaways/giveawayRerolled.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | module.exports = { 3 | async execute(giveaway, winners) { 4 | winners.forEach((member) => { 5 | member.send({ 6 | embeds: [new Discord.EmbedBuilder() 7 | .setTitle(`🎁 Let's goo! We Have A New Winner`) 8 | .setColor("#2F3136") 9 | .setDescription(`Hello there ${member.user}\n I heard that the host rerolled and you have won **[[This Giveaway]](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})**\n Good Job On Winning **${giveaway.prize}!**\nDirect Message the host to claim your prize!!`) 10 | .setTimestamp() 11 | .setFooter({ 12 | text: `${member.user.username}`, 13 | iconURL: member.user.displayAvatarURL() 14 | }) 15 | ] 16 | }).catch(e => {}) 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const { Client, GatewayIntentBits, Partials } = require("discord.js"); 3 | const client = new Client({ 4 | partials: [ 5 | Partials.Message, // for message 6 | Partials.Channel, // for text channel 7 | Partials.GuildMember, // for guild member 8 | Partials.Reaction, // for message reaction 9 | ], 10 | intents: [ 11 | GatewayIntentBits.Guilds, // for guild related things 12 | GatewayIntentBits.GuildInvites, // for guild invite managing 13 | GatewayIntentBits.GuildMessages, // for guild messages things 14 | GatewayIntentBits.GuildMessageReactions, // for message reactions things 15 | GatewayIntentBits.MessageContent, // enable if you need message content things 16 | ], 17 | }); 18 | const fs = require("fs"); 19 | const config = require("./config.json"); 20 | client.config = config; 21 | 22 | // Initialise discord giveaways 23 | const { GiveawaysManager } = require("discord-giveaways"); 24 | client.giveawaysManager = new GiveawaysManager(client, { 25 | storage: "./storage/giveaways.json", 26 | default: { 27 | botsCanWin: false, 28 | embedColor: "#2F3136", 29 | reaction: "🎉", 30 | lastChance: { 31 | enabled: true, 32 | content: `🛑 **Last chance to enter** 🛑`, 33 | threshold: 5000, 34 | embedColor: '#FF0000' 35 | } 36 | } 37 | }); 38 | //Coded by ZeroSync on yt 39 | 40 | /* Load all events (discord based) */ 41 | 42 | 43 | fs.readdir("./events/discord", (_err, files) => { 44 | files.forEach(file => { 45 | if (!file.endsWith(".js")) return; 46 | const event = require(`./events/discord/${file}`); 47 | let eventName = file.split(".")[0]; 48 | console.log(`[Event] ✅ Loaded: ${eventName}`); 49 | client.on(eventName, event.bind(null, client)); 50 | delete require.cache[require.resolve(`./events/discord/${file}`)]; 51 | }); 52 | }); 53 | 54 | /* Load all events (giveaways based) */ 55 | 56 | 57 | fs.readdir("./events/giveaways", (_err, files) => { 58 | files.forEach((file) => { 59 | if (!file.endsWith(".js")) return; 60 | const event = require(`./events/giveaways/${file}`); 61 | let eventName = file.split(".")[0]; 62 | console.log(`[Event] 🎉 Loaded: ${eventName}`); 63 | client.giveawaysManager.on(eventName, (...file) => event.execute(...file, client)), delete require.cache[require.resolve(`./events/giveaways/${file}`)]; 64 | }) 65 | }) 66 | 67 | // Let commands be a new collection ( message commands ) 68 | client.commands = new Discord.Collection(); 69 | /* Load all commands */ 70 | fs.readdir("./commands/", (_err, files) => { 71 | files.forEach(file => { 72 | if (!file.endsWith(".js")) return; 73 | let props = require(`./commands/${file}`); 74 | let commandName = file.split(".")[0]; 75 | client.commands.set(commandName, { 76 | name: commandName, 77 | ...props 78 | }); 79 | console.log(`[Command] ✅ Loaded: ${commandName}`); 80 | }); 81 | }); 82 | 83 | // let interactions be a new collection ( slash commands ) 84 | client.interactions = new Discord.Collection(); 85 | // creating an empty array for registering slash commands 86 | client.register_arr = [] 87 | /* Load all slash commands */ 88 | fs.readdir("./slash/", (_err, files) => { 89 | files.forEach(file => { 90 | if (!file.endsWith(".js")) return; 91 | let props = require(`./slash/${file}`); 92 | let commandName = file.split(".")[0]; 93 | client.interactions.set(commandName, { 94 | name: commandName, 95 | ...props 96 | }); 97 | client.register_arr.push(props) 98 | }); 99 | }); 100 | 101 | 102 | // Login through the client 103 | client.login(config.token); 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GiveawayBot", 3 | "version": "3.0.0", 4 | "description": "Giveaway bot created with discord-giveaways package in discord.js v14", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "Zero", 10 | "license": "CC-Zero", 11 | "keywords": [ 12 | "discord", 13 | "giveaways", 14 | "giveaway", 15 | "bot", 16 | "discord.js", 17 | "discordjs" 18 | ], 19 | "dependencies": { 20 | "discord-giveaways": "^6.0.1", 21 | "discord.js": "^14.6.0", 22 | "ms": "^3.0.0-canary.1", 23 | "parsec": "^2.0.2" 24 | }, 25 | "engines": { 26 | "node": "16.x" 27 | }, 28 | "repository": { 29 | "url": "https://github.com/ZeroDiscord/Giveaway" 30 | }, 31 | "devDependencies": { 32 | "node": "^16.9.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /slash/drop.js: -------------------------------------------------------------------------------- 1 | const messages = require("../utils/message"); 2 | const { ApplicationCommandOptionType } = require('discord.js'); 3 | 4 | module.exports = { 5 | name: 'drop', 6 | description: 'Create a drop giveaway', 7 | options: [ 8 | { 9 | name: 'winners', 10 | description: 'How many winners the giveaway should have', 11 | type: ApplicationCommandOptionType.Integer, 12 | required: true 13 | }, 14 | { 15 | name: 'prize', 16 | description: 'What the prize of the giveaway should be', 17 | type: ApplicationCommandOptionType.String, 18 | required: true 19 | }, 20 | { 21 | name: 'channel', 22 | description: 'The channel to start the giveaway in', 23 | type: ApplicationCommandOptionType.Channel, 24 | required: true 25 | } 26 | ], 27 | 28 | run: async (client, interaction) => { 29 | 30 | // If the member doesn't have enough permissions 31 | if(!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")){ 32 | return interaction.reply({ 33 | content: ':x: You need to have the manage messages permissions to start giveaways.', 34 | ephemeral: true 35 | }); 36 | } 37 | 38 | const giveawayChannel = interaction.options.getChannel('channel'); 39 | const giveawayWinnerCount = interaction.options.getInteger('winners'); 40 | const giveawayPrize = interaction.options.getString('prize'); 41 | 42 | if (!giveawayChannel.isTextBased()) { 43 | return interaction.reply({ 44 | content: ':x: Please select a text channel!', 45 | ephemeral: true 46 | }); 47 | } 48 | if (giveawayWinnerCount < 1) { 49 | return interaction.reply({ 50 | content: ':x: Please select a valid winner count! greater or equal to one.', 51 | }) 52 | } 53 | 54 | // Start the giveaway 55 | client.giveawaysManager.start(giveawayChannel, { 56 | // The number of winners for this drop 57 | winnerCount: giveawayWinnerCount, 58 | // The prize of the giveaway 59 | prize: giveawayPrize, 60 | // Who hosts this giveaway 61 | hostedBy: client.config.hostedBy ? interaction.user : null, 62 | // specify drop 63 | isDrop: true, 64 | // Messages 65 | messages 66 | }); 67 | 68 | interaction.reply(`Giveaway started in ${giveawayChannel}!`); 69 | 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /slash/edit.js: -------------------------------------------------------------------------------- 1 | const { ApplicationCommandOptionType } = require('discord.js'); 2 | const ms = require("ms"); 3 | 4 | module.exports = { 5 | name: 'edit', 6 | description: '🎉 Edit a giveaway', 7 | 8 | options: [ 9 | { 10 | name: 'giveaway', 11 | description: 'The giveaway to end (message ID)', 12 | type: ApplicationCommandOptionType.String, 13 | required: true 14 | }, 15 | { 16 | name: 'duration', 17 | description: 'Setting time of mentioned giveaway. Eg. 1h sets the current giveaway to end after an hour!', 18 | type: ApplicationCommandOptionType.String, 19 | required: true 20 | }, 21 | { 22 | name: 'winners', 23 | description: 'How many winners the giveaway should have', 24 | type: ApplicationCommandOptionType.Integer, 25 | required: true 26 | }, 27 | { 28 | name: 'prize', 29 | description: 'What the prize of the giveaway should be', 30 | type: ApplicationCommandOptionType.String, 31 | required: true 32 | } 33 | ], 34 | 35 | run: async (client, interaction) => { 36 | 37 | // If the member doesn't have enough permissions 38 | if (!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")) { 39 | return interaction.reply({ 40 | content: ':x: You need to have the manage messages permissions to start giveaways.', 41 | ephemeral: true 42 | }); 43 | } 44 | const gid = interaction.options.getString('giveaway'); 45 | const time = interaction.options.getString('duration'); 46 | const winnersCount = interaction.options.getInteger('winners'); 47 | const prize = interaction.options.getString('prize'); 48 | let duration; 49 | if (time.startsWith("-")) { 50 | duration = -ms(time.substring(1)); 51 | } else { 52 | duration = ms(time); 53 | } 54 | 55 | if (isNaN(duration)) { 56 | return interaction.reply({ 57 | content: ":x: Please select a valid duration!", 58 | ephemeral: true, 59 | }); 60 | } 61 | await interaction.deferReply({ 62 | ephemeral: true 63 | }) 64 | // Edit the giveaway 65 | try { 66 | await client.giveawaysManager.edit(gid, { 67 | newWinnerCount: winnersCount, 68 | newPrize: prize, 69 | addTime: time 70 | }) 71 | } catch (e) { 72 | return interaction.editReply({ 73 | content: 74 | `No giveaway found with the given message ID: \`${gid}\``, 75 | ephemeral: true 76 | }); 77 | } 78 | interaction.editReply({ 79 | content: 80 | `This giveaway has now been edited!`, 81 | ephemeral: true 82 | }); 83 | } 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /slash/end.js: -------------------------------------------------------------------------------- 1 | const { ApplicationCommandOptionType } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: "end", 5 | description: '🎉 End an already running giveaway', 6 | 7 | options: [ 8 | { 9 | name: 'giveaway', 10 | description: 'The giveaway to end (message ID or giveaway prize)', 11 | type: ApplicationCommandOptionType.String, 12 | required: true 13 | } 14 | ], 15 | 16 | run: async (client, interaction) => { 17 | 18 | // If the member doesn't have enough permissions 19 | if (!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")) { 20 | return interaction.reply({ 21 | content: ':x: You need to have the manage messages permissions to end giveaways.', 22 | ephemeral: true 23 | }); 24 | } 25 | 26 | const query = interaction.options.getString('giveaway'); 27 | 28 | // fetching the giveaway with message Id or prize 29 | const giveaway = 30 | // Search with giveaway prize 31 | client.giveawaysManager.giveaways.find((g) => g.prize === query && g.guildId === interaction.guild.id) || 32 | // Search with giveaway Id 33 | client.giveawaysManager.giveaways.find((g) => g.messageId === query && g.guildId === interaction.guild.id); 34 | 35 | // If no giveaway was found with the corresponding input 36 | if (!giveaway) { 37 | return interaction.reply({ 38 | content: 'Unable to find a giveaway for `' + query + '`.', 39 | ephemeral: true 40 | }); 41 | } 42 | 43 | if (giveaway.ended) { 44 | return interaction.reply({ 45 | content: 'This giveaway has already ended!', 46 | ephemeral: true 47 | }); 48 | } 49 | 50 | // Edit the giveaway 51 | client.giveawaysManager.end(giveaway.messageId) 52 | // Success message 53 | .then(() => { 54 | // Success message 55 | interaction.reply(`**[This Giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})** Has Now Ended!`); 56 | }) 57 | .catch((e) => { 58 | interaction.reply({ 59 | content: e, 60 | ephemeral: true 61 | }); 62 | }); 63 | 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /slash/help.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder, ActionRowBuilder, SelectMenuBuilder, ComponentType } = require('discord.js'); 2 | const config = require('../config.json'); 3 | 4 | module.exports = { 5 | name: 'help', 6 | description: '📜 View all the commands available to the bot!', 7 | run: async (client, interaction) => { 8 | const embed = new EmbedBuilder() 9 | .setTitle(`Commands of ${client.user.username}`) 10 | .setColor('#2F3136') 11 | .setDescription('**Please Select a category to view all its commands**') 12 | .addFields({ name: `Links:`, value: `- [Youtube Channel](https://youtube.com/c/Zerosync)\n- [Discord Server](https://discord.gg/ARu4hr6hJw)\n- [GitHub](https://github.com/ZeroDiscord/Giveaway)`, inline: true }) 13 | 14 | .setTimestamp() 15 | .setFooter({ 16 | text: `Requested by ${interaction.user.username} | ` + config.copyright, 17 | iconURL: interaction.user.displayAvatarURL() 18 | }); 19 | 20 | const giveaway = new EmbedBuilder() 21 | .setTitle("Categories » Giveaway") 22 | .setColor('#2F3136') 23 | .setDescription("```yaml\nHere are the giveaway commands:```") 24 | .addFields( 25 | { name: 'Create / Start', value: `Start a giveaway in your guild!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 26 | { name: 'Drop', value: `Start a drop giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 27 | { name: 'Edit', value: `Edit an already running giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 28 | { name: 'End', value: `End an already running giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 29 | { name: 'List', value: `List all the giveaways running within this guild!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 30 | { name: 'Pause', value: `Pause an already running giveaway!\n > **Type: __\`slash\`__**`, inline: true }, 31 | { name: 'Reroll', value: `Reroll an ended giveaway!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 32 | { name: 'Resume', value: `Resume a paused giveaway!\n > **Type: __\`slash\`__**`, inline: true }, 33 | ) 34 | .setTimestamp() 35 | .setFooter({ 36 | text: `Requested by ${interaction.user.username} | ` + config.copyright, 37 | iconURL: interaction.user.displayAvatarURL() 38 | }); 39 | 40 | const general = new EmbedBuilder() 41 | .setTitle("Categories » General") 42 | .setColor('#2F3136') 43 | .setDescription("```yaml\nHere are the general bot commands:```") 44 | .addFields( 45 | { name: 'Help', value: `Shows all available commands to this bot!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 46 | { name: 'Invite', value: `Get the bot's invite link!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 47 | { name: 'Ping', value: `Check the bot's websocket latency!\n > **Types: __\`slash\` / \`message\`__**`, inline: true }, 48 | ) 49 | .setTimestamp() 50 | .setFooter({ 51 | text: `Requested by ${interaction.user.username} | ` + config.copyright, 52 | iconURL: interaction.user.displayAvatarURL() 53 | }); 54 | 55 | const components = (state) => [ 56 | new ActionRowBuilder().addComponents( 57 | new SelectMenuBuilder() 58 | .setCustomId("help-menu") 59 | .setPlaceholder("Please Select a Category") 60 | .setDisabled(state) 61 | .addOptions([{ 62 | label: `Giveaways`, 63 | value: `giveaway`, 64 | description: `View all the giveaway based commands!`, 65 | emoji: `🎉` 66 | }, 67 | { 68 | label: `General`, 69 | value: `general`, 70 | description: `View all the general bot commands!`, 71 | emoji: `⚙` 72 | } 73 | ]) 74 | ), 75 | ]; 76 | 77 | const initialMessage = await interaction.reply({ embeds: [embed], components: components(false) }); 78 | 79 | const filter = (interaction) => interaction.user.id === interaction.member.id; 80 | 81 | const collector = interaction.channel.createMessageComponentCollector( 82 | { 83 | filter, 84 | componentType: ComponentType.SelectMenu, 85 | idle: 300000, 86 | dispose: true, 87 | }); 88 | 89 | collector.on('collect', (interaction) => { 90 | if (interaction.values[0] === "giveaway") { 91 | interaction.update({ embeds: [giveaway], components: components(false) }).catch((e) => { }); 92 | } else if (interaction.values[0] === "general") { 93 | interaction.update({ embeds: [general], components: components(false) }).catch((e) => { }); 94 | } 95 | }); 96 | collector.on('end', (collected, reason) => { 97 | if (reason == "time") { 98 | initialMessage.edit({ 99 | content: "Collector Destroyed, Try Again!", 100 | components: [], 101 | }); 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /slash/invite.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); 2 | const config = require('../config.json'); 3 | 4 | module.exports = { 5 | name: 'invite', 6 | description: '➕ Invite the bot to your server!', 7 | run: async (client, interaction) => { 8 | const row = new ActionRowBuilder() 9 | .addComponents( 10 | new ButtonBuilder() 11 | .setLabel(`Invite ${client.user.username}`) 12 | .setStyle(ButtonStyle.Link) 13 | .setURL(`https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=applications.commands%20bot`), 14 | new ButtonBuilder() 15 | .setLabel('Support Server') 16 | .setStyle(ButtonStyle.Link) 17 | .setURL("https://discord.gg/ARu4hr6hJw"), 18 | ) 19 | let invite = new EmbedBuilder() 20 | .setAuthor({ 21 | name: `Invite ${client.user.username}`, 22 | iconURL: client.user.displayAvatarURL() 23 | }) 24 | .setTitle("Invite & Support Link!") 25 | .setDescription(`Invite ${client.user} to your server today & enjoy seamless giveaways with advvanced features!`) 26 | .setColor('#2F3136') 27 | .setTimestamp() 28 | .setFooter({ 29 | text: `Requested by ${interaction.user.username} | ` + config.copyright, 30 | iconURL: interaction.user.displayAvatarURL() 31 | }) 32 | 33 | interaction.reply({ embeds: [invite], components: [row]}); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /slash/list.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js'); 2 | const config = require('../config.json'); 3 | 4 | module.exports = { 5 | name: 'list', 6 | description: '🎉 List all the active giveaways for this server.', 7 | run: async (client, interaction) => { 8 | const select = new Discord.SelectMenuBuilder() 9 | .setCustomId('select') 10 | .setPlaceholder('Choose a type of giveaway to view!') 11 | .addOptions([ 12 | { 13 | label: '🎉 Normal Giveaways', 14 | description: 'Check the giveaways currently running in your server!', 15 | value: 'normal', 16 | }, 17 | ]); 18 | const row = new Discord.ActionRowBuilder().addComponents([select]); 19 | let giveaways = client.giveawaysManager.giveaways.filter( 20 | (g) => g.guildId === `${interaction.guild.id}` && !g.ended 21 | ); 22 | if (!giveaways.some((e) => e.messageId)) { 23 | return interaction.reply('💥 No Giveaways To Be Displayed'); 24 | } 25 | const msg = await interaction.channel.send({ 26 | embeds: [ 27 | new Discord.EmbedBuilder() 28 | .setDescription('Choose an option in the select menu to get started!') 29 | .setColor('#f542ec') 30 | .setTimestamp(), 31 | ], 32 | components: [row], 33 | }); 34 | let embed = new Discord.EmbedBuilder() 35 | .setTitle('Currently Active Giveaways') 36 | .setColor('#f58142') 37 | .setFooter({ 38 | text: `Requested by ${interaction.user.username} | ` + config.copyright, 39 | iconURL: interaction.user.displayAvatarURL() 40 | }) 41 | .setTimestamp(); 42 | const filter = (x) => 43 | x.customId == 'select' && x.user.id == interaction.member.id; 44 | const collector = await interaction.channel.createMessageComponentCollector( 45 | { filter, time: 60000, max: 1 } 46 | ); 47 | await interaction.deferReply(); 48 | collector.on('collect', async (i) => { 49 | const val = i.values[0]; 50 | if (val == 'normal') { 51 | await Promise.all( 52 | giveaways.map(async (x) => { 53 | embed.addFields({ name: 54 | `Normal Giveaway:`, value: `**Prize:** **[${x.prize}](https://discord.com/channels/${x.guildId}/${x.channelId}/${x.messageId})\nStarted:** ()\n**Ends:** ()` 55 | }); 56 | }) 57 | ); 58 | msg.delete(); 59 | interaction.editReply({ embeds: [embed], components: [] }); 60 | } 61 | }); 62 | collector.on('end', (collected, reason) => { 63 | if (reason == 'time') { 64 | interaction.editReply({ 65 | content: 'Collector Destroyed, Try Again!', 66 | components: [], 67 | }); 68 | } 69 | }); 70 | }, 71 | }; -------------------------------------------------------------------------------- /slash/pause.js: -------------------------------------------------------------------------------- 1 | const { ApplicationCommandOptionType } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: "pause", 5 | description: '⏸ Pause a giveaway', 6 | 7 | options: [ 8 | { 9 | name: 'giveaway', 10 | description: 'The giveaway to pause (message ID or giveaway prize)', 11 | type: ApplicationCommandOptionType.String, 12 | required: true 13 | } 14 | ], 15 | 16 | run: async (client, interaction) => { 17 | 18 | // If the member doesn't have enough permissions 19 | if (!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")) { 20 | return interaction.reply({ 21 | content: ':x: You need to have the manage messages permissions to pause giveaways.', 22 | ephemeral: true 23 | }); 24 | } 25 | 26 | const query = interaction.options.getString('giveaway'); 27 | 28 | // try to find the giveaway with prize alternatively with ID 29 | const giveaway = 30 | // Search with giveaway prize 31 | client.giveawaysManager.giveaways.find((g) => g.prize === query && g.guildId === interaction.guild.id) || 32 | // Search with giveaway ID 33 | client.giveawaysManager.giveaways.find((g) => g.messageId === query && g.guildId === interaction.guild.id); 34 | 35 | // If no giveaway was found 36 | if (!giveaway) { 37 | return interaction.reply({ 38 | content: 'Unable to find a giveaway for `' + query + '`.', 39 | ephemeral: true 40 | }); 41 | } 42 | 43 | if (giveaway.pauseOptions.isPaused) { 44 | return interaction.reply({ 45 | content: `**[This giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})** is already paused.`, 46 | ephemeral: true 47 | }); 48 | } 49 | 50 | // Edit the giveaway 51 | client.giveawaysManager.pause(giveaway.messageId) 52 | // Success message 53 | .then(() => { 54 | // Success message 55 | interaction.reply(`**[This giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})** has now been paused!`); 56 | }) 57 | .catch((e) => { 58 | interaction.reply({ 59 | content: e, 60 | ephemeral: true 61 | }); 62 | }); 63 | 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /slash/ping.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: 'ping', 5 | description: '🏓Check my ping!', 6 | run: async (client, interaction) => { 7 | let pembed = new EmbedBuilder() 8 | .setColor('#2F3136') 9 | .setTitle('Client Ping') 10 | .addFields({ name: '**Latency**', 11 | value: `\`${Date.now() - interaction.createdTimestamp}ms\`` 12 | }) 13 | .addFields({ name: '**API Latency**', 14 | value: `\`${Math.round(client.ws.ping)}ms\`` 15 | }) 16 | .setTimestamp() 17 | .setFooter({ 18 | text: `${interaction.user.username}`, 19 | iconURL: interaction.user.displayAvatarURL() 20 | }) 21 | interaction.reply({ 22 | embeds: [pembed] 23 | }); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /slash/reroll.js: -------------------------------------------------------------------------------- 1 | const { ApplicationCommandOptionType } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: "reroll", 5 | description: '🎉 Reroll a giveaway', 6 | 7 | options: [ 8 | { 9 | name: 'giveaway', 10 | description: 'The giveaway to reroll (message ID or prize)', 11 | type: ApplicationCommandOptionType.String, 12 | required: true 13 | } 14 | ], 15 | 16 | run: async (client, interaction) => { 17 | 18 | // If the member doesn't have enough permissions 19 | if (!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")) { 20 | return interaction.reply({ 21 | content: ':x: You need to have the manage messages permission to reroll giveaways.', 22 | ephemeral: true 23 | }); 24 | } 25 | 26 | const query = interaction.options.getString('giveaway'); 27 | 28 | // try to find the giveaway with the provided prize OR with the ID 29 | const giveaway = 30 | // Search with giveaway prize 31 | client.giveawaysManager.giveaways.find((g) => g.prize === query && g.guildId === interaction.guild.id) || 32 | // Search with giveaway ID 33 | client.giveawaysManager.giveaways.find((g) => g.messageId === query && g.guildId === interaction.guild.id); 34 | 35 | // If no giveaway was found 36 | if (!giveaway) { 37 | return interaction.reply({ 38 | content: 'Unable to find a giveaway for `' + query + '`.', 39 | ephemeral: true 40 | }); 41 | } 42 | 43 | if (!giveaway.ended) { 44 | return interaction.reply({ 45 | content: `[This Giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId}) has not been ended yet`, 46 | ephemeral: true 47 | }); 48 | } 49 | 50 | // Reroll the giveaway 51 | client.giveawaysManager.reroll(giveaway.messageId) 52 | .then(() => { 53 | // Success message 54 | interaction.reply(`Rerolled **[this giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})!**`); 55 | }) 56 | .catch((e) => { 57 | interaction.reply({ 58 | content: e, 59 | ephemeral: true 60 | }); 61 | }); 62 | 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /slash/resume.js: -------------------------------------------------------------------------------- 1 | const { ApplicationCommandOptionType } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: "resume", 5 | description: '▶ Resume a paused giveaway', 6 | 7 | options: [ 8 | { 9 | name: 'giveaway', 10 | description: 'The giveaway to resume (message ID or giveaway prize)', 11 | type: ApplicationCommandOptionType.String, 12 | required: true 13 | } 14 | ], 15 | 16 | run: async (client, interaction) => { 17 | 18 | // If the member doesn't have enough permissions 19 | if (!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")) { 20 | return interaction.reply({ 21 | content: ':x: You need to have the manage messages permissions to pause giveaways.', 22 | ephemeral: true 23 | }); 24 | } 25 | 26 | const query = interaction.options.getString('giveaway'); 27 | 28 | // try to find the giveaway with prize alternatively with ID 29 | const giveaway = 30 | // Search with giveaway prize 31 | client.giveawaysManager.giveaways.find((g) => g.prize === query && g.guildId === interaction.guild.id) || 32 | // Search with giveaway ID 33 | client.giveawaysManager.giveaways.find((g) => g.messageId === query && g.guildId === interaction.guild.id); 34 | 35 | // If no giveaway was found 36 | if (!giveaway) { 37 | return interaction.reply({ 38 | content: 'Unable to find a giveaway for `' + query + '`.', 39 | ephemeral: true 40 | }); 41 | } 42 | 43 | if (!giveaway.pauseOptions.isPaused) { 44 | return interaction.reply({ 45 | content: `**[This giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})** is not paused!`, 46 | ephemeral: true 47 | }); 48 | } 49 | 50 | // Edit the giveaway 51 | client.giveawaysManager.unpause(giveaway.messageId) 52 | // Success message 53 | .then(() => { 54 | // Success message 55 | interaction.reply(`**[This giveaway](https://discord.com/channels/${giveaway.guildId}/${giveaway.channelId}/${giveaway.messageId})** has been successfully resumed!`); 56 | }) 57 | .catch((e) => { 58 | interaction.reply({ 59 | content: e, 60 | ephemeral: true 61 | }); 62 | }); 63 | 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /slash/start.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const { ApplicationCommandOptionType } = require("discord.js"); 3 | const messages = require("../utils/message"); 4 | const ms = require("ms") 5 | module.exports = { 6 | name: 'start', 7 | description: '🎉 Start a giveaway', 8 | 9 | options: [ 10 | { 11 | name: 'duration', 12 | description: 'How long the giveaway should last for. Example values: 1m, 1h, 1d', 13 | type: ApplicationCommandOptionType.String, 14 | required: true 15 | }, 16 | { 17 | name: 'winners', 18 | description: 'How many winners the giveaway should have', 19 | type: ApplicationCommandOptionType.Integer, 20 | required: true 21 | }, 22 | { 23 | name: 'prize', 24 | description: 'What the prize of the giveaway should be', 25 | type: ApplicationCommandOptionType.String, 26 | required: true 27 | }, 28 | { 29 | name: 'channel', 30 | description: 'The channel to start the giveaway in', 31 | type: ApplicationCommandOptionType.Channel, 32 | required: true 33 | }, 34 | { 35 | name: 'bonusrole', 36 | description: 'Role which would recieve bonus entries', 37 | type: ApplicationCommandOptionType.Role, 38 | required: false 39 | }, 40 | { 41 | name: 'bonusamount', 42 | description: 'The amount of bonus entries the role will recieve', 43 | type: ApplicationCommandOptionType.Integer, 44 | required: false 45 | }, 46 | { 47 | name: 'invite', 48 | description: 'Invite of the server you want to add as giveaway joining requirement', 49 | type: ApplicationCommandOptionType.String, 50 | required: false 51 | }, 52 | { 53 | name: 'role', 54 | description: 'Role you want to add as giveaway joining requirement', 55 | type: ApplicationCommandOptionType.Role, 56 | required: false 57 | }, 58 | ], 59 | 60 | run: async (client, interaction) => { 61 | 62 | // If the member doesn't have enough permissions 63 | if (!interaction.member.permissions.has('ManageMessages') && !interaction.member.roles.cache.some((r) => r.name === "Giveaways")) { 64 | return interaction.reply({ 65 | content: ':x: You need to have the manage messages permissions to start giveaways.', 66 | ephemeral: true 67 | }); 68 | } 69 | 70 | const giveawayChannel = interaction.options.getChannel('channel'); 71 | const giveawayDuration = interaction.options.getString('duration'); 72 | const giveawayWinnerCount = interaction.options.getInteger('winners'); 73 | const giveawayPrize = interaction.options.getString('prize'); 74 | 75 | if (!giveawayChannel.isTextBased()) { 76 | return interaction.reply({ 77 | content: ':x: Please select a text channel!', 78 | ephemeral: true 79 | }); 80 | } 81 | if(isNaN(ms(giveawayDuration))) { 82 | return interaction.reply({ 83 | content: ':x: Please select a valid duration!', 84 | ephemeral: true 85 | }); 86 | } 87 | if (giveawayWinnerCount < 1) { 88 | return interaction.reply({ 89 | content: ':x: Please select a valid winner count! greater or equal to one.', 90 | }) 91 | } 92 | 93 | const bonusRole = interaction.options.getRole('bonusrole') 94 | const bonusEntries = interaction.options.getInteger('bonusamount') 95 | let rolereq = interaction.options.getRole('role') 96 | let invite = interaction.options.getString('invite') 97 | 98 | if (bonusRole) { 99 | if (!bonusEntries) { 100 | return interaction.reply({ 101 | content: `:x: You must specify how many bonus entries would ${bonusRole} recieve!`, 102 | ephemeral: true 103 | }); 104 | } 105 | } 106 | 107 | 108 | await interaction.deferReply({ ephemeral: true }) 109 | let reqinvite; 110 | if (invite) { 111 | let invitex = await client.fetchInvite(invite) 112 | let client_is_in_server = client.guilds.cache.get( 113 | invitex.guild.id 114 | ) 115 | reqinvite = invitex 116 | if (!client_is_in_server) { 117 | const gaEmbed = { 118 | author: { 119 | name: client.user.username, 120 | iconURL: client.user.displayAvatarURL() 121 | }, 122 | title: "Server Check!", 123 | url: "https://youtube.com/c/ZeroSync", 124 | description: 125 | "Woah woah woah! I see a new server! are you sure I am in that? You need to invite me there to set that as a requirement! 😳", 126 | timestamp: new Date(), 127 | footer: { 128 | iconURL: client.user.displayAvatarURL(), 129 | text: "Server Check" 130 | } 131 | } 132 | return interaction.editReply({ embeds: [gaEmbed]}) 133 | } 134 | } 135 | 136 | if (rolereq && !invite) { 137 | messages.inviteToParticipate = `**React with 🎉 to participate!**\n>>> - Only members having ${rolereq} are allowed to participate in this giveaway!` 138 | } 139 | if (rolereq && invite) { 140 | messages.inviteToParticipate = `**React with 🎉 to participate!**\n>>> - Only members having ${rolereq} are allowed to participate in this giveaway!\n- Members are required to join [this server](${invite}) to participate in this giveaway!` 141 | } 142 | if (!rolereq && invite) { 143 | messages.inviteToParticipate = `**React with 🎉 to participate!**\n>>> - Members are required to join [this server](${invite}) to participate in this giveaway!` 144 | } 145 | 146 | 147 | // start giveaway 148 | client.giveawaysManager.start(giveawayChannel, { 149 | // The giveaway duration 150 | duration: ms(giveawayDuration), 151 | // The giveaway prize 152 | prize: giveawayPrize, 153 | // The giveaway winner count 154 | winnerCount: parseInt(giveawayWinnerCount), 155 | // Hosted by 156 | hostedBy: client.config.hostedBy ? interaction.user : null, 157 | // BonusEntries If Provided 158 | bonusEntries: [ 159 | { 160 | // Members who have the role which is assigned to "rolename" get the amount of bonus entries which are assigned to "BonusEntries" 161 | bonus: new Function('member', `return member.roles.cache.some((r) => r.name === \'${bonusRole ?.name}\') ? ${bonusEntries} : null`), 162 | cumulative: false 163 | } 164 | ], 165 | // Messages 166 | messages, 167 | extraData: { 168 | server: reqinvite == null ? "null" : reqinvite.guild.id, 169 | role: rolereq == null ? "null" : rolereq.id, 170 | } 171 | }); 172 | interaction.editReply({ 173 | content: 174 | `Giveaway started in ${giveawayChannel}!`, 175 | ephemeral: true 176 | }) 177 | 178 | if (bonusRole) { 179 | let giveaway = new Discord.EmbedBuilder() 180 | .setAuthor({ name: `Bonus Entries Alert!` }) 181 | .setDescription( 182 | `**${bonusRole}** Has **${bonusEntries}** Extra Entries in this giveaway!` 183 | ) 184 | .setColor("#2F3136") 185 | .setTimestamp(); 186 | giveawayChannel.send({ embeds: [giveaway] }); 187 | } 188 | 189 | } 190 | 191 | }; 192 | -------------------------------------------------------------------------------- /storage/giveaways.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /utils/message.js: -------------------------------------------------------------------------------- 1 | const config = require('../config.json'); 2 | module.exports = { 3 | giveaway: 4 | (config.everyoneMention ? "@everyone\n\n" : "") + 5 | "🎉 **GIVEAWAY** 🎉", 6 | giveawayEnded: 7 | (config.everyoneMention ? "@everyone\n\n" : "") + 8 | "🎉 **GIVEAWAY ENDED** 🎉", 9 | drawing: `Ends: **{timestamp}**`, 10 | inviteToParticipate: `React with 🎉 to participate!`, 11 | winMessage: "Congratulations, {winners}! You won **{this.prize}**!", 12 | embedFooter: "{this.winnerCount} winner(s)", 13 | noWinner: "Giveaway cancelled, no valid participations.", 14 | hostedBy: "Hosted by: {this.hostedBy}", 15 | winners: "winner(s)", 16 | endedAt: "Ended at" 17 | } 18 | -------------------------------------------------------------------------------- /utils/slashsync.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js'); 2 | 3 | module.exports = async (client, commands, options = { 4 | debug: false, 5 | guildId: null 6 | }) => { 7 | 8 | const log = (message) => options.debug && console.log(message); 9 | 10 | const ready = client.readyAt ? Promise.resolve() : new Promise(resolve => client.once('ready', resolve)); 11 | await ready; 12 | const currentCommands = await client.application.commands.fetch(options.guildId && { guildId: options.guildId }); 13 | 14 | log(`Synchronizing commands...`); 15 | log(`Currently ${currentCommands.size} commands are registered to the bot.`); 16 | 17 | const newCommands = commands.filter((command) => !currentCommands.some((c) => c.name === command.name)); 18 | for (let newCommand of newCommands) { 19 | await client.application.commands.create(newCommand, options.guildId); 20 | } 21 | 22 | log(`Created ${newCommands.length} commands!`); 23 | 24 | const deletedCommands = currentCommands.filter((command) => !commands.some((c) => c.name === command.name)).toJSON(); 25 | for (let deletedCommand of deletedCommands) { 26 | await deletedCommand.delete(); 27 | } 28 | 29 | log(`Deleted ${deletedCommands.length} commands!`); 30 | 31 | const updatedCommands = commands.filter((command) => currentCommands.some((c) => c.name === command.name)); 32 | let updatedCommandCount = 0; 33 | for (let updatedCommand of updatedCommands) { 34 | const newCommand = updatedCommand; 35 | const previousCommand = currentCommands.find((c) => c.name === updatedCommand.name); 36 | let modified = false; 37 | if (previousCommand.description !== newCommand.description) modified = true; 38 | if (!Discord.ApplicationCommand.optionsEqual(previousCommand.options ?? [], newCommand.options ?? [])) modified = true; 39 | if (modified) { 40 | await previousCommand.edit(newCommand); 41 | updatedCommandCount++; 42 | } 43 | } 44 | 45 | log(`Updated ${updatedCommandCount} commands!`); 46 | 47 | log(`Commands synchronized!`); 48 | 49 | return { 50 | currentCommandCount: currentCommands.size, 51 | newCommandCount: newCommands.length, 52 | deletedCommandCount: deletedCommands.length, 53 | updatedCommandCount 54 | }; 55 | 56 | }; --------------------------------------------------------------------------------